From 59719dc6cb7b515b62bac08d1e84635e6b7af5a6 Mon Sep 17 00:00:00 2001 From: sjaakd Date: Mon, 10 Mar 2014 21:33:04 +0100 Subject: [PATCH] #157 refactoring: moving all mapping method resolving stuff to class with own responsibility --- .../ap/processor/MapperCreationProcessor.java | 236 ++------------ .../ap/processor/MappingMethodResolver.java | 302 ++++++++++++++++++ 2 files changed, 320 insertions(+), 218 deletions(-) create mode 100644 processor/src/main/java/org/mapstruct/ap/processor/MappingMethodResolver.java diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index cf9090229..1d163b0fc 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -36,8 +36,6 @@ import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; -import org.mapstruct.ap.conversion.ConversionProvider; -import org.mapstruct.ap.conversion.Conversions; import org.mapstruct.ap.model.BeanMappingMethod; import org.mapstruct.ap.model.Decorator; import org.mapstruct.ap.model.DefaultMapperReference; @@ -51,18 +49,12 @@ import org.mapstruct.ap.model.MappingMethod; import org.mapstruct.ap.model.MethodReference; import org.mapstruct.ap.model.PropertyMapping; import org.mapstruct.ap.model.TypeConversion; -import org.mapstruct.ap.model.VirtualMappingMethod; -import org.mapstruct.ap.model.common.ConversionContext; -import org.mapstruct.ap.model.common.DefaultConversionContext; import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.EnumMapping; import org.mapstruct.ap.model.source.Mapping; -import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.SourceMethod; -import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods; -import org.mapstruct.ap.model.source.builtin.BuiltInMethod; import org.mapstruct.ap.model.source.selector.MethodSelectors; import org.mapstruct.ap.option.Options; import org.mapstruct.ap.option.ReportingPolicy; @@ -86,16 +78,10 @@ public class MapperCreationProcessor implements ModelElementProcessor virtualMethods; @Override public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List sourceModel) { @@ -105,11 +91,9 @@ public class MapperCreationProcessor implements ModelElementProcessor(); - this.methodSelectors = new MethodSelectors( typeUtils, typeFactory ); + this.mappingMethodResolver = new MappingMethodResolver(messager, typeFactory, elementUtils, typeUtils ); + return getMapper( mapperTypeElement, sourceModel ); } @@ -123,7 +107,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences = getReferencedMappers( element ); List mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy ); - mappingMethods.addAll( virtualMethods ); + mappingMethods.addAll( mappingMethodResolver.getVirtualMethodsToCreate() ); Mapper mapper = new Mapper.Builder() .element( element ) @@ -657,7 +641,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences, - List methods, - Type sourceType, - Type targetType, - String targetPropertyName, - String dateFormat) { - // first try to find a matching source method - SourceMethod matchingSourceMethod = getBestMatch( - method, - mappedElement, - methods, - sourceType, - targetType, - targetPropertyName - ); - - if ( matchingSourceMethod != null ) { - return getMappingMethodReference( matchingSourceMethod, mapperReferences, targetType ); - } - - // then a matching built-in method - BuiltInMethod matchingBuiltInMethod = getBestMatch( - method, - mappedElement, - builtInMethods.getBuiltInMethods(), - sourceType, - targetType, - targetPropertyName - ); - - return matchingBuiltInMethod != null ? - getMappingMethodReference( matchingBuiltInMethod, targetType, dateFormat ) : 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 ) ) - * - * @param mappingMethod target mapping method - * @param mappedElement used for error messages - * @param mapperReferences list of references to mapper - * @param methods list of candidate methods - * @param parameterType parameter to match - * @param returnType return type to match - * @param dateFormat used for formatting dates in build in methods that need context information - * - * @return a method reference. - */ - private MethodReference getMappingMethodReferenceBasedOnParameter(SourceMethod mappingMethod, - String mappedElement, - List - mapperReferences, - List methods, - Type parameterType, - Type returnType, - String targetPropertyName, - String dateFormat) { - - List methodYCandidates = new ArrayList( methods ); - methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); - - MethodReference 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 ) { - if ( methodYCandidate.getSourceParameters().size() == 1 ) { - methodRefY = getMappingMethodReferenceBasedOnMethod( - mappingMethod, - mappedElement, - mapperReferences, - methods, - methodYCandidate.getSourceParameters().get( 0 ).getType(), - returnType, - targetPropertyName, - dateFormat - ); - if ( methodRefY != null ) { - MethodReference methodRefX = getMappingMethodReferenceBasedOnMethod( - mappingMethod, - mappedElement, - mapperReferences, - methods, - parameterType, - methodYCandidate.getSourceParameters().get( 0 ).getType(), - targetPropertyName, - dateFormat - ); - if ( methodRefX != null ) { - methodRefY.setMethodRefChild( methodRefX ); - break; - } - else { - // both should match; - methodRefY = null; - } - } - } - } - return methodRefY; - } - - private T getBestMatch(SourceMethod mappingMethod, - String mappedElement, - List methods, - Type sourceType, - Type returnType, - String targetPropertyName) { - - List candidates = methodSelectors.getMatchingMethods( - mappingMethod, - methods, - sourceType, - returnType, - targetPropertyName - ); - - // 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 ) { - - messager.printMessage( - Kind.ERROR, - String.format( - "Ambiguous mapping methods found for mapping " + mappedElement + " from %s to %s: %s.", - sourceType, - returnType, - Strings.join( candidates, ", " ) - ), - mappingMethod.getExecutable() - ); - } - - if ( !candidates.isEmpty() ) { - return candidates.get( 0 ); - } - - return null; - } - - private MethodReference getMappingMethodReference(SourceMethod method, List mapperReferences, - Type targetType) { - MapperReference mapperReference = findMapperReference( mapperReferences, method ); - - return new MethodReference( - method, - mapperReference, - SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null - ); - } - private MapperReference findMapperReference(List mapperReferences, SourceMethod method) { for ( MapperReference ref : mapperReferences ) { if ( ref.getType().equals( method.getDeclaringMapper() ) ) { @@ -1225,12 +1031,6 @@ public class MapperCreationProcessor implements ModelElementProcessor + *
  • conversions
  • + *
  • methods
  • + * + * + * conversions are essentially one line mappings, such as String to Integer and Integer to Long + * methods come in some varieties: + *
      + *
    • referenced mapping methods, these are methods implemented (or referenced) by the user. Sometimes indicated + * with the 'uses' in the mapping annotations or part of the abstract mapper class
    • + *
    • generated mapping methods (by means of MapStruct)
    • + *
    • built in methods
    • + *
    + * + * @author Sjaak Derksen + */ +public class MappingMethodResolver { + + private final Messager messager; + private final TypeFactory typeFactory; + 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 virtualMethods; + + public MappingMethodResolver(Messager messager, TypeFactory typeFactory, Elements elementUtils, Types typeUtils) { + this.messager = messager; + this.typeFactory = typeFactory; + this.conversions = new Conversions( elementUtils, typeFactory ); + this.builtInMethods = new BuiltInMappingMethods( typeFactory ); + this.virtualMethods = new HashSet(); + this.methodSelectors = new MethodSelectors( typeUtils, typeFactory ); + } + + + public TypeConversion getConversion(Type sourceType, Type targetType, String dateFormat, String sourceReference) { + ConversionProvider conversionProvider = conversions.getConversion( sourceType, targetType ); + + if ( conversionProvider == null ) { + return null; + } + + return conversionProvider.to( + sourceReference, + new DefaultConversionContext( typeFactory, targetType, dateFormat ) + ); + } + + /** + * Returns a reference to a method mapping the given source type to the given target type, if such a method exists. + * + * @param mappingMethod target mapping method + * @param mappedElement used for error messages + * @param mapperReferences list of references to mapper + * @param methods list of candidate methods + * @param sourceType parameter to match + * @param targetType return type to match + * @param targetPropertyName name of the target property + * @param dateFormat used for formatting dates in build in methods that need context information + * + * @return a method reference. + */ + public MethodReference getMappingMethodReferenceBasedOnMethod(SourceMethod mappingMethod, + String mappedElement, + List mapperReferences, + List methods, + Type sourceType, + Type targetType, + String targetPropertyName, + String dateFormat) { + // first try to find a matching source method + SourceMethod matchingSourceMethod = getBestMatch( + mappingMethod, + mappedElement, + methods, + sourceType, + targetType, + targetPropertyName + ); + + if ( matchingSourceMethod != null ) { + return getMappingMethodReference( matchingSourceMethod, mapperReferences, targetType ); + } + + // then a matching built-in method + BuiltInMethod matchingBuiltInMethod = getBestMatch( + mappingMethod, + mappedElement, + builtInMethods.getBuiltInMethods(), + sourceType, + targetType, + targetPropertyName + ); + + return matchingBuiltInMethod != null ? + getMappingMethodReference( matchingBuiltInMethod, targetType, dateFormat ) : 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 ) ) + * + * @param mappingMethod target mapping method + * @param mappedElement used for error messages + * @param mapperReferences list of references to mapper + * @param methods list of candidate methods + * @param sourceType parameter to match + * @param targetType return type to match + * @param targetPropertyName name of the target property + * @param dateFormat used for formatting dates in build in methods that need context information + * + * @return a method reference. + */ + public MethodReference getMappingMethodReferenceBasedOnParameter(SourceMethod mappingMethod, + String mappedElement, + List mapperReferences, + List methods, + Type sourceType, + Type targetType, + String targetPropertyName, + String dateFormat) { + + List methodYCandidates = new ArrayList( methods ); + methodYCandidates.addAll( builtInMethods.getBuiltInMethods() ); + + MethodReference 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 ) { + if ( methodYCandidate.getSourceParameters().size() == 1 ) { + methodRefY = getMappingMethodReferenceBasedOnMethod( + mappingMethod, + mappedElement, + mapperReferences, + methods, + methodYCandidate.getSourceParameters().get( 0 ).getType(), + targetType, + targetPropertyName, + dateFormat + ); + if ( methodRefY != null ) { + MethodReference methodRefX = getMappingMethodReferenceBasedOnMethod( + mappingMethod, + mappedElement, + mapperReferences, + methods, + sourceType, + methodYCandidate.getSourceParameters().get( 0 ).getType(), + targetPropertyName, + dateFormat + ); + if ( methodRefX != null ) { + methodRefY.setMethodRefChild( methodRefX ); + break; + } + else { + // both should match; + methodRefY = null; + } + } + } + } + return methodRefY; + } + + private T getBestMatch(SourceMethod mappingMethod, + String mappedElement, + List methods, + Type sourceType, + Type returnType, + String targetPropertyName) { + + List candidates = methodSelectors.getMatchingMethods( + mappingMethod, + methods, + sourceType, + returnType, + targetPropertyName + ); + + // 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 ) { + + messager.printMessage( + Kind.ERROR, + String.format( + "Ambiguous mapping methods found for mapping " + mappedElement + " from %s to %s: %s.", + sourceType, + returnType, + Strings.join( candidates, ", " ) + ), + mappingMethod.getExecutable() + ); + } + + if ( !candidates.isEmpty() ) { + return candidates.get( 0 ); + } + + return null; + } + + public Set getVirtualMethodsToCreate() { + return virtualMethods; + } + + + private MethodReference getMappingMethodReference(SourceMethod method, List mapperReferences, + Type targetType) { + MapperReference mapperReference = findMapperReference( mapperReferences, method ); + + return new MethodReference( + method, + mapperReference, + SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null + ); + } + + private MapperReference findMapperReference(List mapperReferences, SourceMethod method) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + return ref; + } + } + return null; + } + + private MethodReference getMappingMethodReference(BuiltInMethod method, Type returnType, String dateFormat) { + virtualMethods.add( new VirtualMappingMethod( method ) ); + ConversionContext ctx = new DefaultConversionContext( typeFactory, returnType, dateFormat ); + return new MethodReference( method, ctx ); + } + + +}