#695 Check for direct assignment for iterable, stream or map types should be done on their type parameters instead of the types themselves

This commit is contained in:
Filip Hrisafov 2020-05-30 12:01:14 +02:00
parent c23592a7fe
commit 850a55cd5d
6 changed files with 212 additions and 5 deletions

View File

@ -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<Type> 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<Type> 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() {

View File

@ -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);
}

View File

@ -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<OrderItemDto> orders;
private Map<OrderItemKeyDto, OrderItemDto> 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<OrderItemDto> getOrders() {
return orders;
}
public void setOrders(List<OrderItemDto> orders) {
this.orders = orders;
}
public Map<OrderItemKeyDto, OrderItemDto> getStock() {
return stock;
}
public void setStock(Map<OrderItemKeyDto, OrderItemDto> stock) {
this.stock = stock;
}
}

View File

@ -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<OrderItemKeyDto, OrderItemDto> 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<OrderItemKeyDto, OrderItemDto> 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.
* <p>
@ -95,6 +147,7 @@ public class MappingControlTest {
assertThat( fridge ).isNotNull();
assertThat( fridge.getBeerCount() ).isEqualTo( 5 );
assertThat( fridge.getBeerCount() ).isEqualTo( 5 );
}
@Test

View File

@ -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;
}
}

View File

@ -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;
}
}