#385 Refactoring, factory methods should use selectors, preparing selector api for more criteria

This commit is contained in:
sjaakd 2014-12-16 23:33:15 +01:00
parent 7d2932c9b5
commit 65c272286f
14 changed files with 130 additions and 100 deletions

View File

@ -21,15 +21,12 @@ package org.mapstruct.ap.model;
import java.util.List;
import java.util.Set;
import javax.tools.Diagnostic;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.common.ConversionContext;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.model.source.selector.MethodSelectors;
/**
* Factory class for creating all types of assignments
@ -57,50 +54,5 @@ public class AssignmentFactory {
public static Direct createDirect(String sourceRef) {
return new Direct( sourceRef );
}
public static MethodReference createFactoryMethod( Type returnType, MappingBuilderContext ctx ) {
MethodReference result = null;
for ( SourceMethod method : ctx.getSourceModel() ) {
if ( !method.overridesMethod() && !method.isIterableMapping() && !method.isMapMapping()
&& method.getSourceParameters().isEmpty() ) {
List<Type> parameterTypes = MethodSelectors.getParameterTypes(
ctx.getTypeFactory(),
method.getParameters(),
null,
returnType
);
if ( method.matches( parameterTypes, returnType ) ) {
if ( result == null ) {
MapperReference mapperReference = findMapperReference( ctx.getMapperReferences(), method );
result = new MethodReference( method, mapperReference, null );
}
else {
ctx.getMessager().printMessage(
Diagnostic.Kind.ERROR,
String.format(
"Ambiguous factory methods: \"%s\" conflicts with \"%s\".",
result,
method
),
method.getExecutable()
);
}
}
}
}
return result;
}
private static MapperReference findMapperReference( List<MapperReference> mapperReferences, SourceMethod method ) {
for ( MapperReference ref : mapperReferences ) {
if ( ref.getType().equals( method.getDeclaringMapper() ) ) {
ref.setUsed( !method.isStatic() );
return ref;
}
}
return null;
}
}

View File

@ -103,7 +103,7 @@ public class BeanMappingMethod extends MappingMethod {
boolean mapNullToDefault =
MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ).isMapToDefault( prism );
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
MethodReference factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType() );
return new BeanMappingMethod( method, propertyMappings, factoryMethod, mapNullToDefault );
}

View File

@ -124,8 +124,7 @@ public class IterableMappingMethod extends MappingMethod {
boolean mapNullToDefault
= MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ).isMapToDefault( prism );
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
MethodReference factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType() );
return new IterableMappingMethod(
method,
assignment,

View File

@ -141,7 +141,7 @@ public class MapMappingMethod extends MappingMethod {
boolean mapNullToDefault =
MapperConfig.getInstanceOn( ctx.getMapperTypeElement() ).isMapToDefault( prism );
MethodReference factoryMethod = AssignmentFactory.createFactoryMethod( method.getReturnType(), ctx );
MethodReference factoryMethod = ctx.getMappingResolver().getFactoryMethod( method, method.getResultType() );
keyAssignment = new LocalVarWrapper( keyAssignment, method.getThrownTypes() );
valueAssignment = new LocalVarWrapper( valueAssignment, method.getThrownTypes() );

View File

@ -94,6 +94,17 @@ public class MappingBuilderContext {
String targetPropertyName, String dateFormat, List<TypeMirror> qualifiers,
String sourceReference);
/**
* returns a no arg factory method
*
* @param mappingMethod target mapping method
* @param targetType return type to match
*
* @return a method reference to the factory method, or null if no suitable, or ambiguous method found
*
*/
MethodReference getFactoryMethod(Method mappingMethod, Type targetType);
Set<VirtualMappingMethod> getUsedVirtualMappings();
}
@ -190,4 +201,5 @@ public class MappingBuilderContext {
public Set<VirtualMappingMethod> getUsedVirtualMappings() {
return mappingResolver.getUsedVirtualMappings();
}
}

View File

