mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#157 refactoring: moving all mapping method resolving stuff to class with own responsibility
This commit is contained in:
parent
81c936190e
commit
59719dc6cb
@ -36,8 +36,6 @@ import javax.lang.model.util.Elements;
|
|||||||
import javax.lang.model.util.Types;
|
import javax.lang.model.util.Types;
|
||||||
import javax.tools.Diagnostic.Kind;
|
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.BeanMappingMethod;
|
||||||
import org.mapstruct.ap.model.Decorator;
|
import org.mapstruct.ap.model.Decorator;
|
||||||
import org.mapstruct.ap.model.DefaultMapperReference;
|
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.MethodReference;
|
||||||
import org.mapstruct.ap.model.PropertyMapping;
|
import org.mapstruct.ap.model.PropertyMapping;
|
||||||
import org.mapstruct.ap.model.TypeConversion;
|
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.Parameter;
|
||||||
import org.mapstruct.ap.model.common.Type;
|
import org.mapstruct.ap.model.common.Type;
|
||||||
import org.mapstruct.ap.model.common.TypeFactory;
|
import org.mapstruct.ap.model.common.TypeFactory;
|
||||||
import org.mapstruct.ap.model.source.EnumMapping;
|
import org.mapstruct.ap.model.source.EnumMapping;
|
||||||
import org.mapstruct.ap.model.source.Mapping;
|
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.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.model.source.selector.MethodSelectors;
|
||||||
import org.mapstruct.ap.option.Options;
|
import org.mapstruct.ap.option.Options;
|
||||||
import org.mapstruct.ap.option.ReportingPolicy;
|
import org.mapstruct.ap.option.ReportingPolicy;
|
||||||
@ -86,16 +78,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
private Options options;
|
private Options options;
|
||||||
|
|
||||||
private TypeFactory typeFactory;
|
private TypeFactory typeFactory;
|
||||||
private Conversions conversions;
|
|
||||||
private BuiltInMappingMethods builtInMethods;
|
|
||||||
|
|
||||||
private MethodSelectors methodSelectors;
|
private MappingMethodResolver mappingMethodResolver;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private methods which are not present in the original mapper interface and are added to map certain property
|
|
||||||
* types.
|
|
||||||
*/
|
|
||||||
private Set<VirtualMappingMethod> virtualMethods;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
|
public Mapper process(ProcessorContext context, TypeElement mapperTypeElement, List<SourceMethod> sourceModel) {
|
||||||
@ -105,11 +91,9 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
this.options = context.getOptions();
|
this.options = context.getOptions();
|
||||||
|
|
||||||
this.typeFactory = context.getTypeFactory();
|
this.typeFactory = context.getTypeFactory();
|
||||||
this.conversions = new Conversions( elementUtils, typeFactory );
|
|
||||||
|
|
||||||
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
|
this.mappingMethodResolver = new MappingMethodResolver(messager, typeFactory, elementUtils, typeUtils );
|
||||||
this.virtualMethods = new HashSet<VirtualMappingMethod>();
|
|
||||||
this.methodSelectors = new MethodSelectors( typeUtils, typeFactory );
|
|
||||||
|
|
||||||
return getMapper( mapperTypeElement, sourceModel );
|
return getMapper( mapperTypeElement, sourceModel );
|
||||||
}
|
}
|
||||||
@ -123,7 +107,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
|
ReportingPolicy unmappedTargetPolicy = getEffectiveUnmappedTargetPolicy( element );
|
||||||
List<MapperReference> mapperReferences = getReferencedMappers( element );
|
List<MapperReference> mapperReferences = getReferencedMappers( element );
|
||||||
List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy );
|
List<MappingMethod> mappingMethods = getMappingMethods( mapperReferences, methods, unmappedTargetPolicy );
|
||||||
mappingMethods.addAll( virtualMethods );
|
mappingMethods.addAll( mappingMethodResolver.getVirtualMethodsToCreate() );
|
||||||
|
|
||||||
Mapper mapper = new Mapper.Builder()
|
Mapper mapper = new Mapper.Builder()
|
||||||
.element( element )
|
.element( element )
|
||||||
@ -657,7 +641,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
String targetPropertyName = Executables.getPropertyName( targetAcessor );
|
String targetPropertyName = Executables.getPropertyName( targetAcessor );
|
||||||
|
|
||||||
String mappedElement = "property '" + Executables.getPropertyName( sourceAccessor ) + "'";
|
String mappedElement = "property '" + Executables.getPropertyName( sourceAccessor ) + "'";
|
||||||
MethodReference mappingMethodReference = getMappingMethodReferenceBasedOnMethod(
|
MethodReference mappingMethodReference = mappingMethodResolver.getMappingMethodReferenceBasedOnMethod(
|
||||||
method,
|
method,
|
||||||
mappedElement,
|
mappedElement,
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -668,7 +652,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
dateFormat
|
dateFormat
|
||||||
);
|
);
|
||||||
|
|
||||||
TypeConversion conversion = getConversion(
|
TypeConversion conversion = mappingMethodResolver.getConversion(
|
||||||
sourceType,
|
sourceType,
|
||||||
targetType,
|
targetType,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
@ -690,7 +674,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
if ( !isPropertyMappable( property ) ) {
|
if ( !isPropertyMappable( property ) ) {
|
||||||
|
|
||||||
// when not mappable, try again with another property mapping method based on parameter only.
|
// when not mappable, try again with another property mapping method based on parameter only.
|
||||||
mappingMethodReference = getMappingMethodReferenceBasedOnParameter(
|
mappingMethodReference = mappingMethodResolver.getMappingMethodReferenceBasedOnParameter(
|
||||||
method,
|
method,
|
||||||
"property '" + Executables.getPropertyName( sourceAccessor ) + "'",
|
"property '" + Executables.getPropertyName( sourceAccessor ) + "'",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -737,7 +721,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
Type targetElementType = method.getResultType().getTypeParameters().get( 0 );
|
Type targetElementType = method.getResultType().getTypeParameters().get( 0 );
|
||||||
String dateFormat = method.getIterableMapping() != null ? method.getIterableMapping().getDateFormat() : null;
|
String dateFormat = method.getIterableMapping() != null ? method.getIterableMapping().getDateFormat() : null;
|
||||||
|
|
||||||
TypeConversion conversion = getConversion(
|
TypeConversion conversion = mappingMethodResolver.getConversion(
|
||||||
sourceElementType,
|
sourceElementType,
|
||||||
targetElementType,
|
targetElementType,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
@ -747,7 +731,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
MethodReference mappingMethodReference = getMappingMethodReferenceBasedOnMethod(
|
MethodReference mappingMethodReference = mappingMethodResolver.getMappingMethodReferenceBasedOnMethod(
|
||||||
method,
|
method,
|
||||||
"collection element",
|
"collection element",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -761,7 +745,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
if ( !sourceElementType.isAssignableTo( targetElementType ) && conversion == null &&
|
if ( !sourceElementType.isAssignableTo( targetElementType ) && conversion == null &&
|
||||||
mappingMethodReference == null ) {
|
mappingMethodReference == null ) {
|
||||||
// when no conversion is found and no mapping method try match based on parameter only
|
// when no conversion is found and no mapping method try match based on parameter only
|
||||||
mappingMethodReference = getMappingMethodReferenceBasedOnParameter(
|
mappingMethodReference = mappingMethodResolver.getMappingMethodReferenceBasedOnParameter(
|
||||||
method,
|
method,
|
||||||
"collection element",
|
"collection element",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -805,7 +789,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
Type keyTargetType = resultTypeParams.get( 0 );
|
Type keyTargetType = resultTypeParams.get( 0 );
|
||||||
String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null;
|
String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null;
|
||||||
|
|
||||||
MethodReference keyMappingMethod = getMappingMethodReferenceBasedOnMethod(
|
MethodReference keyMappingMethod = mappingMethodResolver.getMappingMethodReferenceBasedOnMethod(
|
||||||
method,
|
method,
|
||||||
"map key",
|
"map key",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -815,11 +799,12 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
null, // there is no targetPropertyName
|
null, // there is no targetPropertyName
|
||||||
keyDateFormat
|
keyDateFormat
|
||||||
);
|
);
|
||||||
TypeConversion keyConversion = getConversion( keySourceType, keyTargetType, keyDateFormat, "entry.getKey()" );
|
TypeConversion keyConversion = mappingMethodResolver.getConversion( keySourceType, keyTargetType,
|
||||||
|
keyDateFormat, "entry.getKey()" );
|
||||||
|
|
||||||
if ( !keySourceType.isAssignableTo( keyTargetType ) && keyConversion == null && keyMappingMethod == null ) {
|
if ( !keySourceType.isAssignableTo( keyTargetType ) && keyConversion == null && keyMappingMethod == null ) {
|
||||||
// when no conversion is found and no mapping method try match based on parameter only
|
// when no conversion is found and no mapping method try match based on parameter only
|
||||||
keyMappingMethod = getMappingMethodReferenceBasedOnParameter(
|
keyMappingMethod = mappingMethodResolver.getMappingMethodReferenceBasedOnParameter(
|
||||||
method,
|
method,
|
||||||
"map key",
|
"map key",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -848,7 +833,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
Type valueTargetType = resultTypeParams.get( 1 );
|
Type valueTargetType = resultTypeParams.get( 1 );
|
||||||
String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null;
|
String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null;
|
||||||
|
|
||||||
MethodReference valueMappingMethod = getMappingMethodReferenceBasedOnMethod(
|
MethodReference valueMappingMethod = mappingMethodResolver.getMappingMethodReferenceBasedOnMethod(
|
||||||
method,
|
method,
|
||||||
"map value",
|
"map value",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -858,7 +843,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
null, // there is no targetPropertyName
|
null, // there is no targetPropertyName
|
||||||
valueDateFormat
|
valueDateFormat
|
||||||
);
|
);
|
||||||
TypeConversion valueConversion = getConversion(
|
TypeConversion valueConversion = mappingMethodResolver.getConversion(
|
||||||
valueSourceType,
|
valueSourceType,
|
||||||
valueTargetType,
|
valueTargetType,
|
||||||
valueDateFormat,
|
valueDateFormat,
|
||||||
@ -868,7 +853,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
if ( !valueSourceType.isAssignableTo( valueTargetType ) && valueConversion == null &&
|
if ( !valueSourceType.isAssignableTo( valueTargetType ) && valueConversion == null &&
|
||||||
valueMappingMethod == null ) {
|
valueMappingMethod == null ) {
|
||||||
// when no conversion is found and no mapping method try match based on parameter only
|
// when no conversion is found and no mapping method try match based on parameter only
|
||||||
keyMappingMethod = getMappingMethodReferenceBasedOnParameter(
|
keyMappingMethod = mappingMethodResolver.getMappingMethodReferenceBasedOnParameter(
|
||||||
method,
|
method,
|
||||||
"map value",
|
"map value",
|
||||||
mapperReferences,
|
mapperReferences,
|
||||||
@ -1037,185 +1022,6 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
return unmappedSourceEnumConstants.isEmpty();
|
return unmappedSourceEnumConstants.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private 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.
|
|
||||||
*/
|
|
||||||
private MethodReference getMappingMethodReferenceBasedOnMethod(SourceMethod method,
|
|
||||||
String mappedElement,
|
|
||||||
List<MapperReference> mapperReferences,
|
|
||||||
List<SourceMethod> 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:
|
|
||||||
* <ul>
|
|
||||||
* <li>no direct referenced mapping method either built-in or referenced is available from A to C</li>
|
|
||||||
* <li>no conversion is available</li>
|
|
||||||
* <li>there is a method from A to B, methodX</li>
|
|
||||||
* <li>there is a method from B to C, methodY</li>
|
|
||||||
* </ul>
|
|
||||||
* 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<MapperReference>
|
|
||||||
mapperReferences,
|
|
||||||
List<SourceMethod> methods,
|
|
||||||
Type parameterType,
|
|
||||||
Type returnType,
|
|
||||||
String targetPropertyName,
|
|
||||||
String dateFormat) {
|
|
||||||
|
|
||||||
List<Method> methodYCandidates = new ArrayList<Method>( 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 extends Method> T getBestMatch(SourceMethod mappingMethod,
|
|
||||||
String mappedElement,
|
|
||||||
List<T> methods,
|
|
||||||
Type sourceType,
|
|
||||||
Type returnType,
|
|
||||||
String targetPropertyName) {
|
|
||||||
|
|
||||||
List<T> 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<MapperReference> mapperReferences,
|
|
||||||
Type targetType) {
|
|
||||||
MapperReference mapperReference = findMapperReference( mapperReferences, method );
|
|
||||||
|
|
||||||
return new MethodReference(
|
|
||||||
method,
|
|
||||||
mapperReference,
|
|
||||||
SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private MapperReference findMapperReference(List<MapperReference> mapperReferences, SourceMethod method) {
|
private MapperReference findMapperReference(List<MapperReference> mapperReferences, SourceMethod method) {
|
||||||
for ( MapperReference ref : mapperReferences ) {
|
for ( MapperReference ref : mapperReferences ) {
|
||||||
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
|
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
|
||||||
@ -1225,12 +1031,6 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Sourc
|
|||||||
return null;
|
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 );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the specified property can be mapped from source to target or not. A mapping if possible if one of
|
* Whether the specified property can be mapped from source to target or not. A mapping if possible if one of
|
||||||
* the following conditions is true:
|
* the following conditions is true:
|
||||||
|
@ -0,0 +1,302 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2012-2014 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.processor;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import javax.annotation.processing.Messager;
|
||||||
|
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.MapperReference;
|
||||||
|
import org.mapstruct.ap.model.MethodReference;
|
||||||
|
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.Type;
|
||||||
|
import org.mapstruct.ap.model.common.TypeFactory;
|
||||||
|
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.util.Strings;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves class is responsible for resolving the most suitable way to resolve a mapping from source to target.
|
||||||
|
*
|
||||||
|
* There are 2 basic types of mappings:
|
||||||
|
* <ul>
|
||||||
|
* <li>conversions</li>
|
||||||
|
* <li>methods</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* conversions are essentially one line mappings, such as String to Integer and Integer to Long
|
||||||
|
* methods come in some varieties:
|
||||||
|
* <ul>
|
||||||
|
* <li>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</li>
|
||||||
|
* <li>generated mapping methods (by means of MapStruct)</li>
|
||||||
|
* <li>built in methods</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @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<VirtualMappingMethod> 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<VirtualMappingMethod>();
|
||||||
|
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<MapperReference> mapperReferences,
|
||||||
|
List<SourceMethod> 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:
|
||||||
|
* <ul>
|
||||||
|
* <li>no direct referenced mapping method either built-in or referenced is available from A to C</li>
|
||||||
|
* <li>no conversion is available</li>
|
||||||
|
* <li>there is a method from A to B, methodX</li>
|
||||||
|
* <li>there is a method from B to C, methodY</li>
|
||||||
|
* </ul>
|
||||||
|
* 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<MapperReference> mapperReferences,
|
||||||
|
List<SourceMethod> methods,
|
||||||
|
Type sourceType,
|
||||||
|
Type targetType,
|
||||||
|
String targetPropertyName,
|
||||||
|
String dateFormat) {
|
||||||
|
|
||||||
|
List<Method> methodYCandidates = new ArrayList<Method>( 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 extends Method> T getBestMatch(SourceMethod mappingMethod,
|
||||||
|
String mappedElement,
|
||||||
|
List<T> methods,
|
||||||
|
Type sourceType,
|
||||||
|
Type returnType,
|
||||||
|
String targetPropertyName) {
|
||||||
|
|
||||||
|
List<T> 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<VirtualMappingMethod> getVirtualMethodsToCreate() {
|
||||||
|
return virtualMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private MethodReference getMappingMethodReference(SourceMethod method, List<MapperReference> mapperReferences,
|
||||||
|
Type targetType) {
|
||||||
|
MapperReference mapperReference = findMapperReference( mapperReferences, method );
|
||||||
|
|
||||||
|
return new MethodReference(
|
||||||
|
method,
|
||||||
|
mapperReference,
|
||||||
|
SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private MapperReference findMapperReference(List<MapperReference> 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 );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user