From 79f87e8833e53b232eb93985c1555378abb52db3 Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Sun, 18 Dec 2016 00:48:20 +0100 Subject: [PATCH] #975 Refactor method-matching to unify selection and rendering of mapping method, factories and lifecycle methods --- .../internal/model/IterableMappingMethod.java | 7 +- .../model/LifecycleCallbackFactory.java | 124 +++---------- .../LifecycleCallbackMethodReference.java | 34 +--- .../ap/internal/model/MapMappingMethod.java | 7 +- .../ap/internal/model/MethodReference.java | 17 +- .../model/ParameterAssignmentUtil.java | 98 ---------- .../ap/internal/model/PropertyMapping.java | 20 +- .../ap/internal/model/common/Parameter.java | 11 +- .../model/common/ParameterBinding.java | 124 +++++++++++++ .../ap/internal/model/common/Type.java | 7 +- .../ap/internal/model/common/TypeFactory.java | 3 +- .../internal/model/source/ForgedMethod.java | 2 +- .../internal/model/source/MethodMatcher.java | 27 +-- .../selector/CreateOrUpdateSelector.java | 19 +- .../source/selector/InheritanceSelector.java | 28 +-- ...elector.java => MethodFamilySelector.java} | 21 ++- .../model/source/selector/MethodSelector.java | 15 +- .../source/selector/MethodSelectors.java | 48 +++-- .../source/selector/QualifierSelector.java | 24 +-- .../selector/SelectedMethod.java} | 36 ++-- .../source/selector/SelectionCriteria.java | 26 ++- .../source/selector/TargetTypeSelector.java | 17 +- .../model/source/selector/TypeSelector.java | 173 +++++++++++++++++- .../selector/XmlElementDeclSelector.java | 19 +- .../creation/MappingResolverImpl.java | 132 +++++-------- .../LifecycleCallbackMethodReference.ftl | 5 +- .../ap/internal/model/MethodReference.ftl | 10 +- .../model/ObjectFactoryMethodReference.ftl | 46 ----- .../runner/AnnotationProcessorTestRunner.java | 3 +- 29 files changed, 589 insertions(+), 514 deletions(-) delete mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/ParameterAssignmentUtil.java create mode 100644 processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java rename processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/{ObjectFactorySelector.java => MethodFamilySelector.java} (53%) rename processor/src/main/java/org/mapstruct/ap/internal/model/{ObjectFactoryMethodReference.java => source/selector/SelectedMethod.java} (52%) delete mode 100644 processor/src/main/resources/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.ftl 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 eff6fa225..2dd04235d 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 @@ -31,6 +31,7 @@ import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper; import org.mapstruct.ap.internal.model.assignment.SetterWrapper; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; @@ -191,7 +192,11 @@ public class IterableMappingMethod extends MappingMethod { forgedMethodHistory ); - Assignment assignment = new MethodReference( forgedMethod, null, targetType ); + Assignment assignment = new MethodReference( + forgedMethod, + null, + ParameterBinding.fromParameters( forgedMethod.getParameters() ) ); + assignment.setAssignment( sourceRHS ); return assignment; 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 648ba8a22..8d658768c 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 @@ -19,17 +19,16 @@ package org.mapstruct.ap.internal.model; import java.util.ArrayList; -import java.util.HashMap; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Set; -import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; import org.mapstruct.ap.internal.model.source.SourceMethod; -import org.mapstruct.ap.internal.model.source.selector.QualifierSelector; +import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; +import org.mapstruct.ap.internal.model.source.selector.MethodSelectors; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; /** @@ -84,45 +83,36 @@ public final class LifecycleCallbackFactory { Method method, SelectionParameters selectionParameters, List callbackMethods, MappingBuilderContext ctx, Set existingVariableNames) { - Map> parameterAssignmentsForSourceMethod - = new HashMap>(); + MethodSelectors selectors = + new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory() ); - List candidates = - filterCandidatesByType( method, callbackMethods, parameterAssignmentsForSourceMethod, ctx ); - - candidates = filterCandidatesByQualifiers( method, selectionParameters, candidates, ctx ); + List> matchingMethods = selectors.getMatchingMethods( + method, + callbackMethods, + Collections. emptyList(), + method.getResultType(), + SelectionCriteria.forLifecycleMethods( selectionParameters ) ); return toLifecycleCallbackMethodRefs( method, - candidates, - parameterAssignmentsForSourceMethod, + matchingMethods, ctx, existingVariableNames ); } - private static List filterCandidatesByQualifiers(Method method, - SelectionParameters selectionParameters, - List candidates, - MappingBuilderContext ctx) { - QualifierSelector selector = new QualifierSelector( ctx.getTypeUtils(), ctx.getElementUtils() ); - - return selector.getMatchingMethods( method, candidates, null, null, new SelectionCriteria( - selectionParameters, - null, - false, - false) ); - } - private static List toLifecycleCallbackMethodRefs(Method method, - List candidates, Map> parameterAssignmentsForSourceMethod, - MappingBuilderContext ctx, Set existingVariableNames) { + List> candidates, + MappingBuilderContext ctx, + Set existingVariableNames) { + List result = new ArrayList(); - for ( SourceMethod candidate : candidates ) { - markMapperReferenceAsUsed( ctx.getMapperReferences(), candidate ); + for ( SelectedMethod candidate : candidates ) { + MapperReference mapperReference = findMapperReference( ctx.getMapperReferences(), candidate.getMethod() ); result.add( new LifecycleCallbackMethodReference( - candidate, - parameterAssignmentsForSourceMethod.get( candidate ), + candidate.getMethod(), + mapperReference, + candidate.getParameterBindings(), method.getReturnType(), method.getResultType(), existingVariableNames ) ); @@ -130,77 +120,15 @@ public final class LifecycleCallbackFactory { return result; } - private static List filterCandidatesByType(Method method, - List callbackMethods, Map> parameterAssignmentsForSourceMethod, - MappingBuilderContext ctx) { - - List candidates = new ArrayList(); - - List availableParams = getAvailableParameters( method, ctx ); - for ( SourceMethod callback : callbackMethods ) { - List parameterAssignments = - ParameterAssignmentUtil.getParameterAssignments( availableParams, callback.getParameters() ); - - if ( isValidCandidate( callback, method, parameterAssignments ) ) { - parameterAssignmentsForSourceMethod.put( callback, parameterAssignments ); - candidates.add( callback ); - } - } - return candidates; - } - - private static boolean isValidCandidate(SourceMethod candidate, Method method, - List parameterAssignments) { - if ( parameterAssignments == null ) { - return false; - } - if ( !candidate.matches( extractSourceTypes( parameterAssignments ), method.getResultType() ) ) { - return false; - } - return ( candidate.getReturnType().isVoid() || candidate.getReturnType().isTypeVar() - || candidate.getReturnType().isAssignableTo( method.getResultType() ) ); - } - - private static List getAvailableParameters(Method method, MappingBuilderContext ctx) { - List availableParams = new ArrayList( method.getParameters() ); - if ( method.getMappingTargetParameter() == null ) { - availableParams.add( new Parameter( null, method.getResultType(), true, false, false) ); - } - - Parameter targetTypeParameter = new Parameter( - null, - ctx.getTypeFactory().classTypeOf( method.getResultType() ), - false, - true, - false ); - - availableParams.add( targetTypeParameter ); - return availableParams; - } - - private static void markMapperReferenceAsUsed(List references, Method method) { - for ( MapperReference ref : references ) { + private static MapperReference findMapperReference(List mapperReferences, SourceMethod method) { + for ( MapperReference ref : mapperReferences ) { if ( ref.getType().equals( method.getDeclaringMapper() ) ) { - if ( !ref.isUsed() && !method.isStatic() ) { - ref.setUsed( true ); - } + ref.setUsed( ref.isUsed() || !method.isStatic() ); ref.setTypeRequiresImport( true ); - - return; + return ref; } } - } - - private static List extractSourceTypes(List parameters) { - List result = new ArrayList( parameters.size() ); - - for ( Parameter param : parameters ) { - if ( !param.isMappingTarget() && !param.isTargetType() ) { - result.add( param.getType() ); - } - } - - return result; + return null; } private static List filterBeforeMappingMethods(List methods) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java index 0ffc67441..e9bb20250 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.java @@ -18,11 +18,10 @@ */ package org.mapstruct.ap.internal.model; -import java.beans.Introspector; import java.util.List; import java.util.Set; -import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceMethod; @@ -34,35 +33,22 @@ import org.mapstruct.ap.internal.util.Strings; * * @author Andreas Gudian */ -public class LifecycleCallbackMethodReference extends MappingMethod { +public class LifecycleCallbackMethodReference extends MethodReference { private final Type declaringType; - private final List parameterAssignments; private final Type methodReturnType; private final Type methodResultType; - private final String instanceVariableName; private final String targetVariableName; - public LifecycleCallbackMethodReference(SourceMethod method, List parameterAssignments, + public LifecycleCallbackMethodReference(SourceMethod method, MapperReference mapperReference, + List parameterBindings, Type methodReturnType, Type methodResultType, Set existingVariableNames) { - super( method ); + super( method, mapperReference, parameterBindings ); this.declaringType = method.getDeclaringMapper(); - this.parameterAssignments = parameterAssignments; this.methodReturnType = methodReturnType; this.methodResultType = methodResultType; - if ( isStatic() ) { - this.instanceVariableName = declaringType.getName(); - } - else if ( declaringType != null ) { - this.instanceVariableName = - Strings.getSaveVariableName( Introspector.decapitalize( declaringType.getName() ) ); - } - else { - this.instanceVariableName = null; - } - if ( hasReturnType() ) { this.targetVariableName = Strings.getSaveVariableName( "target", existingVariableNames ); existingVariableNames.add( this.targetVariableName ); @@ -76,10 +62,6 @@ public class LifecycleCallbackMethodReference extends MappingMethod { return declaringType; } - public String getInstanceVariableName() { - return instanceVariableName; - } - /** * Returns the return type of the mapping method in which this callback method is called * @@ -109,12 +91,8 @@ public class LifecycleCallbackMethodReference extends MappingMethod { return declaringType != null ? Collections.asSet( declaringType ) : java.util.Collections. emptySet(); } - public List getParameterAssignments() { - return parameterAssignments; - } - public boolean hasMappingTargetParameter() { - for ( Parameter param : parameterAssignments ) { + for ( ParameterBinding param : getParameterBindings() ) { if ( param.isMappingTarget() ) { return true; } 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 75179453b..7506b69fd 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 @@ -29,6 +29,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; @@ -211,7 +212,11 @@ public class MapMappingMethod extends MappingMethod { history ); - Assignment assignment = new MethodReference( forgedMethod, null, targetType ); + Assignment assignment = new MethodReference( + forgedMethod, + null, + ParameterBinding.fromParameters( forgedMethod.getParameters() ) ); + assignment.setAssignment( sourceRHS ); forgedMethods.add( forgedMethod ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java index 099655cd8..e6a43955a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MethodReference.java @@ -27,6 +27,7 @@ import java.util.Set; import org.mapstruct.ap.internal.model.assignment.Assignment; import org.mapstruct.ap.internal.model.common.ConversionContext; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod; @@ -62,18 +63,19 @@ public class MethodReference extends MappingMethod implements Assignment { private Assignment assignment; private final Type definingType; + private final List parameterBindings; /** * Creates a new reference to the given method. * * @param method the target method of the reference * @param declaringMapper the method declaring the mapper; {@code null} if the current mapper itself - * @param targetType in case the referenced method has a parameter for passing the target type, the given - * target type, otherwise {@code null} + * @param parameterBindings the parameter bindings of this method reference */ - public MethodReference(Method method, MapperReference declaringMapper, Type targetType) { + public MethodReference(Method method, MapperReference declaringMapper, List parameterBindings) { super( method ); this.declaringMapper = declaringMapper; + this.parameterBindings = parameterBindings; this.contextParam = null; Set imported = new HashSet(); @@ -81,8 +83,8 @@ public class MethodReference extends MappingMethod implements Assignment { imported.addAll( type.getImportTypes() ); } - if ( targetType != null ) { - imported.addAll( targetType.getImportTypes() ); + for ( ParameterBinding binding : parameterBindings ) { + imported.addAll( binding.getImportTypes() ); } this.importTypes = Collections.unmodifiableSet( imported ); @@ -99,6 +101,7 @@ public class MethodReference extends MappingMethod implements Assignment { this.thrownTypes = Collections.emptyList(); this.definingType = null; this.isUpdateMethod = method.getMappingTargetParameter() != null; + this.parameterBindings = ParameterBinding.fromParameters( method.getParameters() ); } public MapperReference getDeclaringMapper() { @@ -214,4 +217,8 @@ public class MethodReference extends MappingMethod implements Assignment { public boolean isCallingUpdateMethod() { return isUpdateMethod; } + + public List getParameterBindings() { + return parameterBindings; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ParameterAssignmentUtil.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ParameterAssignmentUtil.java deleted file mode 100644 index 1a6b1fcad..000000000 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ParameterAssignmentUtil.java +++ /dev/null @@ -1,98 +0,0 @@ -/** - * 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.model; - -import java.util.ArrayList; -import java.util.List; - -import javax.lang.model.type.TypeKind; - -import org.mapstruct.ap.internal.model.common.Parameter; - -public class ParameterAssignmentUtil { - - private ParameterAssignmentUtil() { - } - - public static List getParameterAssignments(List availableParams, - List methodParameters) { - List result = new ArrayList( methodParameters.size() ); - - for ( Parameter methodParam : methodParameters ) { - List assignableParams = findCandidateParameters( availableParams, methodParam ); - - if ( assignableParams.isEmpty() ) { - return null; - } - - if ( assignableParams.size() == 1 ) { - result.add( assignableParams.get( 0 ) ); - } - else if ( assignableParams.size() > 1 ) { - Parameter paramWithMatchingName = findParameterWithName( assignableParams, methodParam.getName() ); - - if ( paramWithMatchingName != null ) { - result.add( paramWithMatchingName ); - } - else { - return null; - } - } - } - - return result; - } - - private static Parameter findParameterWithName(List parameters, String name) { - for ( Parameter param : parameters ) { - if ( name.equals( param.getName() ) ) { - return param; - } - } - - return null; - } - - /** - * @param candidateParameters available for assignment. - * @param parameter that need assignment from one of the candidate parameters. - * @return list of matching candidate parameters that can be assigned. - */ - private static List findCandidateParameters(List candidateParameters, Parameter parameter) { - List result = new ArrayList( candidateParameters.size() ); - - for ( Parameter candidate : candidateParameters ) { - if ( ( isTypeVarOrWildcard( parameter ) || candidate.getType().isAssignableTo( parameter.getType() ) ) - && parameter.isMappingTarget() == candidate.isMappingTarget() && !parameter.isTargetType() - && !candidate.isTargetType() ) { - result.add( candidate ); - } - else if ( parameter.isTargetType() && candidate.isTargetType() ) { - result.add( candidate ); - } - } - - return result; - } - - private static boolean isTypeVarOrWildcard(Parameter parameter) { - TypeKind kind = parameter.getType().getTypeMirror().getKind(); - return kind == TypeKind.TYPEVAR || kind == TypeKind.WILDCARD; - } -} 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 e7dcb6f43..dcb9c08d5 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 @@ -43,13 +43,14 @@ import org.mapstruct.ap.internal.model.assignment.SetterWrapperForCollectionsAnd import org.mapstruct.ap.internal.model.assignment.UpdateWrapper; import org.mapstruct.ap.internal.model.common.ModelElement; import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.ForgedMethod; import org.mapstruct.ap.internal.model.source.ForgedMethodHistory; import org.mapstruct.ap.internal.model.source.FormattingParameters; +import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.PropertyEntry; import org.mapstruct.ap.internal.model.source.SelectionParameters; -import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SourceReference; import org.mapstruct.ap.internal.prism.NullValueCheckStrategyPrism; import org.mapstruct.ap.internal.util.Executables; @@ -566,7 +567,11 @@ public class PropertyMapping extends ModelElement { methodRef = new ForgedMethod( existingName, methodRef ); } - assignment = new MethodReference( methodRef, null, targetType ); + assignment = new MethodReference( + methodRef, + null, + ParameterBinding.fromParameters( methodRef.getParameters() ) ); + assignment.setAssignment( source ); forgedMethods.addAll( iterableMappingMethod.getForgedMethods() ); @@ -608,7 +613,10 @@ public class PropertyMapping extends ModelElement { String existingName = ctx.getExistingMappingMethod( mapMappingMethod ).getName(); methodRef = new ForgedMethod( existingName, methodRef ); } - assignment = new MethodReference( methodRef, null, targetType ); + assignment = new MethodReference( + methodRef, + null, + ParameterBinding.fromParameters( methodRef.getParameters() ) ); assignment.setAssignment( source ); forgedMethods.addAll( mapMappingMethod.getForgedMethods() ); @@ -635,7 +643,11 @@ public class PropertyMapping extends ModelElement { getForgedMethodHistory( sourceRHS ) ); - Assignment assignment = new MethodReference( forgedMethod, null, targetType ); + Assignment assignment = new MethodReference( + forgedMethod, + null, + ParameterBinding.fromParameters( forgedMethod.getParameters() ) ); + assignment.setAssignment( sourceRHS ); this.forgedMethods.add( forgedMethod ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 826dcee0f..76cffe221 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -34,20 +34,18 @@ public class Parameter extends ModelElement { private final Type type; private final boolean mappingTarget; private final boolean targetType; - private final boolean mappingSource; - public Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingSource) { + public Parameter(String name, Type type, boolean mappingTarget, boolean targetType) { // issue #909: FreeMarker doesn't like "values" as a parameter name this.name = "values".equals( name ) ? "values_" : name; this.originalName = name; this.type = type; this.mappingTarget = mappingTarget; this.targetType = targetType; - this.mappingSource = mappingSource; } public Parameter(String name, Type type) { - this( name, type, false, false, false ); + this( name, type, false, false ); } public String getName() { @@ -62,10 +60,6 @@ public class Parameter extends ModelElement { return type; } - public boolean isMappingSource() { - return mappingSource; - } - public boolean isMappingTarget() { return mappingTarget; } @@ -106,5 +100,4 @@ public class Parameter extends ModelElement { } return true; } - } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java new file mode 100644 index 000000000..0b3382a9d --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -0,0 +1,124 @@ +/** + * 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.model.common; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Represents how one parameter of a method to be called is populated. + * + * @author Andreas Gudian + */ +public class ParameterBinding { + + private final Type type; + private final String variableName; + private final boolean targetType; + private final boolean mappingTarget; + + private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType) { + this.type = parameterType; + this.variableName = variableName; + this.targetType = targetType; + this.mappingTarget = mappingTarget; + } + + /** + * @return the name of the variable (or parameter) that is being used as argument for the parameter being bound. + */ + public String getVariableName() { + return variableName; + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter. + */ + public boolean isTargetType() { + return targetType; + } + + /** + * @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter. + */ + public boolean isMappingTarget() { + return mappingTarget; + } + + /** + * @return the type of the parameter that is bound + */ + public Type getType() { + return type; + } + + public Set getImportTypes() { + if ( targetType ) { + return type.getImportTypes(); + } + + return Collections.emptySet(); + } + + /** + * @param parameter parameter + * @return a parameter binding reflecting the given parameter as being used as argument for a method call + */ + public static ParameterBinding fromParameter(Parameter parameter) { + return new ParameterBinding( + parameter.getType(), + parameter.getName(), + parameter.isMappingTarget(), + parameter.isTargetType() ); + } + + public static List fromParameters(List parameters) { + List result = new ArrayList( parameters.size() ); + for ( Parameter param : parameters ) { + result.add( fromParameter( param ) ); + } + return result; + } + + /** + * @param classTypeOf the type representing {@code Class} for the target type {@code X} + * @return a parameter binding representing a target type parameter + */ + public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, false, true ); + } + + /** + * @param resultType type of the mapping target + * @return a parameter binding representing a mapping target parameter + */ + public static ParameterBinding forMappingTargetBinding(Type resultType) { + return new ParameterBinding( resultType, null, true, false ); + } + + /** + * @param sourceType type of the parameter + * @return a parameter binding representing a mapping source type + */ + public static ParameterBinding forSourceTypeBinding(Type sourceType) { + return new ParameterBinding( sourceType, null, false, false ); + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index aaaed4c80..668161ecc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -754,11 +754,7 @@ public class Type extends ModelElement implements Comparable { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ( ( name == null ) ? 0 : name.hashCode() ); - result = prime * result + ( ( packageName == null ) ? 0 : packageName.hashCode() ); - return result; + return typeMirror.hashCode(); } @Override @@ -787,7 +783,6 @@ public class Type extends ModelElement implements Comparable { return typeMirror.toString(); } - /** * * @return an identification that can be used as part in a forged method name. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index d53f2c3cf..e1a2e6e7c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -326,8 +326,7 @@ public class TypeFactory { parameter.getSimpleName().toString(), getType( parameterType ), MappingTargetPrism.getInstanceOn( parameter ) != null, - TargetTypePrism.getInstanceOn( parameter ) != null, - false) ); + TargetTypePrism.getInstanceOn( parameter ) != null ) ); } return result; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java index f0e74a6d5..2202f9671 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ForgedMethod.java @@ -116,7 +116,7 @@ public class ForgedMethod implements Method { return false; } - if ( !first( sourceTypes ).equals( parameters.get( 0 ).getType() ) ) { + if ( !first( sourceTypes ).equals( first( parameters ).getType() ) ) { return false; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index 4e74e9995..7ad386749 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -18,8 +18,6 @@ */ package org.mapstruct.ap.internal.model.source; -import static org.mapstruct.ap.internal.util.Collections.hasNonNullElements; - import java.util.HashMap; import java.util.List; import java.util.Map; @@ -90,27 +88,18 @@ public class MethodMatcher { // check & collect generic types. Map genericTypesMap = new HashMap(); - if ( hasNonNullElements( sourceTypes ) ) { - // if sourceTypes contains non-null elements then only methods with all source parameters matching qualify - if ( candidateMethod.getSourceParameters().size() == sourceTypes.size() ) { - int i = 0; - for ( Parameter candidateSourceParam : candidateMethod.getSourceParameters() ) { - Type sourceType = sourceTypes.get( i++ ); - if ( sourceType == null - || !matchSourceType( sourceType, candidateSourceParam.getType(), genericTypesMap ) ) { - return false; - } + if ( candidateMethod.getParameters().size() == sourceTypes.size() ) { + int i = 0; + for ( Parameter candidateParam : candidateMethod.getParameters() ) { + Type sourceType = sourceTypes.get( i++ ); + if ( sourceType == null + || !matchSourceType( sourceType, candidateParam.getType(), genericTypesMap ) ) { + return false; } } - else { - return false; - } } else { - // if the sourceTypes empty/contains only nulls then only factory and lifecycle methods qualify - if ( !candidateMethod.isObjectFactory() && !candidateMethod.isLifecycleCallbackMethod() ) { - return false; - } + return false; } // check if the method matches the proper result type to construct diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index 1b7573f8e..c4586c086 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -42,14 +42,19 @@ import org.mapstruct.ap.internal.model.source.Method; public class CreateOrUpdateSelector implements MethodSelector { @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { - List createCandidates = new ArrayList(); - List updateCandidates = new ArrayList(); - for ( T method : methods ) { - boolean isCreateCandidate = method.getMappingTargetParameter() == null; + if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) { + return methods; + } + + List> createCandidates = new ArrayList>(); + List> updateCandidates = new ArrayList>(); + for ( SelectedMethod method : methods ) { + boolean isCreateCandidate = method.getMethod().getMappingTargetParameter() == null; if ( isCreateCandidate ) { createCandidates.add( method ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index c190dd2f2..086493d8e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -35,26 +35,26 @@ import org.mapstruct.ap.internal.model.source.Method; public class InheritanceSelector implements MethodSelector { @Override - public List getMatchingMethods( - Method mappingMethod, - List methods, - Type sourceType, - Type targetType, - SelectionCriteria criteria - ) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, + Type targetType, + SelectionCriteria criteria) { - if ( sourceType == null ) { + if ( sourceTypes.size() != 1 ) { return methods; } - List candidatesWithBestMatchingSourceType = new ArrayList(); + Type singleSourceType = first( sourceTypes ); + + List> candidatesWithBestMatchingSourceType = new ArrayList>(); int bestMatchingSourceTypeDistance = Integer.MAX_VALUE; // find the methods with the minimum distance regarding getParameter getParameter type - for ( T method : methods ) { - Parameter singleSourceParam = first( method.getSourceParameters() ); + for ( SelectedMethod method : methods ) { + Parameter singleSourceParam = first( method.getMethod().getSourceParameters() ); - int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() ); + int sourceTypeDistance = singleSourceType.distanceTo( singleSourceParam.getType() ); bestMatchingSourceTypeDistance = addToCandidateListIfMinimal( candidatesWithBestMatchingSourceType, @@ -66,8 +66,8 @@ public class InheritanceSelector implements MethodSelector { return candidatesWithBestMatchingSourceType; } - private int addToCandidateListIfMinimal(List candidatesWithBestMathingType, - int bestMatchingTypeDistance, T method, + private int addToCandidateListIfMinimal(List> candidatesWithBestMathingType, + int bestMatchingTypeDistance, SelectedMethod method, int currentTypeDistance) { if ( currentTypeDistance == bestMatchingTypeDistance ) { candidatesWithBestMathingType.add( method ); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/ObjectFactorySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java similarity index 53% rename from processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/ObjectFactorySelector.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java index 741505f00..c4b4944cb 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/ObjectFactorySelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java @@ -23,23 +23,26 @@ import java.util.List; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.source.Method; -import org.mapstruct.ap.internal.model.source.MethodMatcher; /** - * Selects those methods from the given input set which match whether a factory was requested ({@link MethodMatcher} and - * {@link org.mapstruct.ObjectFactory}). + * Selects those methods from the given input set which match for the requested family of methods: factory methods, + * lifecycle callback methods, or any other mapping methods. * * @author Remo Meier */ -public class ObjectFactorySelector implements MethodSelector { +public class MethodFamilySelector implements MethodSelector { @Override - public List getMatchingMethods(Method mappingMethod, List methods, Type sourceType, - Type targetType, SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, + Type targetType, SelectionCriteria criteria) { + + List> result = new ArrayList>( methods.size() ); + for ( SelectedMethod method : methods ) { + if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired() + && method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() ) { - List result = new ArrayList(); - for ( T method : methods ) { - if ( method.isObjectFactory() == criteria.isObjectFactoryRequired() ) { result.add( method ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java index a52d4f5c1..b6e002529 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java @@ -30,20 +30,21 @@ import org.mapstruct.ap.internal.model.source.Method; * * @author Sjaak Derksen */ -public interface MethodSelector { +interface MethodSelector { /** * Selects those methods which match the given types and other criteria * * @param either SourceMethod or BuiltInMethod * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out - * @param methods list of available methods - * @param sourceType parameter type that should be matched - * @param targetType return type that should be matched + * @param candidates list of available methods + * @param sourceTypes parameter type(s) that should be matched + * @param targetType result type that should be matched * @param criteria criteria used in the selection process - * * @return list of methods that passes the matching process */ - List getMatchingMethods(Method mappingMethod, List methods, Type sourceType, - Type targetType, SelectionCriteria criteria); + List> getMatchingMethods(Method mappingMethod, + List> candidates, + List sourceTypes, + Type targetType, SelectionCriteria criteria); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index 25dd993c9..9ee1629ae 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -34,38 +34,48 @@ import org.mapstruct.ap.internal.model.source.Method; * * @author Sjaak Derksen */ -public class MethodSelectors implements MethodSelector { +public class MethodSelectors { private final List selectors; public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory) { - selectors = - Arrays.asList( - new ObjectFactorySelector(), - new TypeSelector(), - new QualifierSelector( typeUtils, elementUtils ), - new TargetTypeSelector( typeUtils, elementUtils ), - new XmlElementDeclSelector( typeUtils, elementUtils ), - new InheritanceSelector(), - new CreateOrUpdateSelector() - ); + selectors = Arrays.asList( + new MethodFamilySelector(), + new TypeSelector( typeFactory ), + new QualifierSelector( typeUtils, elementUtils ), + new TargetTypeSelector( typeUtils, elementUtils ), + new XmlElementDeclSelector( typeUtils, elementUtils ), + new InheritanceSelector(), + new CreateOrUpdateSelector() ); } - @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + /** + * Selects those methods which match the given types and other criteria + * + * @param either SourceMethod or BuiltInMethod + * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out + * @param methods list of available methods + * @param sourceTypes parameter type(s) that should be matched + * @param targetType return type that should be matched + * @param criteria criteria used in the selection process + * @return list of methods that passes the matching process + */ + public List> getMatchingMethods(Method mappingMethod, List methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { - List candidates = new ArrayList( methods ); + List> candidates = new ArrayList>( methods.size() ); + for ( T method : methods ) { + candidates.add( new SelectedMethod( method ) ); + } for ( MethodSelector selector : selectors ) { candidates = selector.getMatchingMethods( mappingMethod, candidates, - sourceType, + sourceTypes, targetType, - criteria - ); + criteria ); } return candidates; } 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 84e9e3e5b..bd34dc858 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 @@ -63,9 +63,10 @@ public class QualifierSelector implements MethodSelector { } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { int numberOfQualifiersToMatch = 0; @@ -89,11 +90,11 @@ public class QualifierSelector implements MethodSelector { // 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 ) { + List> nonQualiferAnnotatedMethods = new ArrayList>( methods.size() ); + for ( SelectedMethod candidate : methods ) { - if ( candidate instanceof SourceMethod ) { - Set qualifierAnnotations = getQualifierAnnotationMirrors( candidate ); + if ( candidate.getMethod() instanceof SourceMethod ) { + Set qualifierAnnotations = getQualifierAnnotationMirrors( candidate.getMethod() ); if ( qualifierAnnotations.isEmpty() ) { nonQualiferAnnotatedMethods.add( candidate ); } @@ -107,15 +108,16 @@ public class QualifierSelector implements MethodSelector { } 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 ) { + List> matches = new ArrayList>( methods.size() ); + for ( SelectedMethod candidate : methods ) { - if ( !( candidate instanceof SourceMethod ) ) { + if ( !( candidate.getMethod() instanceof SourceMethod ) ) { continue; } // retrieve annotations - Set qualifierAnnotationMirrors = getQualifierAnnotationMirrors( candidate ); + Set qualifierAnnotationMirrors = + getQualifierAnnotationMirrors( candidate.getMethod() ); // now count if all qualifiers are matched int matchingQualifierCounter = 0; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java similarity index 52% rename from processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.java rename to processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java index 68f0b3a3d..0f9d10ee7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectedMethod.java @@ -16,28 +16,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.mapstruct.ap.internal.model; +package org.mapstruct.ap.internal.model.source.selector; import java.util.List; -import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; import org.mapstruct.ap.internal.model.source.Method; /** - * Represents a reference to a factory method. + * A selected method with additional metadata that might be required for further usage of the selected method. * - * @author Remo Meier + * @author Andreas Gudian */ -public class ObjectFactoryMethodReference extends MethodReference { +public class SelectedMethod { + private T method; + private List parameterBindings; - private final List parameterAssignments; - - public ObjectFactoryMethodReference(Method method, MapperReference ref, List parameterAssignments) { - super( method, ref, null ); - this.parameterAssignments = parameterAssignments; + public SelectedMethod(T method) { + this.method = method; } - public List getParameterAssignments() { - return parameterAssignments; + public T getMethod() { + return method; + } + + public List getParameterBindings() { + return parameterBindings; + } + + public void setParameterBindings(List parameterBindings) { + this.parameterBindings = parameterBindings; + } + + @Override + public String toString() { + return method.toString(); } } 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 7c0c161a0..3b7159fd8 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 @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import javax.lang.model.type.TypeMirror; + import org.mapstruct.ap.internal.model.source.SelectionParameters; /** @@ -37,9 +38,11 @@ public class SelectionCriteria { private final TypeMirror qualifyingResultType; private boolean preferUpdateMapping; private final boolean objectFactoryRequired; + private final boolean lifecycleCallbackRequired; public SelectionCriteria(SelectionParameters selectionParameters, String targetPropertyName, - boolean preferUpdateMapping, boolean objectFactoryRequired) { + boolean preferUpdateMapping, boolean objectFactoryRequired, + boolean lifecycleCallbackRequired) { if ( selectionParameters != null ) { qualifiers.addAll( selectionParameters.getQualifiers() ); qualifiedByNames.addAll( selectionParameters.getQualifyingNames() ); @@ -51,6 +54,7 @@ public class SelectionCriteria { this.targetPropertyName = targetPropertyName; this.preferUpdateMapping = preferUpdateMapping; this.objectFactoryRequired = objectFactoryRequired; + this.lifecycleCallbackRequired = lifecycleCallbackRequired; } /** @@ -60,6 +64,13 @@ public class SelectionCriteria { return objectFactoryRequired; } + /** + * @return true if lifecycle callback methods should be selected, false otherwise. + */ + public boolean isLifecycleCallbackRequired() { + return lifecycleCallbackRequired; + } + public List getQualifiers() { return qualifiers; } @@ -84,4 +95,17 @@ public class SelectionCriteria { this.preferUpdateMapping = preferUpdateMapping; } + public static SelectionCriteria forMappingMethods(SelectionParameters selectionParameters, + String targetPropertyName, boolean preferUpdateMapping) { + + return new SelectionCriteria( selectionParameters, targetPropertyName, preferUpdateMapping, false, false ); + } + + public static SelectionCriteria forFactoryMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, false, true, false ); + } + + public static SelectionCriteria forLifecycleMethods(SelectionParameters selectionParameters) { + return new SelectionCriteria( selectionParameters, null, false, false, true ); + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java index 07c90d808..c41e3047e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java @@ -45,16 +45,19 @@ public class TargetTypeSelector implements MethodSelector { } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType(); - if ( qualifyingTypeMirror != null ) { + if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) { - List candidatesWithQualifyingTargetType = new ArrayList(); - for ( T method : methods ) { - TypeMirror resultTypeMirror = method.getResultType().getTypeElement().asType(); + List> candidatesWithQualifyingTargetType = + new ArrayList>( methods.size() ); + + for ( SelectedMethod method : methods ) { + TypeMirror resultTypeMirror = method.getMethod().getResultType().getTypeElement().asType(); if ( typeUtils.isSameType( qualifyingTypeMirror, resultTypeMirror ) ) { candidatesWithQualifyingTargetType.add( method ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index aa7b6ba2a..5ca492e65 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -18,11 +18,15 @@ */ package org.mapstruct.ap.internal.model.source.selector; +import static org.mapstruct.ap.internal.util.Collections.first; + import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import org.mapstruct.ap.internal.model.common.Parameter; +import org.mapstruct.ap.internal.model.common.ParameterBinding; 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.MethodMatcher; @@ -34,17 +38,168 @@ import org.mapstruct.ap.internal.model.source.MethodMatcher; */ public class TypeSelector implements MethodSelector { - @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + private TypeFactory typeFactory; - List result = new ArrayList(); - for ( T method : methods ) { - if ( !method.isLifecycleCallbackMethod() && method.matches( Arrays.asList( sourceType ), targetType ) ) { - result.add( method ); + public TypeSelector(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + } + + @Override + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { + + if ( methods.isEmpty() ) { + return methods; + } + + List> result = new ArrayList>(); + + List availableBindings; + if ( sourceTypes.isEmpty() ) { + // if no source types are given, we have a factory or lifecycle method + availableBindings = getAvailableParameterBindingsFromMethod( mappingMethod ); + } + else { + availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType ); + } + + for ( SelectedMethod method : methods ) { + List> parameterBindingPermutations = + getCandidateParameterBindingPermutations( availableBindings, method.getMethod().getParameters() ); + + if ( parameterBindingPermutations != null ) { + SelectedMethod matchingMethod = + getFirstMatchingParameterBinding( targetType, method, parameterBindingPermutations ); + + if ( matchingMethod != null ) { + result.add( matchingMethod ); + } } } return result; } + + private List getAvailableParameterBindingsFromMethod(Method method) { + List availableParams = new ArrayList( method.getParameters().size() + 2 ); + + availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); + addMappingTargetAndTargetTypeBindings( availableParams, method.getResultType() ); + + return availableParams; + } + + private List getAvailableParameterBindingsFromSourceTypes(List sourceTypes, + Type targetType) { + + List availableParams = new ArrayList( sourceTypes.size() + 2 ); + + addMappingTargetAndTargetTypeBindings( availableParams, targetType ); + + for ( Type sourceType : sourceTypes ) { + availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + } + + return availableParams; + } + + private void addMappingTargetAndTargetTypeBindings(List availableParams, Type targetType) { + availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) ); + availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) ); + } + + private SelectedMethod getFirstMatchingParameterBinding(Type targetType, + SelectedMethod method, List> parameterAssignmentVariants) { + + for ( List parameterAssignments : parameterAssignmentVariants ) { + if ( method.getMethod().matches( extractTypes( parameterAssignments ), targetType ) ) { + method.setParameterBindings( parameterAssignments ); + return method; + } + } + return null; + } + + /** + * @param availableParams parameter bindings available in the scope of the method call + * @param methodParameters parameters of the method that is inspected + * @return all parameter binding permutations for which proper type checks need to be conducted. + */ + private static List> getCandidateParameterBindingPermutations( + List availableParams, + List methodParameters) { + + if ( methodParameters.size() > availableParams.size() ) { + return null; + } + + List> bindingPermutations = new ArrayList>( 1 ); + bindingPermutations.add( new ArrayList( methodParameters.size() ) ); + + for ( Parameter methodParam : methodParameters ) { + List candidateBindings = + findCandidateBindingsForParameter( availableParams, methodParam ); + + if ( candidateBindings.isEmpty() ) { + return null; + } + + if ( candidateBindings.size() == 1 ) { + // short-cut to avoid list-copies for the usual case where only one binding fits + for ( List variant : bindingPermutations ) { + // add binding to each existing variant + variant.add( first( candidateBindings ) ); + } + } + else { + List> newVariants = + new ArrayList>( bindingPermutations.size() * candidateBindings.size() ); + for ( List variant : bindingPermutations ) { + // create a copy of each variant for each binding + for ( ParameterBinding binding : candidateBindings ) { + List extendedVariant = + new ArrayList( methodParameters.size() ); + extendedVariant.addAll( variant ); + extendedVariant.add( binding ); + + newVariants.add( extendedVariant ); + } + } + + bindingPermutations = newVariants; + } + } + + return bindingPermutations; + } + + /** + * @param candidateParameters available for assignment. + * @param parameter that need assignment from one of the candidate parameter bindings. + * @return list of candidate parameter bindings that might be assignable. + */ + private static List findCandidateBindingsForParameter(List candidateParameters, + Parameter parameter) { + List result = new ArrayList( candidateParameters.size() ); + + for ( ParameterBinding candidate : candidateParameters ) { + if ( parameter.isTargetType() == candidate.isTargetType() + && parameter.isMappingTarget() == candidate.isMappingTarget() ) { + result.add( candidate ); + } + } + + return result; + } + + private static List extractTypes(List parameters) { + List result = new ArrayList( parameters.size() ); + + for ( ParameterBinding param : parameters ) { + result.add( param.getType() ); + } + + return result; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index d2b6165af..254d5e666 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -61,9 +61,10 @@ public class XmlElementDeclSelector implements MethodSelector { } @Override - public List getMatchingMethods(Method mappingMethod, List methods, - Type sourceType, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List> methods, + List sourceTypes, Type targetType, + SelectionCriteria criteria) { // only true source methods are qualifying if ( !(mappingMethod instanceof SourceMethod) ) { @@ -72,18 +73,18 @@ public class XmlElementDeclSelector implements MethodSelector { SourceMethod sourceMappingMethod = (SourceMethod) mappingMethod; - List nameMatches = new ArrayList(); - List scopeMatches = new ArrayList(); - List nameAndScopeMatches = new ArrayList(); + List> nameMatches = new ArrayList>(); + List> scopeMatches = new ArrayList>(); + List> nameAndScopeMatches = new ArrayList>(); XmlElementRefInfo xmlElementRefInfo = findXmlElementRef( sourceMappingMethod.getResultType(), criteria.getTargetPropertyName() ); - for ( T candidate : methods ) { - if ( !( candidate instanceof SourceMethod ) ) { + for ( SelectedMethod candidate : methods ) { + if ( !( candidate.getMethod() instanceof SourceMethod ) ) { continue; } - SourceMethod candidateMethod = (SourceMethod) candidate; + SourceMethod candidateMethod = (SourceMethod) candidate.getMethod(); XmlElementDeclPrism xmlElememtDecl = XmlElementDeclPrism.getInstanceOn( candidateMethod.getExecutable() ); if ( xmlElememtDecl == null ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java index 4287a596c..16c1cd103 100755 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java @@ -18,6 +18,9 @@ */ package org.mapstruct.ap.internal.processor.creation; +import static java.util.Collections.singletonList; +import static org.mapstruct.ap.internal.util.Collections.first; + import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -39,23 +42,20 @@ import org.mapstruct.ap.internal.model.HelperMethod; 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.ObjectFactoryMethodReference; -import org.mapstruct.ap.internal.model.ParameterAssignmentUtil; import org.mapstruct.ap.internal.model.SourceRHS; 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.Parameter; import org.mapstruct.ap.internal.model.common.Type; import org.mapstruct.ap.internal.model.common.TypeFactory; import org.mapstruct.ap.internal.model.source.FormattingParameters; import org.mapstruct.ap.internal.model.source.Method; import org.mapstruct.ap.internal.model.source.SelectionParameters; -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.SelectedMethod; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Collections; import org.mapstruct.ap.internal.util.FormattingMessager; @@ -109,7 +109,7 @@ public class MappingResolverImpl implements MappingResolver { boolean preferUpdateMapping) { SelectionCriteria criteria = - new SelectionCriteria( selectionParameters, targetPropertyName, preferUpdateMapping, false ); + SelectionCriteria.forMappingMethods( selectionParameters, targetPropertyName, preferUpdateMapping ); String dateFormat = null; String numberFormat = null; @@ -139,58 +139,46 @@ public class MappingResolverImpl implements MappingResolver { public MethodReference getFactoryMethod(final Method mappingMethod, Type targetType, SelectionParameters selectionParameters) { - SelectionCriteria criteria = new SelectionCriteria( selectionParameters, null, false, true ); + List> matchingFactoryMethods = + methodSelectors.getMatchingMethods( + mappingMethod, + sourceModel, + java.util.Collections. emptyList(), + targetType, + SelectionCriteria.forFactoryMethods( selectionParameters ) ); - ResolvingAttempt attempt = new ResolvingAttempt( sourceModel, mappingMethod, null, null, null, criteria ); - - List matchingSourceMethods = attempt.getMatches( sourceModel, null, targetType ); - - List factoryRefsWithAssigments = new ArrayList(); - List factoryRefSources = new ArrayList(); - - for ( Method matchingSourceMethod : matchingSourceMethods ) { - - if ( matchingSourceMethod != null ) { - MapperReference ref = attempt.findMapperReference( matchingSourceMethod ); - - if ( matchingSourceMethod.getSourceParameters().isEmpty() ) { - // factory taking no argument - factoryRefsWithAssigments.add( new MethodReference( matchingSourceMethod, ref, null ) ); - factoryRefSources.add( matchingSourceMethod ); - } - else { - // check whether factory have has a valid assignment, if so, choose as candidate - List availableParameters = new ArrayList(); - availableParameters.addAll( mappingMethod.getSourceParameters() ); - availableParameters.add( - new Parameter( null, - typeFactory.classTypeOf( targetType ), - false, true, false ) ); - List factoryParamAssinment = - ParameterAssignmentUtil.getParameterAssignments( availableParameters, - matchingSourceMethod.getParameters() ); - if ( factoryParamAssinment != null ) { - factoryRefSources.add( matchingSourceMethod ); - factoryRefsWithAssigments.add( - new ObjectFactoryMethodReference( matchingSourceMethod, ref, factoryParamAssinment ) ); - } - } - } + if (matchingFactoryMethods.isEmpty()) { + return null; } - if ( factoryRefsWithAssigments.size() > 1 ) { + if ( matchingFactoryMethods.size() > 1 ) { messager.printMessage( mappingMethod.getExecutable(), Message.GENERAL_AMBIGIOUS_FACTORY_METHOD, targetType, - Strings.join( factoryRefSources, ", " ) ); - } - else if ( factoryRefsWithAssigments.size() == 1 ) { - // factory methods with assignment are favored over the ones without any - return factoryRefsWithAssigments.get( 0 ); + Strings.join( matchingFactoryMethods, ", " ) ); + + return null; } - // no factory found + SelectedMethod matchingFactoryMethod = first( matchingFactoryMethods ); + + MapperReference ref = findMapperReference( matchingFactoryMethod.getMethod() ); + + return new MethodReference( + matchingFactoryMethod.getMethod(), + ref, + matchingFactoryMethod.getParameterBindings() ); + } + + private MapperReference findMapperReference(Method method) { + for ( MapperReference ref : mapperReferences ) { + if ( ref.getType().equals( method.getDeclaringMapper() ) ) { + ref.setUsed( ref.isUsed() || !method.isStatic() ); + ref.setTypeRequiresImport( true ); + return ref; + } + } return null; } @@ -315,7 +303,7 @@ public class MappingResolverImpl implements MappingResolver { private Assignment resolveViaMethod(Type sourceType, Type targetType, boolean considerBuiltInMethods) { // first try to find a matching source method - Method matchingSourceMethod = getBestMatch( methods, sourceType, targetType ); + SelectedMethod matchingSourceMethod = getBestMatch( methods, sourceType, targetType ); if ( matchingSourceMethod != null ) { return getMappingMethodReference( matchingSourceMethod, targetType ); @@ -329,15 +317,15 @@ public class MappingResolverImpl implements MappingResolver { } private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) { - BuiltInMethod matchingBuiltInMethod = + SelectedMethod matchingBuiltInMethod = getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType ); if ( matchingBuiltInMethod != null ) { - virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod ) ); + virtualMethodCandidates.add( new VirtualMappingMethod( matchingBuiltInMethod.getMethod() ) ); ConversionContext ctx = new DefaultConversionContext( typeFactory, messager, sourceType, targetType, dateFormat, numberFormat); - Assignment methodReference = new MethodReference( matchingBuiltInMethod, ctx ); + Assignment methodReference = new MethodReference( matchingBuiltInMethod.getMethod(), ctx ); methodReference.setAssignment( sourceRHS ); return methodReference; } @@ -491,22 +479,12 @@ public class MappingResolverImpl implements MappingResolver { && !methodCandidate.isLifecycleCallbackMethod(); } - private List getMatches(List methods, Type sourceType, Type returnType) { - return methodSelectors.getMatchingMethods( + private SelectedMethod getBestMatch(List methods, Type sourceType, Type returnType) { + + List> candidates = methodSelectors.getMatchingMethods( mappingMethod, methods, - sourceType, - returnType, - selectionCriteria - ); - } - - private T getBestMatch(List methods, Type sourceType, Type returnType) { - - List candidates = methodSelectors.getMatchingMethods( - mappingMethod, - methods, - sourceType, + singletonList( sourceType ), returnType, selectionCriteria ); @@ -533,33 +511,23 @@ public class MappingResolverImpl implements MappingResolver { } if ( !candidates.isEmpty() ) { - return candidates.get( 0 ); + return first( candidates ); } return null; } - private Assignment getMappingMethodReference(Method method, + private Assignment getMappingMethodReference(SelectedMethod method, Type targetType) { - MapperReference mapperReference = findMapperReference( method ); + MapperReference mapperReference = findMapperReference( method.getMethod() ); - return new MethodReference( method, + return new MethodReference( + method.getMethod(), mapperReference, - SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null + method.getParameterBindings() ); } - private MapperReference findMapperReference(Method 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. diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl index 3fd081ad9..a8dab4aaf 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/LifecycleCallbackMethodReference.ftl @@ -22,10 +22,7 @@ <#if hasReturnType()> <@includeModel object=methodResultType /> ${targetVariableName} = - <#if declaringType??>${instanceVariableName}.${name}( - <#list parameterAssignments as param> - <#if param.targetType><@includeModel object=ext.targetType raw=true/>.class<#elseif param.mappingTarget>${ext.targetBeanName}<#else>${param.name}<#if param_has_next>,<#else> - ); + <#include 'MethodReference.ftl'>; <#if hasReturnType()><#nt> if ( ${targetVariableName} != null ) { diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl index 667b7c81e..f098c9323 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl @@ -28,18 +28,20 @@ <#macro params> <@compress> - ${name}<#if (parameters?size > 0)>( <@arguments/> )<#else>() + ${name}<#if (parameterBindings?size > 0)>( <@arguments/> )<#else>() <#macro arguments> - <#list parameters as param> + <#list parameterBindings as param> <#if param.targetType> <#-- a class is passed on for casting, see @TargetType --> <@includeModel object=ext.targetType raw=true/>.class<#t> <#elseif param.mappingTarget> - ${ext.targetBeanName}.${ext.targetReadAccessorName} + ${ext.targetBeanName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> + <#elseif assignment??> + <@_assignment/><#t> <#else> - <@_assignment/> + ${param.variableName}<#t> <#if param_has_next>, <#t> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.ftl deleted file mode 100644 index 2d7a55df6..000000000 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/ObjectFactoryMethodReference.ftl +++ /dev/null @@ -1,46 +0,0 @@ -<#-- - - 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. - ---> -<@compress single_line=true> - <#-- method is either internal to the mapper class, or external (via uses) declaringMapper!=null --> - <#if declaringMapper??><#if static><@includeModel object=declaringMapper.type/><#else>${mapperVariableName}.<@params/> - <#-- method is referenced java8 static method in the mapper to implement (interface) --> - <#elseif static><@includeModel object=definingType/>.<@params/> - <#else> - <@params/> - - <#macro params> - <@compress> - ${name}<#if (parameterAssignments?size > 0)>( <@arguments/> )<#else>() - - - <#macro arguments> - <#list parameterAssignments as param> - <#if param.targetType> - <#-- a class is passed on for casting, see @TargetType --> - <@includeModel object=ext.targetType raw=true/>.class<#t> - <#elseif param.mappingTarget> - ${ext.targetBeanName}.${ext.targetReadAccessorName}() - <#else>${param.name}<#if param_has_next>, <#t> - - <#-- context parameter, e.g. for builtin methods concerning date conversion --> - <#if contextParam??>, ${contextParam}<#t> - - diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java index 8cfabf29d..782fecac4 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/runner/AnnotationProcessorTestRunner.java @@ -73,7 +73,8 @@ public class AnnotationProcessorTestRunner extends ParentRunner { return Arrays. asList( new InnerAnnotationProcessorRunner( klass, Compiler.JDK ), - new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) ); + new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) + ); } @Override