@ -20,7 +20,6 @@ package org.mapstruct.ap.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.model.common.Parameter;
import org.mapstruct.ap.model.common.Type;
@ -37,12 +36,15 @@ public class InheritanceSelector implements MethodSelector {
public <T extends Method> List<T> getMatchingMethods(
Method mappingMethod,
List<T> methods,
Type parameterType,
Type returnType,
List<TypeMirror> qualifiers,
String targetPropertyName
Type sourceType,
Type targetType,
SelectionCriteria criteria
) {
if ( sourceType == null ) {
return methods;
}
List<T> candidatesWithBestMatchingSourceType = new ArrayList<T>();
int bestMatchingSourceTypeDistance = Integer.MAX_VALUE;
@ -50,7 +52,7 @@ public class InheritanceSelector implements MethodSelector {
for ( T method : methods ) {
Parameter singleSourceParam = method.getSourceParameters().iterator().next();
int sourceTypeDistance = parameterType.distanceTo( singleSourceParam.getType() );
int sourceTypeDistance = sourceType.distanceTo( singleSourceParam.getType() );
bestMatchingSourceTypeDistance =
addToCandidateListIfMinimal(
candidatesWithBestMatchingSourceType,

View File

@ -19,7 +19,6 @@
package org.mapstruct.ap.model.source.selector;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.source.Method;
@ -39,14 +38,12 @@ public interface MethodSelector {
* @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 parameterType parameter type that should be matched
* @param returnType return type that should be matched
* @param qualifiers list of custom annotations, used in the qualifying process
* @param targetPropertyName some information can be derived from the target property
* @param sourceType parameter type 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
*/
<T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods, Type parameterType,
Type returnType, List<TypeMirror> qualifiers,
String targetPropertyName);
<T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods, Type sourceType,
Type targetType, SelectionCriteria criteria);
}

View File

@ -21,7 +21,6 @@ package org.mapstruct.ap.model.source.selector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
@ -51,9 +50,8 @@ public class MethodSelectors implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type parameterType, Type returnType,
List<TypeMirror> qualifiers,
String targetPropertyName) {
Type sourceType, Type targetType,
SelectionCriteria criteria) {
List<T> candidates = new ArrayList<T>( methods );
@ -61,10 +59,9 @@ public class MethodSelectors implements MethodSelector {
candidates = selector.getMatchingMethods(
mappingMethod,
candidates,
parameterType,
returnType,
qualifiers,
targetPropertyName
sourceType,
targetType,
criteria
);
}
return candidates;
@ -80,15 +77,18 @@ public class MethodSelectors implements MethodSelector {
*/
public static List<Type> getParameterTypes(TypeFactory typeFactory, List<Parameter> parameters, Type sourceType,
Type returnType) {
List<Type> result = new ArrayList<Type>( parameters.size() );
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

@ -59,10 +59,10 @@ public class QualifierSelector implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type parameterType, Type returnType,
List<TypeMirror> qualifiers,
String targetPropertyName) {
Type sourceType, Type targetType,
SelectionCriteria criteria) {
List<TypeMirror> qualifiers = criteria.getQualifiers();
if ( qualifiers == null || qualifiers.isEmpty() ) {
// remove the method marked as qualifier from the list
List<T> nonQualiferAnnotatedMethods = new ArrayList<T>();

View File

@ -0,0 +1,47 @@
/**
* 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.List;
import javax.lang.model.type.TypeMirror;
/**
* This class groups the selection criteria in one class
*
* @author Sjaak Derksen
*/
public class SelectionCriteria {
private final List<TypeMirror> qualifiers;
private final String targetPropertyName;
public SelectionCriteria(List<TypeMirror> qualifiers, String targetPropertyName) {
this.qualifiers = qualifiers;
this.targetPropertyName = targetPropertyName;
}
public List<TypeMirror> getQualifiers() {
return qualifiers;
}
public String getTargetPropertyName() {
return targetPropertyName;
}
}

View File

@ -20,7 +20,6 @@ package org.mapstruct.ap.model.source.selector;
import java.util.ArrayList;
import java.util.List;
import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.model.common.Type;
import org.mapstruct.ap.model.common.TypeFactory;
@ -43,19 +42,18 @@ public class TypeSelector implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type parameterType, Type returnType,
List<TypeMirror> qualifiers,
String targetPropertyName) {
Type sourceType, Type targetType,
SelectionCriteria criteria) {
List<T> result = new ArrayList<T>();
for ( T method : methods ) {
if ( method.getSourceParameters().size() != 1 ) {
if ( method.getSourceParameters().size() > 1 ) {
continue;
}
List<Type> parameterTypes =
MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), parameterType, returnType );
if ( method.matches( parameterTypes, returnType ) ) {
MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), sourceType, targetType );
if ( method.matches( parameterTypes, targetType ) ) {
result.add( method );
}
}

View File

@ -52,9 +52,8 @@ public class XmlElementDeclSelector implements MethodSelector {
@Override
public <T extends Method> List<T> getMatchingMethods(Method mappingMethod, List<T> methods,
Type parameterType, Type returnType,
List<TypeMirror> qualifiers,
String targetPropertyName) {
Type sourceType, Type targetType,
SelectionCriteria criteria) {
// only true source methods are qualifying
if ( !(mappingMethod instanceof SourceMethod) ) {
@ -83,7 +82,7 @@ public class XmlElementDeclSelector implements MethodSelector {
TypeMirror scope = xmlElememtDecl.scope();
TypeMirror target = sourceMappingMethod.getExecutable().getReturnType();
boolean nameIsSetAndMatches = name != null && name.equals( targetPropertyName );
boolean nameIsSetAndMatches = name != null && name.equals( criteria.getTargetPropertyName() );
boolean scopeIsSetAndMatches = scope != null && typeUtils.isSameType( scope, target );
if ( nameIsSetAndMatches ) {

View File

@ -34,6 +34,7 @@ import org.mapstruct.ap.conversion.Conversions;
import org.mapstruct.ap.model.AssignmentFactory;
import org.mapstruct.ap.model.MapperReference;
import org.mapstruct.ap.model.MappingBuilderContext.MappingResolver;
import org.mapstruct.ap.model.MethodReference;
import org.mapstruct.ap.model.VirtualMappingMethod;
import org.mapstruct.ap.model.assignment.Assignment;
import org.mapstruct.ap.model.common.ConversionContext;
@ -45,6 +46,7 @@ import org.mapstruct.ap.model.source.SourceMethod;
import org.mapstruct.ap.model.source.builtin.BuiltInMappingMethods;
import org.mapstruct.ap.model.source.builtin.BuiltInMethod;
import org.mapstruct.ap.model.source.selector.MethodSelectors;
import org.mapstruct.ap.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.util.Strings;
/**
@ -140,6 +142,28 @@ public class MappingResolverImpl implements MappingResolver {
return usedVirtualMappings;
}
@Override
public MethodReference getFactoryMethod( Method mappingMethod, Type targetType ) {
ResolvingAttempt attempt = new ResolvingAttempt(
sourceModel,
mappingMethod,
null,
null,
null,
null,
null
);
SourceMethod matchingSourceMethod = attempt.getBestMatch( sourceModel, null, targetType );
if ( matchingSourceMethod != null ) {
MapperReference ref = attempt.findMapperReference( matchingSourceMethod );
return new MethodReference( matchingSourceMethod, ref, null );
}
return null;
}
private class ResolvingAttempt {
private final Method mappingMethod;
@ -416,13 +440,13 @@ public class MappingResolverImpl implements MappingResolver {
private <T extends Method> T getBestMatch(List<T> methods, Type sourceType, Type returnType) {
SelectionCriteria criteria = new SelectionCriteria( qualifiers, targetPropertyName );
List<T> candidates = methodSelectors.getMatchingMethods(
mappingMethod,
methods,
sourceType,
returnType,
qualifiers,
targetPropertyName
criteria
);
// raise an error if more than one mapping method is suitable to map the given source type
@ -430,7 +454,8 @@ public class MappingResolverImpl implements MappingResolver {
if ( candidates.size() > 1 ) {
String errorMsg = String.format(
"Ambiguous mapping methods found for mapping " + mappedElement + " to %s: %s.",
"Ambiguous mapping methods found for %s %s: %s.",
mappedElement != null ? "mapping " + mappedElement + " to" : "factorizing",
returnType,
Strings.join( candidates, ", " )
);

View File

@ -44,14 +44,13 @@ public class FactoryTest {
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = BarFactory.class,
@Diagnostic(type = SourceTargetMapperAndBarFactory.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
line = 29,
messageRegExp = "Ambiguous factory methods: \"org\\.mapstruct\\.ap\\.test\\.erroneous\\."
+ "ambiguousfactorymethod\\.Bar createBar\\(\\)\" conflicts with "
+ "\"org\\.mapstruct\\.ap\\.test\\.erroneous\\.ambiguousfactorymethod\\.Bar "
+ "org\\.mapstruct\\.ap\\.test\\.erroneous\\.ambiguousfactorymethod"
+ "\\.a\\.BarFactory\\.createBar\\(\\)\"\\")
line = 35,
messageRegExp = "Ambiguous mapping methods found for factorizing "
+ "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar: "
+ "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar createBar\\(\\), "
+ "org.mapstruct.ap.test.erroneous.ambiguousfactorymethod.Bar .*BarFactory.createBar\\(\\)." )
}
)
public void shouldUseTwoFactoryMethods() {