From faedebc119565dec8f025e2049606fd60658b5dd Mon Sep 17 00:00:00 2001 From: sjaakd Date: Sun, 14 Feb 2016 20:49:02 +0100 Subject: [PATCH] #750 Initial implementation of @Named annotation --- .../ap/internal/model/BeanMappingMethod.java | 11 + .../internal/model/IterableMappingMethod.java | 10 +- .../model/LifecycleCallbackFactory.java | 1 + .../ap/internal/model/MapMappingMethod.java | 17 +- .../internal/model/MappingBuilderContext.java | 8 +- .../ap/internal/model/PropertyMapping.java | 20 +- .../ap/internal/model/source/BeanMapping.java | 12 +- .../model/source/IterableMapping.java | 13 +- .../ap/internal/model/source/MapMapping.java | 21 +- .../ap/internal/model/source/Mapping.java | 11 +- .../source/selector/QualifierSelector.java | 73 +- .../source/selector/SelectionCriteria.java | 10 +- .../ap/internal/prism/PrismGenerator.java | 2 + .../processor/MapperCreationProcessor.java | 12 + .../creation/MappingResolverImpl.java | 1165 +++++++++-------- 15 files changed, 770 insertions(+), 616 deletions(-) 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 c9d4fcf3e..246ba70c5 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 @@ -80,6 +80,7 @@ public class BeanMappingMethod extends MappingMethod { private final List propertyMappings = new ArrayList(); private final Set unprocessedSourceParameters = new HashSet(); private List qualifiers; + private List qualifyingNames; private NullValueMappingStrategyPrism nullValueMappingStrategy; private TypeMirror resultTypeMirror; private final Set existingVariableNames = new HashSet(); @@ -107,6 +108,11 @@ public class BeanMappingMethod extends MappingMethod { return this; } + public Builder qualifyingNames(List qualifyingNames) { + this.qualifyingNames = qualifyingNames; + return this; + } + public Builder nullValueMappingStrategy(NullValueMappingStrategyPrism nullValueMappingStrategy) { this.nullValueMappingStrategy = nullValueMappingStrategy; return this; @@ -145,6 +151,7 @@ public class BeanMappingMethod extends MappingMethod { method, method.getResultType(), qualifiers, + qualifyingNames, resultTypeMirror ); } @@ -300,6 +307,7 @@ public class BeanMappingMethod extends MappingMethod { .targetPropertyName( mapping.getTargetName() ) .sourceReference( sourceRef ) .qualifiers( mapping.getQualifiers() ) + .qualifyingNames( mapping.getQualifyingNames() ) .resultType( mapping.getResultType() ) .dateFormat( mapping.getDateFormat() ) .existingVariableNames( existingVariableNames ) @@ -327,6 +335,7 @@ public class BeanMappingMethod extends MappingMethod { .targetPropertyName( mapping.getTargetName() ) .dateFormat( mapping.getDateFormat() ) .qualifiers( mapping.getQualifiers() ) + .qualifyingNames( mapping.getQualifyingNames() ) .resultType( mapping.getResultType() ) .existingVariableNames( existingVariableNames ) .dependsOn( mapping.getDependsOn() ) @@ -424,6 +433,7 @@ public class BeanMappingMethod extends MappingMethod { .targetPropertyName( targetProperty.getKey() ) .sourceReference( sourceRef ) .qualifiers( mapping != null ? mapping.getQualifiers() : null ) + .qualifyingNames( mapping != null ? mapping.getQualifyingNames() : null ) .resultType( mapping != null ? mapping.getResultType() : null ) .dateFormat( mapping != null ? mapping.getDateFormat() : null ) .defaultValue( mapping != null ? mapping.getDefaultValue() : null ) @@ -489,6 +499,7 @@ public class BeanMappingMethod extends MappingMethod { .targetPropertyName( targetProperty.getKey() ) .sourceReference( sourceRef ) .qualifiers( mapping != null ? mapping.getQualifiers() : null ) + .qualifyingNames( mapping != null ? mapping.getQualifyingNames() : null ) .resultType( mapping != null ? mapping.getResultType() : null ) .dateFormat( mapping != null ? mapping.getDateFormat() : null ) .existingVariableNames( existingVariableNames ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java index b3447b5fb..e7e2606b0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/IterableMappingMethod.java @@ -57,6 +57,7 @@ public class IterableMappingMethod extends MappingMethod { private MappingBuilderContext ctx; private String dateFormat; private List qualifiers; + private List qualifyingNames; private TypeMirror qualifyingElementTargetType; private NullValueMappingStrategyPrism nullValueMappingStrategy; @@ -80,6 +81,11 @@ public class IterableMappingMethod extends MappingMethod { return this; } + public Builder qualifyingNames(List qualifyingNames) { + this.qualifyingNames = qualifyingNames; + return this; + } + public Builder qualifyingElementTargetType(TypeMirror qualifyingElementTargetType) { this.qualifyingElementTargetType = qualifyingElementTargetType; return this; @@ -112,6 +118,7 @@ public class IterableMappingMethod extends MappingMethod { null, // there is no targetPropertyName dateFormat, qualifiers, + qualifyingNames, qualifyingElementTargetType, loopVariableName, false @@ -148,7 +155,8 @@ public class IterableMappingMethod extends MappingMethod { MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { - factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null ); + factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null, + null ); } List beforeMappingMethods = diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java index 9349f9a3b..fe88454ae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackFactory.java @@ -94,6 +94,7 @@ public final class LifecycleCallbackFactory { return selector.getMatchingMethods( method, candidates, null, null, new SelectionCriteria( qualifiers, + null, /* todo */ null, null, false ) ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java index 1259f3704..e710b07cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MapMappingMethod.java @@ -54,7 +54,9 @@ public class MapMappingMethod extends MappingMethod { private String keyDateFormat; private String valueDateFormat; private List keyQualifiers; + private List keyQualifyingNames; private List valueQualifiers; + private List valueQualifyingNames; private TypeMirror keyQualifyingTargetType; private TypeMirror valueQualifyingTargetType; private Method method; @@ -86,11 +88,21 @@ public class MapMappingMethod extends MappingMethod { return this; } + public Builder keyQualifyingNames(List keyQualifyingNames) { + this.keyQualifyingNames = keyQualifyingNames; + return this; + } + public Builder valueQualifiers(List valueQualifiers) { this.valueQualifiers = valueQualifiers; return this; } + public Builder valueQualifyingNames(List valueQualifyingNames) { + this.valueQualifyingNames = valueQualifyingNames; + return this; + } + public Builder keyQualifyingTargetType(TypeMirror keyQualifyingTargetType) { this.keyQualifyingTargetType = keyQualifyingTargetType; return this; @@ -125,6 +137,7 @@ public class MapMappingMethod extends MappingMethod { null, // there is no targetPropertyName keyDateFormat, keyQualifiers, + keyQualifyingNames, keyQualifyingTargetType, "entry.getKey()", false @@ -153,6 +166,7 @@ public class MapMappingMethod extends MappingMethod { null, // there is no targetPropertyName valueDateFormat, valueQualifiers, + valueQualifyingNames, valueQualifyingTargetType, "entry.getValue()", false @@ -187,7 +201,8 @@ public class MapMappingMethod extends MappingMethod { MethodReference factoryMethod = null; if ( !method.isUpdateMethod() ) { - factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null ); + factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType(), null, null, + null ); } keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index f2b1744fa..95e0b81a2 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -80,6 +80,7 @@ public class MappingBuilderContext { * @param targetPropertyName name of the target property * @param dateFormat used for formatting dates in build in methods that need context information * @param qualifiers used for further select the appropriate mapping method based on class and name + * @param qualifyingNames see qualifiers, used in combination with with @Named * @param resultType used for further select the appropriate mapping method based on resultType (bean mapping) * targetType (Iterable- and MapMapping) * @param sourceReference call to source type as string @@ -93,9 +94,11 @@ public class MappingBuilderContext { *
  • null, no assignment found
  • * */ + @SuppressWarnings("checkstyle:parameternumber") Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, Type targetType, String targetPropertyName, String dateFormat, List qualifiers, - TypeMirror resultType, String sourceReference, boolean preferUpdateMethods); + List qualifyingNames, TypeMirror resultType, String sourceReference, + boolean preferUpdateMethods); /** * returns a no arg factory method @@ -103,6 +106,7 @@ public class MappingBuilderContext { * @param mappingMethod target mapping method * @param target return type to match * @param qualifiers used for further select the appropriate mapping method based on class and name + * @param qualifyingNames see qualifiers, used in combination with with @Named * @param resultType used for further select the appropriate mapping method based on resultType (bean mapping) * targetType (Iterable- and MapMapping) * * @@ -110,7 +114,7 @@ public class MappingBuilderContext { * */ MethodReference getFactoryMethod(Method mappingMethod, Type target, List qualifiers, - TypeMirror resultType); + List qualifyingNames, TypeMirror resultType); Set getUsedVirtualMappings(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index b6468b5ab..640037b1a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -169,6 +169,7 @@ public class PropertyMapping extends ModelElement { private String dateFormat; private String defaultValue; private List qualifiers; + private List qualifyingNames; private TypeMirror resultType; private SourceReference sourceReference; @@ -182,6 +183,11 @@ public class PropertyMapping extends ModelElement { return this; } + public PropertyMappingBuilder qualifyingNames(List qualifyingNames) { + this.qualifyingNames = qualifyingNames; + return this; + } + public PropertyMappingBuilder resultType(TypeMirror resultType) { this.resultType = resultType; return this; @@ -229,6 +235,7 @@ public class PropertyMapping extends ModelElement { targetPropertyName, dateFormat, qualifiers, + qualifyingNames, resultType, sourceRefStr, preferUpdateMethods @@ -316,7 +323,7 @@ public class PropertyMapping extends ModelElement { targetPropertyName ); } Assignment factoryMethod = - ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); + ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null, null ); result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod, targetType ); } @@ -397,7 +404,7 @@ public class PropertyMapping extends ModelElement { targetPropertyName ); } Assignment factoryMethod - = ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); + = ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null, null ); result = new UpdateWrapper( result, method.getThrownTypes(), factoryMethod, targetType ); } @@ -605,6 +612,7 @@ public class PropertyMapping extends ModelElement { private String constantExpression; private String dateFormat; private List qualifiers; + private List qualifyingNames; private TypeMirror resultType; public ConstantMappingBuilder constantExpression(String constantExpression) { @@ -622,6 +630,11 @@ public class PropertyMapping extends ModelElement { return this; } + public ConstantMappingBuilder qualifyingNames(List qualifyingNames) { + this.qualifyingNames = qualifyingNames; + return this; + } + public ConstantMappingBuilder resultType(TypeMirror resultType) { this.resultType = resultType; return this; @@ -640,6 +653,7 @@ public class PropertyMapping extends ModelElement { targetPropertyName, dateFormat, qualifiers, + qualifyingNames, resultType, constantExpression, method.getMappingTargetParameter() != null @@ -657,7 +671,7 @@ public class PropertyMapping extends ModelElement { targetPropertyName ); } Assignment factoryMethod = - ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null ); + ctx.getMappingResolver().getFactoryMethod( method, targetType, null, null, null ); assignment = new UpdateWrapper( assignment, method.getThrownTypes(), factoryMethod, targetType ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java index 58276c694..253652201 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/BeanMapping.java @@ -38,6 +38,7 @@ import org.mapstruct.ap.internal.util.Message; public class BeanMapping { private final List qualifiers; + private final List qualifyingNames; private final TypeMirror resultType; private final NullValueMappingStrategyPrism nullValueMappingStrategy; @@ -55,7 +56,7 @@ public class BeanMapping { ? null : NullValueMappingStrategyPrism.valueOf( beanMapping.nullValueMappingStrategy() ); - if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() + if ( !resultTypeIsDefined && beanMapping.qualifiedBy().isEmpty() && beanMapping.qualifiedByName().isEmpty() && ( nullValueMappingStrategy == null ) ) { messager.printMessage( method, Message.BEANMAPPING_NO_ELEMENTS ); @@ -63,14 +64,17 @@ public class BeanMapping { return new BeanMapping( beanMapping.qualifiedBy(), + beanMapping.qualifiedByName(), resultTypeIsDefined ? beanMapping.resultType() : null, nullValueMappingStrategy ); } - private BeanMapping(List qualifiers, TypeMirror mirror, NullValueMappingStrategyPrism nvms) { + private BeanMapping(List qualifiers, List qualyfyingNames, TypeMirror mirror, + NullValueMappingStrategyPrism nvms) { this.qualifiers = qualifiers; + this.qualifyingNames = qualyfyingNames; this.resultType = mirror; this.nullValueMappingStrategy = nvms; } @@ -79,6 +83,10 @@ public class BeanMapping { return qualifiers; } + public List getQualifyingNames() { + return qualifyingNames; + } + public TypeMirror getResultType() { return resultType; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java index 3e8b9db16..83edf0935 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/IterableMapping.java @@ -40,6 +40,7 @@ public class IterableMapping { private final String dateFormat; private final List qualifiers; + private final List qualifyingNames; private final TypeMirror qualifyingElementTargetType; private final AnnotationMirror mirror; private final AnnotationValue dateFormatAnnotationValue; @@ -61,6 +62,7 @@ public class IterableMapping { if ( !elementTargetTypeIsDefined && iterableMapping.dateFormat().isEmpty() && iterableMapping.qualifiedBy().isEmpty() + && iterableMapping.qualifiedByName().isEmpty() && ( nullValueMappingStrategy == null ) ) { messager.printMessage( method, Message.ITERABLEMAPPING_NO_ELEMENTS ); @@ -68,6 +70,7 @@ public class IterableMapping { return new IterableMapping(iterableMapping.dateFormat(), iterableMapping.qualifiedBy(), + iterableMapping.qualifiedByName(), elementTargetTypeIsDefined ? iterableMapping.elementTargetType() : null, iterableMapping.mirror, iterableMapping.values.dateFormat(), @@ -75,11 +78,13 @@ public class IterableMapping { ); } - private IterableMapping(String dateFormat, List qualifiers, TypeMirror resultType, - AnnotationMirror mirror, AnnotationValue dateFormatAnnotationValue, NullValueMappingStrategyPrism nvms) { + private IterableMapping(String dateFormat, List qualifiers, List qualifyingNames, + TypeMirror resultType, AnnotationMirror mirror, AnnotationValue dateFormatAnnotationValue, + NullValueMappingStrategyPrism nvms) { this.dateFormat = dateFormat; this.qualifiers = qualifiers; + this.qualifyingNames = qualifyingNames; this.qualifyingElementTargetType = resultType; this.mirror = mirror; this.dateFormatAnnotationValue = dateFormatAnnotationValue; @@ -94,6 +99,10 @@ public class IterableMapping { return qualifiers; } + public List getQualifyingNames() { + return qualifyingNames; + } + public TypeMirror getQualifyingElementTargetType() { return qualifyingElementTargetType; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java index 92702d85b..1263995e8 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MapMapping.java @@ -39,8 +39,10 @@ public class MapMapping { private final String keyFormat; private final List keyQualifiers; + private final List keyQualifyingNames; private final String valueFormat; private final List valueQualifiers; + private final List valueQualifyingNames; private final AnnotationMirror mirror; private final TypeMirror keyQualifyingTargetType; private final TypeMirror valueQualifyingTargetType; @@ -62,8 +64,10 @@ public class MapMapping { boolean valueTargetTypeIsDefined = !TypeKind.VOID.equals( mapMapping.valueTargetType().getKind() ); if ( mapMapping.keyDateFormat().isEmpty() && mapMapping.keyQualifiedBy().isEmpty() + && mapMapping.keyQualifiedByName().isEmpty() && mapMapping.valueDateFormat().isEmpty() && mapMapping.valueQualifiedBy().isEmpty() + && mapMapping.valueQualifiedByName().isEmpty() && !keyTargetTypeIsDefined && !valueTargetTypeIsDefined && ( nullValueMappingStrategy == null ) ) { @@ -75,23 +79,28 @@ public class MapMapping { return new MapMapping( mapMapping.keyDateFormat(), mapMapping.keyQualifiedBy(), + mapMapping.keyQualifiedByName(), keyTargetTypeIsDefined ? mapMapping.keyTargetType() : null, mapMapping.valueDateFormat(), mapMapping.valueQualifiedBy(), + mapMapping.valueQualifiedByName(), valueTargetTypeIsDefined ? mapMapping.valueTargetType() : null, mapMapping.mirror, nullValueMappingStrategy ); } - private MapMapping(String keyFormat, List keyQualifiers, TypeMirror keyResultType, String valueFormat, - List valueQualifiers, TypeMirror valueResultType, AnnotationMirror mirror, + private MapMapping(String keyFormat, List keyQualifiers, List keyQualifyingNames, + TypeMirror keyResultType, String valueFormat, List valueQualifiers, + List valueQualifyingNames, TypeMirror valueResultType, AnnotationMirror mirror, NullValueMappingStrategyPrism nvms ) { this.keyFormat = keyFormat; this.keyQualifiers = keyQualifiers; + this.keyQualifyingNames = keyQualifyingNames; this.keyQualifyingTargetType = keyResultType; this.valueFormat = valueFormat; this.valueQualifiers = valueQualifiers; + this.valueQualifyingNames = valueQualifyingNames; this.valueQualifyingTargetType = valueResultType; this.mirror = mirror; this.nullValueMappingStrategy = nvms; @@ -105,6 +114,10 @@ public class MapMapping { return keyQualifiers; } + public List getKeyQualifyingNames() { + return keyQualifyingNames; + } + public TypeMirror getKeyQualifyingTargetType() { return keyQualifyingTargetType; } @@ -117,6 +130,10 @@ public class MapMapping { return valueQualifiers; } + public List getValueQualifyingNames() { + return valueQualifyingNames; + } + public TypeMirror getValueQualifyingTargetType() { return valueQualifyingTargetType; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java index 9e7d75c6f..206e3cbd6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Mapping.java @@ -57,6 +57,7 @@ public class Mapping { private final String dateFormat; private final String defaultValue; private final List qualifiers; + private final List qualifyingNames; private final TypeMirror resultType; private final boolean isIgnored; private final List dependsOn; @@ -147,6 +148,7 @@ public class Mapping { dateFormat, defaultValue, mappingPrism.qualifiedBy(), + mappingPrism.qualifiedByName(), mappingPrism.ignore(), mappingPrism.mirror, mappingPrism.values.source(), @@ -160,7 +162,7 @@ public class Mapping { @SuppressWarnings("checkstyle:parameternumber") private Mapping(String sourceName, String constant, String javaExpression, String targetName, String dateFormat, String defaultValue, List qualifiers, - boolean isIgnored, AnnotationMirror mirror, + List qualifyingNames, boolean isIgnored, AnnotationMirror mirror, AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue, AnnotationValue dependsOnAnnotationValue, TypeMirror resultType, List dependsOn) { @@ -171,6 +173,7 @@ public class Mapping { this.dateFormat = dateFormat; this.defaultValue = defaultValue; this.qualifiers = qualifiers; + this.qualifyingNames = qualifyingNames; this.isIgnored = isIgnored; this.mirror = mirror; this.sourceAnnotationValue = sourceAnnotationValue; @@ -250,6 +253,10 @@ public class Mapping { return qualifiers; } + public List getQualifyingNames() { + return qualifyingNames; + } + public boolean isIgnored() { return isIgnored; } @@ -325,6 +332,7 @@ public class Mapping { dateFormat, null, qualifiers, + qualifyingNames, isIgnored, mirror, sourceAnnotationValue, @@ -353,6 +361,7 @@ public class Mapping { dateFormat, defaultValue, qualifiers, + qualifyingNames, isIgnored, mirror, sourceAnnotationValue, diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java index 8f7e7b45c..84e9e3e5b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Set; import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; @@ -31,6 +32,7 @@ import javax.lang.model.util.Types; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.prism.NamedPrism; import org.mapstruct.ap.internal.prism.QualifierPrism; /** @@ -53,10 +55,11 @@ import org.mapstruct.ap.internal.prism.QualifierPrism; public class QualifierSelector implements MethodSelector { private final Types typeUtils; - + private final TypeMirror namedAnnotationTypeMirror; public QualifierSelector( Types typeUtils, Elements elementUtils ) { this.typeUtils = typeUtils; + namedAnnotationTypeMirror = elementUtils.getTypeElement( "org.mapstruct.Named" ).asType(); } @Override @@ -64,14 +67,33 @@ public class QualifierSelector implements MethodSelector { Type sourceType, Type targetType, SelectionCriteria criteria) { - List qualifiers = criteria.getQualifiers(); - if ( qualifiers == null || qualifiers.isEmpty() ) { - // remove the method marked as qualifier from the list + int numberOfQualifiersToMatch = 0; + + // Define some local collections and make sure that they are defined. + List qualifierTypes = new ArrayList(); + if ( criteria.getQualifiers() != null ) { + qualifierTypes.addAll( criteria.getQualifiers() ); + numberOfQualifiersToMatch += criteria.getQualifiers().size(); + } + List qualfiedByNames = new ArrayList(); + if ( criteria.getQualifiedByNames() != null ) { + qualfiedByNames.addAll( criteria.getQualifiedByNames() ); + numberOfQualifiersToMatch += criteria.getQualifiedByNames().size(); + } + + // add the mapstruct @Named annotation as annotation to look for + if ( !qualfiedByNames.isEmpty() ) { + qualifierTypes.add( namedAnnotationTypeMirror ); + } + + // Check there are qualfiers for this mapping: Mapping#qualifier or Mapping#qualfiedByName + if ( qualifierTypes.isEmpty() ) { + // When no qualifiers, disqualify all methods marked with a qualifier by removing them from the candidates List nonQualiferAnnotatedMethods = new ArrayList(); for ( T candidate : methods ) { if ( candidate instanceof SourceMethod ) { - Set qualifierAnnotations = getQualifierAnnotations( candidate ); + Set qualifierAnnotations = getQualifierAnnotationMirrors( candidate ); if ( qualifierAnnotations.isEmpty() ) { nonQualiferAnnotatedMethods.add( candidate ); } @@ -84,7 +106,7 @@ public class QualifierSelector implements MethodSelector { return nonQualiferAnnotatedMethods; } else { - + // Check all methods marked with qualfier (or methods in Mappers marked wiht a qualfier) for matches. List matches = new ArrayList(); for ( T candidate : methods ) { @@ -93,21 +115,37 @@ public class QualifierSelector implements MethodSelector { } // retrieve annotations - Set qualifierAnnotations = getQualifierAnnotations( candidate ); + Set qualifierAnnotationMirrors = getQualifierAnnotationMirrors( candidate ); // now count if all qualifiers are matched int matchingQualifierCounter = 0; - for ( TypeMirror qualifier : qualifiers ) { - for ( TypeMirror annotationType : qualifierAnnotations ) { - if ( typeUtils.isSameType( qualifier, annotationType ) ) { - matchingQualifierCounter++; + for ( AnnotationMirror qualifierAnnotationMirror : qualifierAnnotationMirrors ) { + for ( TypeMirror qualifierType : qualifierTypes ) { + + // get the type of the annotation mirror. + DeclaredType qualifierAnnotationType = qualifierAnnotationMirror.getAnnotationType(); + if ( typeUtils.isSameType( qualifierType, qualifierAnnotationType ) ) { + // Match! we have an annotation which has the @Qualifer marker ( could be @Named as well ) + if ( typeUtils.isSameType( qualifierAnnotationType, namedAnnotationTypeMirror ) ) { + // Match! its an @Named, so do the additional check on name. + NamedPrism namedPrism = NamedPrism.getInstance( qualifierAnnotationMirror ); + if ( namedPrism.value() != null && qualfiedByNames.contains( namedPrism.value() ) ) { + // Match! its an @Name and the value matches as well. Oh boy. + matchingQualifierCounter++; + } + } + else { + // Match! its a self declared qualifer annoation (marked with @Qualifier) + matchingQualifierCounter++; + } break; } + } } - if ( matchingQualifierCounter == qualifiers.size() ) { - // all qualifiers are matched with a qualifying annotation, add candidate + if ( matchingQualifierCounter == numberOfQualifiersToMatch ) { + // Only if all qualifiers are matched with a qualifying annotation, add candidate matches.add( candidate ); } } @@ -120,10 +158,10 @@ public class QualifierSelector implements MethodSelector { } } - private Set getQualifierAnnotations( Method candidate ) { + private Set getQualifierAnnotationMirrors( Method candidate ) { // retrieve annotations - Set qualiferAnnotations = new HashSet(); + Set qualiferAnnotations = new HashSet(); // first from the method itself SourceMethod candidateSM = (SourceMethod) candidate; @@ -144,12 +182,11 @@ public class QualifierSelector implements MethodSelector { return qualiferAnnotations; } - private void addOnlyWhenQualifier( Set annotationSet, AnnotationMirror candidate ) { + private void addOnlyWhenQualifier( Set annotationSet, AnnotationMirror candidate ) { // only add the candidate annotation when the candidate itself has the annotation 'Qualifier' if ( QualifierPrism.getInstanceOn( candidate.getAnnotationType().asElement() ) != null ) { - annotationSet.add( candidate.getAnnotationType() ); + annotationSet.add( candidate ); } } } - diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java index 3bc0ef2ab..497fa5a10 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java @@ -30,13 +30,15 @@ import javax.lang.model.type.TypeMirror; public class SelectionCriteria { private final List qualifiers; + private final List qualifiedByNames; private final String targetPropertyName; private final TypeMirror qualifyingResultType; private boolean preferUpdateMapping; - public SelectionCriteria(List qualifiers, String targetPropertyName, TypeMirror qualifyingResultType, - boolean preferUpdateMapping ) { + public SelectionCriteria(List qualifiers, List qualifiedByNames, String targetPropertyName, + TypeMirror qualifyingResultType, boolean preferUpdateMapping ) { this.qualifiers = qualifiers; + this.qualifiedByNames = qualifiedByNames; this.targetPropertyName = targetPropertyName; this.qualifyingResultType = qualifyingResultType; this.preferUpdateMapping = preferUpdateMapping; @@ -46,6 +48,10 @@ public class SelectionCriteria { return qualifiers; } + public List getQualifiedByNames() { + return qualifiedByNames; + } + public String getTargetPropertyName() { return targetPropertyName; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java index 02d3d812b..57d77445e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/prism/PrismGenerator.java @@ -39,6 +39,7 @@ import org.mapstruct.TargetType; import net.java.dev.hickory.prism.GeneratePrism; import net.java.dev.hickory.prism.GeneratePrisms; +import org.mapstruct.Named; /** * Triggers the generation of prism types using Hickory. @@ -59,6 +60,7 @@ import net.java.dev.hickory.prism.GeneratePrisms; @GeneratePrism(value = InheritConfiguration.class, publicAccess = true), @GeneratePrism(value = InheritInverseConfiguration.class, publicAccess = true), @GeneratePrism(value = Qualifier.class, publicAccess = true), + @GeneratePrism(value = Named.class, publicAccess = true), @GeneratePrism(value = AfterMapping.class, publicAccess = true), @GeneratePrism(value = BeforeMapping.class, publicAccess = true), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java index 66c36a1bb..f497e8f19 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MapperCreationProcessor.java @@ -269,12 +269,14 @@ public class MapperCreationProcessor implements ModelElementProcessor qualifiers = null; + List qualifyingNames = null; TypeMirror qualifyingElementTargetType = null; NullValueMappingStrategyPrism nullValueMappingStrategy = null; if ( mappingOptions.getIterableMapping() != null ) { dateFormat = mappingOptions.getIterableMapping().getDateFormat(); qualifiers = mappingOptions.getIterableMapping().getQualifiers(); + qualifyingNames = mappingOptions.getIterableMapping().getQualifyingNames(); qualifyingElementTargetType = mappingOptions.getIterableMapping().getQualifyingElementTargetType(); nullValueMappingStrategy = mappingOptions.getIterableMapping().getNullValueMappingStrategy(); } @@ -284,6 +286,7 @@ public class MapperCreationProcessor implements ModelElementProcessor keyQualifiers = null; + List keyQualifyingNames = null; List valueQualifiers = null; + List valueQualifyingNames = null; TypeMirror keyQualifyingTargetType = null; TypeMirror valueQualifyingTargetType = null; NullValueMappingStrategyPrism nullValueMappingStrategy = null; @@ -307,7 +312,9 @@ public class MapperCreationProcessor implements ModelElementProcessor qualifiers = null; + List qualifyingNames = null; if ( mappingOptions.getBeanMapping() != null ) { nullValueMappingStrategy = mappingOptions.getBeanMapping().getNullValueMappingStrategy(); resultType = mappingOptions.getBeanMapping().getResultType(); qualifiers = mappingOptions.getBeanMapping().getQualifiers(); + qualifyingNames = mappingOptions.getBeanMapping().getQualifyingNames(); } BeanMappingMethod.Builder builder = new BeanMappingMethod.Builder(); BeanMappingMethod beanMappingMethod = builder @@ -357,6 +368,7 @@ public class MapperCreationProcessor implements ModelElementProcessor sourceModel; - private final List mapperReferences; - - private final Conversions conversions; - private final BuiltInMappingMethods builtInMethods; - private final MethodSelectors methodSelectors; - - /** - * Private methods which are not present in the original mapper interface and are added to map certain property - * types. - */ - private final Set usedVirtualMappings = new HashSet(); - - public MappingResolverImpl(FormattingMessager messager, Elements elementUtils, Types typeUtils, - TypeFactory typeFactory, List sourceModel, - List mapperReferences) { - this.messager = messager; - this.typeUtils = typeUtils; - this.typeFactory = typeFactory; - - this.sourceModel = sourceModel; - this.mapperReferences = mapperReferences; - - this.conversions = new Conversions( elementUtils, typeFactory ); - this.builtInMethods = new BuiltInMappingMethods( typeFactory ); - this.methodSelectors = new MethodSelectors( - typeUtils, - elementUtils, - typeFactory - ); - } - - @Override - public Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, - Type targetType, String targetPropertyName, String dateFormat, List qualifiers, - TypeMirror resultType, String sourceReference, boolean preferUpdateMapping) { - - SelectionCriteria criteria = - new SelectionCriteria(qualifiers, targetPropertyName, resultType, preferUpdateMapping ); - - ResolvingAttempt attempt = new ResolvingAttempt( - sourceModel, - mappingMethod, - mappedElement, - dateFormat, - sourceReference, - criteria - ); - - return attempt.getTargetAssignment( sourceType, targetType ); - } - - @Override - public Set getUsedVirtualMappings() { - return usedVirtualMappings; - } - - @Override - public MethodReference getFactoryMethod( Method mappingMethod, Type targetType, List qualifiers, - TypeMirror resultType ) { - - SelectionCriteria criteria = new SelectionCriteria( qualifiers, null, resultType, false ); - - ResolvingAttempt attempt = new ResolvingAttempt( - sourceModel, - mappingMethod, - null, - null, - null, - criteria - ); - - SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType ); - if ( matchingSourceMethod != null ) { - MapperReference ref = attempt.findMapperReference( matchingSourceMethod ); - return new MethodReference( matchingSourceMethod, ref, null ); - } - return null; - - } - - private class ResolvingAttempt { - - private final Method mappingMethod; - private final String mappedElement; - private final List methods; - private final String dateFormat; - private final SelectionCriteria selectionCriteria; - private final String sourceReference; - private final boolean savedPreferUpdateMapping; - - // resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches, - // second doesn't. In that case, the first builtin method should not lead to a virtual method - // so this set must be cleared. - private final Set virtualMethodCandidates; - - private ResolvingAttempt(List sourceModel, Method mappingMethod, String mappedElement, - String dateFormat, String sourceReference, SelectionCriteria criteria) { - - this.mappingMethod = mappingMethod; - this.mappedElement = mappedElement; - this.methods = filterPossibleCandidateMethods( sourceModel ); - this.dateFormat = dateFormat; - this.sourceReference = sourceReference; - this.virtualMethodCandidates = new HashSet(); - this.selectionCriteria = criteria; - this.savedPreferUpdateMapping = criteria.isPreferUpdateMapping(); - } - - private List filterPossibleCandidateMethods(List candidateMethods) { - List result = new ArrayList( candidateMethods.size() ); - for ( T candidate : candidateMethods ) { - if ( isCandidateForMapping( candidate ) ) { - result.add( candidate ); - } - } - - return result; - } - - private Assignment getTargetAssignment(Type sourceType, Type targetType) { - - // first simple mapping method - Assignment referencedMethod = resolveViaMethod( sourceType, targetType, false ); - if ( referencedMethod != null ) { - referencedMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return referencedMethod; - } - - // then direct assignable - if ( sourceType.isAssignableTo( targetType ) || - isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) { - Assignment simpleAssignment = AssignmentFactory.createDirect( sourceReference ); - return simpleAssignment; - } - - // then type conversion - Assignment conversion = resolveViaConversion( sourceType, targetType ); - if ( conversion != null ) { - conversion.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return conversion; - } - - // check for a built-in method - Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType ); - if ( builtInMethod != null ) { - builtInMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - usedVirtualMappings.addAll( virtualMethodCandidates ); - return builtInMethod; - } - - // 2 step method, first: method(method(source)) - referencedMethod = resolveViaMethodAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return referencedMethod; - } - - // 2 step method, then: method(conversion(source)) - referencedMethod = resolveViaConversionAndMethod( sourceType, targetType ); - if ( referencedMethod != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return referencedMethod; - } - - // stop here when looking for update methods. - selectionCriteria.setPreferUpdateMapping( false ); - - // 2 step method, finally: conversion(method(source)) - conversion = resolveViaMethodAndConversion( sourceType, targetType ); - if ( conversion != null ) { - usedVirtualMappings.addAll( virtualMethodCandidates ); - return conversion; - } - - // if nothing works, alas, the result is null - return null; - } - - private Assignment resolveViaConversion(Type sourceType, Type targetType) { - ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); - - if ( conversionProvider == null ) { - return null; - } - - ConversionContext ctx = - new DefaultConversionContext( typeFactory, messager, sourceType, targetType, dateFormat ); - return conversionProvider.to( ctx ); - } - - /** - * Returns a reference to a method mapping the given source type to the given target type, if such a method - * exists. - */ - private Assignment resolveViaMethod(Type sourceType, Type targetType, boolean considerBuiltInMethods) { - - // first try to find a matching source method - SourceMethod matchingSourceMethod = getBestMatch( methods, sourceType, targetType ); - - if ( matchingSourceMethod != null ) { - return getMappingMethodReference( matchingSourceMethod, targetType ); - } - - if ( considerBuiltInMethods ) { - return resolveViaBuiltInMethod( sourceType, targetType ); - } - - return null; - } - - private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) { - BuiltInMethod matchingBuiltInMethod = - getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); - - if ( matchingBuiltInMethod != null ) { - virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) ); - ConversionContext ctx = new DefaultConversionContext( typeFactory, messager, - sourceType, - targetType, dateFormat ); - Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx ); - methodReference.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - return methodReference; - } - - return null; - } - - /** - * Suppose mapping required from A to C and: - *
      - *
    • no direct referenced mapping method either built-in or referenced is available from A to C
    • - *
    • no conversion is available
    • - *
    • there is a method from A to B, methodX
    • - *
    • there is a method from B to C, methodY
    • - *
    - * then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) ) - */ - private Assignment resolveViaMethodAndMethod(Type sourceType, Type targetType) { - - List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - Assignment methodRefY = null; - - // Iterate over all source methods. Check if the return type matches with the parameter that we need. - // so assume we need a method from A to C we look for a methodX from A to B (all methods in the - // list form such a candidate). - // For each of the candidates, we need to look if there's a methodY, either - // sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match - // a nested method call can be called. so C = methodY( methodX (A) ) - for ( Method methodYCandidate : methodYCandidates ) { - methodRefY = - resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); - - if ( methodRefY != null ) { - selectionCriteria.setPreferUpdateMapping( false ); - Assignment methodRefX = - resolveViaMethod( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType(), true ); - selectionCriteria.setPreferUpdateMapping( savedPreferUpdateMapping ); - if ( methodRefX != null ) { - methodRefY.setAssignment( methodRefX ); - methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match; - virtualMethodCandidates.clear(); - methodRefY = null; - } - } - } - return methodRefY; - } - - /** - * Suppose mapping required from A to C and: - *
      - *
    • there is a conversion from A to B, conversionX
    • - *
    • there is a method from B to C, methodY
    • - *
    - * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) - */ - private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) { - - List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - Assignment methodRefY = null; - - for ( Method methodYCandidate : methodYCandidates ) { - methodRefY = - resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); - - if ( methodRefY != null ) { - Assignment conversionXRef = - resolveViaConversion( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType() ); - if ( conversionXRef != null ) { - methodRefY.setAssignment( conversionXRef ); - conversionXRef.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match - virtualMethodCandidates.clear(); - methodRefY = null; - } - } - } - return methodRefY; - } - - /** - * Suppose mapping required from A to C and: - *
      - *
    • there is a conversion from A to B, conversionX
    • - *
    • there is a method from B to C, methodY
    • - *
    - * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) - */ - private Assignment resolveViaMethodAndConversion(Type sourceType, Type targetType) { - - List methodXCandidates = new ArrayList( methods ); - methodXCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - Assignment conversionYRef = null; - - // search the other way around - for ( Method methodXCandidate : methodXCandidates ) { - if ( methodXCandidate.getMappingTargetParameter() != null ) { - continue; - } - - Assignment methodRefX = resolveViaMethod( - sourceType, - methodXCandidate.getReturnType(), - true - ); - if ( methodRefX != null ) { - conversionYRef = resolveViaConversion( methodXCandidate.getReturnType(), targetType ); - if ( conversionYRef != null ) { - conversionYRef.setAssignment( methodRefX ); - methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); - break; - } - else { - // both should match; - virtualMethodCandidates.clear(); - conversionYRef = null; - } - } - } - return conversionYRef; - } - - private boolean isCandidateForMapping(Method methodCandidate) { - return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); - } - - private boolean isCreateMethodForMapping(Method methodCandidate) { - // a create method may not return void and has no target parameter - return methodCandidate.getSourceParameters().size() == 1 - && !methodCandidate.getReturnType().isVoid() - && methodCandidate.getMappingTargetParameter() == null - && !methodCandidate.isLifecycleCallbackMethod(); - } - - private boolean isUpdateMethodForMapping(Method methodCandidate) { - // an update method may, or may not return void and has a target parameter - return methodCandidate.getSourceParameters().size() == 1 - && methodCandidate.getMappingTargetParameter() != null - && !methodCandidate.isLifecycleCallbackMethod(); - } - - private T getBestMatch(List methods, Type sourceType, Type returnType) { - - List candidates = methodSelectors.getMatchingMethods( - mappingMethod, - methods, - sourceType, - returnType, - selectionCriteria - ); - - // raise an error if more than one mapping method is suitable to map the given source type - // into the target type - if ( candidates.size() > 1 ) { - - if ( mappedElement != null ) { - messager.printMessage( mappingMethod.getExecutable(), - Message.GENERAL_AMBIGIOUS_MAPPING_METHOD, - mappedElement, - returnType, - Strings.join( candidates, ", " ) - ); - } - else { - messager.printMessage( mappingMethod.getExecutable(), - Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, - returnType, - Strings.join( candidates, ", " ) - ); - } - } - - if ( !candidates.isEmpty() ) { - return candidates.get( 0 ); - } - - return null; - } - - private Assignment getMappingMethodReference(SourceMethod method, - Type targetType) { - MapperReference mapperReference = findMapperReference( method ); - - return AssignmentFactory.createMethodReference( - method, - mapperReference, - SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null - ); - } - - private MapperReference findMapperReference(SourceMethod method) { - for ( MapperReference ref : mapperReferences ) { - if ( ref.getType().equals( method.getDeclaringMapper() ) ) { - ref.setUsed( !method.isStatic() ); - ref.setTypeRequiresImport( true ); - return ref; - } - } - return null; - } - - /** - * Whether the given source and target type are both a collection type or both a map type and the source value - * can be propagated via a copy constructor. - */ - private boolean isAssignableThroughCollectionCopyConstructor(Type sourceType, Type targetType) { - boolean bothCollectionOrMap = false; - - if ( ( sourceType.isCollectionType() && targetType.isCollectionType() ) || - ( sourceType.isMapType() && targetType.isMapType() ) ) { - bothCollectionOrMap = true; - } - - if ( bothCollectionOrMap ) { - return hasCompatibleCopyConstructor( - sourceType, - targetType.getImplementationType() != null ? targetType.getImplementationType() : targetType - ); - } - - return false; - } - - /** - * Whether the given target type has a single-argument constructor which accepts the given source type. - * - * @param sourceType the source type - * @param targetType the target type - * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} - * otherwise. - */ - private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) { - if ( targetType.isPrimitive() ) { - return false; - } - - List targetTypeConstructors = ElementFilter.constructorsIn( - targetType.getTypeElement().getEnclosedElements() ); - - for ( ExecutableElement constructor : targetTypeConstructors ) { - if ( constructor.getParameters().size() != 1 ) { - continue; - } - - // get the constructor resolved against the type arguments of specific target type - ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf( - (DeclaredType) targetType.getTypeMirror(), - constructor ); - - TypeMirror parameterType = Collections.first( typedConstructor.getParameterTypes() ); - if ( parameterType.getKind() == TypeKind.DECLARED ) { - // replace any possible type bounds in the type parameters of the parameter types, as in JDK super - // type bounds in the arguments are returned from asMemberOf with "? extends ? super XX" - // - // It might also be enough to just remove "? super" from type parameters of - // targetType.getTypeMirror() in case we're in JDK. And that would be something that should be - // handled in SpecificCompilerWorkarounds... - - DeclaredType p = (DeclaredType) parameterType; - List typeArguments = new ArrayList( p.getTypeArguments().size() ); - - for ( TypeMirror tArg : p.getTypeArguments() ) { - typeArguments.add( typeFactory.getTypeBound( tArg ) ); - } - parameterType = typeUtils.getDeclaredType( - (TypeElement) p.asElement(), - typeArguments.toArray( new TypeMirror[typeArguments.size()] ) ); - } - - if ( typeUtils.isAssignable( sourceType.getTypeMirror(), parameterType ) ) { - return true; - } - } - - return false; - } - } -} +/** + * Copyright 2012-2016 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.internal.processor.creation; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import org.mapstruct.ap.internal.conversion.ConversionProvider; +import org.mapstruct.ap.internal.conversion.Conversions; +import org.mapstruct.ap.internal.model.AssignmentFactory; +import org.mapstruct.ap.internal.model.MapperReference; +import org.mapstruct.ap.internal.model.MappingBuilderContext.MappingResolver; +import org.mapstruct.ap.internal.model.MethodReference; +import org.mapstruct.ap.internal.model.VirtualMappingMethod; +import org.mapstruct.ap.internal.model.assignment.Assignment; +import org.mapstruct.ap.internal.model.common.ConversionContext; +import org.mapstruct.ap.internal.model.common.DefaultConversionContext; +import org.mapstruct.ap.internal.model.common.Type; +import org.mapstruct.ap.internal.model.common.TypeFactory; +import org.mapstruct.ap.internal.model.source.Method; +import org.mapstruct.ap.internal.model.source.SourceMethod; +import org.mapstruct.ap.internal.model.source.builtin.BuiltInMappingMethods; +import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; +import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; +import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; +import org.mapstruct.ap.internal.util.Collections; +import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.Message; +import org.mapstruct.ap.internal.util.Strings; + +/** + * The one and only implementation of {@link MappingResolver}. The class has been split into an interface an + * implementation for the sake of avoiding package dependencies. Specifically, this implementation refers to classes + * which should not be exposed to the {@code model} package. + * + * @author Sjaak Derksen + */ +public class MappingResolverImpl implements MappingResolver { + + private final FormattingMessager messager; + private final Types typeUtils; + private final TypeFactory typeFactory; + + private final List sourceModel; + private final List mapperReferences; + + private final Conversions conversions; + private final BuiltInMappingMethods builtInMethods; + private final MethodSelectors methodSelectors; + + /** + * Private methods which are not present in the original mapper interface and are added to map certain property + * types. + */ + private final Set usedVirtualMappings = new HashSet(); + + public MappingResolverImpl(FormattingMessager messager, Elements elementUtils, Types typeUtils, + TypeFactory typeFactory, List sourceModel, + List mapperReferences) { + this.messager = messager; + this.typeUtils = typeUtils; + this.typeFactory = typeFactory; + + this.sourceModel = sourceModel; + this.mapperReferences = mapperReferences; + + this.conversions = new Conversions( elementUtils, typeFactory ); + this.builtInMethods = new BuiltInMappingMethods( typeFactory ); + this.methodSelectors = new MethodSelectors( + typeUtils, + elementUtils, + typeFactory + ); + } + + @Override + @SuppressWarnings("checkstyle:parameternumber") + public Assignment getTargetAssignment(Method mappingMethod, String mappedElement, Type sourceType, + Type targetType, String targetPropertyName, String dateFormat, List qualifiers, + List qualifyingNames, TypeMirror resultType, String sourceReference, boolean preferUpdateMapping) { + + SelectionCriteria criteria = + new SelectionCriteria(qualifiers, qualifyingNames, targetPropertyName, resultType, preferUpdateMapping ); + + ResolvingAttempt attempt = new ResolvingAttempt( + sourceModel, + mappingMethod, + mappedElement, + dateFormat, + sourceReference, + criteria + ); + + return attempt.getTargetAssignment( sourceType, targetType ); + } + + @Override + public Set getUsedVirtualMappings() { + return usedVirtualMappings; + } + + @Override + public MethodReference getFactoryMethod( Method mappingMethod, Type targetType, List qualifiers, + List qualifyingNames, TypeMirror resultType ) { + + SelectionCriteria criteria = new SelectionCriteria( qualifiers, qualifyingNames, null, resultType, false ); + + ResolvingAttempt attempt = new ResolvingAttempt( + sourceModel, + mappingMethod, + null, + null, + null, + criteria + ); + + SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType ); + if ( matchingSourceMethod != null ) { + MapperReference ref = attempt.findMapperReference( matchingSourceMethod ); + return new MethodReference( matchingSourceMethod, ref, null ); + } + return null; + + } + + private class ResolvingAttempt { + + private final Method mappingMethod; + private final String mappedElement; + private final List methods; + private final String dateFormat; + private final SelectionCriteria selectionCriteria; + private final String sourceReference; + private final boolean savedPreferUpdateMapping; + + // resolving via 2 steps creates the possibillity of wrong matches, first builtin method matches, + // second doesn't. In that case, the first builtin method should not lead to a virtual method + // so this set must be cleared. + private final Set virtualMethodCandidates; + + private ResolvingAttempt(List sourceModel, Method mappingMethod, String mappedElement, + String dateFormat, String sourceReference, SelectionCriteria criteria) { + + this.mappingMethod = mappingMethod; + this.mappedElement = mappedElement; + this.methods = filterPossibleCandidateMethods( sourceModel ); + this.dateFormat = dateFormat; + this.sourceReference = sourceReference; + this.virtualMethodCandidates = new HashSet(); + this.selectionCriteria = criteria; + this.savedPreferUpdateMapping = criteria.isPreferUpdateMapping(); + } + + private List filterPossibleCandidateMethods(List candidateMethods) { + List result = new ArrayList( candidateMethods.size() ); + for ( T candidate : candidateMethods ) { + if ( isCandidateForMapping( candidate ) ) { + result.add( candidate ); + } + } + + return result; + } + + private Assignment getTargetAssignment(Type sourceType, Type targetType) { + + // first simple mapping method + Assignment referencedMethod = resolveViaMethod( sourceType, targetType, false ); + if ( referencedMethod != null ) { + referencedMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + return referencedMethod; + } + + // then direct assignable + if ( sourceType.isAssignableTo( targetType ) || + isAssignableThroughCollectionCopyConstructor( sourceType, targetType ) ) { + Assignment simpleAssignment = AssignmentFactory.createDirect( sourceReference ); + return simpleAssignment; + } + + // then type conversion + Assignment conversion = resolveViaConversion( sourceType, targetType ); + if ( conversion != null ) { + conversion.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + return conversion; + } + + // check for a built-in method + Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType ); + if ( builtInMethod != null ) { + builtInMethod.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + usedVirtualMappings.addAll( virtualMethodCandidates ); + return builtInMethod; + } + + // 2 step method, first: method(method(source)) + referencedMethod = resolveViaMethodAndMethod( sourceType, targetType ); + if ( referencedMethod != null ) { + usedVirtualMappings.addAll( virtualMethodCandidates ); + return referencedMethod; + } + + // 2 step method, then: method(conversion(source)) + referencedMethod = resolveViaConversionAndMethod( sourceType, targetType ); + if ( referencedMethod != null ) { + usedVirtualMappings.addAll( virtualMethodCandidates ); + return referencedMethod; + } + + // stop here when looking for update methods. + selectionCriteria.setPreferUpdateMapping( false ); + + // 2 step method, finally: conversion(method(source)) + conversion = resolveViaMethodAndConversion( sourceType, targetType ); + if ( conversion != null ) { + usedVirtualMappings.addAll( virtualMethodCandidates ); + return conversion; + } + + // if nothing works, alas, the result is null + return null; + } + + private Assignment resolveViaConversion(Type sourceType, Type targetType) { + ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); + + if ( conversionProvider == null ) { + return null; + } + + ConversionContext ctx = + new DefaultConversionContext( typeFactory, messager, sourceType, targetType, dateFormat ); + return conversionProvider.to( ctx ); + } + + /** + * Returns a reference to a method mapping the given source type to the given target type, if such a method + * exists. + */ + private Assignment resolveViaMethod(Type sourceType, Type targetType, boolean considerBuiltInMethods) { + + // first try to find a matching source method + SourceMethod matchingSourceMethod = getBestMatch( methods, sourceType, targetType ); + + if ( matchingSourceMethod != null ) { + return getMappingMethodReference( matchingSourceMethod, targetType ); + } + + if ( considerBuiltInMethods ) { + return resolveViaBuiltInMethod( sourceType, targetType ); + } + + return null; + } + + private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) { + BuiltInMethod matchingBuiltInMethod = + getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); + + if ( matchingBuiltInMethod != null ) { + virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) ); + ConversionContext ctx = new DefaultConversionContext( typeFactory, messager, + sourceType, + targetType, dateFormat ); + Assignment methodReference = AssignmentFactory.createMethodReference( matchingBuiltInMethod, ctx ); + methodReference.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + return methodReference; + } + + return null; + } + + /** + * Suppose mapping required from A to C and: + *
      + *
    • no direct referenced mapping method either built-in or referenced is available from A to C
    • + *
    • no conversion is available
    • + *
    • there is a method from A to B, methodX
    • + *
    • there is a method from B to C, methodY
    • + *
    + * then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) ) + */ + private Assignment resolveViaMethodAndMethod(Type sourceType, Type targetType) { + + List methodYCandidates = new ArrayList( methods ); + methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); + + Assignment methodRefY = null; + + // Iterate over all source methods. Check if the return type matches with the parameter that we need. + // so assume we need a method from A to C we look for a methodX from A to B (all methods in the + // list form such a candidate). + // For each of the candidates, we need to look if there's a methodY, either + // sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match + // a nested method call can be called. so C = methodY( methodX (A) ) + for ( Method methodYCandidate : methodYCandidates ) { + methodRefY = + resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); + + if ( methodRefY != null ) { + selectionCriteria.setPreferUpdateMapping( false ); + Assignment methodRefX = + resolveViaMethod( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType(), true ); + selectionCriteria.setPreferUpdateMapping( savedPreferUpdateMapping ); + if ( methodRefX != null ) { + methodRefY.setAssignment( methodRefX ); + methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + break; + } + else { + // both should match; + virtualMethodCandidates.clear(); + methodRefY = null; + } + } + } + return methodRefY; + } + + /** + * Suppose mapping required from A to C and: + *
      + *
    • there is a conversion from A to B, conversionX
    • + *
    • there is a method from B to C, methodY
    • + *
    + * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) + */ + private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) { + + List methodYCandidates = new ArrayList( methods ); + methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); + + Assignment methodRefY = null; + + for ( Method methodYCandidate : methodYCandidates ) { + methodRefY = + resolveViaMethod( methodYCandidate.getSourceParameters().get( 0 ).getType(), targetType, true ); + + if ( methodRefY != null ) { + Assignment conversionXRef = + resolveViaConversion( sourceType, methodYCandidate.getSourceParameters().get( 0 ).getType() ); + if ( conversionXRef != null ) { + methodRefY.setAssignment( conversionXRef ); + conversionXRef.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + break; + } + else { + // both should match + virtualMethodCandidates.clear(); + methodRefY = null; + } + } + } + return methodRefY; + } + + /** + * Suppose mapping required from A to C and: + *
      + *
    • there is a conversion from A to B, conversionX
    • + *
    • there is a method from B to C, methodY
    • + *
    + * then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) ) + */ + private Assignment resolveViaMethodAndConversion(Type sourceType, Type targetType) { + + List methodXCandidates = new ArrayList( methods ); + methodXCandidates.addAll( builtInMethods.getBuiltInMethods() ); + + Assignment conversionYRef = null; + + // search the other way around + for ( Method methodXCandidate : methodXCandidates ) { + if ( methodXCandidate.getMappingTargetParameter() != null ) { + continue; + } + + Assignment methodRefX = resolveViaMethod( + sourceType, + methodXCandidate.getReturnType(), + true + ); + if ( methodRefX != null ) { + conversionYRef = resolveViaConversion( methodXCandidate.getReturnType(), targetType ); + if ( conversionYRef != null ) { + conversionYRef.setAssignment( methodRefX ); + methodRefX.setAssignment( AssignmentFactory.createDirect( sourceReference ) ); + break; + } + else { + // both should match; + virtualMethodCandidates.clear(); + conversionYRef = null; + } + } + } + return conversionYRef; + } + + private boolean isCandidateForMapping(Method methodCandidate) { + return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate ); + } + + private boolean isCreateMethodForMapping(Method methodCandidate) { + // a create method may not return void and has no target parameter + return methodCandidate.getSourceParameters().size() == 1 + && !methodCandidate.getReturnType().isVoid() + && methodCandidate.getMappingTargetParameter() == null + && !methodCandidate.isLifecycleCallbackMethod(); + } + + private boolean isUpdateMethodForMapping(Method methodCandidate) { + // an update method may, or may not return void and has a target parameter + return methodCandidate.getSourceParameters().size() == 1 + && methodCandidate.getMappingTargetParameter() != null + && !methodCandidate.isLifecycleCallbackMethod(); + } + + private T getBestMatch(List methods, Type sourceType, Type returnType) { + + List candidates = methodSelectors.getMatchingMethods( + mappingMethod, + methods, + sourceType, + returnType, + selectionCriteria + ); + + // raise an error if more than one mapping method is suitable to map the given source type + // into the target type + if ( candidates.size() > 1 ) { + + if ( mappedElement != null ) { + messager.printMessage( mappingMethod.getExecutable(), + Message.GENERAL_AMBIGIOUS_MAPPING_METHOD, + mappedElement, + returnType, + Strings.join( candidates, ", " ) + ); + } + else { + messager.printMessage( mappingMethod.getExecutable(), + Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, + returnType, + Strings.join( candidates, ", " ) + ); + } + } + + if ( !candidates.isEmpty() ) { + return candidates.get( 0 ); + } + + return null; + } + + private Assignment getMappingMethodReference(SourceMethod method, + Type targetType) { + MapperReference mapperReference = findMapperReference( method ); + + return AssignmentFactory.createMethodReference( + method, + mapperReference, + SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null + ); + } + + private MapperReference findMapperReference(SourceMethod method) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + ref.setUsed( !method.isStatic() ); + ref.setTypeRequiresImport( true ); + return ref; + } + } + return null; + } + + /** + * Whether the given source and target type are both a collection type or both a map type and the source value + * can be propagated via a copy constructor. + */ + private boolean isAssignableThroughCollectionCopyConstructor(Type sourceType, Type targetType) { + boolean bothCollectionOrMap = false; + + if ( ( sourceType.isCollectionType() && targetType.isCollectionType() ) || + ( sourceType.isMapType() && targetType.isMapType() ) ) { + bothCollectionOrMap = true; + } + + if ( bothCollectionOrMap ) { + return hasCompatibleCopyConstructor( + sourceType, + targetType.getImplementationType() != null ? targetType.getImplementationType() : targetType + ); + } + + return false; + } + + /** + * Whether the given target type has a single-argument constructor which accepts the given source type. + * + * @param sourceType the source type + * @param targetType the target type + * @return {@code true} if the target type has a constructor accepting the given source type, {@code false} + * otherwise. + */ + private boolean hasCompatibleCopyConstructor(Type sourceType, Type targetType) { + if ( targetType.isPrimitive() ) { + return false; + } + + List targetTypeConstructors = ElementFilter.constructorsIn( + targetType.getTypeElement().getEnclosedElements() ); + + for ( ExecutableElement constructor : targetTypeConstructors ) { + if ( constructor.getParameters().size() != 1 ) { + continue; + } + + // get the constructor resolved against the type arguments of specific target type + ExecutableType typedConstructor = (ExecutableType) typeUtils.asMemberOf( + (DeclaredType) targetType.getTypeMirror(), + constructor ); + + TypeMirror parameterType = Collections.first( typedConstructor.getParameterTypes() ); + if ( parameterType.getKind() == TypeKind.DECLARED ) { + // replace any possible type bounds in the type parameters of the parameter types, as in JDK super + // type bounds in the arguments are returned from asMemberOf with "? extends ? super XX" + // + // It might also be enough to just remove "? super" from type parameters of + // targetType.getTypeMirror() in case we're in JDK. And that would be something that should be + // handled in SpecificCompilerWorkarounds... + + DeclaredType p = (DeclaredType) parameterType; + List typeArguments = new ArrayList( p.getTypeArguments().size() ); + + for ( TypeMirror tArg : p.getTypeArguments() ) { + typeArguments.add( typeFactory.getTypeBound( tArg ) ); + } + parameterType = typeUtils.getDeclaredType( + (TypeElement) p.asElement(), + typeArguments.toArray( new TypeMirror[typeArguments.size()] ) ); + } + + if ( typeUtils.isAssignable( sourceType.getTypeMirror(), parameterType ) ) { + return true; + } + } + + return false; + } + } +}