diff --git a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
index 46513eed0..347750ecc 100644
--- a/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-3-defining-a-mapper.asciidoc
@@ -233,34 +233,36 @@ public interface AddressMapper {
In this case the source parameter is directly mapped into the target as the example above demonstrates. The parameter `hn`, a non bean type (in this case `java.lang.Integer`) is mapped to `houseNumber`.
[[mapping-nested-bean-properties-to-current-target]]
- === Mapping nested bean properties to current target
+=== Mapping nested bean properties to current target
- If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
+If you don't want explicitly name all properties from nested source bean, you can use `.` as target.
This will tell MapStruct to map every property from source bean to target object. The following shows an example:
- .Nested multi mapping method
- ====
- [source, java, linenums]
- [subs="verbatim,attributes"]
- ----
+.use of "target this" annotation "."
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
@Mapper
public interface CustomerMapper {
- @Mapping(source = "record", target = ".")
+ @Mapping( target = "name", source = "record.name" )
+ @Mapping( target = ".", source = "record" )
+ @Mapping( target = ".", source = "account" )
Customer customerDtoToCustomer(CustomerDto customerDto);
}
- ----
- ====
+----
+====
- The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
+The generated code will map every property from `CustomerDto.record` to `Customer` directly, without need to manually name any of them.
+The same goes for `Customer.account`.
+
+When there are conflicts, these can be resolved by explicitely defining the mapping. For instance in the example above. `name` occurs in `CustomerDto.record` and in `CustomerDto.account`. The mapping `@Mapping( target = "name", source = "record.name" )` resolves this conflict.
+
+
+This "target this" notation can be very useful when mapping hierarchical objects to flat objects and vice versa (`@InheritInverseConfiguration`).
- Multi mapping notation can be very useful when mapping hierarchical objects to flat objects and opposite way.
- [TIP]
- ====
- If you need to send your domain object using JSON, one way to do this is to convert it to single object, where every parent class is represented as separate field in JSON.
- And MapStruct allows you easily convert between these 2 types.
- ====
[[updating-bean-instances]]
=== Updating existing bean instances
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
index d9103ea64..8a9e4129f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
@@ -657,15 +657,30 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
*
* When a target property matches its name with the (nested) source property, it is added to the list if and
* only if it is an unprocessed target property.
+ *
+ * duplicates will be handled by {@link #applyPropertyNameBasedMapping(List)}
*/
private void applyTargetThisMapping() {
- if ( mappingReferences.getTargetThis() != null ) {
- List sourceRefs = mappingReferences.getTargetThis()
+ Set handledTargetProperties = new HashSet<>();
+ for ( MappingReference targetThis : mappingReferences.getTargetThisReferences() ) {
+
+ // handle all prior unprocessed target properties, but let duplicates fall through
+ List sourceRefs = targetThis
.getSourceReference()
- .push( ctx.getTypeFactory(), ctx.getMessager(), method ).stream()
- .filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() ) )
+ .push( ctx.getTypeFactory(), ctx.getMessager(), method )
+ .stream()
+ .filter( sr -> unprocessedTargetProperties.containsKey( sr.getDeepestPropertyName() )
+ || handledTargetProperties.contains( sr.getDeepestPropertyName() ) )
.collect( Collectors.toList() );
+
+ // apply name based mapping
applyPropertyNameBasedMapping( sourceRefs );
+
+ // add handled target properties
+ handledTargetProperties.addAll( sourceRefs.stream()
+ .map( SourceReference::getDeepestPropertyName )
+ .collect(
+ Collectors.toList() ) );
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java
index 1a4ede039..424cf99b6 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/MappingReferences.java
@@ -5,8 +5,10 @@
*/
package org.mapstruct.ap.internal.model.beanmapping;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
+import java.util.List;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.TypeFactory;
@@ -19,7 +21,7 @@ public class MappingReferences {
private static final MappingReferences EMPTY = new MappingReferences( Collections.emptySet(), false );
private final Set mappingReferences;
- private final MappingReference targetThis;
+ private final List targetThisReferences;
private final boolean restrictToDefinedMappings;
private final boolean forForgedMethods;
@@ -31,7 +33,7 @@ public class MappingReferences {
TypeFactory typeFactory) {
Set references = new LinkedHashSet<>();
- MappingReference targetThisReference = null;
+ List targetThisReferences = new ArrayList<>( );
for ( Mapping mapping : sourceMethod.getMappingOptions().getMappings() ) {
@@ -53,29 +55,29 @@ public class MappingReferences {
MappingReference mappingReference = new MappingReference( mapping, targetReference, sourceReference );
if ( isValidWhenInversed( mappingReference ) ) {
if ( ".".equals( mapping.getTargetName() ) ) {
- targetThisReference = mappingReference;
+ targetThisReferences.add( mappingReference );
}
else {
references.add( mappingReference );
}
}
}
- return new MappingReferences( references, targetThisReference, false );
+ return new MappingReferences( references, targetThisReferences, false );
}
- public MappingReferences(Set mappingReferences, MappingReference targetThis,
+ public MappingReferences(Set mappingReferences, List targetThisReferences,
boolean restrictToDefinedMappings) {
this.mappingReferences = mappingReferences;
this.restrictToDefinedMappings = restrictToDefinedMappings;
this.forForgedMethods = restrictToDefinedMappings;
- this.targetThis = targetThis;
+ this.targetThisReferences = targetThisReferences;
}
public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings) {
this.mappingReferences = mappingReferences;
this.restrictToDefinedMappings = restrictToDefinedMappings;
this.forForgedMethods = restrictToDefinedMappings;
- this.targetThis = null;
+ this.targetThisReferences = Collections.emptyList();
}
public MappingReferences(Set mappingReferences, boolean restrictToDefinedMappings,
@@ -83,7 +85,7 @@ public class MappingReferences {
this.mappingReferences = mappingReferences;
this.restrictToDefinedMappings = restrictToDefinedMappings;
this.forForgedMethods = forForgedMethods;
- this.targetThis = null;
+ this.targetThisReferences = Collections.emptyList();
}
public Set getMappingReferences() {
@@ -127,8 +129,8 @@ public class MappingReferences {
return false;
}
- public MappingReference getTargetThis() {
- return targetThis;
+ public List getTargetThisReferences() {
+ return targetThisReferences;
}
/**
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java
index 789b22126..00e6d9bef 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java
@@ -482,6 +482,10 @@ public class Mapping {
if ( this == o ) {
return true;
}
+ if ( ".".equals( this.targetName ) ) {
+ // target this will never be equal to any other target this or any other.
+ return false;
+ }
if ( o == null || getClass() != o.getClass() ) {
return false;
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/ConfictsResolvedNestedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ConfictsResolvedNestedMapper.java
new file mode 100644
index 000000000..37f966794
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ConfictsResolvedNestedMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.targetthis;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public interface ConfictsResolvedNestedMapper {
+
+ ConfictsResolvedNestedMapper INSTANCE = Mappers.getMapper( ConfictsResolvedNestedMapper.class );
+
+ @Mapping( target = "id", source = "customer.item.id" )
+ @Mapping( target = ".", source = "customer.item" )
+ @Mapping( target = "status", source = "item.status" )
+ @Mapping( target = ".", source = "item" )
+ OrderItem map(OrderDTO order);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerDTO.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerDTO.java
index c62bef5bb..2ca0fc72f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerDTO.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerDTO.java
@@ -5,6 +5,9 @@
*/
package org.mapstruct.ap.test.targetthis;
+/**
+ * @author Dainius Figoras
+ */
public class CustomerDTO {
private String name;
private int level;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerItem.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerItem.java
index 8bac4dbb4..2bec3eb18 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerItem.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/CustomerItem.java
@@ -5,6 +5,9 @@
*/
package org.mapstruct.ap.test.targetthis;
+/**
+ * @author Dainius Figoras
+ */
public class CustomerItem {
private String id;
private int status;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/ErroneousNestedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ErroneousNestedMapper.java
new file mode 100644
index 000000000..10960416f
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ErroneousNestedMapper.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.targetthis;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public interface ErroneousNestedMapper {
+
+ ErroneousNestedMapper INSTANCE = Mappers.getMapper( ErroneousNestedMapper.class );
+
+ @Mapping( target = ".", source = "customer.item" )
+ @Mapping( target = ".", source = "item" )
+ OrderItem map(OrderDTO order);
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/FlatteningMapper.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/FlatteningMapper.java
new file mode 100644
index 000000000..2d4309dea
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/FlatteningMapper.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.targetthis;
+
+import org.mapstruct.InheritInverseConfiguration;
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Sjaak Derksen
+ */
+@Mapper
+public interface FlatteningMapper {
+
+ FlatteningMapper INSTANCE = Mappers.getMapper( FlatteningMapper.class );
+
+ @Mapping(target = ".", source = "name")
+ @Mapping(target = ".", source = "account")
+ Customer flatten(CustomerDTO customer);
+
+ @InheritInverseConfiguration
+ CustomerDTO expand(Customer customer);
+
+ class Customer {
+
+ private String name;
+ private String id;
+ private String details;
+ private String number;
+
+ public String getName() {
+ return name;
+ }
+
+ public Customer setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Customer setId(String id) {
+ this.id = id;
+ return this;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ public Customer setDetails(String details) {
+ this.details = details;
+ return this;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+
+ public Customer setNumber(String number) {
+ this.number = number;
+ return this;
+ }
+ }
+
+ class CustomerDTO {
+
+ private NameDTO name;
+ private AccountDTO account;
+
+ public NameDTO getName() {
+ return name;
+ }
+
+ public CustomerDTO setName(NameDTO name) {
+ this.name = name;
+ return this;
+ }
+
+ public AccountDTO getAccount() {
+ return account;
+ }
+
+ public CustomerDTO setAccount(AccountDTO account) {
+ this.account = account;
+ return this;
+ }
+ }
+
+ class NameDTO {
+ private String name;
+ private String id;
+
+ public String getName() {
+ return name;
+ }
+
+ public NameDTO setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public NameDTO setId(String id) {
+ this.id = id;
+ return this;
+ }
+ }
+
+ class AccountDTO {
+ private String number;
+ private String details;
+
+ public String getNumber() {
+ return number;
+ }
+
+ public AccountDTO setNumber(String number) {
+ this.number = number;
+ return this;
+ }
+
+ public String getDetails() {
+ return details;
+ }
+
+ public AccountDTO setDetails(String details) {
+ this.details = details;
+ return this;
+ }
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/ItemDTO.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ItemDTO.java
index 409a1caa5..103253be0 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/ItemDTO.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/ItemDTO.java
@@ -5,6 +5,9 @@
*/
package org.mapstruct.ap.test.targetthis;
+/**
+ * @author Dainius Figoras
+ */
public class ItemDTO {
private String id;
private int status;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/NestedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/NestedMapper.java
index e989578a0..c33bcb514 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/NestedMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/NestedMapper.java
@@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
+/**
+ * @author Sjaak Derksen
+ */
@Mapper
public interface NestedMapper {
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderDTO.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderDTO.java
index eb701f9cd..b0f94ae2c 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderDTO.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderDTO.java
@@ -5,6 +5,9 @@
*/
package org.mapstruct.ap.test.targetthis;
+/**
+ * @author Dainius Figoras
+ */
public class OrderDTO {
private ItemDTO item;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderItem.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderItem.java
index f0c28a3d1..63c57386f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderItem.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/OrderItem.java
@@ -5,6 +5,9 @@
*/
package org.mapstruct.ap.test.targetthis;
+/**
+ * @author Dainius Figoras
+ */
public class OrderItem {
private String id;
private int status;
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapper.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapper.java
index 46a27b331..3fbee8b9f 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapper.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapper.java
@@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
+/**
+ * @author Sjaak Derksen
+ */
@Mapper
public interface SimpleMapper {
SimpleMapper INSTANCE = Mappers.getMapper( SimpleMapper.class );
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapperWithIgnore.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapperWithIgnore.java
index 7db8914f9..dfbb3b6fa 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapperWithIgnore.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/SimpleMapperWithIgnore.java
@@ -9,6 +9,9 @@ import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
+/**
+ * @author Sjaak Derksen
+ */
@Mapper
public interface SimpleMapperWithIgnore {
SimpleMapperWithIgnore INSTANCE = Mappers.getMapper( SimpleMapperWithIgnore.class );
diff --git a/processor/src/test/java/org/mapstruct/ap/test/targetthis/TargetThisMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/targetthis/TargetThisMappingTest.java
index 249a697f8..dea128b27 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/targetthis/TargetThisMappingTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/targetthis/TargetThisMappingTest.java
@@ -8,10 +8,16 @@ package org.mapstruct.ap.test.targetthis;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
+import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
+import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
import static org.assertj.core.api.Assertions.assertThat;
+/**
+ * @author Dainius Figoras
+ */
@RunWith(AnnotationProcessorTestRunner.class)
@WithClasses( {
OrderDTO.class,
@@ -82,4 +88,72 @@ public class TargetThisMappingTest {
assertThat( c.getId() ).isNull();
assertThat( c.getStatus() ).isEqualTo( ce.getItem().getStatus() );
}
+
+ @Test
+ @WithClasses( ErroneousNestedMapper.class )
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(type = ErroneousNestedMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 22,
+ messageRegExp = "^Several possible source properties for target property \"id\"\\.$"),
+ @Diagnostic(type = ErroneousNestedMapper.class,
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ line = 22,
+ messageRegExp = "^Several possible source properties for target property \"status\"\\.$")
+ }
+ )
+ public void testNestedDuplicates() {
+ }
+
+ @Test
+ @WithClasses( ConfictsResolvedNestedMapper.class )
+ public void testWithConflictsResolved() {
+
+ OrderDTO orderDTO = new OrderDTO();
+ orderDTO.setItem( new ItemDTO() );
+ orderDTO.getItem().setId( "item1" );
+ orderDTO.getItem().setStatus( 1 );
+ orderDTO.setCustomer( new CustomerDTO() );
+ orderDTO.getCustomer().setName( "customer name" );
+ orderDTO.getCustomer().setItem( new ItemDTO() );
+ orderDTO.getCustomer().getItem().setId( "item2" );
+ orderDTO.getCustomer().getItem().setStatus( 2 );
+
+ OrderItem c = ConfictsResolvedNestedMapper.INSTANCE.map( orderDTO );
+
+ assertThat( c ).isNotNull();
+ assertThat( c.getStatus() ).isEqualTo( orderDTO.getItem().getStatus() );
+ assertThat( c.getId() ).isEqualTo( orderDTO.getCustomer().getItem().getId() );
+ }
+
+ @Test
+ @WithClasses( FlatteningMapper.class )
+ public void testFlattening() {
+
+ FlatteningMapper.CustomerDTO customerDTO = new FlatteningMapper.CustomerDTO();
+ customerDTO.setName( new FlatteningMapper.NameDTO() );
+ customerDTO.getName().setName( "john doe" );
+ customerDTO.getName().setId( "1" );
+ customerDTO.setAccount( new FlatteningMapper.AccountDTO() );
+ customerDTO.getAccount().setDetails( "nice guys" );
+ customerDTO.getAccount().setNumber( "11223344" );
+
+ FlatteningMapper.Customer customer = FlatteningMapper.INSTANCE.flatten( customerDTO );
+
+ assertThat( customer ).isNotNull();
+ assertThat( customer.getName() ).isEqualTo( "john doe" );
+ assertThat( customer.getId() ).isEqualTo( "1" );
+ assertThat( customer.getDetails() ).isEqualTo( "nice guys" );
+ assertThat( customer.getNumber() ).isEqualTo( "11223344" );
+
+ FlatteningMapper.CustomerDTO customerDTO2 = FlatteningMapper.INSTANCE.expand( customer );
+
+ assertThat( customerDTO2 ).isNotNull();
+ assertThat( customerDTO2.getName().getName() ).isEqualTo( "john doe" );
+ assertThat( customerDTO2.getName().getId() ).isEqualTo( "1" );
+ assertThat( customerDTO2.getAccount().getDetails() ).isEqualTo( "nice guys" );
+ assertThat( customerDTO2.getAccount().getNumber() ).isEqualTo( "11223344" );
+ }
}