diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 47e3e57f6..2875cef67 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -190,8 +190,9 @@ public class MappingResolverImpl implements MappingResolver { // then direct assignable if ( !hasQualfiers() ) { - if ( ( sourceType.isAssignableTo( targetType ) && allowDirect( sourceType, targetType ) ) || - isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) { + if ( ( sourceType.isAssignableTo( targetType ) || + isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) + && allowDirect( sourceType, targetType ) ) { Assignment simpleAssignment = sourceRHS; return simpleAssignment; } @@ -280,11 +281,40 @@ public class MappingResolverImpl implements MappingResolver { } private boolean allowDirect( Type sourceType, Type targetType ) { - if ( sourceType.isPrimitive() || targetType.isPrimitive() - || sourceType.isJavaLangType() || targetType.isJavaLangType() ) { + if ( selectionCriteria != null && selectionCriteria.isAllowDirect() ) { return true; } - return selectionCriteria != null && selectionCriteria.isAllowDirect(); + + return allowDirect( sourceType ) || allowDirect( targetType ); + } + + private boolean allowDirect(Type type) { + if ( type.isPrimitive() ) { + return true; + } + + if ( type.isArrayType() ) { + return type.isJavaLangType(); + } + + if ( type.isIterableOrStreamType() ) { + List typeParameters = type.getTypeParameters(); + // For iterable or stream direct mapping is enabled when: + // - The type is raw (no type parameters) + // - The type parameter is allowed + return typeParameters.isEmpty() || allowDirect( Collections.first( typeParameters ) ); + } + + if ( type.isMapType() ) { + List typeParameters = type.getTypeParameters(); + // For map type direct mapping is enabled when: + // - The type os raw (no type parameters + // - The key and value are direct assignable + return typeParameters.isEmpty() || + ( allowDirect( typeParameters.get( 0 ) ) && allowDirect( typeParameters.get( 1 ) ) ); + } + + return type.isJavaLangType(); } private boolean allowConversion() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.java new file mode 100644 index 000000000..5a74527d3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CloningListMapper.java @@ -0,0 +1,19 @@ +/* + * 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.mappingcontrol; + +import org.mapstruct.Mapper; +import org.mapstruct.control.DeepClone; +import org.mapstruct.factory.Mappers; + +@Mapper(mappingControl = DeepClone.class) +public interface CloningListMapper { + + CloningListMapper INSTANCE = Mappers.getMapper( CloningListMapper.class ); + + CustomerDto clone(CustomerDto in); + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.java new file mode 100644 index 000000000..810d53f41 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/CustomerDto.java @@ -0,0 +1,52 @@ +/* + * 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.mappingcontrol; + +import java.util.List; +import java.util.Map; + +/** + * @author Sjaak Derksen + */ +public class CustomerDto { + + private Long id; + private String customerName; + private List orders; + private Map stock; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getCustomerName() { + return customerName; + } + + public void setCustomerName(String customerName) { + this.customerName = customerName; + } + + public List getOrders() { + return orders; + } + + public void setOrders(List orders) { + this.orders = orders; + } + + public Map getStock() { + return stock; + } + + public void setStock(Map stock) { + this.stock = stock; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java index a1a1fd607..5807931ce 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/MappingControlTest.java @@ -5,6 +5,11 @@ */ package org.mapstruct.ap.test.mappingcontrol; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + import org.junit.Test; import org.junit.runner.RunWith; import org.mapstruct.ap.testutil.IssueKey; @@ -15,6 +20,7 @@ import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutco import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; /** * @author Sjaak Derksen @@ -25,6 +31,9 @@ import static org.assertj.core.api.Assertions.assertThat; ShelveDTO.class, Fridge.class, FridgeDTO.class, + CustomerDto.class, + OrderItemDto.class, + OrderItemKeyDto.class, UseDirect.class, UseComplex.class }) @@ -63,6 +72,49 @@ public class MappingControlTest { assertThat( out.getShelve().getCoolBeer().getBeerCount() ).isEqualTo( "5" ); } + /** + * Test the deep cloning annotation with lists + */ + @Test + @WithClasses(CloningListMapper.class) + public void testDeepCloningListsAndMaps() { + + CustomerDto in = new CustomerDto(); + in.setId( 10L ); + in.setCustomerName( "Jaques" ); + OrderItemDto order1 = new OrderItemDto(); + order1.setName( "Table" ); + order1.setQuantity( 2L ); + in.setOrders( new ArrayList<>( Collections.singleton( order1 ) ) ); + OrderItemKeyDto key = new OrderItemKeyDto(); + key.setStockNumber( 5 ); + Map stock = new HashMap<>(); + stock.put( key, order1 ); + in.setStock( stock ); + + CustomerDto out = CloningListMapper.INSTANCE.clone( in ); + + assertThat( out.getId() ).isEqualTo( 10 ); + assertThat( out.getCustomerName() ).isEqualTo( "Jaques" ); + assertThat( out.getOrders() ) + .extracting( "name", "quantity" ) + .containsExactly( tuple( "Table", 2L ) ); + assertThat( out.getStock() ).isNotNull(); + assertThat( out.getStock() ).hasSize( 1 ); + + Map.Entry entry = out.getStock().entrySet().iterator().next(); + assertThat( entry.getKey().getStockNumber() ).isEqualTo( 5 ); + assertThat( entry.getValue().getName() ).isEqualTo( "Table" ); + assertThat( entry.getValue().getQuantity() ).isEqualTo( 2L ); + + // check mapper really created new objects + assertThat( out ).isNotSameAs( in ); + assertThat( out.getOrders().get( 0 ) ).isNotSameAs( order1 ); + assertThat( entry.getKey() ).isNotSameAs( key ); + assertThat( entry.getValue() ).isNotSameAs( order1 ); + assertThat( entry.getValue() ).isNotSameAs( out.getOrders().get( 0 ) ); + } + /** * This is a nice test. MapStruct looks for a way to map ShelveDto to ShelveDto. *

@@ -95,6 +147,7 @@ public class MappingControlTest { assertThat( fridge ).isNotNull(); assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); + assertThat( fridge.getBeerCount() ).isEqualTo( 5 ); } @Test diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.java new file mode 100644 index 000000000..dbaad654d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemDto.java @@ -0,0 +1,31 @@ +/* + * 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.mappingcontrol; + +/** + * @author Sjaak Derksen + */ +public class OrderItemDto { + + private String name; + private Long quantity; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Long getQuantity() { + return quantity; + } + + public void setQuantity(Long quantity) { + this.quantity = quantity; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.java b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.java new file mode 100644 index 000000000..6154f4450 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/mappingcontrol/OrderItemKeyDto.java @@ -0,0 +1,22 @@ +/* + * 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.mappingcontrol; + +/** + * @author Sjaak Derksen + */ +public class OrderItemKeyDto { + + private long stockNumber; + + public long getStockNumber() { + return stockNumber; + } + + public void setStockNumber(long stockNumber) { + this.stockNumber = stockNumber; + } +}