#3144 Map to Bean should only be possible for single source mappings and if explicitly used in multi source mappings

This commit is contained in:
Filip Hrisafov 2023-05-28 09:55:40 +02:00 committed by GitHub
parent d075d9a5b6
commit 86919c637f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 14 deletions

View File

@ -586,7 +586,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.build(); .build();
ReadAccessor targetPropertyReadAccessor = ReadAccessor targetPropertyReadAccessor =
method.getResultType().getReadAccessor( propertyName ); method.getResultType().getReadAccessor( propertyName, forceUpdateMethod );
MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true );
PropertyMapping propertyMapping = new PropertyMappingBuilder() PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
@ -1160,7 +1160,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
} }
Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName );
ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor(
targetPropertyName,
method.getSourceParameters().size() == 1
);
if ( targetWriteAccessor == null ) { if ( targetWriteAccessor == null ) {
if ( targetReadAccessor == null ) { if ( targetReadAccessor == null ) {
@ -1513,7 +1516,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
} }
ReadAccessor targetPropertyReadAccessor = ReadAccessor targetPropertyReadAccessor =
method.getResultType().getReadAccessor( targetPropertyName ); method.getResultType()
.getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 );
MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false );
PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx )
.sourceMethod( method ) .sourceMethod( method )
@ -1556,7 +1560,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
.build(); .build();
ReadAccessor targetPropertyReadAccessor = ReadAccessor targetPropertyReadAccessor =
method.getResultType().getReadAccessor( targetProperty.getKey() ); method.getResultType()
.getReadAccessor( targetProperty.getKey(), method.getSourceParameters().size() == 1 );
MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false );
PropertyMapping propertyMapping = new PropertyMappingBuilder() PropertyMapping propertyMapping = new PropertyMappingBuilder()
.mappingContext( ctx ) .mappingContext( ctx )
@ -1600,7 +1605,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return sourceRef; return sourceRef;
} }
ReadAccessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); ReadAccessor sourceReadAccessor = sourceParameter.getType()
.getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 );
if ( sourceReadAccessor != null ) { if ( sourceReadAccessor != null ) {
// property mapping // property mapping
PresenceCheckAccessor sourcePresenceChecker = PresenceCheckAccessor sourcePresenceChecker =

View File

@ -643,7 +643,10 @@ public class NestedTargetPropertyMappingHolder {
boolean forceUpdateMethod) { boolean forceUpdateMethod) {
Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName );
ReadAccessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); ReadAccessor targetReadAccessor = targetType.getReadAccessor(
targetPropertyName,
method.getSourceParameters().size() == 1
);
if ( targetWriteAccessor == null ) { if ( targetWriteAccessor == null ) {
Set<String> readAccessors = targetType.getPropertyReadAccessors().keySet(); Set<String> readAccessors = targetType.getPropertyReadAccessors().keySet();
String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors );

View File

@ -152,15 +152,27 @@ public class SourceReference extends AbstractReference {
private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) {
boolean foundEntryMatch; boolean foundEntryMatch;
boolean allowedMapToBean = false;
if ( segments.length > 0 ) {
if ( parameter.getType().isMapType() ) {
// When the parameter type is a map and the parameter name matches the first segment
// then the first segment should not be treated as a property of the map
allowedMapToBean = !segments[0].equals( parameter.getName() );
}
}
String[] propertyNames = segments; String[] propertyNames = segments;
List<PropertyEntry> entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); List<PropertyEntry> entries = matchWithSourceAccessorTypes(
parameter.getType(),
propertyNames,
allowedMapToBean
);
foundEntryMatch = ( entries.size() == propertyNames.length ); foundEntryMatch = ( entries.size() == propertyNames.length );
if ( !foundEntryMatch ) { if ( !foundEntryMatch ) {
//Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2
if ( getSourceParameterFromMethodOrTemplate( segments[0] ) != null ) { if ( getSourceParameterFromMethodOrTemplate( segments[0] ) != null ) {
propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); propertyNames = Arrays.copyOfRange( segments, 1, segments.length );
entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true );
foundEntryMatch = ( entries.size() == propertyNames.length ); foundEntryMatch = ( entries.size() == propertyNames.length );
} }
else { else {
@ -193,7 +205,7 @@ public class SourceReference extends AbstractReference {
if ( segments.length > 1 && parameter != null ) { if ( segments.length > 1 && parameter != null ) {
propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); propertyNames = Arrays.copyOfRange( segments, 1, segments.length );
entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true );
foundEntryMatch = ( entries.size() == propertyNames.length ); foundEntryMatch = ( entries.size() == propertyNames.length );
} }
else { else {
@ -306,13 +318,14 @@ public class SourceReference extends AbstractReference {
} }
} }
private List<PropertyEntry> matchWithSourceAccessorTypes(Type type, String[] entryNames) { private List<PropertyEntry> matchWithSourceAccessorTypes(Type type, String[] entryNames,
boolean allowedMapToBean) {
List<PropertyEntry> sourceEntries = new ArrayList<>(); List<PropertyEntry> sourceEntries = new ArrayList<>();
Type newType = type; Type newType = type;
for ( int i = 0; i < entryNames.length; i++ ) { for ( int i = 0; i < entryNames.length; i++ ) {
boolean matchFound = false; boolean matchFound = false;
Type noBoundsType = newType.withoutBounds(); Type noBoundsType = newType.withoutBounds();
ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i] ); ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean );
if ( readAccessor != null ) { if ( readAccessor != null ) {
PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] );
newType = typeFactory.getReturnType( newType = typeFactory.getReturnType(

View File

@ -652,8 +652,8 @@ public class Type extends ModelElement implements Comparable<Type> {
} }
} }
public ReadAccessor getReadAccessor(String propertyName) { public ReadAccessor getReadAccessor(String propertyName, boolean allowedMapToBean) {
if ( hasStringMapSignature() ) { if ( allowedMapToBean && hasStringMapSignature() ) {
ExecutableElement getMethod = getAllMethods() ExecutableElement getMethod = getAllMethods()
.stream() .stream()
.filter( m -> m.getSimpleName().contentEquals( "get" ) ) .filter( m -> m.getSimpleName().contentEquals( "get" ) )

View File

@ -0,0 +1,66 @@
/*
* 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.bugs._3144;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface Issue3144Mapper {
Issue3144Mapper INSTANCE = Mappers.getMapper( Issue3144Mapper.class );
@Mapping(target = "map", source = "sourceMap")
Target mapExplicitDefined(Map<String, String> sourceMap);
@Mapping(target = "map", ignore = true)
Target map(Map<String, String> sourceMap);
Target mapMultiParameters(Source source, Map<String, String> map);
@Mapping(target = "value", source = "map.testValue")
Target mapMultiParametersDefinedMapping(Source source, Map<String, String> map);
class Source {
private final String sourceValue;
public Source(String sourceValue) {
this.sourceValue = sourceValue;
}
public String getSourceValue() {
return sourceValue;
}
}
class Target {
private String value;
private Map<String, String> map;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.bugs._3144;
import java.util.HashMap;
import java.util.Map;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* @author Filip Hrisafov
*/
@WithClasses(Issue3144Mapper.class)
@IssueKey("3144")
class Issue3144Test {
@ProcessorTest
void shouldCorrectlyHandleMapBeanMapping() {
Map<String, String> map = new HashMap<>();
map.put( "value", "Map Value" );
map.put( "testValue", "Map Test Value" );
Issue3144Mapper.Target target = Issue3144Mapper.INSTANCE.mapExplicitDefined( map );
assertThat( target ).isNotNull();
assertThat( target.getValue() ).isEqualTo( "Map Value" );
assertThat( target.getMap() )
.containsOnly(
entry( "value", "Map Value" ),
entry( "testValue", "Map Test Value" )
);
target = Issue3144Mapper.INSTANCE.map( map );
assertThat( target ).isNotNull();
assertThat( target.getValue() ).isEqualTo( "Map Value" );
assertThat( target.getMap() ).isNull();
target = Issue3144Mapper.INSTANCE.mapMultiParameters( null, map );
assertThat( target ).isNotNull();
assertThat( target.getValue() ).isNull();
assertThat( target.getMap() )
.containsOnly(
entry( "value", "Map Value" ),
entry( "testValue", "Map Test Value" )
);
target = Issue3144Mapper.INSTANCE.mapMultiParametersDefinedMapping( null, map );
assertThat( target ).isNotNull();
assertThat( target.getValue() ).isEqualTo( "Map Test Value" );
assertThat( target.getMap() ).isNull();
}
}

View File

@ -19,7 +19,7 @@ public interface MapToBeanUsingMappingMethodMapper {
MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class ); MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class );
@Mapping(target = "normalInt", source = "number") @Mapping(target = "normalInt", source = "source.number")
Target toTarget(Map<String, Integer> source); Target toTarget(Map<String, Integer> source);
default String mapIntegerToString( Integer input ) { default String mapIntegerToString( Integer input ) {