diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java index 6be230211..fcab698d0 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java @@ -50,7 +50,7 @@ public abstract class MappingMethod extends ModelElement { this.name = method.getName(); this.parameters = method.getParameters(); this.returnType = method.getReturnType(); - this.targetParameter = method.getTargetParameter(); + this.targetParameter = method.getMappingTargetParameter(); this.accessibility = method.getAccessibility(); this.thrownTypes = method.getThrownTypes(); this.isStatic = method.isStatic(); diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java index 7179c025a..b6b1cd930 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/ForgedMethod.java @@ -71,19 +71,18 @@ public class ForgedMethod implements Method { } @Override - public boolean matches(List sourceTypes, Type targetType) { + public boolean matches(Type sourceTypes, Type targetType) { if ( !targetType.equals( returnType ) ) { return false; } - if ( sourceTypes.size() == parameters.size() ) { + if ( parameters.size() != 1 ) { return false; } - for ( int i = 0; i < sourceTypes.size(); i++ ) { - if ( !sourceTypes.get( i ).equals( parameters.get( i ).getType() ) ) { - return false; - } + + if ( !sourceTypes.equals( parameters.get( 0 ).getType() ) ) { + return false; } return true; @@ -110,7 +109,12 @@ public class ForgedMethod implements Method { } @Override - public Parameter getTargetParameter() { + public Parameter getMappingTargetParameter() { + return null; + } + + @Override + public Parameter getTargetTypeParameter() { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java index b2e13d06c..d7ffc369c 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java @@ -40,12 +40,12 @@ public interface Method { * Checks whether the provided sourceType and provided targetType match with the parameter respectively return type * of the method. The check also should incorporate wild card and generic type variables * - * @param sourceTypes the sourceTypes to match to the parameter + * @param sourceType the sourceType to match to the parameter * @param targetType the targetType to match to the returnType * * @return true when match */ - boolean matches(List sourceTypes, Type targetType); + boolean matches(Type sourceType, Type targetType ); /** * Returns the mapper type declaring this method if it is not declared by the mapper interface currently processed @@ -71,18 +71,26 @@ public interface Method { /** * returns the list of 'true' source parameters excluding the parameter(s) that is designated as - * target by means of the target annotation {@link #getTargetParameter() }. + * target by means of the target annotation {@link #getMappingTargetParameter() }. * * @return list of 'true' source parameters */ List getSourceParameters(); /** - * Returns the parameter designated as target parameter (if present) {@link #getSourceParameters() } + * Returns the parameter designated as mapping target (if present) {@link org.mapstruct.MappingTarget } * - * @return target parameter (when present) null otherwise. + * @return mapping target parameter (when present) null otherwise. */ - Parameter getTargetParameter(); + Parameter getMappingTargetParameter(); + + /** + * Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType } + * + * @return target type parameter (when present) null otherwise. + */ + Parameter getTargetTypeParameter(); + /** * Returns the {@link Accessibility} of this method. diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java index 2a381dd19..ca3c25cf7 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java @@ -24,7 +24,6 @@ import java.util.Map; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; @@ -34,8 +33,10 @@ import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; +import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.common.TypeFactory; import static org.mapstruct.ap.util.SpecificCompilerWorkarounds.isSubType; @@ -68,80 +69,65 @@ public class MethodMatcher { private final SourceMethod candidateMethod; private final Types typeUtils; + private final TypeFactory typeFactory; - MethodMatcher(Types typeUtils, SourceMethod candidateMethod) { + MethodMatcher(Types typeUtils, TypeFactory typeFactory, SourceMethod candidateMethod) { this.typeUtils = typeUtils; this.candidateMethod = candidateMethod; + this.typeFactory = typeFactory; } /** * Whether the given source and target types are matched by this matcher's candidate method. * - * @param sourceTypes the source types - * @param targetType the target type + * @param sourceType the source types + * @param resultType the target type * * @return {@code true} when both, source type and target types match the signature of this matcher's method; * {@code false} otherwise. */ - boolean matches(List sourceTypes, Type targetType) { - // check & collect generic types. - List candidateParameters = candidateMethod.getExecutable().getParameters(); + boolean matches(Type sourceType, Type resultType) { - if ( candidateParameters.size() != sourceTypes.size() ) { + // check & collect generic types. + Map genericTypesMap = new HashMap(); + + // only factory / mapping methods with zero or one source parameters qualify + if ( candidateMethod.getSourceParameters().size() > 1 ) { return false; } - Map genericTypesMap = new HashMap(); - - int i = 0; - for ( VariableElement candidateParameter : candidateParameters ) { - TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); - Type sourceType = sourceTypes.get( i++ ); - if ( !parameterMatcher.visit( candidateParameter.asType(), sourceType.getTypeMirror() ) ) { - if (sourceType.isPrimitive() ) { - // the candidate source is primitive, so promote to its boxed type and check again (autobox) - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) sourceType.getTypeMirror() ).asType(); - if ( !parameterMatcher.visit( candidateParameter.asType(), boxedType ) ) { - return false; - } - } - else { - // NOTE: unboxing is deliberately not considered here. This should be handled via type-conversion - // (for NPE safety). + if ( sourceType != null ) { + // if the sourcetype is not null then only methods with one source parameter qualfiy + if ( candidateMethod.getSourceParameters().size() == 1 ) { + Parameter sourceParam = candidateMethod.getSourceParameters().iterator().next(); + if ( !matchSourceType( sourceType, sourceParam.getType(), genericTypesMap ) ) { return false; } } - } - - // check return type - TypeMirror candidateReturnType = candidateMethod.getExecutable().getReturnType(); - TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); - - if ( !returnTypeMatcher.visit( candidateReturnType, targetType.getTypeMirror() ) ) { - if ( targetType.isPrimitive() ) { - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) targetType.getTypeMirror() ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); - - if ( !boxedReturnTypeMatcher.visit( candidateReturnType, boxedType ) ) { - return false; - } - } - else if ( candidateReturnType.getKind().isPrimitive() ) { - TypeMirror boxedCandidateReturnType = - typeUtils.boxedClass( (PrimitiveType) candidateReturnType ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); - - if ( !boxedReturnTypeMatcher.visit( boxedCandidateReturnType, targetType.getTypeMirror() ) ) { - return false; - } - - } else { return false; } } + else { + // if the sourcetype is null then only methods with zero source parameters qualfiy + if ( !candidateMethod.getSourceParameters().isEmpty() ) { + return false; + } + } + + // check if the method matches the proper result type to construct + Parameter targetTypeParameter = candidateMethod.getTargetTypeParameter(); + if ( targetTypeParameter != null ) { + Type returnClassType = typeFactory.classTypeOf( resultType ); + if ( !matchSourceType( returnClassType, targetTypeParameter.getType(), genericTypesMap ) ) { + return false; + } + } + + // check result type + if ( !matchResultType( resultType, candidateMethod.getResultType(), genericTypesMap ) ) { + return false; + } // check if all type parameters are indeed mapped if ( candidateMethod.getExecutable().getTypeParameters().size() != genericTypesMap.size() ) { @@ -158,6 +144,63 @@ public class MethodMatcher { return true; } + private boolean matchSourceType(Type sourceType, + Type candidateSourceType, + Map genericTypesMap) { + + TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); + if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), sourceType.getTypeMirror() ) ) { + if ( sourceType.isPrimitive() ) { + // the candidate source is primitive, so promote to its boxed type and check again (autobox) + TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) sourceType.getTypeMirror() ).asType(); + if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), boxedType ) ) { + return false; + } + } + else { + // NOTE: unboxing is deliberately not considered here. This should be handled via type-conversion + // (for NPE safety). + return false; + } + } + return true; + } + + private boolean matchResultType(Type resultType, + Type candidateResultType, + Map genericTypesMap) { + + TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + + if ( !returnTypeMatcher.visit( candidateResultType.getTypeMirror(), resultType.getTypeMirror() ) ) { + if ( resultType.isPrimitive() ) { + TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) resultType.getTypeMirror() ).asType(); + TypeMatcher boxedReturnTypeMatcher = + new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + + if ( !boxedReturnTypeMatcher.visit( candidateResultType.getTypeMirror(), boxedType ) ) { + return false; + } + } + else if ( candidateResultType.getTypeMirror().getKind().isPrimitive() ) { + TypeMirror boxedCandidateReturnType = + typeUtils.boxedClass( (PrimitiveType) candidateResultType.getTypeMirror() ).asType(); + TypeMatcher boxedReturnTypeMatcher = + new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + + if ( !boxedReturnTypeMatcher.visit( boxedCandidateReturnType, resultType.getTypeMirror() ) ) { + return false; + } + + } + else { + return false; + } + } + return true; + } + + private enum Assignability { VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java index 3f6e493e9..09a9307bb 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java @@ -55,7 +55,8 @@ public class SourceMethod implements Method { private final Type declaringMapper; private final ExecutableElement executable; private final List parameters; - private final Parameter targetParameter; + private final Parameter mappingTargetParameter; + private final Parameter targetTypeParameter; private final Type returnType; private final Accessibility accessibility; private final List exceptionTypes; @@ -185,7 +186,8 @@ public class SourceMethod implements Method { this.mapMapping = mapMapping; this.accessibility = Accessibility.fromModifiers( executable.getModifiers() ); - this.targetParameter = determineTargetParameter( parameters ); + this.mappingTargetParameter = determineMappingTargetParameter( parameters ); + this.targetTypeParameter = determineTargetTypeParameter( parameters ); this.typeUtils = typeUtils; this.typeFactory = typeFactory; @@ -193,7 +195,7 @@ public class SourceMethod implements Method { this.config = config; } - private Parameter determineTargetParameter(Iterable parameters) { + private Parameter determineMappingTargetParameter(Iterable parameters) { for ( Parameter parameter : parameters ) { if ( parameter.isMappingTarget() ) { return parameter; @@ -203,6 +205,16 @@ public class SourceMethod implements Method { return null; } + private Parameter determineTargetTypeParameter(Iterable parameters) { + for ( Parameter parameter : parameters ) { + if ( parameter.isTargetType() ) { + return parameter; + } + } + + return null; + } + /** * {@inheritDoc} {@link Method} */ @@ -261,7 +273,7 @@ public class SourceMethod implements Method { @Override public Type getResultType() { - return targetParameter != null ? targetParameter.getType() : returnType; + return mappingTargetParameter != null ? mappingTargetParameter.getType() : returnType; } /** @@ -357,12 +369,14 @@ public class SourceMethod implements Method { return equals( getResultType(), method.getResultType() ); } - /** - * {@inheritDoc} {@link Method} - */ @Override - public Parameter getTargetParameter() { - return targetParameter; + public Parameter getMappingTargetParameter() { + return mappingTargetParameter; + } + + @Override + public Parameter getTargetTypeParameter() { + return targetTypeParameter; } public boolean isIterableMapping() { @@ -455,9 +469,9 @@ public class SourceMethod implements Method { * {@inheritDoc} {@link Method} */ @Override - public boolean matches(List sourceTypes, Type targetType) { - MethodMatcher matcher = new MethodMatcher( typeUtils, this ); - return matcher.matches( sourceTypes, targetType ); + public boolean matches(Type sourceType, Type targetType) { + MethodMatcher matcher = new MethodMatcher( typeUtils, typeFactory, this ); + return matcher.matches( sourceType, targetType ); } /** diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java index d1d68b2fb..8ae65a78a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java @@ -71,11 +71,7 @@ public abstract class BuiltInMethod implements Method { * excluding generic type variables. When the implementor sees a need for this, this method can be overridden. */ @Override - public boolean matches(List sourceTypes, Type targetType) { - if ( sourceTypes.size() > 1 ) { - return false; - } - Type sourceType = sourceTypes.iterator().next(); + public boolean matches(Type sourceType, Type targetType) { if ( getReturnType().isAssignableTo( targetType.erasure() ) && sourceType.erasure().isAssignableTo( getParameter().getType() ) ) { @@ -118,12 +114,22 @@ public abstract class BuiltInMethod implements Method { } /** - * target parameter mechanism not supported for built-in methods + * mapping target parameter mechanism not supported for built-in methods * * @return {@code null} */ @Override - public Parameter getTargetParameter() { + public Parameter getMappingTargetParameter() { + return null; + } + + /** + * target type parameter mechanism not supported for built-in methods + * + * @return {@code null} + */ + @Override + public Parameter getTargetTypeParameter() { return null; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/CreateOrUpdateSelector.java new file mode 100644 index 000000000..f9b08880f --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/CreateOrUpdateSelector.java @@ -0,0 +1,51 @@ +/** + * Copyright 2012-2015 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.model.source.selector; + +import java.util.ArrayList; +import java.util.List; + +import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.Method; +import org.mapstruct.ap.model.source.MethodMatcher; + +/** + * Selects only create method candidates (so methods not containing {@link @MappingTarget} ) + * {@link MethodMatcher}). + * + * @author Sjaak Derksen + */ +public class CreateOrUpdateSelector implements MethodSelector { + + @Override + public List getMatchingMethods(Method mappingMethod, List methods, + Type sourceType, Type targetType, + SelectionCriteria criteria) { + + List createCandidates = new ArrayList(); + for ( T method : methods ) { + boolean isCreateCandidate = method.getMappingTargetParameter() == null; + if ( isCreateCandidate ) { + createCandidates.add( method ); + } + } + return createCandidates; + + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java index c046f83c1..98c1c4ecf 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java @@ -24,7 +24,6 @@ import java.util.List; import javax.lang.model.util.Elements; import javax.lang.model.util.Types; -import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.Method; @@ -41,11 +40,12 @@ public class MethodSelectors implements MethodSelector { public MethodSelectors(Types typeUtils, Elements elementUtils, TypeFactory typeFactory) { selectors = Arrays.asList( - new TypeSelector( typeFactory ), + new TypeSelector(), new QualifierSelector( typeUtils, elementUtils ), new TargetTypeSelector( typeUtils, elementUtils ), new XmlElementDeclSelector( typeUtils ), - new InheritanceSelector() + new InheritanceSelector(), + new CreateOrUpdateSelector() ); } @@ -67,30 +67,4 @@ public class MethodSelectors implements MethodSelector { } return candidates; } - - /** - * @param typeFactory the type factory to use - * @param parameters the parameters to map the types for - * @param sourceType the source type - * @param returnType the return type - * - * @return the list of actual parameter types - */ - public static List getParameterTypes(TypeFactory typeFactory, List parameters, Type sourceType, - Type returnType) { - List result = new ArrayList(); - for ( Parameter param : parameters ) { - if ( param.isTargetType() ) { - result.add( typeFactory.classTypeOf( returnType ) ); - } - else { - if ( sourceType != null ) { - /* for factory methods (sourceType==null), no parameter must be added */ - result.add( sourceType ); - } - } - } - - return result; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java index 52be353d1..71502ba63 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import org.mapstruct.ap.model.common.Type; -import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.MethodMatcher; @@ -34,12 +33,6 @@ import org.mapstruct.ap.model.source.MethodMatcher; */ public class TypeSelector implements MethodSelector { - private final TypeFactory typeFactory; - - public TypeSelector(TypeFactory typeFactory) { - this.typeFactory = typeFactory; - } - @Override public List getMatchingMethods(Method mappingMethod, List methods, Type sourceType, Type targetType, @@ -47,13 +40,7 @@ public class TypeSelector implements MethodSelector { List result = new ArrayList(); for ( T method : methods ) { - if ( method.getSourceParameters().size() > 1 ) { - continue; - } - - List parameterTypes = - MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), sourceType, targetType ); - if ( method.matches( parameterTypes, targetType ) ) { + if ( method.matches( sourceType, targetType ) ) { result.add( method ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index a89c543ed..98ff02bc8 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -204,6 +204,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor T getBestMatch(List methods, Type sourceType, Type returnType) {