mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
parent
74f281fa3e
commit
2a849dca12
@ -482,6 +482,24 @@ public class Type extends ModelElement implements Comparable<Type> {
|
||||
return typeUtils.isAssignable( typeMirrorToMatch, other.typeMirror );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this type is raw assignable to the given other type. We can't make a verdict on typevars,
|
||||
* they need to be resolved first.
|
||||
*
|
||||
* @param other The other type.
|
||||
*
|
||||
* @return {@code true} if and only if this type is assignable to the given other type.
|
||||
*/
|
||||
public boolean isRawAssignableTo(Type other) {
|
||||
if ( isTypeVar() || other.isTypeVar() ) {
|
||||
return true;
|
||||
}
|
||||
if ( equals( other ) ) {
|
||||
return true;
|
||||
}
|
||||
return typeUtils.isAssignable( typeUtils.erasure( typeMirror ), typeUtils.erasure( other.typeMirror ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* getPropertyReadAccessors
|
||||
*
|
||||
|
@ -187,4 +187,18 @@ public interface Method {
|
||||
default boolean isMappingTargetAssignableToReturnType() {
|
||||
return isUpdateMethod() && getResultType().isAssignableTo( getReturnType() );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first source type, intended for mapping methods from single source to target
|
||||
*/
|
||||
default Type getMappingSourceType() {
|
||||
return getSourceParameters().get( 0 ).getType();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the short name for error messages
|
||||
*/
|
||||
default String shortName() {
|
||||
return getResultType().getName() + ":" + getName() + "(" + getMappingSourceType().getName() + ")";
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ public class SourceMethod implements Method {
|
||||
return method.getDeclaringMapper() == null
|
||||
&& method.isAbstract()
|
||||
&& getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1
|
||||
&& first( getSourceParameters() ).getType().isAssignableTo( method.getResultType() )
|
||||
&& getMappingSourceType().isAssignableTo( method.getResultType() )
|
||||
&& getResultType().isAssignableTo( first( method.getSourceParameters() ).getType() );
|
||||
}
|
||||
|
||||
@ -326,7 +326,7 @@ public class SourceMethod implements Method {
|
||||
public boolean isIterableMapping() {
|
||||
if ( isIterableMapping == null ) {
|
||||
isIterableMapping = getSourceParameters().size() == 1
|
||||
&& first( getSourceParameters() ).getType().isIterableType()
|
||||
&& getMappingSourceType().isIterableType()
|
||||
&& getResultType().isIterableType();
|
||||
}
|
||||
return isIterableMapping;
|
||||
@ -335,9 +335,9 @@ public class SourceMethod implements Method {
|
||||
public boolean isStreamMapping() {
|
||||
if ( isStreamMapping == null ) {
|
||||
isStreamMapping = getSourceParameters().size() == 1
|
||||
&& ( first( getSourceParameters() ).getType().isIterableType() && getResultType().isStreamType()
|
||||
|| first( getSourceParameters() ).getType().isStreamType() && getResultType().isIterableType()
|
||||
|| first( getSourceParameters() ).getType().isStreamType() && getResultType().isStreamType() );
|
||||
&& ( getMappingSourceType().isIterableType() && getResultType().isStreamType()
|
||||
|| getMappingSourceType().isStreamType() && getResultType().isIterableType()
|
||||
|| getMappingSourceType().isStreamType() && getResultType().isStreamType() );
|
||||
}
|
||||
return isStreamMapping;
|
||||
}
|
||||
@ -345,7 +345,7 @@ public class SourceMethod implements Method {
|
||||
public boolean isMapMapping() {
|
||||
if ( isMapMapping == null ) {
|
||||
isMapMapping = getSourceParameters().size() == 1
|
||||
&& first( getSourceParameters() ).getType().isMapType()
|
||||
&& getMappingSourceType().isMapType()
|
||||
&& getResultType().isMapType();
|
||||
}
|
||||
return isMapMapping;
|
||||
|
@ -67,23 +67,15 @@ public abstract class BuiltInMethod implements Method {
|
||||
|
||||
Type sourceType = first( sourceTypes );
|
||||
|
||||
if ( getReturnType().isAssignableTo( targetType.erasure() )
|
||||
&& sourceType.erasure().isAssignableTo( getParameter().getType() ) ) {
|
||||
return doTypeVarsMatch( sourceType, targetType );
|
||||
}
|
||||
if ( getReturnType().getFullyQualifiedName().equals( "java.lang.Object" )
|
||||
&& sourceType.erasure().isAssignableTo( getParameter().getType() ) ) {
|
||||
// return type could be a type parameter T
|
||||
return doTypeVarsMatch( sourceType, targetType );
|
||||
}
|
||||
if ( getReturnType().isAssignableTo( targetType.erasure() )
|
||||
&& getParameter().getType().getFullyQualifiedName().equals( "java.lang.Object" ) ) {
|
||||
// parameter type could be a type parameter T
|
||||
return doTypeVarsMatch( sourceType, targetType );
|
||||
}
|
||||
Type returnType = getReturnType().resolveTypeVarToType( sourceType, getParameter().getType() );
|
||||
if ( returnType == null ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return returnType.isAssignableTo( targetType )
|
||||
&& sourceType.erasure().isAssignableTo( getParameter().getType() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Parameter> getSourceParameters() {
|
||||
return getParameters();
|
||||
|
@ -5,15 +5,16 @@
|
||||
*/
|
||||
package org.mapstruct.ap.internal.model.source.builtin;
|
||||
|
||||
import static org.mapstruct.ap.internal.util.Collections.asSet;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.xml.bind.JAXBElement;
|
||||
|
||||
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 static org.mapstruct.ap.internal.util.Collections.asSet;
|
||||
|
||||
/**
|
||||
* @author Sjaak Derksen
|
||||
*/
|
||||
@ -24,8 +25,9 @@ public class JaxbElemToValue extends BuiltInMethod {
|
||||
private final Set<Type> importTypes;
|
||||
|
||||
public JaxbElemToValue(TypeFactory typeFactory) {
|
||||
this.parameter = new Parameter( "element", typeFactory.getType( JAXBElement.class ) );
|
||||
this.returnType = typeFactory.getType( Object.class );
|
||||
Type type = typeFactory.getType( JAXBElement.class );
|
||||
this.parameter = new Parameter( "element", type );
|
||||
this.returnType = type.getTypeParameters().get( 0 );
|
||||
this.importTypes = asSet( parameter.getType() );
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
package org.mapstruct.ap.internal.model.source.selector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.ParameterBinding;
|
||||
import org.mapstruct.ap.internal.model.source.Method;
|
||||
@ -39,4 +40,21 @@ public class SelectedMethod<T extends Method> {
|
||||
public String toString() {
|
||||
return method.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
SelectedMethod<?> that = (SelectedMethod<?>) o;
|
||||
return method.equals( that.method );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( method );
|
||||
}
|
||||
}
|
||||
|
@ -69,7 +69,7 @@ public class XmlElementDeclSelector implements MethodSelector {
|
||||
}
|
||||
|
||||
String name = xmlElementDecl.name().get();
|
||||
TypeMirror scope = xmlElementDecl.scope().get();
|
||||
TypeMirror scope = xmlElementDecl.scope().getValue();
|
||||
|
||||
boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() );
|
||||
boolean scopeIsSetAndMatches =
|
||||
|
@ -5,12 +5,23 @@
|
||||
*/
|
||||
package org.mapstruct.ap.internal.processor.creation;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.mapstruct.ap.internal.util.Collections.first;
|
||||
import static org.mapstruct.ap.internal.util.Collections.firstKey;
|
||||
import static org.mapstruct.ap.internal.util.Collections.firstValue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
@ -42,7 +53,6 @@ import org.mapstruct.ap.internal.model.common.SourceRHS;
|
||||
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.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;
|
||||
@ -55,9 +65,6 @@ import org.mapstruct.ap.internal.util.MessageConstants;
|
||||
import org.mapstruct.ap.internal.util.NativeTypes;
|
||||
import org.mapstruct.ap.internal.util.Strings;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.mapstruct.ap.internal.util.Collections.first;
|
||||
|
||||
/**
|
||||
* The one and only implementation of {@link MappingResolver}. The class has been split into an interface an
|
||||
* implementation for the sake of avoiding package dependencies. Specifically, this implementation refers to classes
|
||||
@ -78,6 +85,8 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
private final BuiltInMappingMethods builtInMethods;
|
||||
private final MethodSelectors methodSelectors;
|
||||
|
||||
private static final String JL_OBJECT_NAME = Object.class.getName();
|
||||
|
||||
/**
|
||||
* Private methods which are not present in the original mapper interface and are added to map certain property
|
||||
* types.
|
||||
@ -113,7 +122,9 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
sourceRHS,
|
||||
criteria,
|
||||
positionHint,
|
||||
forger
|
||||
forger,
|
||||
builtInMethods.getBuiltInMethods(),
|
||||
messager
|
||||
);
|
||||
|
||||
return attempt.getTargetAssignment( sourceRHS.getSourceTypeForMatching(), targetType );
|
||||
@ -141,10 +152,11 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
private final List<Method> methods;
|
||||
private final SelectionCriteria selectionCriteria;
|
||||
private final SourceRHS sourceRHS;
|
||||
private final boolean savedPreferUpdateMapping;
|
||||
private final FormattingParameters formattingParameters;
|
||||
private final AnnotationMirror positionHint;
|
||||
private final Supplier<Assignment> forger;
|
||||
private final List<BuiltInMethod> builtIns;
|
||||
private final FormattingMessager messager;
|
||||
|
||||
// resolving via 2 steps creates the possibility of wrong matches, first builtin method matches,
|
||||
// second doesn't. In that case, the first builtin method should not lead to a supported method
|
||||
@ -155,7 +167,9 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
FormattingParameters formattingParameters, SourceRHS sourceRHS,
|
||||
SelectionCriteria criteria,
|
||||
AnnotationMirror positionHint,
|
||||
Supplier<Assignment> forger) {
|
||||
Supplier<Assignment> forger,
|
||||
List<BuiltInMethod> builtIns,
|
||||
FormattingMessager messager) {
|
||||
|
||||
this.mappingMethod = mappingMethod;
|
||||
this.methods = filterPossibleCandidateMethods( sourceModel );
|
||||
@ -164,9 +178,10 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
this.sourceRHS = sourceRHS;
|
||||
this.supportingMethodCandidates = new HashSet<>();
|
||||
this.selectionCriteria = criteria;
|
||||
this.savedPreferUpdateMapping = criteria.isPreferUpdateMapping();
|
||||
this.positionHint = positionHint;
|
||||
this.forger = forger;
|
||||
this.builtIns = builtIns;
|
||||
this.messager = messager;
|
||||
}
|
||||
|
||||
private <T extends Method> List<T> filterPossibleCandidateMethods(List<T> candidateMethods) {
|
||||
@ -182,14 +197,16 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
|
||||
private Assignment getTargetAssignment(Type sourceType, Type targetType) {
|
||||
|
||||
Assignment referencedMethod;
|
||||
Assignment assignment;
|
||||
|
||||
// first simple mapping method
|
||||
if ( allowMappingMethod() ) {
|
||||
referencedMethod = resolveViaMethod( sourceType, targetType, false );
|
||||
if ( referencedMethod != null ) {
|
||||
referencedMethod.setAssignment( sourceRHS );
|
||||
return referencedMethod;
|
||||
List<SelectedMethod<Method>> matches = getBestMatch( methods, sourceType, targetType );
|
||||
reportErrorWhenAmbigious( matches, targetType );
|
||||
if ( !matches.isEmpty() ) {
|
||||
assignment = toMethodRef( first( matches ) );
|
||||
assignment.setAssignment( sourceRHS );
|
||||
return assignment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,38 +245,40 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
|
||||
// check for a built-in method
|
||||
if ( !hasQualfiers() ) {
|
||||
Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType );
|
||||
if ( builtInMethod != null ) {
|
||||
builtInMethod.setAssignment( sourceRHS );
|
||||
List<SelectedMethod<BuiltInMethod>> matches = getBestMatch( builtIns, sourceType, targetType );
|
||||
reportErrorWhenAmbigious( matches, targetType );
|
||||
if ( !matches.isEmpty() ) {
|
||||
assignment = toBuildInRef( first( matches ) );
|
||||
assignment.setAssignment( sourceRHS );
|
||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||
return builtInMethod;
|
||||
return assignment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( allow2Steps() ) {
|
||||
// 2 step method, first: method(method(source))
|
||||
referencedMethod = resolveViaMethodAndMethod( sourceType, targetType );
|
||||
if ( referencedMethod != null ) {
|
||||
assignment = MethodMethod.getBestMatch( this, sourceType, targetType );
|
||||
if ( assignment != null ) {
|
||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||
return referencedMethod;
|
||||
return assignment;
|
||||
}
|
||||
|
||||
// 2 step method, then: method(conversion(source))
|
||||
referencedMethod = resolveViaConversionAndMethod( sourceType, targetType );
|
||||
if ( referencedMethod != null ) {
|
||||
assignment = ConversionMethod.getBestMatch( this, sourceType, targetType );
|
||||
if ( assignment != null ) {
|
||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||
return referencedMethod;
|
||||
return assignment;
|
||||
}
|
||||
|
||||
// stop here when looking for update methods.
|
||||
selectionCriteria.setPreferUpdateMapping( false );
|
||||
|
||||
// 2 step method, finally: conversion(method(source))
|
||||
ConversionAssignment conversion = resolveViaMethodAndConversion( sourceType, targetType );
|
||||
if ( conversion != null ) {
|
||||
assignment = MethodConversion.getBestMatch( this, sourceType, targetType );
|
||||
if ( assignment != null ) {
|
||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||
return conversion.getAssignment();
|
||||
return assignment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -395,232 +414,6 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to a method mapping the given source type to the given target type, if such a method
|
||||
* exists.
|
||||
*/
|
||||
private Assignment resolveViaMethod(Type sourceType, Type targetType, boolean considerBuiltInMethods) {
|
||||
|
||||
// first try to find a matching source method
|
||||
SelectedMethod<Method> matchingSourceMethod = getBestMatch( methods, sourceType, targetType );
|
||||
|
||||
if ( matchingSourceMethod != null ) {
|
||||
return getMappingMethodReference( matchingSourceMethod );
|
||||
}
|
||||
|
||||
if ( considerBuiltInMethods ) {
|
||||
return resolveViaBuiltInMethod( sourceType, targetType );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies matching to given method only
|
||||
*
|
||||
* @param sourceType the source type
|
||||
* @param targetType the target type
|
||||
* @param method the method to match
|
||||
* @return an assignment if a match, given the criteria could be found. When the method is a
|
||||
* buildIn method, all the bookkeeping is applied.
|
||||
*/
|
||||
private Assignment applyMatching(Type sourceType, Type targetType, Method method) {
|
||||
|
||||
if ( method instanceof SourceMethod ) {
|
||||
SelectedMethod<Method> selectedMethod =
|
||||
getBestMatch( java.util.Collections.singletonList( method ), sourceType, targetType );
|
||||
return selectedMethod != null ? getMappingMethodReference( selectedMethod ) : null;
|
||||
}
|
||||
else if ( method instanceof BuiltInMethod ) {
|
||||
return resolveViaBuiltInMethod( sourceType, targetType );
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Assignment resolveViaBuiltInMethod(Type sourceType, Type targetType) {
|
||||
SelectedMethod<BuiltInMethod> matchingBuiltInMethod =
|
||||
getBestMatch( builtInMethods.getBuiltInMethods(), sourceType, targetType );
|
||||
|
||||
if ( matchingBuiltInMethod != null ) {
|
||||
return createFromBuiltInMethod( matchingBuiltInMethod.getMethod() );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Assignment createFromBuiltInMethod(BuiltInMethod method) {
|
||||
Set<Field> allUsedFields = new HashSet<>( mapperReferences );
|
||||
SupportingField.addAllFieldsIn( supportingMethodCandidates, allUsedFields );
|
||||
SupportingMappingMethod supportingMappingMethod = new SupportingMappingMethod( method, allUsedFields );
|
||||
supportingMethodCandidates.add( supportingMappingMethod );
|
||||
ConversionContext ctx = new DefaultConversionContext(
|
||||
typeFactory,
|
||||
messager,
|
||||
method.getSourceParameters().get( 0 ).getType(),
|
||||
method.getResultType(),
|
||||
formattingParameters
|
||||
);
|
||||
Assignment methodReference = MethodReference.forBuiltInMethod( method, ctx );
|
||||
methodReference.setAssignment( sourceRHS );
|
||||
return methodReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>no direct referenced mapping method either built-in or referenced is available from A to C</li>
|
||||
* <li>no conversion is available</li>
|
||||
* <li>there is a method from A to B, methodX</li>
|
||||
* <li>there is a method from B to C, methodY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) )
|
||||
*/
|
||||
private Assignment resolveViaMethodAndMethod(Type sourceType, Type targetType) {
|
||||
|
||||
List<Method> methodYCandidates = new ArrayList<>( methods );
|
||||
methodYCandidates.addAll( builtInMethods.getBuiltInMethods() );
|
||||
|
||||
Assignment methodRefY = null;
|
||||
|
||||
// Iterate over all source methods. Check if the return type matches with the parameter that we need.
|
||||
// so assume we need a method from A to C we look for a methodX from A to B (all methods in the
|
||||
// list form such a candidate).
|
||||
// For each of the candidates, we need to look if there's a methodY, either
|
||||
// sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match
|
||||
// a nested method call can be called. so C = methodY( methodX (A) )
|
||||
for ( Method methodYCandidate : methodYCandidates ) {
|
||||
Type ySourceType = methodYCandidate.getSourceParameters().get( 0 ).getType();
|
||||
if ( Object.class.getName().equals( ySourceType.getName() ) ) {
|
||||
// java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
|
||||
ySourceType = ySourceType.resolveTypeVarToType( targetType, methodYCandidate.getResultType() );
|
||||
|
||||
if ( ySourceType != null ) {
|
||||
methodRefY = applyMatching( ySourceType, targetType, methodYCandidate );
|
||||
if ( methodRefY != null ) {
|
||||
|
||||
selectionCriteria.setPreferUpdateMapping( false );
|
||||
Assignment methodRefX = resolveViaMethod( sourceType, ySourceType, true );
|
||||
selectionCriteria.setPreferUpdateMapping( savedPreferUpdateMapping );
|
||||
if ( methodRefX != null ) {
|
||||
methodRefY.setAssignment( methodRefX );
|
||||
methodRefX.setAssignment( sourceRHS );
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// both should match;
|
||||
supportingMethodCandidates.clear();
|
||||
methodRefY = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodRefY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>there is a conversion from A to B, conversionX</li>
|
||||
* <li>there is a method from B to C, methodY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) )
|
||||
*
|
||||
* Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in
|
||||
* methods are used to resolve a mapping (assignment) from result type to 'B'. If a match is found, an attempt
|
||||
* is done to find a matching type conversion.
|
||||
*/
|
||||
private Assignment resolveViaConversionAndMethod(Type sourceType, Type targetType) {
|
||||
|
||||
List<Method> methodYCandidates = new ArrayList<>( methods );
|
||||
methodYCandidates.addAll( builtInMethods.getBuiltInMethods() );
|
||||
|
||||
Assignment methodRefY = null;
|
||||
|
||||
for ( Method methodYCandidate : methodYCandidates ) {
|
||||
Type ySourceType = methodYCandidate.getSourceParameters().get( 0 ).getType();
|
||||
if ( Object.class.getName().equals( ySourceType.getName() ) ) {
|
||||
// java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
|
||||
ySourceType = ySourceType.resolveTypeVarToType( targetType, methodYCandidate.getResultType() );
|
||||
|
||||
if ( ySourceType != null ) {
|
||||
methodRefY = applyMatching( ySourceType, targetType, methodYCandidate );
|
||||
if ( methodRefY != null ) {
|
||||
ConversionAssignment conversionXRef = resolveViaConversion( sourceType, ySourceType );
|
||||
if ( conversionXRef != null ) {
|
||||
methodRefY.setAssignment( conversionXRef.getAssignment() );
|
||||
conversionXRef.getAssignment().setAssignment( sourceRHS );
|
||||
conversionXRef.reportMessageWhenNarrowing( messager, this );
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// both should match
|
||||
supportingMethodCandidates.clear();
|
||||
methodRefY = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodRefY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>there is a method from A to B, methodX</li>
|
||||
* <li>there is a conversion from B to C, conversionY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping conversionY( methodX ( parameter ) )
|
||||
*
|
||||
* Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in
|
||||
* methods are used to resolve a mapping (assignment) from source type to 'B'. If a match is found, an attempt
|
||||
* is done to find a matching type conversion.
|
||||
*/
|
||||
private ConversionAssignment resolveViaMethodAndConversion(Type sourceType, Type targetType) {
|
||||
|
||||
List<Method> methodXCandidates = new ArrayList<>( methods );
|
||||
methodXCandidates.addAll( builtInMethods.getBuiltInMethods() );
|
||||
|
||||
ConversionAssignment conversionYRef = null;
|
||||
|
||||
// search the other way around
|
||||
for ( Method methodXCandidate : methodXCandidates ) {
|
||||
Type xTargetType = methodXCandidate.getReturnType();
|
||||
if ( methodXCandidate.isUpdateMethod() ||
|
||||
Object.class.getName().equals( xTargetType.getFullyQualifiedName() ) ) {
|
||||
// skip update methods || java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
|
||||
xTargetType =
|
||||
xTargetType.resolveTypeVarToType( sourceType, methodXCandidate.getParameters().get( 0 ).getType() );
|
||||
|
||||
if ( xTargetType != null ) {
|
||||
Assignment methodRefX = applyMatching( sourceType, xTargetType, methodXCandidate );
|
||||
if ( methodRefX != null ) {
|
||||
conversionYRef = resolveViaConversion( xTargetType, targetType );
|
||||
if ( conversionYRef != null ) {
|
||||
conversionYRef.getAssignment().setAssignment( methodRefX );
|
||||
methodRefX.setAssignment( sourceRHS );
|
||||
conversionYRef.reportMessageWhenNarrowing( messager, this );
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// both should match;
|
||||
supportingMethodCandidates.clear();
|
||||
conversionYRef = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return conversionYRef;
|
||||
}
|
||||
|
||||
private boolean isCandidateForMapping(Method methodCandidate) {
|
||||
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
|
||||
}
|
||||
@ -640,15 +433,17 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
&& !methodCandidate.isLifecycleCallbackMethod();
|
||||
}
|
||||
|
||||
private <T extends Method> SelectedMethod<T> getBestMatch(List<T> methods, Type sourceType, Type returnType) {
|
||||
|
||||
List<SelectedMethod<T>> candidates = methodSelectors.getMatchingMethods(
|
||||
private <T extends Method> List<SelectedMethod<T>> getBestMatch(List<T> methods, Type source, Type target) {
|
||||
return methodSelectors.getMatchingMethods(
|
||||
mappingMethod,
|
||||
methods,
|
||||
singletonList( sourceType ),
|
||||
returnType,
|
||||
singletonList( source ),
|
||||
target,
|
||||
selectionCriteria
|
||||
);
|
||||
}
|
||||
|
||||
private <T extends Method> void reportErrorWhenAmbigious(List<SelectedMethod<T>> candidates, Type target) {
|
||||
|
||||
// raise an error if more than one mapping method is suitable to map the given source type
|
||||
// into the target type
|
||||
@ -660,7 +455,7 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
positionHint,
|
||||
Message.GENERAL_AMBIGIOUS_MAPPING_METHOD,
|
||||
sourceRHS.getSourceErrorMessagePart(),
|
||||
returnType,
|
||||
target,
|
||||
Strings.join( candidates, ", " )
|
||||
);
|
||||
}
|
||||
@ -669,29 +464,41 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
mappingMethod.getExecutable(),
|
||||
positionHint,
|
||||
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
|
||||
returnType,
|
||||
target,
|
||||
Strings.join( candidates, ", " )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( !candidates.isEmpty() ) {
|
||||
return first( candidates );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Assignment getMappingMethodReference(SelectedMethod<Method> method) {
|
||||
MapperReference mapperReference = findMapperReference( method.getMethod() );
|
||||
private Assignment toMethodRef(SelectedMethod<Method> selectedMethod) {
|
||||
MapperReference mapperReference = findMapperReference( selectedMethod.getMethod() );
|
||||
|
||||
return MethodReference.forMapperReference(
|
||||
method.getMethod(),
|
||||
selectedMethod.getMethod(),
|
||||
mapperReference,
|
||||
method.getParameterBindings()
|
||||
selectedMethod.getParameterBindings()
|
||||
);
|
||||
}
|
||||
|
||||
private Assignment toBuildInRef(SelectedMethod<BuiltInMethod> selectedMethod) {
|
||||
BuiltInMethod method = selectedMethod.getMethod();
|
||||
Set<Field> allUsedFields = new HashSet<>( mapperReferences );
|
||||
SupportingField.addAllFieldsIn( supportingMethodCandidates, allUsedFields );
|
||||
SupportingMappingMethod supportingMappingMethod = new SupportingMappingMethod( method, allUsedFields );
|
||||
supportingMethodCandidates.add( supportingMappingMethod );
|
||||
ConversionContext ctx = new DefaultConversionContext(
|
||||
typeFactory,
|
||||
messager,
|
||||
method.getMappingSourceType(),
|
||||
method.getResultType(),
|
||||
formattingParameters
|
||||
);
|
||||
Assignment methodReference = MethodReference.forBuiltInMethod( method, ctx );
|
||||
methodReference.setAssignment( sourceRHS );
|
||||
return methodReference;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@ -770,6 +577,7 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class ConversionAssignment {
|
||||
@ -813,5 +621,408 @@ public class MappingResolverImpl implements MappingResolver {
|
||||
);
|
||||
}
|
||||
|
||||
String shortName() {
|
||||
return sourceType.getName() + "-->" + targetType.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
ConversionAssignment that = (ConversionAssignment) o;
|
||||
return sourceType.equals( that.sourceType ) && targetType.equals( that.targetType );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( sourceType, targetType );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>no direct referenced mapping method either built-in or referenced is available from A to C</li>
|
||||
* <li>no conversion is available</li>
|
||||
* <li>there is a method from A to B, methodX</li>
|
||||
* <li>there is a method from B to C, methodY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping methodY( methodX ( parameter ) )
|
||||
*
|
||||
* NOTE method X cannot be an update method
|
||||
*/
|
||||
private static class MethodMethod<T1 extends Method, T2 extends Method> {
|
||||
|
||||
private final ResolvingAttempt attempt;
|
||||
private final List<T1> xMethods;
|
||||
private final List<T2> yMethods;
|
||||
private final Function<SelectedMethod<T1>, Assignment> xCreate;
|
||||
private final Function<SelectedMethod<T2>, Assignment> yCreate;
|
||||
|
||||
// results
|
||||
private boolean hasResult = false;
|
||||
private Assignment result = null;
|
||||
|
||||
static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) {
|
||||
MethodMethod<Method, Method> mmAttempt =
|
||||
new MethodMethod<>( att, att.methods, att.methods, att::toMethodRef, att::toMethodRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
if ( mmAttempt.hasResult ) {
|
||||
return mmAttempt.result;
|
||||
}
|
||||
MethodMethod<Method, BuiltInMethod> mbAttempt =
|
||||
new MethodMethod<>( att, att.methods, att.builtIns, att::toMethodRef, att::toBuildInRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
if ( mbAttempt.hasResult ) {
|
||||
return mbAttempt.result;
|
||||
}
|
||||
MethodMethod<BuiltInMethod, Method> bmAttempt =
|
||||
new MethodMethod<>( att, att.builtIns, att.methods, att::toBuildInRef, att::toMethodRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
if ( bmAttempt.hasResult ) {
|
||||
return bmAttempt.result;
|
||||
}
|
||||
MethodMethod<BuiltInMethod, BuiltInMethod> bbAttempt =
|
||||
new MethodMethod<>( att, att.builtIns, att.builtIns, att::toBuildInRef, att::toBuildInRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
return bbAttempt.result;
|
||||
}
|
||||
|
||||
MethodMethod(ResolvingAttempt attempt, List<T1> xMethods, List<T2> yMethods,
|
||||
Function<SelectedMethod<T1>, Assignment> xCreate,
|
||||
Function<SelectedMethod<T2>, Assignment> yCreate) {
|
||||
this.attempt = attempt;
|
||||
this.xMethods = xMethods;
|
||||
this.yMethods = yMethods;
|
||||
this.xCreate = xCreate;
|
||||
this.yCreate = yCreate;
|
||||
}
|
||||
|
||||
private MethodMethod<T1, T2> getBestMatch(Type sourceType, Type targetType) {
|
||||
|
||||
Set<T2> yCandidates = new HashSet<>();
|
||||
Map<SelectedMethod<T1>, List<SelectedMethod<T2>>> xCandidates = new LinkedHashMap<>();
|
||||
Map<SelectedMethod<T1>, Type> typesInTheMiddle = new LinkedHashMap<>();
|
||||
|
||||
// Iterate over all source methods. Check if the return type matches with the parameter that we need.
|
||||
// so assume we need a method from A to C we look for a methodX from A to B (all methods in the
|
||||
// list form such a candidate).
|
||||
// For each of the candidates, we need to look if there's a methodY, either
|
||||
// sourceMethod or builtIn that fits the signature B to C. Only then there is a match. If we have a match
|
||||
// a nested method call can be called. so C = methodY( methodX (A) )
|
||||
attempt.selectionCriteria.setPreferUpdateMapping( false );
|
||||
for ( T2 yCandidate : yMethods ) {
|
||||
Type ySourceType = yCandidate.getMappingSourceType();
|
||||
ySourceType = ySourceType.resolveTypeVarToType( targetType, yCandidate.getResultType() );
|
||||
Type yTargetType = yCandidate.getResultType();
|
||||
if ( ySourceType == null
|
||||
|| !yTargetType.isRawAssignableTo( targetType )
|
||||
|| JL_OBJECT_NAME.equals( ySourceType.getFullyQualifiedName() ) ) {
|
||||
// java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
List<SelectedMethod<T1>> xMatches = attempt.getBestMatch( xMethods, sourceType, ySourceType );
|
||||
if ( !xMatches.isEmpty() ) {
|
||||
xMatches.stream().forEach( x -> xCandidates.put( x, new ArrayList<>() ) );
|
||||
final Type typeInTheMiddle = ySourceType;
|
||||
xMatches.stream().forEach( x -> typesInTheMiddle.put( x, typeInTheMiddle ) );
|
||||
yCandidates.add( yCandidate );
|
||||
}
|
||||
}
|
||||
attempt.selectionCriteria.setPreferUpdateMapping( true );
|
||||
|
||||
// collect all results
|
||||
List<T2> yCandidatesList = new ArrayList<>( yCandidates );
|
||||
Iterator<Map.Entry<SelectedMethod<T1>, List<SelectedMethod<T2>>>> i = xCandidates.entrySet().iterator();
|
||||
while ( i.hasNext() ) {
|
||||
Map.Entry<SelectedMethod<T1>, List<SelectedMethod<T2>>> entry = i.next();
|
||||
Type typeInTheMiddle = typesInTheMiddle.get( entry.getKey() );
|
||||
entry.getValue().addAll( attempt.getBestMatch( yCandidatesList, typeInTheMiddle, targetType ) );
|
||||
if ( entry.getValue().isEmpty() ) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// no results left
|
||||
if ( xCandidates.isEmpty() ) {
|
||||
return this;
|
||||
}
|
||||
hasResult = true;
|
||||
|
||||
// get result, there should be one entry left with only one value
|
||||
if ( xCandidates.size() == 1 && firstValue( xCandidates ).size() == 1 ) {
|
||||
Assignment methodRefY = yCreate.apply( first( firstValue( xCandidates ) ) );
|
||||
Assignment methodRefX = xCreate.apply( firstKey( xCandidates ) );
|
||||
methodRefY.setAssignment( methodRefX );
|
||||
methodRefX.setAssignment( attempt.sourceRHS );
|
||||
result = methodRefY;
|
||||
}
|
||||
else {
|
||||
reportAmbigiousError( xCandidates, targetType );
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
void reportAmbigiousError(Map<SelectedMethod<T1>, List<SelectedMethod<T2>>> xCandidates, Type target) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
xCandidates.entrySet()
|
||||
.stream()
|
||||
.forEach( e -> result.append( "method(s)Y: " )
|
||||
.append( e.getValue()
|
||||
.stream()
|
||||
.map( v -> v.getMethod().shortName() )
|
||||
.collect( Collectors.joining( ", " ) ) )
|
||||
.append( ", methodX: " )
|
||||
.append( e.getKey().getMethod().shortName() )
|
||||
.append( "; " ) );
|
||||
attempt.messager.printMessage(
|
||||
attempt.mappingMethod.getExecutable(),
|
||||
attempt.positionHint,
|
||||
Message.GENERAL_AMBIGIOUS_MAPPING_METHODY_METHODX,
|
||||
attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(),
|
||||
target.getName(),
|
||||
result.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>there is a conversion from A to B, conversionX</li>
|
||||
* <li>there is a method from B to C, methodY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping methodY( conversionX ( parameter ) )
|
||||
*
|
||||
* Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in
|
||||
* methods are used to resolve a mapping (assignment) from result type to 'B'. If a match is found, an attempt
|
||||
* is done to find a matching type conversion.
|
||||
*/
|
||||
private static class ConversionMethod<T extends Method> {
|
||||
|
||||
private final ResolvingAttempt attempt;
|
||||
private final List<T> methods;
|
||||
private final Function<SelectedMethod<T>, Assignment> create;
|
||||
|
||||
// results
|
||||
private boolean hasResult = false;
|
||||
private Assignment result = null;
|
||||
|
||||
static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) {
|
||||
ConversionMethod<Method> mAttempt = new ConversionMethod<>( att, att.methods, att::toMethodRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
if ( mAttempt.hasResult ) {
|
||||
return mAttempt.result;
|
||||
}
|
||||
ConversionMethod<BuiltInMethod> bAttempt =
|
||||
new ConversionMethod<>( att, att.builtIns, att::toBuildInRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
return bAttempt.result;
|
||||
}
|
||||
|
||||
ConversionMethod(ResolvingAttempt attempt, List<T> methods, Function<SelectedMethod<T>, Assignment> create) {
|
||||
this.attempt = attempt;
|
||||
this.methods = methods;
|
||||
this.create = create;
|
||||
}
|
||||
|
||||
private ConversionMethod<T> getBestMatch(Type sourceType, Type targetType) {
|
||||
|
||||
List<T> yCandidates = new ArrayList<>();
|
||||
Map<ConversionAssignment, List<SelectedMethod<T>>> xRefCandidates = new LinkedHashMap<>();
|
||||
|
||||
for ( T yCandidate : methods ) {
|
||||
Type ySourceType = yCandidate.getMappingSourceType();
|
||||
ySourceType = ySourceType.resolveTypeVarToType( targetType, yCandidate.getResultType() );
|
||||
Type yTargetType = yCandidate.getResultType();
|
||||
if ( ySourceType == null
|
||||
|| !yTargetType.isRawAssignableTo( targetType )
|
||||
|| JL_OBJECT_NAME.equals( ySourceType.getFullyQualifiedName() ) ) {
|
||||
// java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
ConversionAssignment xRefCandidate = attempt.resolveViaConversion( sourceType, ySourceType );
|
||||
if ( xRefCandidate != null ) {
|
||||
xRefCandidates.put( xRefCandidate, new ArrayList<>() );
|
||||
yCandidates.add( yCandidate );
|
||||
}
|
||||
}
|
||||
|
||||
// collect all results
|
||||
Iterator<Map.Entry<ConversionAssignment, List<SelectedMethod<T>>>> i = xRefCandidates.entrySet().iterator();
|
||||
while ( i.hasNext() ) {
|
||||
Map.Entry<ConversionAssignment, List<SelectedMethod<T>>> entry = i.next();
|
||||
entry.getValue().addAll( attempt.getBestMatch( yCandidates, entry.getKey().targetType, targetType ) );
|
||||
if ( entry.getValue().isEmpty() ) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// no results left
|
||||
if ( xRefCandidates.isEmpty() ) {
|
||||
return this;
|
||||
}
|
||||
hasResult = true;
|
||||
|
||||
// get result, there should be one entry left with only one value
|
||||
if ( xRefCandidates.size() == 1 && firstValue( xRefCandidates ).size() == 1 ) {
|
||||
Assignment methodRefY = create.apply( first( firstValue( xRefCandidates ) ) );
|
||||
ConversionAssignment conversionRefX = firstKey( xRefCandidates );
|
||||
conversionRefX.reportMessageWhenNarrowing( attempt.messager, attempt );
|
||||
methodRefY.setAssignment( conversionRefX.assignment );
|
||||
conversionRefX.assignment.setAssignment( attempt.sourceRHS );
|
||||
result = methodRefY;
|
||||
}
|
||||
else {
|
||||
reportAmbigiousError( xRefCandidates, targetType );
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
void reportAmbigiousError(Map<ConversionAssignment, List<SelectedMethod<T>>> xRefCandidates, Type target) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
xRefCandidates.entrySet()
|
||||
.stream()
|
||||
.forEach( e -> result.append( "method(s)Y: " )
|
||||
.append( e.getValue()
|
||||
.stream()
|
||||
.map( v -> v.getMethod().shortName() )
|
||||
.collect( Collectors.joining( ", " ) ) )
|
||||
.append( ", conversionX: " )
|
||||
.append( e.getKey().shortName() )
|
||||
.append( "; " ) );
|
||||
attempt.messager.printMessage(
|
||||
attempt.mappingMethod.getExecutable(),
|
||||
attempt.positionHint,
|
||||
Message.GENERAL_AMBIGIOUS_MAPPING_METHODY_CONVERSIONX,
|
||||
attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(),
|
||||
target.getName(),
|
||||
result.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suppose mapping required from A to C and:
|
||||
* <ul>
|
||||
* <li>there is a method from A to B, methodX</li>
|
||||
* <li>there is a conversion from B to C, conversionY</li>
|
||||
* </ul>
|
||||
* then this method tries to resolve this combination and make a mapping conversionY( methodX ( parameter ) )
|
||||
*
|
||||
* Instead of directly using a built in method candidate, all the return types as 'B' of all available built-in
|
||||
* methods are used to resolve a mapping (assignment) from source type to 'B'. If a match is found, an attempt
|
||||
* is done to find a matching type conversion.
|
||||
*
|
||||
* NOTE methodX cannot be an update method
|
||||
*/
|
||||
private static class MethodConversion<T extends Method> {
|
||||
|
||||
private final ResolvingAttempt attempt;
|
||||
private final List<T> methods;
|
||||
private final Function<SelectedMethod<T>, Assignment> create;
|
||||
|
||||
// results
|
||||
private boolean hasResult = false;
|
||||
private Assignment result = null;
|
||||
|
||||
static Assignment getBestMatch(ResolvingAttempt att, Type sourceType, Type targetType) {
|
||||
MethodConversion<Method> mAttempt = new MethodConversion<>( att, att.methods, att::toMethodRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
if ( mAttempt.hasResult ) {
|
||||
return mAttempt.result;
|
||||
}
|
||||
MethodConversion<BuiltInMethod> bAttempt = new MethodConversion<>( att, att.builtIns, att::toBuildInRef )
|
||||
.getBestMatch( sourceType, targetType );
|
||||
return bAttempt.result;
|
||||
}
|
||||
|
||||
MethodConversion(ResolvingAttempt attempt, List<T> methods, Function<SelectedMethod<T>, Assignment> create) {
|
||||
this.attempt = attempt;
|
||||
this.methods = methods;
|
||||
this.create = create;
|
||||
}
|
||||
|
||||
private MethodConversion<T> getBestMatch(Type sourceType, Type targetType) {
|
||||
|
||||
List<T> xCandidates = new ArrayList<>();
|
||||
Map<ConversionAssignment, List<SelectedMethod<T>>> yRefCandidates = new LinkedHashMap<>();
|
||||
|
||||
// search through methods, and select egible candidates
|
||||
for ( T xCandidate : methods ) {
|
||||
Type xTargetType = xCandidate.getReturnType();
|
||||
Type xSourceType = xCandidate.getMappingSourceType();
|
||||
xTargetType = xTargetType.resolveTypeVarToType( sourceType, xSourceType );
|
||||
if ( xTargetType == null
|
||||
|| xCandidate.isUpdateMethod()
|
||||
|| !sourceType.isRawAssignableTo( xSourceType )
|
||||
|| JL_OBJECT_NAME.equals( xTargetType.getFullyQualifiedName() ) ) {
|
||||
// skip update methods || java.lang.Object as intermediate result
|
||||
continue;
|
||||
}
|
||||
ConversionAssignment yRefCandidate = attempt.resolveViaConversion( xTargetType, targetType );
|
||||
if ( yRefCandidate != null ) {
|
||||
yRefCandidates.put( yRefCandidate, new ArrayList<>() );
|
||||
xCandidates.add( xCandidate );
|
||||
}
|
||||
}
|
||||
|
||||
// collect all results
|
||||
Iterator<Map.Entry<ConversionAssignment, List<SelectedMethod<T>>>> i = yRefCandidates.entrySet().iterator();
|
||||
while ( i.hasNext() ) {
|
||||
Map.Entry<ConversionAssignment, List<SelectedMethod<T>>> entry = i.next();
|
||||
entry.getValue().addAll( attempt.getBestMatch( xCandidates, sourceType, entry.getKey().sourceType ) );
|
||||
if ( entry.getValue().isEmpty() ) {
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// no results left
|
||||
if ( yRefCandidates.isEmpty() ) {
|
||||
return this;
|
||||
}
|
||||
hasResult = true;
|
||||
|
||||
// get result, there should be one entry left with only one value
|
||||
if ( yRefCandidates.size() == 1 && firstValue( yRefCandidates ).size() == 1 ) {
|
||||
Assignment methodRefX = create.apply( first( firstValue( yRefCandidates ) ) );
|
||||
ConversionAssignment conversionRefY = firstKey( yRefCandidates );
|
||||
conversionRefY.reportMessageWhenNarrowing( attempt.messager, attempt );
|
||||
methodRefX.setAssignment( attempt.sourceRHS );
|
||||
conversionRefY.assignment.setAssignment( methodRefX );
|
||||
result = conversionRefY.assignment;
|
||||
}
|
||||
else {
|
||||
reportAmbigiousError( yRefCandidates, targetType );
|
||||
}
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
void reportAmbigiousError(Map<ConversionAssignment, List<SelectedMethod<T>>> yRefCandidates, Type target) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
yRefCandidates.entrySet()
|
||||
.stream()
|
||||
.forEach( e -> result.append( "conversionY: " )
|
||||
.append( e.getKey().shortName() )
|
||||
.append( ", method(s)X: " )
|
||||
.append( e.getValue()
|
||||
.stream()
|
||||
.map( v -> v.getMethod().shortName() )
|
||||
.collect( Collectors.joining( ", " ) ) )
|
||||
.append( "; " ) );
|
||||
attempt.messager.printMessage(
|
||||
attempt.mappingMethod.getExecutable(),
|
||||
attempt.positionHint,
|
||||
Message.GENERAL_AMBIGIOUS_MAPPING_CONVERSIONY_METHODX,
|
||||
attempt.sourceRHS.getSourceType().getName() + " " + attempt.sourceRHS.getSourceParameterName(),
|
||||
target.getName(),
|
||||
result.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -63,4 +64,16 @@ public class Collections {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map.Entry<K, V> first(Map<K, V> map) {
|
||||
return map.entrySet().iterator().next();
|
||||
}
|
||||
|
||||
public static <K, V> V firstValue(Map<K, V> map) {
|
||||
return first( map ).getValue();
|
||||
}
|
||||
|
||||
public static <K, V> K firstKey(Map<K, V> map) {
|
||||
return first( map ).getKey();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -125,6 +125,10 @@ public enum Message {
|
||||
GENERAL_NO_QUALIFYING_METHOD_NAMED( "Qualifier error. No method found annotated with @Named#value: [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ),
|
||||
GENERAL_NO_QUALIFYING_METHOD_COMBINED( "Qualifier error. No method found annotated with @Named#value: [ %s ], annotated with [ %s ]. See " + FAQ_QUALIFIER_URL + " for more info." ),
|
||||
|
||||
GENERAL_AMBIGIOUS_MAPPING_METHODY_METHODX( "Ambiguous 2step methods found, mapping %s to %s. Found methodY( methodX ( parameter ) ): %s." ),
|
||||
GENERAL_AMBIGIOUS_MAPPING_CONVERSIONY_METHODX( "Ambiguous 2step methods found, mapping %s to %s. Found conversionY( methodX ( parameter ) ): %s." ),
|
||||
GENERAL_AMBIGIOUS_MAPPING_METHODY_CONVERSIONX( "Ambiguous 2step methods found, mapping %s to %s. Found methodY( conversionX ( parameter ) ): %s." ),
|
||||
|
||||
BUILDER_MORE_THAN_ONE_BUILDER_CREATION_METHOD( "More than one builder creation method for \"%s\". Found methods: \"%s\". Builder will not be used. Consider implementing a custom BuilderProvider SPI.", Diagnostic.Kind.WARNING ),
|
||||
BUILDER_NO_BUILD_METHOD_FOUND("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\".", Diagnostic.Kind.ERROR ),
|
||||
BUILDER_NO_BUILD_METHOD_FOUND_DEFAULT("No build method \"%s\" found in \"%s\" for \"%s\". Found methods: \"%s\". Consider to add @Builder in order to select the correct build method.", Diagnostic.Kind.ERROR ),
|
||||
|
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._2145;
|
||||
|
||||
import javax.xml.bind.JAXBElement;
|
||||
import javax.xml.bind.annotation.XmlElementDecl;
|
||||
import javax.xml.namespace.QName;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper( uses = Issue2145Mapper.ObjectFactory.class )
|
||||
public interface Issue2145Mapper {
|
||||
|
||||
Issue2145Mapper INSTANCE = Mappers.getMapper( Issue2145Mapper.class );
|
||||
|
||||
@Mapping(target = "nested", source = "value")
|
||||
Target map(Source source);
|
||||
|
||||
default Nested map(String in) {
|
||||
Nested nested = new Nested();
|
||||
nested.setValue( in );
|
||||
return nested;
|
||||
}
|
||||
|
||||
class Target {
|
||||
|
||||
private JAXBElement<Nested> nested;
|
||||
|
||||
public JAXBElement<Nested> getNested() {
|
||||
return nested;
|
||||
}
|
||||
|
||||
public void setNested(JAXBElement<Nested> nested) {
|
||||
this.nested = nested;
|
||||
}
|
||||
}
|
||||
|
||||
class Nested {
|
||||
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class Source {
|
||||
|
||||
private String value;
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
class ObjectFactory {
|
||||
|
||||
private static final QName Q_NAME = new QName( "http://www.test.com/test", "" );
|
||||
private static final QName Q_NAME_NESTED = new QName( "http://www.test.com/test", "nested" );
|
||||
|
||||
@XmlElementDecl(namespace = "http://www.test.com/test", name = "Nested")
|
||||
public JAXBElement<Nested> createNested(Nested value) {
|
||||
return new JAXBElement<Nested>( Q_NAME, Nested.class, null, value );
|
||||
}
|
||||
|
||||
@XmlElementDecl(namespace = "http://www.test.com/test", name = "nested", scope = Nested.class)
|
||||
public JAXBElement<Nested> createNestedInNestedTarget(Nested value) {
|
||||
return new JAXBElement<Nested>( Q_NAME_NESTED, Nested.class, Target.class, value );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.bugs._2145;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.testutil.IssueKey;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@IssueKey("2145")
|
||||
@WithClasses(Issue2145Mapper.class)
|
||||
@RunWith(AnnotationProcessorTestRunner.class)
|
||||
public class Issue2145Test {
|
||||
|
||||
@Test
|
||||
public void test() {
|
||||
Issue2145Mapper.Source source = new Issue2145Mapper.Source();
|
||||
source.setValue( "test" );
|
||||
|
||||
Issue2145Mapper.Target target = Issue2145Mapper.INSTANCE.map( source );
|
||||
|
||||
assertThat( target ).isNotNull();
|
||||
assertThat( target.getNested() ).isNotNull();
|
||||
assertThat( target.getNested().getScope() ).isEqualTo( Issue2145Mapper.Target.class );
|
||||
assertThat( target.getNested().getValue() ).isNotNull();
|
||||
assertThat( target.getNested().getValue().getValue() ).isEqualTo( "test" );
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.selection.twosteperror;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface ErroneousMapperCM {
|
||||
|
||||
ErroneousMapperCM INSTANCE = Mappers.getMapper( ErroneousMapperCM.class );
|
||||
|
||||
Target map(Source s);
|
||||
|
||||
default TargetType methodY1(String s) {
|
||||
return new TargetType( s );
|
||||
}
|
||||
|
||||
default TargetType methodY2(Double d) {
|
||||
return new TargetType( d.toString() );
|
||||
}
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
class Target {
|
||||
public TargetType t1;
|
||||
}
|
||||
|
||||
class Source {
|
||||
public BigDecimal t1;
|
||||
}
|
||||
|
||||
class TargetType {
|
||||
public String t1;
|
||||
|
||||
public TargetType(String test) {
|
||||
this.t1 = test;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeInTheMiddleA {
|
||||
|
||||
TypeInTheMiddleA(String t1) {
|
||||
this.test = t1;
|
||||
}
|
||||
|
||||
public String test;
|
||||
}
|
||||
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.selection.twosteperror;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface ErroneousMapperMC {
|
||||
|
||||
ErroneousMapperMC INSTANCE = Mappers.getMapper( ErroneousMapperMC.class );
|
||||
|
||||
Target map(Source s);
|
||||
|
||||
default BigDecimal methodX1(SourceType s) {
|
||||
return new BigDecimal( s.t1 );
|
||||
}
|
||||
|
||||
default Double methodX2(SourceType s) {
|
||||
return new Double( s.t1 );
|
||||
}
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
class Target {
|
||||
public String t1;
|
||||
}
|
||||
|
||||
class Source {
|
||||
public SourceType t1;
|
||||
}
|
||||
|
||||
class SourceType {
|
||||
public String t1;
|
||||
}
|
||||
|
||||
class TargetType {
|
||||
public String t1;
|
||||
|
||||
public TargetType(String test) {
|
||||
this.t1 = test;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.selection.twosteperror;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
@Mapper
|
||||
public interface ErroneousMapperMM {
|
||||
|
||||
ErroneousMapperMM INSTANCE = Mappers.getMapper( ErroneousMapperMM.class );
|
||||
|
||||
Target map( Source s );
|
||||
|
||||
default TargetType methodY1(TypeInTheMiddleA s) {
|
||||
return new TargetType( s.test );
|
||||
}
|
||||
|
||||
default TypeInTheMiddleA methodX1(SourceType s) {
|
||||
return new TypeInTheMiddleA( s.t1 );
|
||||
}
|
||||
|
||||
default TargetType methodY2(TypeInTheMiddleB s) {
|
||||
return new TargetType( s.test );
|
||||
}
|
||||
|
||||
default TypeInTheMiddleB methodX2(SourceType s) {
|
||||
return new TypeInTheMiddleB( s.t1 );
|
||||
}
|
||||
|
||||
// CHECKSTYLE:OFF
|
||||
class Target {
|
||||
public TargetType t1;
|
||||
}
|
||||
|
||||
class Source {
|
||||
public SourceType t1;
|
||||
}
|
||||
|
||||
class SourceType {
|
||||
public String t1;
|
||||
}
|
||||
|
||||
class TargetType {
|
||||
public String t1;
|
||||
|
||||
public TargetType(String test) {
|
||||
this.t1 = test;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeInTheMiddleA {
|
||||
|
||||
TypeInTheMiddleA(String t1) {
|
||||
this.test = t1;
|
||||
}
|
||||
|
||||
public String test;
|
||||
}
|
||||
|
||||
class TypeInTheMiddleB {
|
||||
|
||||
TypeInTheMiddleB(String t1) {
|
||||
this.test = t1;
|
||||
}
|
||||
|
||||
public String test;
|
||||
}
|
||||
// CHECKSTYLE:ON
|
||||
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright MapStruct Authors.
|
||||
*
|
||||
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
package org.mapstruct.ap.test.selection.twosteperror;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
|
||||
@RunWith( AnnotationProcessorTestRunner.class )
|
||||
public class TwoStepMappingTest {
|
||||
|
||||
@Test
|
||||
@WithClasses( ErroneousMapperMM.class )
|
||||
@ExpectedCompilationOutcome( value = CompilationResult.FAILED,
|
||||
diagnostics = @Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousMapperMM.class,
|
||||
line = 16,
|
||||
message = "Ambiguous 2step methods found, mapping SourceType s to TargetType. "
|
||||
+ "Found methodY( methodX ( parameter ) ): "
|
||||
+ "method(s)Y: TargetType:methodY1(TypeInTheMiddleA), methodX: TypeInTheMiddleA:methodX1(SourceType); "
|
||||
+ "method(s)Y: TargetType:methodY2(TypeInTheMiddleB), methodX: TypeInTheMiddleB:methodX2(SourceType); ."
|
||||
) )
|
||||
public void methodAndMethodTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses( ErroneousMapperCM.class )
|
||||
@ExpectedCompilationOutcome( value = CompilationResult.FAILED,
|
||||
diagnostics = {
|
||||
@Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousMapperCM.class,
|
||||
line = 18,
|
||||
message = "Ambiguous 2step methods found, mapping BigDecimal s to TargetType. "
|
||||
+ "Found methodY( conversionX ( parameter ) ): "
|
||||
+ "method(s)Y: TargetType:methodY1(String), conversionX: BigDecimal-->String; "
|
||||
+ "method(s)Y: TargetType:methodY2(Double), conversionX: BigDecimal-->Double; ."
|
||||
),
|
||||
@Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousMapperCM.class,
|
||||
line = 18,
|
||||
messageRegExp = "Can't map property.*"
|
||||
)
|
||||
} )
|
||||
public void conversionAndMethodTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses( ErroneousMapperMC.class )
|
||||
@ExpectedCompilationOutcome( value = CompilationResult.FAILED,
|
||||
diagnostics = {
|
||||
@Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousMapperMC.class,
|
||||
line = 18,
|
||||
message = "Ambiguous 2step methods found, mapping SourceType s to String. "
|
||||
+ "Found conversionY( methodX ( parameter ) ): "
|
||||
+ "conversionY: BigDecimal-->String, method(s)X: BigDecimal:methodX1(SourceType); "
|
||||
+ "conversionY: Double-->String, method(s)X: Double:methodX2(SourceType); ."
|
||||
),
|
||||
@Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousMapperMC.class,
|
||||
line = 18,
|
||||
messageRegExp = "Can't map property.*"
|
||||
)
|
||||
} )
|
||||
public void methodAndConversionTest() {
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user