#160 allow selection of update methods by refactoring MethodMatcher but stopping selection of update method at new filter

This commit is contained in:
sjaakd 2015-02-14 11:50:46 +01:00
parent 0c226f8388
commit 37e5942c86
11 changed files with 228 additions and 130 deletions

View File

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

View File

@ -71,20 +71,19 @@ public class ForgedMethod implements Method {
}
@Override
public boolean matches(List<Type> 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() ) ) {
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;
}

View File

@ -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<Type> 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<Parameter> 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.

View File

@ -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,81 +69,66 @@ 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<Type> sourceTypes, Type targetType) {
boolean matches(Type sourceType, Type resultType) {
// check & collect generic types.
List<? extends VariableElement> candidateParameters = candidateMethod.getExecutable().getParameters();
if ( candidateParameters.size() != sourceTypes.size() ) {
return false;
}
Map<TypeVariable, TypeMirror> genericTypesMap = new HashMap<TypeVariable, TypeMirror>();
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 ) ) {
// only factory / mapping methods with zero or one source parameters qualify
if ( candidateMethod.getSourceParameters().size() > 1 ) {
return false;
}
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;
}
}
else {
// NOTE: unboxing is deliberately not considered here. This should be handled via type-conversion
// (for NPE safety).
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 {
// 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() ) {
return false;
@ -158,6 +144,63 @@ public class MethodMatcher {
return true;
}
private boolean matchSourceType(Type sourceType,
Type candidateSourceType,
Map<TypeVariable, TypeMirror> 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<TypeVariable, TypeMirror> 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
}

View File

@ -55,7 +55,8 @@ public class SourceMethod implements Method {
private final Type declaringMapper;
private final ExecutableElement executable;
private final List<Parameter> parameters;
private final Parameter targetParameter;
private final Parameter mappingTargetParameter;
private final Parameter targetTypeParameter;
private final Type returnType;
private final Accessibility accessibility;
private final List<Type> 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<Parameter> parameters) {
private Parameter determineMappingTargetParameter(Iterable<Parameter> 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<Parameter> 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<Type> 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 );
}
/**

View File

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

View File

@ -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 <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
SelectionCriteria criteria) {
List<T> createCandidates = new ArrayList<T>();
for ( T method : methods ) {
boolean isCreateCandidate = method.getMappingTargetParameter() == null;
if ( isCreateCandidate ) {
createCandidates.add( method );
}
}
return createCandidates;
}
}

View File

@ -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.<MethodSelector>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<Type> getParameterTypes(TypeFactory typeFactory, List<Parameter> parameters, Type sourceType,
Type returnType) {
List<Type> result = new ArrayList<Type>();
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;
}
}

View File

@ -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 <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type sourceType, Type targetType,
@ -47,13 +40,7 @@ public class TypeSelector implements MethodSelector {
List<T> result = new ArrayList<T>();
for ( T method : methods ) {
if ( method.getSourceParameters().size() > 1 ) {
continue;
}
List<Type> parameterTypes =
MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), sourceType, targetType );
if ( method.matches( parameterTypes, targetType ) ) {
if ( method.matches( sourceType, targetType ) ) {
result.add( method );
}
}

View File

@ -204,6 +204,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
.setReturnType( returnType )
.setExceptionTypes( exceptionTypes )
.setTypeUtils( typeUtils )
.setTypeFactory( typeFactory )
.createSourceMethod();
}

View File

@ -444,9 +444,19 @@ public class MappingResolverImpl implements MappingResolver {
}
private boolean isCandidateForMapping(Method methodCandidate) {
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
}
private boolean isCreateMethodForMapping(Method methodCandidate) {
// a create method may not return void and has no target parameter
return methodCandidate.getSourceParameters().size() == 1 && !methodCandidate.getReturnType().isVoid()
&& methodCandidate.getTargetParameter() == null; // @MappingTarget is not yet supported for property
// mappings
&& methodCandidate.getMappingTargetParameter() == null;
}
private boolean isUpdateMethodForMapping(Method methodCandidate) {
// an update method may, or may not return void and has a target parameter
return methodCandidate.getSourceParameters().size() == 1
&& methodCandidate.getMappingTargetParameter() != null;
}
private <T extends Method> T getBestMatch(List<T> methods, Type sourceType, Type returnType) {