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 0b299067f..37dec1641 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 @@ -504,7 +504,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .build(); Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( propertyName ); + method.getResultType().getReadAccessor( propertyName ); MappingReferences mappingRefs = extractMappingReferences( propertyName, true ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1047,7 +1047,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } Accessor targetWriteAccessor = unprocessedTargetProperties.get( targetPropertyName ); - Accessor targetReadAccessor = resultTypeToMap.getPropertyReadAccessors().get( targetPropertyName ); + Accessor targetReadAccessor = resultTypeToMap.getReadAccessor( targetPropertyName ); if ( targetWriteAccessor == null ) { if ( targetReadAccessor == null ) { @@ -1389,7 +1389,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { } Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( targetPropertyName ); + method.getResultType().getReadAccessor( targetPropertyName ); MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) @@ -1432,7 +1432,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { .build(); Accessor targetPropertyReadAccessor = - method.getResultType().getPropertyReadAccessors().get( targetProperty.getKey() ); + method.getResultType().getReadAccessor( targetProperty.getKey() ); MappingReferences mappingRefs = extractMappingReferences( targetProperty.getKey(), false ); PropertyMapping propertyMapping = new PropertyMappingBuilder() .mappingContext( ctx ) @@ -1473,22 +1473,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { return sourceRef; } - if ( sourceParameter.getType().isMapType() ) { - List typeParameters = sourceParameter.getType().getTypeParameters(); - if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) { - return SourceReference.fromMapSource( - new String[] { targetPropertyName }, - sourceParameter - ); - } - } - - Accessor sourceReadAccessor = - sourceParameter.getType().getPropertyReadAccessors().get( targetPropertyName ); + Accessor sourceReadAccessor = sourceParameter.getType().getReadAccessor( targetPropertyName ); if ( sourceReadAccessor != null ) { // property mapping Accessor sourcePresenceChecker = - sourceParameter.getType().getPropertyPresenceCheckers().get( targetPropertyName ); + sourceParameter.getType().getPresenceChecker( targetPropertyName ); DeclaredType declaredSourceType = (DeclaredType) sourceParameter.getType().getTypeMirror(); Type returnType = ctx.getTypeFactory().getReturnType( declaredSourceType, sourceReadAccessor ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java index 82c02f538..dd87f8e00 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/NestedPropertyMappingMethod.java @@ -14,9 +14,12 @@ import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.PresenceCheck; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.beanmapping.PropertyEntry; +import org.mapstruct.ap.internal.model.presence.SourceReferenceContainsKeyPresenceCheck; import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.ValueProvider; +import org.mapstruct.ap.internal.util.accessor.Accessor; +import org.mapstruct.ap.internal.util.accessor.AccessorType; /** * This method is used to convert the nested properties as listed in propertyEntries into a method @@ -165,11 +168,20 @@ public class NestedPropertyMappingMethod extends MappingMethod { public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) { this.safeName = safeName; this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue(); - if ( entry.getPresenceChecker() != null ) { - this.presenceChecker = new SourceReferenceMethodPresenceCheck( - previousPropertyName, - entry.getPresenceChecker().getSimpleName() - ); + Accessor presenceChecker = entry.getPresenceChecker(); + if ( presenceChecker != null ) { + if ( presenceChecker.getAccessorType() == AccessorType.MAP_CONTAINS ) { + this.presenceChecker = new SourceReferenceContainsKeyPresenceCheck( + previousPropertyName, + presenceChecker.getSimpleName() + ); + } + else { + this.presenceChecker = new SourceReferenceMethodPresenceCheck( + previousPropertyName, + presenceChecker.getSimpleName() + ); + } } else { this.presenceChecker = null; 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 254ba6287..402c2447b 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 @@ -642,7 +642,7 @@ public class NestedTargetPropertyMappingHolder { boolean forceUpdateMethod) { Accessor targetWriteAccessor = targetPropertiesWriteAccessors.get( targetPropertyName ); - Accessor targetReadAccessor = targetType.getPropertyReadAccessors().get( targetPropertyName ); + Accessor targetReadAccessor = targetType.getReadAccessor( targetPropertyName ); 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 15b10b578..47f483083 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 @@ -7,16 +7,13 @@ package org.mapstruct.ap.internal.model.beanmapping; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; -import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeMirror; import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; @@ -27,8 +24,6 @@ import org.mapstruct.ap.internal.util.FormattingMessager; import org.mapstruct.ap.internal.util.Message; import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.accessor.Accessor; -import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; -import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; import static org.mapstruct.ap.internal.model.beanmapping.PropertyEntry.forSourceReference; import static org.mapstruct.ap.internal.util.Collections.last; @@ -51,32 +46,13 @@ import static org.mapstruct.ap.internal.util.Collections.last; *
  • {@code propertyEntries[1]} will describe {@code propB}
  • * * - * After building, {@link #isValid()} will return true when when no problems are detected during building. + * After building, {@link #isValid()} will return true when no problems are detected during building. * * @author Sjaak Derksen + * @author Filip Hrisafov */ public class SourceReference extends AbstractReference { - public static SourceReference fromMapSource(String[] segments, Parameter parameter) { - Type parameterType = parameter.getType(); - Type valueType = parameterType.getTypeParameters().get( 1 ); - - TypeElement typeElement = parameterType.getTypeElement(); - TypeMirror typeMirror = valueType.getTypeMirror(); - String simpleName = String.join( ".", segments ); - - MapValueAccessor mapValueAccessor = new MapValueAccessor( typeElement, typeMirror, simpleName ); - MapValuePresenceChecker mapValuePresenceChecker = new MapValuePresenceChecker( - typeElement, - typeMirror, - simpleName - ); - List entries = Collections.singletonList( - PropertyEntry.forSourceReference( segments, mapValueAccessor, mapValuePresenceChecker, valueType ) - ); - return new SourceReference( parameter, entries, true ); - } - /** * Builds a {@link SourceReference} from an {@code @Mappping}. */ @@ -173,11 +149,6 @@ public class SourceReference extends AbstractReference { * @return the source reference */ private SourceReference buildFromSingleSourceParameters(String[] segments, Parameter parameter) { - - if ( canBeTreatedAsMapSourceType( parameter.getType() ) ) { - return fromMapSource( segments, parameter ); - } - boolean foundEntryMatch; String[] propertyNames = segments; @@ -214,14 +185,6 @@ public class SourceReference extends AbstractReference { */ private SourceReference buildFromMultipleSourceParameters(String[] segments, Parameter parameter) { - if (parameter != null && canBeTreatedAsMapSourceType( parameter.getType() )) { - String[] propertyNames = new String[0]; - if ( segments.length > 1 ) { - propertyNames = Arrays.copyOfRange( segments, 1, segments.length ); - } - return fromMapSource( propertyNames, parameter ); - } - boolean foundEntryMatch; String[] propertyNames = new String[0]; @@ -244,17 +207,8 @@ public class SourceReference extends AbstractReference { return new SourceReference( parameter, entries, foundEntryMatch ); } - private boolean canBeTreatedAsMapSourceType(Type type) { - if ( !type.isMapType() ) { - return false; - } - - List typeParameters = type.getTypeParameters(); - return typeParameters.size() == 2 && typeParameters.get( 0 ).isString(); - } - /** - * When there are more than one source parameters, the first segment name of the propery + * When there are more than one source parameters, the first segment name of the property * needs to match the parameter name to avoid ambiguity * * consider: {@code Target map( Source1 source1, Source2 source2 )} @@ -356,24 +310,20 @@ public class SourceReference extends AbstractReference { Type newType = type; for ( int i = 0; i < entryNames.length; i++ ) { boolean matchFound = false; - Map sourceReadAccessors = newType.getPropertyReadAccessors(); - Map sourcePresenceCheckers = newType.getPropertyPresenceCheckers(); - - for ( Map.Entry getter : sourceReadAccessors.entrySet() ) { - if ( getter.getKey().equals( entryNames[i] ) ) { - newType = typeFactory.getReturnType( - (DeclaredType) newType.getTypeMirror(), - getter.getValue() - ); - sourceEntries.add( forSourceReference( - Arrays.copyOf( entryNames, i + 1 ), - getter.getValue(), - sourcePresenceCheckers.get( entryNames[i] ), - newType - ) ); - matchFound = true; - break; - } + Accessor readAccessor = newType.getReadAccessor( entryNames[i] ); + if ( readAccessor != null ) { + Accessor presenceChecker = newType.getPresenceChecker( entryNames[i] ); + newType = typeFactory.getReturnType( + (DeclaredType) newType.getTypeMirror(), + readAccessor + ); + sourceEntries.add( forSourceReference( + Arrays.copyOf( entryNames, i + 1 ), + readAccessor, + presenceChecker, + newType + ) ); + matchFound = true; } if ( !matchFound ) { break; 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 00bcae371..4abec925c 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 @@ -48,6 +48,8 @@ import org.mapstruct.ap.internal.util.Nouns; import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.accessor.Accessor; import org.mapstruct.ap.internal.util.accessor.AccessorType; +import org.mapstruct.ap.internal.util.accessor.MapValueAccessor; +import org.mapstruct.ap.internal.util.accessor.MapValuePresenceChecker; import static org.mapstruct.ap.internal.util.Collections.first; @@ -60,6 +62,7 @@ import static org.mapstruct.ap.internal.util.Collections.first; * through {@link TypeFactory}. * * @author Gunnar Morling + * @author Filip Hrisafov */ public class Type extends ModelElement implements Comparable { @@ -308,6 +311,17 @@ public class Type extends ModelElement implements Comparable { return isMapType; } + private boolean hasStringMapSignature() { + if ( isMapType() ) { + List typeParameters = getTypeParameters(); + if ( typeParameters.size() == 2 && typeParameters.get( 0 ).isString() ) { + return true; + } + } + + return false; + } + public boolean isCollectionOrMapType() { return isCollectionType || isMapType; } @@ -597,6 +611,42 @@ public class Type extends ModelElement implements Comparable { } } + public Accessor getReadAccessor(String propertyName) { + if ( hasStringMapSignature() ) { + ExecutableElement getMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "get" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + return new MapValueAccessor( getMethod, typeParameters.get( 1 ).getTypeMirror(), propertyName ); + } + + Map readAccessors = getPropertyReadAccessors(); + + return readAccessors.get( propertyName ); + } + + public Accessor getPresenceChecker(String propertyName) { + if ( hasStringMapSignature() ) { + ExecutableElement containsKeyMethod = getAllMethods() + .stream() + .filter( m -> m.getSimpleName().contentEquals( "containsKey" ) ) + .filter( m -> m.getParameters().size() == 1 ) + .findAny() + .orElse( null ); + + return new MapValuePresenceChecker( + containsKeyMethod, + typeParameters.get( 1 ).getTypeMirror(), + propertyName + ); + } + + Map presenceCheckers = getPropertyPresenceCheckers(); + return presenceCheckers.get( propertyName ); + } + /** * getPropertyReadAccessors * diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java index 548aecc1e..bcb78d8dc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/FromMapMappingTest.java @@ -261,6 +261,20 @@ class FromMapMappingTest { assertThat( target.getNestedTarget().getStringFromNestedMap() ).isEqualTo( "valueFromNestedMap" ); } + @IssueKey("2553") + @ProcessorTest + @WithClasses(MapToBeanFromMapAndNestedMapWithDefinedMapping.class) + void shouldMapFromNestedMapWithDefinedMapping() { + + MapToBeanFromMapAndNestedMapWithDefinedMapping.Source source = + new MapToBeanFromMapAndNestedMapWithDefinedMapping.Source(); + MapToBeanFromMapAndNestedMapWithDefinedMapping.Target target = + MapToBeanFromMapAndNestedMapWithDefinedMapping.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getNested() ).isEqualTo( "valueFromNestedMap" ); + } + @ProcessorTest @WithClasses(ObjectMapToBeanWithQualifierMapper.class) void shouldUseObjectQualifiedMethod() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java new file mode 100644 index 000000000..2203836ea --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/frommap/MapToBeanFromMapAndNestedMapWithDefinedMapping.java @@ -0,0 +1,57 @@ +/* + * 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.frommap; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + */ +@Mapper +public interface MapToBeanFromMapAndNestedMapWithDefinedMapping { + + MapToBeanFromMapAndNestedMapWithDefinedMapping INSTANCE = Mappers.getMapper( + MapToBeanFromMapAndNestedMapWithDefinedMapping.class ); + + @Mapping(target = "nested", source = "nestedTarget.nested") + Target toTarget(Source source); + + class Source { + + private Map nestedTarget = new HashMap<>(); + + public Map getNestedTarget() { + return nestedTarget; + } + + public void setNestedTarget(Map nestedTarget) { + this.nestedTarget = nestedTarget; + } + + public Source() { + nestedTarget.put( "nested", "valueFromNestedMap" ); + } + } + + class Target { + + private String nested; + + public String getNested() { + return nested; + } + + public void setNested(String nested) { + this.nested = nested; + } + } + +}