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 );
|
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
|
* getPropertyReadAccessors
|
||||||
*
|
*
|
||||||
|
@ -187,4 +187,18 @@ public interface Method {
|
|||||||
default boolean isMappingTargetAssignableToReturnType() {
|
default boolean isMappingTargetAssignableToReturnType() {
|
||||||
return isUpdateMethod() && getResultType().isAssignableTo( getReturnType() );
|
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
|
return method.getDeclaringMapper() == null
|
||||||
&& method.isAbstract()
|
&& method.isAbstract()
|
||||||
&& getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1
|
&& getSourceParameters().size() == 1 && method.getSourceParameters().size() == 1
|
||||||
&& first( getSourceParameters() ).getType().isAssignableTo( method.getResultType() )
|
&& getMappingSourceType().isAssignableTo( method.getResultType() )
|
||||||
&& getResultType().isAssignableTo( first( method.getSourceParameters() ).getType() );
|
&& getResultType().isAssignableTo( first( method.getSourceParameters() ).getType() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,7 +326,7 @@ public class SourceMethod implements Method {
|
|||||||
public boolean isIterableMapping() {
|
public boolean isIterableMapping() {
|
||||||
if ( isIterableMapping == null ) {
|
if ( isIterableMapping == null ) {
|
||||||
isIterableMapping = getSourceParameters().size() == 1
|
isIterableMapping = getSourceParameters().size() == 1
|
||||||
&& first( getSourceParameters() ).getType().isIterableType()
|
&& getMappingSourceType().isIterableType()
|
||||||
&& getResultType().isIterableType();
|
&& getResultType().isIterableType();
|
||||||
}
|
}
|
||||||
return isIterableMapping;
|
return isIterableMapping;
|
||||||
@ -335,9 +335,9 @@ public class SourceMethod implements Method {
|
|||||||
public boolean isStreamMapping() {
|
public boolean isStreamMapping() {
|
||||||
if ( isStreamMapping == null ) {
|
if ( isStreamMapping == null ) {
|
||||||
isStreamMapping = getSourceParameters().size() == 1
|
isStreamMapping = getSourceParameters().size() == 1
|
||||||
&& ( first( getSourceParameters() ).getType().isIterableType() && getResultType().isStreamType()
|
&& ( getMappingSourceType().isIterableType() && getResultType().isStreamType()
|
||||||
|| first( getSourceParameters() ).getType().isStreamType() && getResultType().isIterableType()
|
|| getMappingSourceType().isStreamType() && getResultType().isIterableType()
|
||||||
|| first( getSourceParameters() ).getType().isStreamType() && getResultType().isStreamType() );
|
|| getMappingSourceType().isStreamType() && getResultType().isStreamType() );
|
||||||
}
|
}
|
||||||
return isStreamMapping;
|
return isStreamMapping;
|
||||||
}
|
}
|
||||||
@ -345,7 +345,7 @@ public class SourceMethod implements Method {
|
|||||||
public boolean isMapMapping() {
|
public boolean isMapMapping() {
|
||||||
if ( isMapMapping == null ) {
|
if ( isMapMapping == null ) {
|
||||||
isMapMapping = getSourceParameters().size() == 1
|
isMapMapping = getSourceParameters().size() == 1
|
||||||
&& first( getSourceParameters() ).getType().isMapType()
|
&& getMappingSourceType().isMapType()
|
||||||
&& getResultType().isMapType();
|
&& getResultType().isMapType();
|
||||||
}
|
}
|
||||||
return isMapMapping;
|
return isMapMapping;
|
||||||
|
@ -67,21 +67,13 @@ public abstract class BuiltInMethod implements Method {
|
|||||||
|
|
||||||
Type sourceType = first( sourceTypes );
|
Type sourceType = first( sourceTypes );
|
||||||
|
|
||||||
if ( getReturnType().isAssignableTo( targetType.erasure() )
|
Type returnType = getReturnType().resolveTypeVarToType( sourceType, getParameter().getType() );
|
||||||
&& sourceType.erasure().isAssignableTo( getParameter().getType() ) ) {
|
if ( returnType == null ) {
|
||||||
return doTypeVarsMatch( sourceType, targetType );
|
return false;
|
||||||
}
|
}
|
||||||
if ( getReturnType().getFullyQualifiedName().equals( "java.lang.Object" )
|
|
||||||
&& sourceType.erasure().isAssignableTo( getParameter().getType() ) ) {
|
return returnType.isAssignableTo( targetType )
|
||||||
// return type could be a type parameter T
|
&& sourceType.erasure().isAssignableTo( getParameter().getType() );
|
||||||
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 );
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5,15 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.mapstruct.ap.internal.model.source.builtin;
|
package org.mapstruct.ap.internal.model.source.builtin;
|
||||||
|
|
||||||
|
import static org.mapstruct.ap.internal.util.Collections.asSet;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.xml.bind.JAXBElement;
|
import javax.xml.bind.JAXBElement;
|
||||||
|
|
||||||
import org.mapstruct.ap.internal.model.common.Parameter;
|
import org.mapstruct.ap.internal.model.common.Parameter;
|
||||||
import org.mapstruct.ap.internal.model.common.Type;
|
import org.mapstruct.ap.internal.model.common.Type;
|
||||||
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
||||||
|
|
||||||
import static org.mapstruct.ap.internal.util.Collections.asSet;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Sjaak Derksen
|
* @author Sjaak Derksen
|
||||||
*/
|
*/
|
||||||
@ -24,8 +25,9 @@ public class JaxbElemToValue extends BuiltInMethod {
|
|||||||
private final Set<Type> importTypes;
|
private final Set<Type> importTypes;
|
||||||
|
|
||||||
public JaxbElemToValue(TypeFactory typeFactory) {
|
public JaxbElemToValue(TypeFactory typeFactory) {
|
||||||
this.parameter = new Parameter( "element", typeFactory.getType( JAXBElement.class ) );
|
Type type = typeFactory.getType( JAXBElement.class );
|
||||||
this.returnType = typeFactory.getType( Object.class );
|
this.parameter = new Parameter( "element", type );
|
||||||
|
this.returnType = type.getTypeParameters().get( 0 );
|
||||||
this.importTypes = asSet( parameter.getType() );
|
this.importTypes = asSet( parameter.getType() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
package org.mapstruct.ap.internal.model.source.selector;
|
package org.mapstruct.ap.internal.model.source.selector;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.mapstruct.ap.internal.model.common.ParameterBinding;
|
import org.mapstruct.ap.internal.model.common.ParameterBinding;
|
||||||
import org.mapstruct.ap.internal.model.source.Method;
|
import org.mapstruct.ap.internal.model.source.Method;
|
||||||
@ -39,4 +40,21 @@ public class SelectedMethod<T extends Method> {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return method.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();
|
String name = xmlElementDecl.name().get();
|
||||||
TypeMirror scope = xmlElementDecl.scope().get();
|
TypeMirror scope = xmlElementDecl.scope().getValue();
|
||||||
|
|
||||||
boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() );
|
boolean nameIsSetAndMatches = name != null && name.equals( xmlElementRefInfo.nameValue() );
|
||||||
boolean scopeIsSetAndMatches =
|
boolean scopeIsSetAndMatches =
|
||||||
|
@ -5,12 +5,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.mapstruct.ap.internal.processor.creation;
|
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.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.lang.model.element.AnnotationMirror;
|
import javax.lang.model.element.AnnotationMirror;
|
||||||
import javax.lang.model.element.Element;
|
import javax.lang.model.element.Element;
|
||||||
import javax.lang.model.element.ExecutableElement;
|
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.Type;
|
||||||
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
||||||
import org.mapstruct.ap.internal.model.source.Method;
|
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.BuiltInMappingMethods;
|
||||||
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod;
|
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod;
|
||||||
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
|
import org.mapstruct.ap.internal.model.source.selector.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.NativeTypes;
|
||||||
import org.mapstruct.ap.internal.util.Strings;
|
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
|
* 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
|
* 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 BuiltInMappingMethods builtInMethods;
|
||||||
private final MethodSelectors methodSelectors;
|
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
|
* Private methods which are not present in the original mapper interface and are added to map certain property
|
||||||
* types.
|
* types.
|
||||||
@ -113,7 +122,9 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
sourceRHS,
|
sourceRHS,
|
||||||
criteria,
|
criteria,
|
||||||
positionHint,
|
positionHint,
|
||||||
forger
|
forger,
|
||||||
|
builtInMethods.getBuiltInMethods(),
|
||||||
|
messager
|
||||||
);
|
);
|
||||||
|
|
||||||
return attempt.getTargetAssignment( sourceRHS.getSourceTypeForMatching(), targetType );
|
return attempt.getTargetAssignment( sourceRHS.getSourceTypeForMatching(), targetType );
|
||||||
@ -141,10 +152,11 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
private final List<Method> methods;
|
private final List<Method> methods;
|
||||||
private final SelectionCriteria selectionCriteria;
|
private final SelectionCriteria selectionCriteria;
|
||||||
private final SourceRHS sourceRHS;
|
private final SourceRHS sourceRHS;
|
||||||
private final boolean savedPreferUpdateMapping;
|
|
||||||
private final FormattingParameters formattingParameters;
|
private final FormattingParameters formattingParameters;
|
||||||
private final AnnotationMirror positionHint;
|
private final AnnotationMirror positionHint;
|
||||||
private final Supplier<Assignment> forger;
|
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,
|
// 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
|
// 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,
|
FormattingParameters formattingParameters, SourceRHS sourceRHS,
|
||||||
SelectionCriteria criteria,
|
SelectionCriteria criteria,
|
||||||
AnnotationMirror positionHint,
|
AnnotationMirror positionHint,
|
||||||
Supplier<Assignment> forger) {
|
Supplier<Assignment> forger,
|
||||||
|
List<BuiltInMethod> builtIns,
|
||||||
|
FormattingMessager messager) {
|
||||||
|
|
||||||
this.mappingMethod = mappingMethod;
|
this.mappingMethod = mappingMethod;
|
||||||
this.methods = filterPossibleCandidateMethods( sourceModel );
|
this.methods = filterPossibleCandidateMethods( sourceModel );
|
||||||
@ -164,9 +178,10 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
this.sourceRHS = sourceRHS;
|
this.sourceRHS = sourceRHS;
|
||||||
this.supportingMethodCandidates = new HashSet<>();
|
this.supportingMethodCandidates = new HashSet<>();
|
||||||
this.selectionCriteria = criteria;
|
this.selectionCriteria = criteria;
|
||||||
this.savedPreferUpdateMapping = criteria.isPreferUpdateMapping();
|
|
||||||
this.positionHint = positionHint;
|
this.positionHint = positionHint;
|
||||||
this.forger = forger;
|
this.forger = forger;
|
||||||
|
this.builtIns = builtIns;
|
||||||
|
this.messager = messager;
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends Method> List<T> filterPossibleCandidateMethods(List<T> candidateMethods) {
|
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) {
|
private Assignment getTargetAssignment(Type sourceType, Type targetType) {
|
||||||
|
|
||||||
Assignment referencedMethod;
|
Assignment assignment;
|
||||||
|
|
||||||
// first simple mapping method
|
// first simple mapping method
|
||||||
if ( allowMappingMethod() ) {
|
if ( allowMappingMethod() ) {
|
||||||
referencedMethod = resolveViaMethod( sourceType, targetType, false );
|
List<SelectedMethod<Method>> matches = getBestMatch( methods, sourceType, targetType );
|
||||||
if ( referencedMethod != null ) {
|
reportErrorWhenAmbigious( matches, targetType );
|
||||||
referencedMethod.setAssignment( sourceRHS );
|
if ( !matches.isEmpty() ) {
|
||||||
return referencedMethod;
|
assignment = toMethodRef( first( matches ) );
|
||||||
|
assignment.setAssignment( sourceRHS );
|
||||||
|
return assignment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,38 +245,40 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
|
|
||||||
// check for a built-in method
|
// check for a built-in method
|
||||||
if ( !hasQualfiers() ) {
|
if ( !hasQualfiers() ) {
|
||||||
Assignment builtInMethod = resolveViaBuiltInMethod( sourceType, targetType );
|
List<SelectedMethod<BuiltInMethod>> matches = getBestMatch( builtIns, sourceType, targetType );
|
||||||
if ( builtInMethod != null ) {
|
reportErrorWhenAmbigious( matches, targetType );
|
||||||
builtInMethod.setAssignment( sourceRHS );
|
if ( !matches.isEmpty() ) {
|
||||||
|
assignment = toBuildInRef( first( matches ) );
|
||||||
|
assignment.setAssignment( sourceRHS );
|
||||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||||
return builtInMethod;
|
return assignment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( allow2Steps() ) {
|
if ( allow2Steps() ) {
|
||||||
// 2 step method, first: method(method(source))
|
// 2 step method, first: method(method(source))
|
||||||
referencedMethod = resolveViaMethodAndMethod( sourceType, targetType );
|
assignment = MethodMethod.getBestMatch( this, sourceType, targetType );
|
||||||
if ( referencedMethod != null ) {
|
if ( assignment != null ) {
|
||||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||||
return referencedMethod;
|
return assignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2 step method, then: method(conversion(source))
|
// 2 step method, then: method(conversion(source))
|
||||||
referencedMethod = resolveViaConversionAndMethod( sourceType, targetType );
|
assignment = ConversionMethod.getBestMatch( this, sourceType, targetType );
|
||||||
if ( referencedMethod != null ) {
|
if ( assignment != null ) {
|
||||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||||
return referencedMethod;
|
return assignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
// stop here when looking for update methods.
|
// stop here when looking for update methods.
|
||||||
selectionCriteria.setPreferUpdateMapping( false );
|
selectionCriteria.setPreferUpdateMapping( false );
|
||||||
|
|
||||||
// 2 step method, finally: conversion(method(source))
|
// 2 step method, finally: conversion(method(source))
|
||||||
ConversionAssignment conversion = resolveViaMethodAndConversion( sourceType, targetType );
|
assignment = MethodConversion.getBestMatch( this, sourceType, targetType );
|
||||||
if ( conversion != null ) {
|
if ( assignment != null ) {
|
||||||
usedSupportedMappings.addAll( supportingMethodCandidates );
|
usedSupportedMappings.addAll( supportingMethodCandidates );
|
||||||
return conversion.getAssignment();
|
return assignment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,232 +414,6 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
return null;
|
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) {
|
private boolean isCandidateForMapping(Method methodCandidate) {
|
||||||
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
|
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
|
||||||
}
|
}
|
||||||
@ -640,15 +433,17 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
&& !methodCandidate.isLifecycleCallbackMethod();
|
&& !methodCandidate.isLifecycleCallbackMethod();
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends Method> SelectedMethod<T> getBestMatch(List<T> methods, Type sourceType, Type returnType) {
|
private <T extends Method> List<SelectedMethod<T>> getBestMatch(List<T> methods, Type source, Type target) {
|
||||||
|
return methodSelectors.getMatchingMethods(
|
||||||
List<SelectedMethod<T>> candidates = methodSelectors.getMatchingMethods(
|
|
||||||
mappingMethod,
|
mappingMethod,
|
||||||
methods,
|
methods,
|
||||||
singletonList( sourceType ),
|
singletonList( source ),
|
||||||
returnType,
|
target,
|
||||||
selectionCriteria
|
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
|
// raise an error if more than one mapping method is suitable to map the given source type
|
||||||
// into the target type
|
// into the target type
|
||||||
@ -660,7 +455,7 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
positionHint,
|
positionHint,
|
||||||
Message.GENERAL_AMBIGIOUS_MAPPING_METHOD,
|
Message.GENERAL_AMBIGIOUS_MAPPING_METHOD,
|
||||||
sourceRHS.getSourceErrorMessagePart(),
|
sourceRHS.getSourceErrorMessagePart(),
|
||||||
returnType,
|
target,
|
||||||
Strings.join( candidates, ", " )
|
Strings.join( candidates, ", " )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -669,29 +464,41 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
mappingMethod.getExecutable(),
|
mappingMethod.getExecutable(),
|
||||||
positionHint,
|
positionHint,
|
||||||
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
|
Message.GENERAL_AMBIGIOUS_FACTORY_METHOD,
|
||||||
returnType,
|
target,
|
||||||
Strings.join( candidates, ", " )
|
Strings.join( candidates, ", " )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !candidates.isEmpty() ) {
|
|
||||||
return first( candidates );
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Assignment getMappingMethodReference(SelectedMethod<Method> method) {
|
private Assignment toMethodRef(SelectedMethod<Method> selectedMethod) {
|
||||||
MapperReference mapperReference = findMapperReference( method.getMethod() );
|
MapperReference mapperReference = findMapperReference( selectedMethod.getMethod() );
|
||||||
|
|
||||||
return MethodReference.forMapperReference(
|
return MethodReference.forMapperReference(
|
||||||
method.getMethod(),
|
selectedMethod.getMethod(),
|
||||||
mapperReference,
|
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
|
* 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.
|
* can be propagated via a copy constructor.
|
||||||
@ -770,6 +577,7 @@ public class MappingResolverImpl implements MappingResolver {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ConversionAssignment {
|
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.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,4 +64,16 @@ public class Collections {
|
|||||||
return result;
|
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_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_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_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("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 ),
|
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