#975 Refactor method-matching to unify selection and rendering of mapping method, factories and lifecycle methods

This commit is contained in:
Andreas Gudian 2016-12-18 00:48:20 +01:00
parent 46363028bd
commit 79f87e8833
29 changed files with 589 additions and 514 deletions

View File

@ -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;

View File

@ -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<SourceMethod> callbackMethods,
MappingBuilderContext ctx, Set<String> existingVariableNames) {
Map<SourceMethod, List<Parameter>> parameterAssignmentsForSourceMethod
= new HashMap<SourceMethod, List<Parameter>>();
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory() );
List<SourceMethod> candidates =
filterCandidatesByType( method, callbackMethods, parameterAssignmentsForSourceMethod, ctx );
candidates = filterCandidatesByQualifiers( method, selectionParameters, candidates, ctx );
List<SelectedMethod<SourceMethod>> matchingMethods = selectors.getMatchingMethods(
method,
callbackMethods,
Collections.<Type> emptyList(),
method.getResultType(),
SelectionCriteria.forLifecycleMethods( selectionParameters ) );
return toLifecycleCallbackMethodRefs(
method,
candidates,
parameterAssignmentsForSourceMethod,
matchingMethods,
ctx,
existingVariableNames );
}
private static List<SourceMethod> filterCandidatesByQualifiers(Method method,
SelectionParameters selectionParameters,
List<SourceMethod> 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<LifecycleCallbackMethodReference> toLifecycleCallbackMethodRefs(Method method,
List<SourceMethod> candidates, Map<SourceMethod, List<Parameter>> parameterAssignmentsForSourceMethod,
MappingBuilderContext ctx, Set<String> existingVariableNames) {
List<SelectedMethod<SourceMethod>> candidates,
MappingBuilderContext ctx,
Set<String> existingVariableNames) {
List<LifecycleCallbackMethodReference> result = new ArrayList<LifecycleCallbackMethodReference>();
for ( SourceMethod candidate : candidates ) {
markMapperReferenceAsUsed( ctx.getMapperReferences(), candidate );
for ( SelectedMethod<SourceMethod> 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<SourceMethod> filterCandidatesByType(Method method,
List<SourceMethod> callbackMethods, Map<SourceMethod, List<Parameter>> parameterAssignmentsForSourceMethod,
MappingBuilderContext ctx) {
List<SourceMethod> candidates = new ArrayList<SourceMethod>();
List<Parameter> availableParams = getAvailableParameters( method, ctx );
for ( SourceMethod callback : callbackMethods ) {
List<Parameter> 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<Parameter> 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<Parameter> getAvailableParameters(Method method, MappingBuilderContext ctx) {
List<Parameter> availableParams = new ArrayList<Parameter>( 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<MapperReference> references, Method method) {
for ( MapperReference ref : references ) {
private static MapperReference findMapperReference(List<MapperReference> 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<Type> extractSourceTypes(List<Parameter> parameters) {
List<Type> result = new ArrayList<Type>( parameters.size() );
for ( Parameter param : parameters ) {
if ( !param.isMappingTarget() && !param.isTargetType() ) {
result.add( param.getType() );
}
}
return result;
return null;
}
private static List<SourceMethod> filterBeforeMappingMethods(List<SourceMethod> methods) {

View File

@ -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<Parameter> parameterAssignments;
private final Type methodReturnType;
private final Type methodResultType;
private final String instanceVariableName;
private final String targetVariableName;
public LifecycleCallbackMethodReference(SourceMethod method, List<Parameter> parameterAssignments,
public LifecycleCallbackMethodReference(SourceMethod method, MapperReference mapperReference,
List<ParameterBinding> parameterBindings,
Type methodReturnType, Type methodResultType,
Set<String> 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.<Type> emptySet();
}
public List<Parameter> getParameterAssignments() {
return parameterAssignments;
}
public boolean hasMappingTargetParameter() {
for ( Parameter param : parameterAssignments ) {
for ( ParameterBinding param : getParameterBindings() ) {
if ( param.isMappingTarget() ) {
return true;
}

View File

@ -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 );

View File

@ -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<ParameterBinding> 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<ParameterBinding> parameterBindings) {
super( method );
this.declaringMapper = declaringMapper;
this.parameterBindings = parameterBindings;
this.contextParam = null;
Set<Type> imported = new HashSet<Type>();
@ -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.<Type>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<ParameterBinding> getParameterBindings() {
return parameterBindings;
}
}

View File

@ -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<Parameter> getParameterAssignments(List<Parameter> availableParams,
List<Parameter> methodParameters) {
List<Parameter> result = new ArrayList<Parameter>( methodParameters.size() );
for ( Parameter methodParam : methodParameters ) {
List<Parameter> 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<Parameter> 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<Parameter> findCandidateParameters(List<Parameter> candidateParameters, Parameter parameter) {
List<Parameter> result = new ArrayList<Parameter>( 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;
}
}

View File

@ -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 );

View File

@ -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;
}
}

View File

@ -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<Type> 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<ParameterBinding> fromParameters(List<Parameter> parameters) {
List<ParameterBinding> result = new ArrayList<ParameterBinding>( parameters.size() );
for ( Parameter param : parameters ) {
result.add( fromParameter( param ) );
}
return result;
}
/**
* @param classTypeOf the type representing {@code Class<X>} 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 );
}
}

View File

@ -754,11 +754,7 @@ public class Type extends ModelElement implements Comparable<Type> {
@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<Type> {
return typeMirror.toString();
}
/**
*
* @return an identification that can be used as part in a forged method name.

View File

@ -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;

View File

@ -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;
}

View File

@ -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<TypeVariable, TypeMirror> genericTypesMap = new HashMap<TypeVariable, TypeMirror>();
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

View File

@ -42,14 +42,19 @@ import org.mapstruct.ap.internal.model.source.Method;
public class CreateOrUpdateSelector implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<T> createCandidates = new ArrayList<T>();
List<T> updateCandidates = new ArrayList<T>();
for ( T method : methods ) {
boolean isCreateCandidate = method.getMappingTargetParameter() == null;
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) {
return methods;
}
List<SelectedMethod<T>> createCandidates = new ArrayList<SelectedMethod<T>>();
List<SelectedMethod<T>> updateCandidates = new ArrayList<SelectedMethod<T>>();
for ( SelectedMethod<T> method : methods ) {
boolean isCreateCandidate = method.getMethod().getMappingTargetParameter() == null;
if ( isCreateCandidate ) {
createCandidates.add( method );
}

View File

@ -35,26 +35,26 @@ import org.mapstruct.ap.internal.model.source.Method;
public class InheritanceSelector implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(
Method mappingMethod,
List<T> methods,
Type sourceType,
Type targetType,
SelectionCriteria criteria
) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type targetType,
SelectionCriteria criteria) {
if ( sourceType == null ) {
if ( sourceTypes.size() != 1 ) {
return methods;
}
List<T> candidatesWithBestMatchingSourceType = new ArrayList<T>();
Type singleSourceType = first( sourceTypes );
List<SelectedMethod<T>> candidatesWithBestMatchingSourceType = new ArrayList<SelectedMethod<T>>();
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<T> 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 <T extends Method> int addToCandidateListIfMinimal(List<T> candidatesWithBestMathingType,
int bestMatchingTypeDistance, T method,
private <T extends Method> int addToCandidateListIfMinimal(List<SelectedMethod<T>> candidatesWithBestMathingType,
int bestMatchingTypeDistance, SelectedMethod<T> method,
int currentTypeDistance) {
if ( currentTypeDistance == bestMatchingTypeDistance ) {
candidatesWithBestMathingType.add( method );

View File

@ -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 <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods, Type sourceType,
Type targetType, SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type targetType, SelectionCriteria criteria) {
List<SelectedMethod<T>> result = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> method : methods ) {
if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired()
&& method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() ) {
List<T> result = new ArrayList<T>();
for ( T method : methods ) {
if ( method.isObjectFactory() == criteria.isObjectFactoryRequired() ) {
result.add( method );
}
}

View File

@ -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 <T> 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
*/
<T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods, Type sourceType,
Type targetType, SelectionCriteria criteria);
<T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> candidates,
List<Type> sourceTypes,
Type targetType, SelectionCriteria criteria);
}

View File

@ -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<MethodSelector> selectors;
public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory) {
selectors =
Arrays.<MethodSelector>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 <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
/**
* Selects those methods which match the given types and other criteria
*
* @param <T> 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 <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod, List<T> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<T> candidates = new ArrayList<T>( methods );
List<SelectedMethod<T>> candidates = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( T method : methods ) {
candidates.add( new SelectedMethod<T>( method ) );
}
for ( MethodSelector selector : selectors ) {
candidates = selector.getMatchingMethods(
mappingMethod,
candidates,
sourceType,
sourceTypes,
targetType,
criteria
);
criteria );
}
return candidates;
}

View File

@ -63,9 +63,10 @@ public class QualifierSelector implements MethodSelector {
}
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> 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<T> nonQualiferAnnotatedMethods = new ArrayList<T>();
for ( T candidate : methods ) {
List<SelectedMethod<T>> nonQualiferAnnotatedMethods = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> candidate : methods ) {
if ( candidate instanceof SourceMethod ) {
Set<AnnotationMirror> qualifierAnnotations = getQualifierAnnotationMirrors( candidate );
if ( candidate.getMethod() instanceof SourceMethod ) {
Set<AnnotationMirror> 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<T> matches = new ArrayList<T>();
for ( T candidate : methods ) {
List<SelectedMethod<T>> matches = new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> candidate : methods ) {
if ( !( candidate instanceof SourceMethod ) ) {
if ( !( candidate.getMethod() instanceof SourceMethod ) ) {
continue;
}
// retrieve annotations
Set<AnnotationMirror> qualifierAnnotationMirrors = getQualifierAnnotationMirrors( candidate );
Set<AnnotationMirror> qualifierAnnotationMirrors =
getQualifierAnnotationMirrors( candidate.getMethod() );
// now count if all qualifiers are matched
int matchingQualifierCounter = 0;

View File

@ -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<T extends Method> {
private T method;
private List<ParameterBinding> parameterBindings;
private final List<Parameter> parameterAssignments;
public ObjectFactoryMethodReference(Method method, MapperReference ref, List<Parameter> parameterAssignments) {
super( method, ref, null );
this.parameterAssignments = parameterAssignments;
public SelectedMethod(T method) {
this.method = method;
}
public List<Parameter> getParameterAssignments() {
return parameterAssignments;
public T getMethod() {
return method;
}
public List<ParameterBinding> getParameterBindings() {
return parameterBindings;
}
public void setParameterBindings(List<ParameterBinding> parameterBindings) {
this.parameterBindings = parameterBindings;
}
@Override
public String toString() {
return method.toString();
}
}

View File

@ -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<TypeMirror> 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 );
}
}

View File

@ -45,16 +45,19 @@ public class TargetTypeSelector implements MethodSelector {
}
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType();
if ( qualifyingTypeMirror != null ) {
if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) {
List<T> candidatesWithQualifyingTargetType = new ArrayList<T>();
for ( T method : methods ) {
TypeMirror resultTypeMirror = method.getResultType().getTypeElement().asType();
List<SelectedMethod<T>> candidatesWithQualifyingTargetType =
new ArrayList<SelectedMethod<T>>( methods.size() );
for ( SelectedMethod<T> method : methods ) {
TypeMirror resultTypeMirror = method.getMethod().getResultType().getTypeElement().asType();
if ( typeUtils.isSameType( qualifyingTypeMirror, resultTypeMirror ) ) {
candidatesWithQualifyingTargetType.add( method );
}

View File

@ -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 <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
private TypeFactory typeFactory;
List<T> result = new ArrayList<T>();
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 <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
if ( methods.isEmpty() ) {
return methods;
}
List<SelectedMethod<T>> result = new ArrayList<SelectedMethod<T>>();
List<ParameterBinding> 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<T> method : methods ) {
List<List<ParameterBinding>> parameterBindingPermutations =
getCandidateParameterBindingPermutations( availableBindings, method.getMethod().getParameters() );
if ( parameterBindingPermutations != null ) {
SelectedMethod<T> matchingMethod =
getFirstMatchingParameterBinding( targetType, method, parameterBindingPermutations );
if ( matchingMethod != null ) {
result.add( matchingMethod );
}
}
}
return result;
}
private List<ParameterBinding> getAvailableParameterBindingsFromMethod(Method method) {
List<ParameterBinding> availableParams = new ArrayList<ParameterBinding>( method.getParameters().size() + 2 );
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
addMappingTargetAndTargetTypeBindings( availableParams, method.getResultType() );
return availableParams;
}
private List<ParameterBinding> getAvailableParameterBindingsFromSourceTypes(List<Type> sourceTypes,
Type targetType) {
List<ParameterBinding> availableParams = new ArrayList<ParameterBinding>( sourceTypes.size() + 2 );
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
for ( Type sourceType : sourceTypes ) {
availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) );
}
return availableParams;
}
private void addMappingTargetAndTargetTypeBindings(List<ParameterBinding> availableParams, Type targetType) {
availableParams.add( ParameterBinding.forMappingTargetBinding( targetType ) );
availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
}
private <T extends Method> SelectedMethod<T> getFirstMatchingParameterBinding(Type targetType,
SelectedMethod<T> method, List<List<ParameterBinding>> parameterAssignmentVariants) {
for ( List<ParameterBinding> 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<List<ParameterBinding>> getCandidateParameterBindingPermutations(
List<ParameterBinding> availableParams,
List<Parameter> methodParameters) {
if ( methodParameters.size() > availableParams.size() ) {
return null;
}
List<List<ParameterBinding>> bindingPermutations = new ArrayList<List<ParameterBinding>>( 1 );
bindingPermutations.add( new ArrayList<ParameterBinding>( methodParameters.size() ) );
for ( Parameter methodParam : methodParameters ) {
List<ParameterBinding> 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<ParameterBinding> variant : bindingPermutations ) {
// add binding to each existing variant
variant.add( first( candidateBindings ) );
}
}
else {
List<List<ParameterBinding>> newVariants =
new ArrayList<List<ParameterBinding>>( bindingPermutations.size() * candidateBindings.size() );
for ( List<ParameterBinding> variant : bindingPermutations ) {
// create a copy of each variant for each binding
for ( ParameterBinding binding : candidateBindings ) {
List<ParameterBinding> extendedVariant =
new ArrayList<ParameterBinding>( 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<ParameterBinding> findCandidateBindingsForParameter(List<ParameterBinding> candidateParameters,
Parameter parameter) {
List<ParameterBinding> result = new ArrayList<ParameterBinding>( candidateParameters.size() );
for ( ParameterBinding candidate : candidateParameters ) {
if ( parameter.isTargetType() == candidate.isTargetType()
&& parameter.isMappingTarget() == candidate.isMappingTarget() ) {
result.add( candidate );
}
}
return result;
}
private static List<Type> extractTypes(List<ParameterBinding> parameters) {
List<Type> result = new ArrayList<Type>( parameters.size() );
for ( ParameterBinding param : parameters ) {
result.add( param.getType() );
}
return result;
}
}

View File

@ -61,9 +61,10 @@ public class XmlElementDeclSelector implements MethodSelector {
}
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> 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<T> nameMatches = new ArrayList<T>();
List<T> scopeMatches = new ArrayList<T>();
List<T> nameAndScopeMatches = new ArrayList<T>();
List<SelectedMethod<T>> nameMatches = new ArrayList<SelectedMethod<T>>();
List<SelectedMethod<T>> scopeMatches = new ArrayList<SelectedMethod<T>>();
List<SelectedMethod<T>> nameAndScopeMatches = new ArrayList<SelectedMethod<T>>();
XmlElementRefInfo xmlElementRefInfo =
findXmlElementRef( sourceMappingMethod.getResultType(), criteria.getTargetPropertyName() );
for ( T candidate : methods ) {
if ( !( candidate instanceof SourceMethod ) ) {
for ( SelectedMethod<T> 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 ) {

View File

@ -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<SelectedMethod<Method>> matchingFactoryMethods =
methodSelectors.getMatchingMethods(
mappingMethod,
sourceModel,
java.util.Collections.<Type> emptyList(),
targetType,
SelectionCriteria.forFactoryMethods( selectionParameters ) );
ResolvingAttempt attempt = new ResolvingAttempt( sourceModel, mappingMethod, null, null, null, criteria );
List<Method> matchingSourceMethods = attempt.getMatches( sourceModel, null, targetType );
List<MethodReference> factoryRefsWithAssigments = new ArrayList<MethodReference>();
List<Method> factoryRefSources = new ArrayList<Method>();
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<Parameter> availableParameters = new ArrayList<Parameter>();
availableParameters.addAll( mappingMethod.getSourceParameters() );
availableParameters.add(
new Parameter( null,
typeFactory.classTypeOf( targetType ),
false, true, false ) );
List<Parameter> 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<Method> 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<Method> 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<BuiltInMethod> 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 <T extends Method> List<T> getMatches(List<T> methods, Type sourceType, Type returnType) {
return methodSelectors.getMatchingMethods(
private <T extends Method> SelectedMethod<T> getBestMatch(List<T> methods, Type sourceType, Type returnType) {
List<SelectedMethod<T>> candidates = methodSelectors.getMatchingMethods(
mappingMethod,
methods,
sourceType,
returnType,
selectionCriteria
);
}
private <T extends Method> T getBestMatch(List<T> methods, Type sourceType, Type returnType) {
List<T> 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> 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.

View File

@ -22,10 +22,7 @@
<#if hasReturnType()>
<@includeModel object=methodResultType /> ${targetVariableName} =
</#if>
<#if declaringType??>${instanceVariableName}.</#if>${name}(
<#list parameterAssignments as param>
<#if param.targetType><@includeModel object=ext.targetType raw=true/>.class<#elseif param.mappingTarget>${ext.targetBeanName}<#else>${param.name}</#if><#if param_has_next>,<#else> </#if>
</#list>);
<#include 'MethodReference.ftl'>;
</@compress>
<#if hasReturnType()><#nt>
if ( ${targetVariableName} != null ) {

View File

@ -28,18 +28,20 @@
</#if>
<#macro params>
<@compress>
${name}<#if (parameters?size > 0)>( <@arguments/> )<#else>()</#if>
${name}<#if (parameterBindings?size > 0)>( <@arguments/> )<#else>()</#if>
</@compress>
</#macro>
<#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}</#if><#t>
<#elseif assignment??>
<@_assignment/><#t>
<#else>
<@_assignment/>
${param.variableName}<#t>
</#if>
<#if param_has_next>, </#if><#t>
</#list>

View File

@ -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}</#if>.<@params/>
<#-- method is referenced java8 static method in the mapper to implement (interface) -->
<#elseif static><@includeModel object=definingType/>.<@params/>
<#else>
<@params/>
</#if>
<#macro params>
<@compress>
${name}<#if (parameterAssignments?size > 0)>( <@arguments/> )<#else>()</#if>
</@compress>
</#macro>
<#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><#if param_has_next>, </#if><#t>
</#list>
<#-- context parameter, e.g. for builtin methods concerning date conversion -->
<#if contextParam??>, ${contextParam}</#if><#t>
</#macro>
</@compress>

View File

@ -73,7 +73,8 @@ public class AnnotationProcessorTestRunner extends ParentRunner<Runner> {
return Arrays.<Runner> asList(
new InnerAnnotationProcessorRunner( klass, Compiler.JDK ),
new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE ) );
new InnerAnnotationProcessorRunner( klass, Compiler.ECLIPSE )
);
}
@Override