From 86919c637f228ec3a668f8140b9b68e2092f5189 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Sun, 28 May 2023 09:55:40 +0200 Subject: [PATCH] #3144 Map to Bean should only be possible for single source mappings and if explicitly used in multi source mappings --- .../ap/internal/model/BeanMappingMethod.java | 16 +++-- .../NestedTargetPropertyMappingHolder.java | 5 +- .../model/beanmapping/SourceReference.java | 23 +++++-- .../ap/internal/model/common/Type.java | 4 +- .../ap/test/bugs/_3144/Issue3144Mapper.java | 66 +++++++++++++++++++ .../ap/test/bugs/_3144/Issue3144Test.java | 63 ++++++++++++++++++ .../MapToBeanUsingMappingMethodMapper.java | 2 +- 7 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java 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 361d04b39..903dfa5cc 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 @@ -586,7 +586,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .build(); ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( propertyName ); + method.getResultType().getReadAccessor( propertyName, forceUpdateMethod ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1160,7 +1160,10 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); - ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = resultTypeToMap.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { @@ -1513,7 +1516,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( targetPropertyName ); + method.getResultType() + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) @@ -1556,7 +1560,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .build(); ReadAccessor targetPropertyReadAccessor = - method.getResultType().getReadAccessor( targetProperty.getKey() ); + method.getResultType() + .getReadAccessor( targetProperty.getKey(), method.getSourceParameters().size() == 1 ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1600,7 +1605,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return sourceRef; } - ReadAccessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); + ReadAccessor sourceReadAccessor = sourceParameter.getType() + .getReadAccessor( targetPropertyName, method.getSourceParameters().size() == 1 ); if ( sourceReadAccessor != null ) { // property mapping PresenceCheckAccessor sourcePresenceChecker = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java index 61e392604..b7bfc6978 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedTargetPropertyMappingHolder.java @@ -643,7 +643,10 @@ public class NestedTargetPropertyMappingHolder { boolean forceUpdateMethod) { Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); - ReadAccessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); + ReadAccessor targetReadAccessor = targetType.getReadAccessor( + targetPropertyName, + method.getSourceParameters().size() == 1 + ); if ( targetWriteAccessor == null ) { Set readAccessors = targetType.getPropertyReadAccessors().keySet(); String mostSimilarProperty = Strings.getMostSimilarWord( targetPropertyName, readAccessors ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java index 4e7dad220..73ec84b6b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/beanmapping/SourceReference.java @@ -152,15 +152,27 @@ public class SourceReference extends AbstractReference { private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { 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; - List entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + List entries = matchWithSourceAccessorTypes( + parameter.getType(), + propertyNames, + allowedMapToBean + ); foundEntryMatch = ( entries.size() == propertyNames.length ); if ( !foundEntryMatch ) { //Lets see if the expression contains the parameterName, so parameterName.propName1.propName2 if ( getSourceParameterFromMethodOrTemplate( segments[0] ) != null ) { propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); foundEntryMatch = ( entries.size() == propertyNames.length ); } else { @@ -193,7 +205,7 @@ public class SourceReference extends AbstractReference { if ( segments.length > 1 && parameter != null ) { propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames ); + entries = matchWithSourceAccessorTypes( parameter.getType(), propertyNames, true ); foundEntryMatch = ( entries.size() == propertyNames.length ); } else { @@ -306,13 +318,14 @@ public class SourceReference extends AbstractReference { } } - private List matchWithSourceAccessorTypes(Type type, String[] entryNames) { + private List matchWithSourceAccessorTypes(Type type, String[] entryNames, + boolean allowedMapToBean) { List sourceEntries = new ArrayList<>(); Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; Type noBoundsType = newType.withoutBounds(); - ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i] ); + ReadAccessor readAccessor = noBoundsType.getReadAccessor( entryNames[i], i > 0 || allowedMapToBean ); if ( readAccessor != null ) { PresenceCheckAccessor presenceChecker = noBoundsType.getPresenceChecker( entryNames[i] ); newType = typeFactory.getReturnType( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index 4254913d8..93358c2a2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -652,8 +652,8 @@ public class Type extends ModelElement implements Comparable { } } - public ReadAccessor getReadAccessor(String propertyName) { - if ( hasStringMapSignature() ) { + public ReadAccessor getReadAccessor(String propertyName, boolean allowedMapToBean) { + if ( allowedMapToBean && hasStringMapSignature() ) { ExecutableElement getMethod = getAllMethods() .stream() .filter( m -> m.getSimpleName().contentEquals( "get" ) ) diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java new file mode 100644 index 000000000..df5b76787 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Mapper.java @@ -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 sourceMap); + + @Mapping(target = "map", ignore = true) + Target map(Map sourceMap); + + Target mapMultiParameters(Source source, Map map); + + @Mapping(target = "value", source = "map.testValue") + Target mapMultiParametersDefinedMapping(Source source, Map 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 map; + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public Map getMap() { + return map; + } + + public void setMap(Map map) { + this.map = map; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java new file mode 100644 index 000000000..13f349414 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_3144/Issue3144Test.java @@ -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 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(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java index 9a6344ff1..225ca00ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanUsingMappingMethodMapper.java @@ -19,7 +19,7 @@ public interface MapToBeanUsingMappingMethodMapper { MapToBeanUsingMappingMethodMapper INSTANCE = Mappers.getMapper( MapToBeanUsingMappingMethodMapper.class ); - @Mapping(target = "normalInt", source = "number") + @Mapping(target = "normalInt", source = "source.number") Target toTarget(Map source); default String mapIntegerToString( Integer input ) {