diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java index e02107881..820a09c22 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ForgedMethod.java @@ -340,6 +340,11 @@ public class ForgedMethod implements Method { return basedOn.getOptions(); } + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + @Override public String describe() { // the name of the forged method is never fully qualified, so no need to distinguish diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java index fcf8068ed..35b35aee7 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/HelperMethod.java @@ -128,6 +128,11 @@ public abstract class HelperMethod implements Method { return false; } + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + /** * the conversion context is used to format an auxiliary parameter in the method call with context specific * information such as a date format. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java index 61610badf..87527e87f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/LifecycleMethodResolver.java @@ -141,6 +141,7 @@ public final class LifecycleMethodResolver { callbackMethods, Collections.emptyList(), targetType, + method.getReturnType(), SelectionCriteria.forLifecycleMethods( selectionParameters ) ); return toLifecycleCallbackMethodRefs( diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java index 46e1d4ee4..a9d4fd091 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/MappingBuilderContext.java @@ -78,12 +78,13 @@ public class MappingBuilderContext { * returns a parameter assignment * * @param mappingMethod target mapping method - * @param description + * @param description the description source * @param targetType return type to match * @param formattingParameters used for formatting dates and numbers * @param criteria parameters criteria in the selection process * @param sourceRHS source information * @param positionHint the mirror for reporting problems + * @param forger the supplier of the callback method to forge a method * * @return an assignment to a method parameter, which can either be: *
    diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java index d320bb49d..f0bd9c4df 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/ObjectFactoryMethodResolver.java @@ -5,9 +5,12 @@ */ package org.mapstruct.ap.internal.model; +import static org.mapstruct.ap.internal.util.Collections.first; + import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; + import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; @@ -23,8 +26,6 @@ import org.mapstruct.ap.internal.model.source.selector.SelectedMethod; import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria; import org.mapstruct.ap.internal.util.Message; -import static org.mapstruct.ap.internal.util.Collections.first; - /** * * @author Sjaak Derksen @@ -129,11 +130,13 @@ public class ObjectFactoryMethodResolver { new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getTypeFactory(), ctx.getMessager() ); return selectors.getMatchingMethods( - method, - getAllAvailableMethods( method, ctx.getSourceModel() ), - java.util.Collections.emptyList(), - alternativeTarget, - SelectionCriteria.forFactoryMethods( selectionParameters ) ); + method, + getAllAvailableMethods( method, ctx.getSourceModel() ), + java.util.Collections.emptyList(), + alternativeTarget, + alternativeTarget, + SelectionCriteria.forFactoryMethods( selectionParameters ) + ); } public static MethodReference getBuilderFactoryMethod(Method method, BuilderType builder ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java index b3d01f413..15f787829 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java @@ -12,15 +12,18 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; + import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeKind; @@ -103,6 +106,8 @@ public class Type extends ModelElement implements Comparable { private Type boundingBase = null; + private Type boxedEquivalent = null; + private Boolean hasAccessibleConstructor; private final Filters filters; @@ -311,7 +316,12 @@ public class Type extends ModelElement implements Comparable { return isStream; } - public boolean isWildCardSuperBound() { + /** + * A wild card type can have two types of bounds (mutual exclusive): extends and super. + * + * @return true if the bound has a wild card super bound (e.g. ? super Number) + */ + public boolean hasSuperBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -320,7 +330,12 @@ public class Type extends ModelElement implements Comparable { return result; } - public boolean isWildCardExtendsBound() { + /** + * A wild card type can have two types of bounds (mutual exclusive): extends and super. + * + * @return true if the bound has a wild card super bound (e.g. ? extends Number) + */ + public boolean hasExtendsBound() { boolean result = false; if ( typeMirror.getKind() == TypeKind.WILDCARD ) { WildcardType wildcardType = (WildcardType) typeMirror; @@ -329,6 +344,40 @@ public class Type extends ModelElement implements Comparable { return result; } + /** + * A type variable type can have two types of bounds (mutual exclusive): lower and upper. + * + * Note that its use is only permitted on a definition (not on the place where its used). For instance: + * {@code T map( T in)} + * + * @return true if the bound has a type variable lower bound (e.g. T super Number) + */ + public boolean hasLowerBound() { + boolean result = false; + if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { + TypeVariable typeVarType = (TypeVariable) typeMirror; + result = typeVarType.getLowerBound() != null; + } + return result; + } + + /** + * A type variable type can have two types of bounds (mutual exclusive): lower and upper. + * + * Note that its use is only permitted on a definition (not on the place where its used). For instance: + * {@code> T map( T in)} + * + * @return true if the bound has a type variable upper bound (e.g. T extends Number) + */ + public boolean hasUpperBound() { + boolean result = false; + if ( typeMirror.getKind() == TypeKind.TYPEVAR ) { + TypeVariable typeVarType = (TypeVariable) typeMirror; + result = typeVarType.getUpperBound() != null; + } + return result; + } + public String getFullyQualifiedName() { return qualifiedName; } @@ -356,7 +405,7 @@ public class Type extends ModelElement implements Comparable { result.addAll( parameter.getImportTypes() ); } - if ( ( isWildCardExtendsBound() || isWildCardSuperBound() ) && getTypeBound() != null ) { + if ( ( hasExtendsBound() || hasSuperBound() ) && getTypeBound() != null ) { result.addAll( getTypeBound().getImportTypes() ); } @@ -470,22 +519,19 @@ public class Type extends ModelElement implements Comparable { } /** - * Whether this type is assignable to the given other type. + * Whether this type is assignable to the given other type, considering the "extends / upper bounds" + * as well. * * @param other The other type. * * @return {@code true} if and only if this type is assignable to the given other type. */ - // TODO This doesn't yet take super wild card types into account; - // e.g. Number wouldn't be assignable to ? super Number atm. (is there any practical use case) public boolean isAssignableTo(Type other) { - if ( equals( other ) ) { - return true; + if ( TypeKind.WILDCARD == typeMirror.getKind() ) { + return typeUtils.contains( typeMirror, other.typeMirror ); } - TypeMirror typeMirrorToMatch = isWildCardExtendsBound() ? getTypeBound().typeMirror : typeMirror; - - return typeUtils.isAssignable( typeMirrorToMatch, other.typeMirror ); + return typeUtils.isAssignable( typeMirror, other.typeMirror ); } /** @@ -506,6 +552,19 @@ public class Type extends ModelElement implements Comparable { return typeUtils.isAssignable( typeUtils.erasure( typeMirror ), typeUtils.erasure( other.typeMirror ) ); } + /** + * removes any bounds from this type. + * @return the raw type + */ + public Type asRawType() { + if ( getTypeBound() != null ) { + return typeFactory.getType( typeUtils.erasure( typeMirror ) ); + } + else { + return this; + } + } + /** * getPropertyReadAccessors * @@ -1004,7 +1063,14 @@ public class Type extends ModelElement implements Comparable { } Type other = (Type) obj; - return typeUtils.isSameType( typeMirror, other.typeMirror ); + if ( this.isWildCardBoundByTypeVar() && other.isWildCardBoundByTypeVar() ) { + return ( this.hasExtendsBound() == this.hasExtendsBound() + || this.hasSuperBound() == this.hasSuperBound() ) + && typeUtils.isSameType( getTypeBound().getTypeMirror(), other.getTypeBound().getTypeMirror() ); + } + else { + return typeUtils.isSameType( typeMirror, other.typeMirror ); + } } @Override @@ -1085,6 +1151,19 @@ public class Type extends ModelElement implements Comparable { return hasAccessibleConstructor; } + /** + * Returns the direct supertypes of a type. The interface types, if any, + * will appear last in the list. + * + * @return the direct supertypes, or an empty list if none + */ + public List getDirectSuperTypes() { + return typeUtils.directSupertypes( typeMirror ) + .stream() + .map( typeFactory::getType ) + .collect( Collectors.toList() ); + } + /** * Searches for the given superclass and collects all type arguments for the given class * @@ -1121,59 +1200,239 @@ public class Type extends ModelElement implements Comparable { } /** - * Steps through the declaredType in order to find a match for this typevar Type. It allignes with + * Steps through the declaredType in order to find a match for this typeVar Type. It aligns with * the provided parameterized type where this typeVar type is used. * - * @param declaredType the type - * @param parameterizedType the parameterized type + * For example: + * {@code + * this: T + * declaredType: JAXBElement + * parameterizedType: JAXBElement + * result: String * - * @return the matching declared type. + * + * this: T, T[] or ? extends T, + * declaredType: E.g. Callable + * parameterizedType: Callable + * return: BigDecimal + * } + * + * @param declared the type + * @param parameterized the parameterized type + * + * @return - the same type when this is not a type var in the broadest sense (T, T[], or ? extends T) + * - the matching parameter in the parameterized type when this is a type var when found + * - null in all other cases */ - public Type resolveTypeVarToType(Type declaredType, Type parameterizedType) { - if ( isTypeVar() ) { - TypeVarMatcher typeVarMatcher = new TypeVarMatcher( typeUtils, this ); - return typeVarMatcher.visit( parameterizedType.getTypeMirror(), declaredType ); + public ResolvedPair resolveParameterToType(Type declared, Type parameterized) { + if ( isTypeVar() || isArrayTypeVar() || isWildCardBoundByTypeVar() ) { + TypeVarMatcher typeVarMatcher = new TypeVarMatcher( typeFactory, typeUtils, this ); + return typeVarMatcher.visit( parameterized.getTypeMirror(), declared ); } - return this; + return new ResolvedPair( this, this ); } - private static class TypeVarMatcher extends SimpleTypeVisitor8 { + public boolean isWildCardBoundByTypeVar() { + return ( hasExtendsBound() || hasSuperBound() ) && getTypeBound().isTypeVar(); + } - private TypeVariable typeVarToMatch; - private TypeUtils types; + public boolean isArrayTypeVar() { + return isArrayType() && getComponentType().isTypeVar(); + } - TypeVarMatcher(TypeUtils types, Type typeVarToMatch ) { - super( null ); - this.typeVarToMatch = (TypeVariable) typeVarToMatch.getTypeMirror(); + private static class TypeVarMatcher extends SimpleTypeVisitor8 { + + private final TypeFactory typeFactory; + private final Type typeToMatch; + private final TypeUtils types; + + /** + * @param typeFactory factory + * @param types type utils + * @param typeToMatch the typeVar or wildcard with typeVar bound + */ + TypeVarMatcher(TypeFactory typeFactory, TypeUtils types, Type typeToMatch) { + super( new ResolvedPair( typeToMatch, null ) ); + this.typeFactory = typeFactory; + this.typeToMatch = typeToMatch; this.types = types; } @Override - public Type visitTypeVariable(TypeVariable t, Type parameterized) { - if ( types.isSameType( t, typeVarToMatch ) ) { - return parameterized; + public ResolvedPair visitTypeVariable(TypeVariable parameterized, Type declared) { + if ( typeToMatch.isTypeVar() && types.isSameType( parameterized, typeToMatch.getTypeMirror() ) ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); } - return super.visitTypeVariable( t, parameterized ); + return super.DEFAULT_VALUE; + } + + /** + * If ? extends SomeTime equals the boundary set in typeVarToMatch (NOTE: you can't compare the wildcard itself) + * then return a result; + */ + @Override + public ResolvedPair visitWildcard(WildcardType parameterized, Type declared) { + if ( typeToMatch.hasExtendsBound() && parameterized.getExtendsBound() != null + && types.isSameType( typeToMatch.getTypeBound().getTypeMirror(), parameterized.getExtendsBound() ) ) { + return new ResolvedPair( typeToMatch, declared); + } + else if ( typeToMatch.hasSuperBound() && parameterized.getSuperBound() != null + && types.isSameType( typeToMatch.getTypeBound().getTypeMirror(), parameterized.getSuperBound() ) ) { + return new ResolvedPair( typeToMatch, declared); + } + if ( parameterized.getExtendsBound() != null ) { + ResolvedPair match = visit( parameterized.getExtendsBound(), declared ); + if ( match.match != null ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + } + else if (parameterized.getSuperBound() != null ) { + ResolvedPair match = visit( parameterized.getSuperBound(), declared ); + if ( match.match != null ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + + } + return super.DEFAULT_VALUE; } @Override - public Type visitDeclared(DeclaredType t, Type parameterized) { - if ( types.isAssignable( types.erasure( t ), types.erasure( parameterized.getTypeMirror() ) ) ) { + public ResolvedPair visitArray(ArrayType parameterized, Type declared) { + if ( types.isSameType( parameterized.getComponentType(), typeToMatch.getTypeMirror() ) ) { + return new ResolvedPair( typeFactory.getType( parameterized ), declared ); + } + if ( declared.isArrayType() ) { + return visit( parameterized.getComponentType(), declared.getComponentType() ); + } + return super.DEFAULT_VALUE; + } + + @Override + public ResolvedPair visitDeclared(DeclaredType parameterized, Type declared) { + + List results = new ArrayList<>( ); + if ( parameterized.getTypeArguments().isEmpty() ) { + return super.DEFAULT_VALUE; + } + else if ( types.isSameType( types.erasure( parameterized ), types.erasure( declared.getTypeMirror() ) ) ) { // We can't assume that the type args are the same // e.g. List is assignable to Object - if ( t.getTypeArguments().size() != parameterized.getTypeParameters().size() ) { - return super.visitDeclared( t, parameterized ); + if ( parameterized.getTypeArguments().size() != declared.getTypeParameters().size() ) { + return super.visitDeclared( parameterized, declared ); } - for ( int i = 0; i < t.getTypeArguments().size(); i++ ) { - Type result = visit( t.getTypeArguments().get( i ), parameterized.getTypeParameters().get( i ) ); - if ( result != null ) { - return result; + // only possible to compare parameters when the types are exactly the same + for ( int i = 0; i < parameterized.getTypeArguments().size(); i++ ) { + TypeMirror parameterizedTypeArg = parameterized.getTypeArguments().get( i ); + Type declaredTypeArg = declared.getTypeParameters().get( i ); + ResolvedPair result = visit( parameterizedTypeArg, declaredTypeArg ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); } } } - return super.visitDeclared( t, parameterized ); + else { + // Also check whether the implemented interfaces are parameterized + for ( Type declaredSuperType : declared.getDirectSuperTypes() ) { + if ( Object.class.getName().equals( declaredSuperType.getFullyQualifiedName() ) ) { + continue; + } + ResolvedPair result = visitDeclared( parameterized, declaredSuperType ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } + } + + for ( TypeMirror parameterizedSuper : types.directSupertypes( parameterized ) ) { + if ( isJavaLangObject( parameterizedSuper ) ) { + continue; + } + ResolvedPair result = visitDeclared( (DeclaredType) parameterizedSuper, declared ); + if ( result != super.DEFAULT_VALUE ) { + results.add( result ); + } + } + } + if ( results.isEmpty() ) { + return super.DEFAULT_VALUE; + } + else { + return results.stream().allMatch( results.get( 0 )::equals ) ? results.get( 0 ) : super.DEFAULT_VALUE; + } } + + private boolean isJavaLangObject(TypeMirror type) { + if ( type instanceof DeclaredType ) { + return ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName() + .contentEquals( Object.class.getName() ); + } + return false; + } + } + + /** + * Reflects any Resolved Pair, examples are + * T, String + * ? extends T, BigDecimal + * T[], Integer[] + */ + public static class ResolvedPair { + + public ResolvedPair(Type parameter, Type match) { + this.parameter = parameter; + this.match = match; + } + + /** + * parameter, e.g. T, ? extends T or T[] + */ + private Type parameter; + + /** + * match, e.g. String, BigDecimal, Integer[] + */ + private Type match; + + public Type getParameter() { + return parameter; + } + + public Type getMatch() { + return match; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + ResolvedPair that = (ResolvedPair) o; + return Objects.equals( parameter, that.parameter ) && Objects.equals( match, that.match ); + } + + @Override + public int hashCode() { + return Objects.hash( parameter ); + } + } + + /** + * Gets the boxed equivalent type if the type is primitive, int will return Integer + * + * @return boxed equivalent + */ + public Type getBoxedEquivalent() { + if ( boxedEquivalent != null ) { + return boxedEquivalent; + } + else if ( isPrimitive() ) { + boxedEquivalent = typeFactory.getType( typeUtils.boxedClass( (PrimitiveType) typeMirror ) ); + return boxedEquivalent; + } + return this; } /** diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java index 8d3d215c5..f8c67f740 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/TypeFactory.java @@ -613,7 +613,11 @@ public class TypeFactory { if ( typeVariableType.getUpperBound() != null ) { return typeVariableType.getUpperBound(); } - // Lowerbounds intentionally left out: Type variables otherwise have a lower bound of NullType. + // lower bounds ( T super Number ) cannot be used for argument parameters, but can be used for + // method parameters: e.g. T map (T in); + if ( typeVariableType.getLowerBound() != null ) { + return typeVariableType.getLowerBound(); + } } return typeMirror; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java index 2039aa86a..8a38ed2ac 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java @@ -199,4 +199,11 @@ public interface Method { * @return the short name for error messages when verbose, full name when not */ String describe(); + + /** + * Returns the formal type parameters of this method in declaration order. + * + * @return the formal type parameters, or an empty list if there are none + */ + List getTypeParameters(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java index ea3f72044..136828adf 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/MethodMatcher.java @@ -5,25 +5,19 @@ */ package org.mapstruct.ap.internal.model.source; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.TypeParameterElement; -import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.PrimitiveType; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.lang.model.type.TypeVariable; -import javax.lang.model.type.WildcardType; -import javax.lang.model.util.SimpleTypeVisitor6; -import org.mapstruct.ap.internal.util.TypeUtils; 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 org.mapstruct.ap.internal.util.TypeUtils; /** * SourceMethodMatcher $8.4 of the JavaLanguage specification describes a method body as such: @@ -66,362 +60,348 @@ public class MethodMatcher { * Whether the given source and target types are matched by this matcher's candidate method. * * @param sourceTypes the source types - * @param resultType the target type + * @param targetType the target type * @return {@code true} when both, source type and target types match the signature of this matcher's method; * {@code false} otherwise. */ - boolean matches(List sourceTypes, Type resultType) { + boolean matches(List sourceTypes, Type targetType) { - // check & collect generic types. - Map genericTypesMap = new HashMap<>(); - - if ( candidateMethod.getParameters().size() == sourceTypes.size() ) { - int i = 0; - for ( Parameter candidateParam : candidateMethod.getParameters() ) { - Type sourceType = sourceTypes.get( i++ ); - if ( sourceType == null - || !matchSourceType( sourceType, candidateParam.getType(), genericTypesMap ) ) { - return false; - } - } - } - else { + GenericAnalyser analyser = + new GenericAnalyser( typeFactory, typeUtils, candidateMethod, sourceTypes, targetType ); + if ( !analyser.lineUp() ) { return false; } - // check if the method matches the proper result type to construct - Parameter targetTypeParameter = candidateMethod.getTargetTypeParameter(); - if ( targetTypeParameter != null ) { - Type returnClassType = typeFactory.classTypeOf( resultType ); - if ( !matchSourceType( returnClassType, targetTypeParameter.getType(), genericTypesMap ) ) { + for ( int i = 0; i < sourceTypes.size(); i++ ) { + Type candidateSourceParType = analyser.candidateParTypes.get( i ); + if ( !sourceTypes.get( i ).isAssignableTo( candidateSourceParType ) + || isPrimitiveToObject( sourceTypes.get( i ), candidateSourceParType ) ) { + return false; + } + } + // TODO: TargetType checking should not be part of method selection, it should be in checking the annotation + // (the relation target / target type, target type being a class) + + if ( !analyser.candidateReturnType.isVoid() ) { + if ( !( analyser.candidateReturnType.isAssignableTo( targetType ) ) ) { return false; } } - // check result type - if ( !matchResultType( resultType, genericTypesMap ) ) { - return false; + return true; + } + + /** + * Primitive to Object (Boxed Type) should be handled by a type conversion rather than a direct mapping + * Direct mapping runs the risk of null pointer exceptions: e.g. in the assignment of Integer to int, Integer + * can be null. + * + * @param type the type to be assigned + * @param isAssignableTo the type to which @param type should be assignable to + * @return true if isAssignable is a primitive type and type is an object + */ + private boolean isPrimitiveToObject( Type type, Type isAssignableTo ) { + if ( isAssignableTo.isPrimitive() ) { + return !type.isPrimitive(); + } + return false; + } + + private static class GenericAnalyser { + + private TypeFactory typeFactory; + private TypeUtils typeUtils; + private Method candidateMethod; + private List sourceTypes; + private Type targetType; + + GenericAnalyser(TypeFactory typeFactory, TypeUtils typeUtils, Method candidateMethod, + List sourceTypes, Type targetType) { + this.typeFactory = typeFactory; + this.typeUtils = typeUtils; + this.candidateMethod = candidateMethod; + this.sourceTypes = sourceTypes; + this.targetType = targetType; } - // check if all type parameters are indeed mapped - if ( candidateMethod.getExecutable().getTypeParameters().size() != genericTypesMap.size() ) { - return false; - } + Type candidateReturnType = null; + List candidateParTypes; - // check if all entries are in the bounds - for ( Map.Entry entry : genericTypesMap.entrySet() ) { - if ( !isWithinBounds( entry.getValue(), getTypeParamFromCandidate( entry.getKey() ) ) ) { - // checks if the found Type is in bounds of the TypeParameters bounds. + private boolean lineUp() { + + if ( candidateMethod.getParameters().size() != sourceTypes.size() ) { return false; } - } - return true; - } - private boolean matchSourceType(Type sourceType, - Type candidateSourceType, - Map genericTypesMap) { + if ( !candidateMethod.getTypeParameters().isEmpty() ) { - if ( !isJavaLangObject( candidateSourceType.getTypeMirror() ) ) { - TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); - if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), sourceType.getTypeMirror() ) ) { - if ( sourceType.isPrimitive() ) { - // the candidate source is primitive, so promote to its boxed type and check again (autobox) - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) sourceType.getTypeMirror() ).asType(); - if ( !parameterMatcher.visit( candidateSourceType.getTypeMirror(), boxedType ) ) { + this.candidateParTypes = new ArrayList<>(); + + // Per generic method parameter the associated type variable candidates + Map methodParCandidates = new HashMap<>(); + + // Get candidates + boolean success = getCandidates( methodParCandidates ); + if ( !success ) { + return false; + } + + // Check type bounds + boolean withinBounds = candidatesWithinBounds( methodParCandidates ); + if ( !withinBounds ) { + return false; + } + + // Represent result as map. + Map resolvedPairs = new HashMap<>(); + for ( TypeVarCandidate candidate : methodParCandidates.values() ) { + for ( Type.ResolvedPair pair : candidate.pairs) { + resolvedPairs.put( pair.getParameter(), pair.getMatch() ); + } + } + + // Resolve parameters and return type by using the found candidates + int nrOfMethodPars = candidateMethod.getParameters().size(); + for ( int i = 0; i < nrOfMethodPars; i++ ) { + Type candidateType = resolve( candidateMethod.getParameters().get( i ).getType(), resolvedPairs ); + if ( candidateType == null ) { + return false; + } + this.candidateParTypes.add( candidateType ); + + } + if ( !candidateMethod.getReturnType().isVoid() ) { + this.candidateReturnType = resolve( candidateMethod.getReturnType(), resolvedPairs ); + if ( this.candidateReturnType == null ) { return false; } } else { - // NOTE: unboxing is deliberately not considered here. This should be handled via type-conversion - // (for NPE safety). + this.candidateReturnType = candidateMethod.getReturnType(); + } + } + else { + this.candidateParTypes = candidateMethod.getParameters().stream() + .map( Parameter::getType ) + .collect( Collectors.toList() ); + this.candidateReturnType = candidateMethod.getReturnType(); + } + return true; + } + + /** + * {@code T map( U in ) } + * + * Resolves all method generic parameter candidates + * + * @param methodParCandidates Map, keyed by the method generic parameter (T, U extends Number), with their + * respective candidates + * + * @return false no match or conflict has been found * + */ + boolean getCandidates( Map methodParCandidates) { + + int nrOfMethodPars = candidateMethod.getParameters().size(); + Type returnType = candidateMethod.getReturnType(); + + for ( int i = 0; i < nrOfMethodPars; i++ ) { + Type sourceType = sourceTypes.get( i ); + Parameter par = candidateMethod.getParameters().get( i ); + Type parType = par.getType(); + boolean success = getCandidates( parType, sourceType, methodParCandidates ); + if ( !success ) { return false; } } - } - return true; - } - - private boolean matchResultType(Type resultType, Map genericTypesMap) { - - Type candidateResultType = candidateMethod.getResultType(); - - if ( !isJavaLangObject( candidateResultType.getTypeMirror() ) && !candidateResultType.isVoid() ) { - - final Assignability visitedAssignability; - if ( candidateMethod.getReturnType().isVoid() ) { - // for void-methods, the result-type of the candidate needs to be assignable from the given result type - visitedAssignability = Assignability.VISITED_ASSIGNABLE_FROM; - } - else { - // for non-void methods, the result-type of the candidate needs to be assignable to the given result - // type - visitedAssignability = Assignability.VISITED_ASSIGNABLE_TO; - } - - TypeMatcher returnTypeMatcher = new TypeMatcher( visitedAssignability, genericTypesMap ); - if ( !returnTypeMatcher.visit( candidateResultType.getTypeMirror(), resultType.getTypeMirror() ) ) { - if ( resultType.isPrimitive() ) { - TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) resultType.getTypeMirror() ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( visitedAssignability, genericTypesMap ); - - if ( !boxedReturnTypeMatcher.visit( candidateResultType.getTypeMirror(), boxedType ) ) { - return false; - } - } - else if ( candidateResultType.getTypeMirror().getKind().isPrimitive() ) { - TypeMirror boxedCandidateReturnType = - typeUtils.boxedClass( (PrimitiveType) candidateResultType.getTypeMirror() ).asType(); - TypeMatcher boxedReturnTypeMatcher = - new TypeMatcher( visitedAssignability, genericTypesMap ); - - if ( !boxedReturnTypeMatcher.visit( boxedCandidateReturnType, resultType.getTypeMirror() ) ) { - return false; - } - - } - else { - return false; - } - } - } - return true; - } - - /** - * @param type the type - * @return {@code true}, if the type represents java.lang.Object - */ - private boolean isJavaLangObject(TypeMirror type) { - return type.getKind() == TypeKind.DECLARED - && ( (TypeElement) ( (DeclaredType) type ).asElement() ).getQualifiedName().contentEquals( - Object.class.getName() ); - } - - private enum Assignability { - VISITED_ASSIGNABLE_FROM, VISITED_ASSIGNABLE_TO; - - Assignability invert() { - return this == VISITED_ASSIGNABLE_FROM - ? VISITED_ASSIGNABLE_TO - : VISITED_ASSIGNABLE_FROM; - } - } - - private class TypeMatcher extends SimpleTypeVisitor6 { - private final Assignability assignability; - private final Map genericTypesMap; - private final TypeMatcher inverse; - - TypeMatcher(Assignability assignability, Map genericTypesMap) { - super( Boolean.FALSE ); // default value - this.assignability = assignability; - this.genericTypesMap = genericTypesMap; - this.inverse = new TypeMatcher( this, genericTypesMap ); - } - - TypeMatcher(TypeMatcher inverse, Map genericTypesMap) { - super( Boolean.FALSE ); // default value - this.assignability = inverse.assignability.invert(); - this.genericTypesMap = genericTypesMap; - this.inverse = inverse; - } - - @Override - public Boolean visitPrimitive(PrimitiveType t, TypeMirror p) { - return typeUtils.isSameType( t, p ); - } - - @Override - public Boolean visitArray(ArrayType t, TypeMirror p) { - - if ( p.getKind().equals( TypeKind.ARRAY ) ) { - return t.getComponentType().accept( this, ( (ArrayType) p ).getComponentType() ); - } - else { - return Boolean.FALSE; - } - } - - @Override - public Boolean visitDeclared(DeclaredType t, TypeMirror p) { - // its a match when: 1) same kind of type, name is equals, nr of type args are the same - // (type args are checked later). - if ( p.getKind() == TypeKind.DECLARED ) { - DeclaredType t1 = (DeclaredType) p; - if ( rawAssignabilityMatches( t, t1 ) ) { - if ( t.getTypeArguments().size() == t1.getTypeArguments().size() ) { - // compare type var side by side - for ( int i = 0; i < t.getTypeArguments().size(); i++ ) { - if ( !visit( t.getTypeArguments().get( i ), t1.getTypeArguments().get( i ) ) ) { - return Boolean.FALSE; - } - } - return Boolean.TRUE; - } - else { - // return true (e.g. matching Enumeration with an enumeration E) - // but do not try to line up raw type arguments with types that do have arguments. - return assignability == Assignability.VISITED_ASSIGNABLE_TO ? - !t1.getTypeArguments().isEmpty() : !t.getTypeArguments().isEmpty(); - } - } - else { - return Boolean.FALSE; - } - } - else if ( p.getKind() == TypeKind.WILDCARD ) { - return inverse.visit( p, t ); // inverse, as we switch the params - } - else { - return Boolean.FALSE; - } - } - - private boolean rawAssignabilityMatches(DeclaredType t1, DeclaredType t2) { - if ( assignability == Assignability.VISITED_ASSIGNABLE_TO ) { - return typeUtils.isAssignable( toRawType( t1 ), toRawType( t2 ) ); - } - else { - return typeUtils.isAssignable( toRawType( t2 ), toRawType( t1 ) ); - } - } - - private DeclaredType toRawType(DeclaredType t) { - return typeUtils.getDeclaredType( (TypeElement) t.asElement() ); - } - - @Override - public Boolean visitTypeVariable(TypeVariable t, TypeMirror p) { - if ( genericTypesMap.containsKey( t ) ) { - // when already found, the same mapping should apply - // Then we should visit the resolved generic type. - // Which can potentially be another generic type - // e.g. - // T fromOptional(Optional optional) - // T resolves to Collection - // We know what T resolves to, so we should treat it as if the method signature was - // Collection fromOptional(Optional optional) - TypeMirror p1 = genericTypesMap.get( t ); - // p (Integer) should be a subType of p1 (Number) - // i.e. you can assign p (Integer) to p1 (Number) - return visit( p, p1 ); - } - else { - // check if types are in bound - TypeMirror lowerBound = t.getLowerBound(); - TypeMirror upperBound = t.getUpperBound(); - if ( ( isNullType( lowerBound ) || typeUtils.isSubtypeErased( lowerBound, p ) ) - && ( isNullType( upperBound ) || typeUtils.isSubtypeErased( p, upperBound ) ) ) { - genericTypesMap.put( t, p ); - return Boolean.TRUE; - } - else { - return Boolean.FALSE; - } - } - } - - private boolean isNullType(TypeMirror type) { - return type == null || type.getKind() == TypeKind.NULL; - } - - @Override - public Boolean visitWildcard(WildcardType t, TypeMirror p) { - - // check extends bound - TypeMirror extendsBound = t.getExtendsBound(); - if ( !isNullType( extendsBound ) ) { - switch ( extendsBound.getKind() ) { - case DECLARED: - // for example method: String method(? extends String) - // isSubType checks range [subtype, type], e.g. isSubtype [Object, String]==true - return visit( extendsBound, p ); - - case TYPEVAR: - // for example method: T method(? extends T) - // this can be done the directly by checking: ? extends String & Serializable - // this checks the part? - return isWithinBounds( p, getTypeParamFromCandidate( extendsBound ) ); - - default: - // does this situation occur? - return Boolean.FALSE; - } - } - - // check super bound - TypeMirror superBound = t.getSuperBound(); - if ( !isNullType( superBound ) ) { - switch ( superBound.getKind() ) { - case DECLARED: - // for example method: String method(? super String) - // to check super type, we can simply inverse the argument, but that would initially yield - // a result: T method(? super T) - if ( !isWithinBounds( p, typeParameter ) ) { - // this checks the part? - return Boolean.FALSE; - } - // now, it becomes a bit more hairy. We have the relation (? super T). From T we know that - // it is a subclass of String & Serializable. However, The Java Language Secification, - // Chapter 4.4, states that a bound is either: 'A type variable-', 'A class-' or 'An - // interface-' type followed by further interface types. So we must compare with the first - // argument in the Expression String & Serializable & ..., so, in this case String. - // to check super type, we can simply inverse the argument, but that would initially yield - // a result: bounds = tpe != null ? tpe.getBounds() : null; - if ( t != null && bounds != null ) { - for ( TypeMirror bound : bounds ) { - if ( !( bound.getKind() == TypeKind.DECLARED && typeUtils.isSubtypeErased( t, bound ) ) ) { + if ( !returnType.isVoid() ) { + boolean success = getCandidates( returnType, targetType, methodParCandidates ); + if ( !success ) { return false; } } return true; } - return false; + + /** + * @param aCandidateMethodType parameter type or return type from candidate method + * @param matchingType source type / target type to match + * @param candidates Map, keyed by the method generic parameter, with the candidates + * + * @return false no match or conflict has been found + */ + boolean getCandidates(Type aCandidateMethodType, Type matchingType, Map candidates ) { + + if ( !( aCandidateMethodType.isTypeVar() + || aCandidateMethodType.isArrayTypeVar() + || aCandidateMethodType.isWildCardBoundByTypeVar() + || hasGenericTypeParameters( aCandidateMethodType ) ) ) { + // the typeFromCandidateMethod is not a generic (parameterized) type + return true; + } + + for ( Type mthdParType : candidateMethod.getTypeParameters() ) { + + // typeFromCandidateMethod itself is a generic type, e.g. String method( T par ); + // typeFromCandidateMethod is a generic arrayType e.g. String method( T[] par ); + // typeFromCandidateMethod is embedded in another type e.g. String method( Callable par ); + // typeFromCandidateMethod is a wildcard, bounded by a typeVar + // e.g. String method( List in ) + + Type.ResolvedPair resolved = mthdParType.resolveParameterToType( matchingType, aCandidateMethodType ); + if ( resolved == null ) { + // cannot find a candidate type, but should have since the typeFromCandidateMethod had parameters + // to be resolved + return !hasGenericTypeParameters( aCandidateMethodType ); + } + + // resolved something at this point, a candidate can be fetched or created + TypeVarCandidate typeVarCandidate; + if ( candidates.containsKey( mthdParType ) ) { + typeVarCandidate = candidates.get( mthdParType ); + } + else { + // add a new type + typeVarCandidate = new TypeVarCandidate( ); + candidates.put( mthdParType, typeVarCandidate ); + } + + // check what we've resolved + if ( resolved.getParameter().isTypeVar() ) { + // it might be already set, but we just checked if its an equivalent type + if ( typeVarCandidate.match == null ) { + typeVarCandidate.match = resolved.getMatch(); + if ( typeVarCandidate.match == null) { + return false; + } + typeVarCandidate.pairs.add( resolved ); + } + else if ( !areEquivalent( resolved.getMatch(), typeVarCandidate.match ) ) { + // type has been resolved twice, but with a different candidate (conflict) + return false; + } + + } + else if ( resolved.getParameter().isArrayTypeVar() + && resolved.getParameter().getComponentType().isAssignableTo( mthdParType ) ) { + // e.g. T map( List in ), the match for T should be assignable + // to the parameter T extends Number + typeVarCandidate.pairs.add( resolved ); + } + else if ( resolved.getParameter().isWildCardBoundByTypeVar() + && resolved.getParameter().getTypeBound().isAssignableTo( mthdParType ) ) { + // e.g. T map( List in ), the match for ? super T should be assignable + // to the parameter T extends Number + typeVarCandidate.pairs.add( resolved ); + } + else { + // none of the above + return false; + } + } + return true; + } + + /** + * Checks whether all found candidates are within the bounds of the method type var. For instance + * @ U map( T in ). Note that only the relation between the + * match for U and Callable are checked. Not the correct parameter. + * + * @param methodParCandidates + * + * @return true when all within bounds. + */ + private boolean candidatesWithinBounds(Map methodParCandidates ) { + for ( Map.Entry entry : methodParCandidates.entrySet() ) { + Type bound = entry.getKey().getTypeBound(); + if ( bound != null ) { + for ( Type.ResolvedPair pair : entry.getValue().pairs ) { + if ( entry.getKey().hasUpperBound() ) { + if ( !pair.getMatch().asRawType().isAssignableTo( bound.asRawType() ) ) { + return false; + } + } + else { + // lower bound + if ( !bound.asRawType().isAssignableTo( pair.getMatch().asRawType() ) ) { + return false; + } + } + } + } + } + return true; + } + + private boolean hasGenericTypeParameters(Type typeFromCandidateMethod) { + for ( Type typeParam : typeFromCandidateMethod.getTypeParameters() ) { + if ( typeParam.isTypeVar() || typeParam.isWildCardBoundByTypeVar() || typeParam.isArrayTypeVar() ) { + return true; + } + else { + if ( hasGenericTypeParameters( typeParam ) ) { + return true; + } + } + } + return false; + } + + private Type resolve( Type typeFromCandidateMethod, Map pairs ) { + if ( typeFromCandidateMethod.isTypeVar() || typeFromCandidateMethod.isArrayTypeVar() ) { + return pairs.get( typeFromCandidateMethod ); + } + else if ( hasGenericTypeParameters( typeFromCandidateMethod ) ) { + TypeMirror[] typeArgs = new TypeMirror[ typeFromCandidateMethod.getTypeParameters().size() ]; + for ( int i = 0; i < typeFromCandidateMethod.getTypeParameters().size(); i++ ) { + Type typeFromCandidateMethodTypeParameter = typeFromCandidateMethod.getTypeParameters().get( i ); + if ( hasGenericTypeParameters( typeFromCandidateMethodTypeParameter ) ) { + // nested type var, lets resolve some more (recur) + Type matchingType = resolve( typeFromCandidateMethodTypeParameter, pairs ); + if ( matchingType == null ) { + // something went wrong + return null; + } + typeArgs[i] = matchingType.getTypeMirror(); + } + else if ( typeFromCandidateMethodTypeParameter.isWildCardBoundByTypeVar() + || typeFromCandidateMethodTypeParameter.isTypeVar() + || typeFromCandidateMethodTypeParameter.isArrayTypeVar() + ) { + Type matchingType = pairs.get( typeFromCandidateMethodTypeParameter ); + if ( matchingType == null ) { + // something went wrong + return null; + } + typeArgs[i] = matchingType.getTypeMirror(); + } + else { + // it is not a type var (e.g. Map ), String is not a type var + typeArgs[i] = typeFromCandidateMethodTypeParameter.getTypeMirror(); + } + } + DeclaredType typeArg = typeUtils.getDeclaredType( typeFromCandidateMethod.getTypeElement(), typeArgs ); + return typeFactory.getType( typeArg ); + } + else { + // its not a type var or generic parameterized (e.g. just a plain type) + return typeFromCandidateMethod; + } + } + + boolean areEquivalent( Type a, Type b ) { + if ( a == null || b == null ) { + return false; + } + return a.getBoxedEquivalent().equals( b.getBoxedEquivalent() ); + } } + + private static class TypeVarCandidate { + + private Type match; + private List pairs = new ArrayList<>(); + + } + } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index bac1346d9..83898d4b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -11,6 +11,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import org.mapstruct.ap.internal.util.TypeUtils; @@ -56,6 +57,7 @@ public class SourceMethod implements Method { private final List sourceParameters; private final List contextParameters; private final ParameterProvidedMethods contextProvidedMethods; + private final List typeParameters; private List parameterNames; @@ -89,6 +91,8 @@ public class SourceMethod implements Method { private List valueMappings; private EnumMappingOptions enumMappingOptions; private ParameterProvidedMethods contextProvidedMethods; + private List typeParameters; + private boolean verboseLogging; public Builder setDeclaringMapper(Type declaringMapper) { @@ -197,6 +201,12 @@ public class SourceMethod implements Method { valueMappings ); + this.typeParameters = this.executable.getTypeParameters() + .stream() + .map( Element::asType ) + .map( typeFactory::getType ) + .collect( Collectors.toList() ); + return new SourceMethod( this, mappingMethodOptions ); } } @@ -214,6 +224,7 @@ public class SourceMethod implements Method { this.sourceParameters = Parameter.getSourceParameters( parameters ); this.contextParameters = Parameter.getContextParameters( parameters ); this.contextProvidedMethods = builder.contextProvidedMethods; + this.typeParameters = builder.typeParameters; this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); @@ -533,6 +544,11 @@ public class SourceMethod implements Method { return hasObjectFactoryAnnotation; } + @Override + public List getTypeParameters() { + return this.typeParameters; + } + @Override public String describe() { if ( verboseLogging ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java index 7854534ed..e54a8ea4b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/builtin/BuiltInMethod.java @@ -67,7 +67,7 @@ public abstract class BuiltInMethod implements Method { Type sourceType = first( sourceTypes ); - Type returnType = getReturnType().resolveTypeVarToType( sourceType, getParameter().getType() ); + Type returnType = getReturnType().resolveParameterToType( sourceType, getParameter().getType() ).getMatch(); if ( returnType == null ) { return false; } @@ -151,6 +151,11 @@ public abstract class BuiltInMethod implements Method { return null; } + @Override + public List getTypeParameters() { + return Collections.emptyList(); + } + /** * hashCode based on class * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java index be729220c..0e2477402 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java @@ -30,9 +30,11 @@ public class CreateOrUpdateSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) { return methods; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java index 4e5273122..41d37e8b6 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/FactoryParameterSelector.java @@ -22,10 +22,10 @@ public class FactoryParameterSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - ListsourceTypes, - Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, Type returnType, + SelectionCriteria criteria) { if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) { return methods; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java index 3866857bb..a624c1acc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/InheritanceSelector.java @@ -23,10 +23,10 @@ public class InheritanceSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, Type returnType, + SelectionCriteria criteria) { if ( sourceTypes.size() != 1 ) { return methods; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java index c4a029da9..7e7925ca0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java @@ -21,9 +21,11 @@ public class MethodFamilySelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, - Type targetType, SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { List> result = new ArrayList<>( methods.size() ); for ( SelectedMethod method : methods ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java index 587a37a7f..86d35bb3a 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelector.java @@ -26,12 +26,15 @@ interface MethodSelector { * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out * @param candidates list of available methods * @param sourceTypes parameter type(s) that should be matched - * @param targetType result type that should be matched + * @param mappingTargetType mappingTargetType that should be matched + * @param returnType return type that should be matched * @param criteria criteria used in the selection process * @return list of methods that passes the matching process */ List> getMatchingMethods(Method mappingMethod, - List> candidates, - List sourceTypes, - Type targetType, SelectionCriteria criteria); + List> candidates, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java index 74fedcb75..b88de4f22 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodSelectors.java @@ -8,13 +8,13 @@ package org.mapstruct.ap.internal.model.source.selector; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import org.mapstruct.ap.internal.util.ElementUtils; -import org.mapstruct.ap.internal.util.TypeUtils; 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.util.ElementUtils; import org.mapstruct.ap.internal.util.FormattingMessager; +import org.mapstruct.ap.internal.util.TypeUtils; /** * Applies all known {@link MethodSelector}s in order. @@ -45,13 +45,17 @@ public class MethodSelectors { * @param mappingMethod mapping method, defined in Mapper for which this selection is carried out * @param methods list of available methods * @param sourceTypes parameter type(s) that should be matched - * @param targetType return type that should be matched + * @param mappingTargetType the mapping target type that should be matched + * @param returnType return type that should be matched * @param criteria criteria used in the selection process * @return list of methods that passes the matching process */ - public List> getMatchingMethods(Method mappingMethod, List methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + public List> getMatchingMethods(Method mappingMethod, + List methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { List> candidates = new ArrayList<>( methods.size() ); for ( T method : methods ) { @@ -63,7 +67,8 @@ public class MethodSelectors { mappingMethod, candidates, sourceTypes, - targetType, + mappingTargetType, + returnType, criteria ); } return candidates; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java index bca471f9c..210e462e3 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/QualifierSelector.java @@ -50,9 +50,11 @@ public class QualifierSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { int numberOfQualifiersToMatch = 0; diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java index ad1a5d47b..38a907aed 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TargetTypeSelector.java @@ -32,9 +32,11 @@ public class TargetTypeSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType(); if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index c00a90760..3d0bd4b9b 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -39,9 +39,11 @@ public class TypeSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, - List> methods, - List sourceTypes, Type targetType, - SelectionCriteria criteria) { + List> methods, + List sourceTypes, + Type mappingTargetType, + Type returnType, + SelectionCriteria criteria) { if ( methods.isEmpty() ) { return methods; @@ -54,12 +56,16 @@ public class TypeSelector implements MethodSelector { // if no source types are given, we have a factory or lifecycle method availableBindings = getAvailableParameterBindingsFromMethod( mappingMethod, - targetType, + mappingTargetType, criteria.getSourceRHS() ); } else { - availableBindings = getAvailableParameterBindingsFromSourceTypes( sourceTypes, targetType, mappingMethod ); + availableBindings = getAvailableParameterBindingsFromSourceTypes( + sourceTypes, + mappingTargetType, + mappingMethod + ); } for ( SelectedMethod method : methods ) { @@ -68,7 +74,7 @@ public class TypeSelector implements MethodSelector { if ( parameterBindingPermutations != null ) { SelectedMethod matchingMethod = - getMatchingParameterBinding( targetType, mappingMethod, method, parameterBindingPermutations ); + getMatchingParameterBinding( returnType, mappingMethod, method, parameterBindingPermutations ); if ( matchingMethod != null ) { result.add( matchingMethod ); @@ -143,7 +149,7 @@ public class TypeSelector implements MethodSelector { } } - private SelectedMethod getMatchingParameterBinding(Type targetType, + private SelectedMethod getMatchingParameterBinding(Type returnType, Method mappingMethod, SelectedMethod selectedMethodInfo, List> parameterAssignmentVariants) { @@ -155,7 +161,7 @@ public class TypeSelector implements MethodSelector { // remove all assignment variants that doesn't match the types from the method matchingParameterAssignmentVariants.removeIf( parameterAssignments -> - !selectedMethod.matches( extractTypes( parameterAssignments ), targetType ) + !selectedMethod.matches( extractTypes( parameterAssignments ), returnType ) ); if ( matchingParameterAssignmentVariants.isEmpty() ) { diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java index ecf665799..7bdae0b77 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/XmlElementDeclSelector.java @@ -46,7 +46,9 @@ public class XmlElementDeclSelector implements MethodSelector { @Override public List> getMatchingMethods(Method mappingMethod, List> methods, - List sourceTypes, Type targetType, + List sourceTypes, + Type mappingTargetType, + Type returnType, SelectionCriteria criteria) { List> nameMatches = new ArrayList<>(); diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index 5985d735c..b959a15dc 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -513,14 +513,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.common.Type" --> <@compress single_line=true> - <#if wildCardExtendsBound> + <#if hasExtendsBound()> ? extends <@includeModel object=typeBound /> - <#elseif wildCardSuperBound> + <#elseif hasSuperBound()> ? super <@includeModel object=typeBound /> <#else> <#if ext.asVarArgs!false>${createReferenceName()?remove_ending("[]")}...<#else>${createReferenceName()}<#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java index 74f692373..4b77eb2c3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/Issue1482Test.java @@ -20,8 +20,6 @@ import static org.assertj.core.api.Assertions.assertThat; Source2.class, Target.class, SourceEnum.class, - SourceTargetMapper.class, - TargetSourceMapper.class, BigDecimalWrapper.class, ValueWrapper.class }) @@ -30,6 +28,7 @@ import static org.assertj.core.api.Assertions.assertThat; public class Issue1482Test { @Test + @WithClasses( SourceTargetMapper.class ) public void testForward() { Source source = new Source(); @@ -45,6 +44,7 @@ public class Issue1482Test { } @Test + @WithClasses( TargetSourceMapper.class ) public void testReverse() { Target target = new Target(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java index d589927a1..401d0c42a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/bugs/_1482/TargetSourceMapper.java @@ -20,9 +20,9 @@ public abstract class TargetSourceMapper { @Mapping(target = "wrapper", source = "bigDecimal") abstract Source2 map(Target target); - protected > Enum map(String in, @TargetType Classclz ) { + protected > T map(String in, @TargetType Classclz ) { if ( clz.isAssignableFrom( SourceEnum.class )) { - return (Enum) SourceEnum.valueOf( in ); + return (T) SourceEnum.valueOf( in ); } return null; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java index 1a07dd1f7..1782ebdb5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/SourceTargetMapper.java @@ -37,8 +37,6 @@ public abstract class SourceTargetMapper { @InheritConfiguration public abstract Target sourceToTargetTwoArg(Source source, @MappingTarget Target target); - public abstract Set integerSetToStringSet(Set integers); - @InheritInverseConfiguration public abstract Set stringSetToIntegerSet(Set strings); diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ConversionTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ConversionTest.java index 9d17f1725..2e86c9605 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ConversionTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/ConversionTest.java @@ -5,6 +5,8 @@ */ package org.mapstruct.ap.test.selection.generics; +import static org.assertj.core.api.Assertions.assertThat; + import java.math.BigDecimal; import org.junit.Test; @@ -17,8 +19,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; -import static org.assertj.core.api.Assertions.assertThat; - /** * Tests for the invocation of generic methods for mapping bean properties. * @@ -39,7 +39,6 @@ public class ConversionTest { // setup used types TypeB typeB = new TypeB(); - TypeC typeC = new TypeC(); // setup source Source source = new Source(); @@ -51,10 +50,7 @@ public class ConversionTest { source.setFooNested( new Wrapper<>( new Wrapper<>( new BigDecimal( 5 ) ) ) ); source.setFooUpperBoundCorrect( new UpperBoundWrapper<>( typeB ) ); source.setFooWildCardExtendsString( new WildCardExtendsWrapper<>( "test3" ) ); - source.setFooWildCardExtendsTypeCCorrect( new WildCardExtendsWrapper<>( typeC ) ); - source.setFooWildCardExtendsTypeBCorrect( new WildCardExtendsWrapper<>( typeB ) ); source.setFooWildCardSuperString( new WildCardSuperWrapper<>( "test4" ) ); - source.setFooWildCardExtendsMBTypeCCorrect( new WildCardExtendsMBWrapper<>( typeC ) ); source.setFooWildCardSuperTypeBCorrect( new WildCardSuperWrapper<>( typeB ) ); // define wrapper @@ -71,12 +67,8 @@ public class ConversionTest { assertThat( target.getFooNested() ).isEqualTo( new BigDecimal( 5 ) ); assertThat( target.getFooUpperBoundCorrect() ).isEqualTo( typeB ); assertThat( target.getFooWildCardExtendsString() ).isEqualTo( "test3" ); - assertThat( target.getFooWildCardExtendsTypeCCorrect() ).isEqualTo( typeC ); - assertThat( target.getFooWildCardExtendsTypeBCorrect() ).isEqualTo( typeB ); assertThat( target.getFooWildCardSuperString() ).isEqualTo( "test4" ); - assertThat( target.getFooWildCardExtendsMBTypeCCorrect() ).isEqualTo( typeC ); assertThat( target.getFooWildCardSuperTypeBCorrect() ).isEqualTo( typeB ); - } @Test @@ -140,20 +132,6 @@ public class ConversionTest { public void shouldFailOnSuperBounds1() { } - @Test - @WithClasses({ ErroneousSource5.class, ErroneousTarget5.class, ErroneousSourceTargetMapper5.class }) - @ExpectedCompilationOutcome(value = CompilationResult.FAILED, - diagnostics = { - @Diagnostic(type = ErroneousSourceTargetMapper5.class, - kind = javax.tools.Diagnostic.Kind.ERROR, - line = 16, - message = "No target bean properties found: can't map property \"WildCardSuperWrapper " + - "fooWildCardSuperTypeCFailure\" to \"TypeC fooWildCardSuperTypeCFailure\". " + - "Consider to declare/implement a mapping method: \"TypeC map(WildCardSuperWrapper value)\".") - }) - public void shouldFailOnSuperBounds2() { - } - @Test @WithClasses({ ErroneousSource6.class, diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericTypeMapper.java index 33b93473b..4dcd03386 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericTypeMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/GenericTypeMapper.java @@ -5,8 +5,6 @@ */ package org.mapstruct.ap.test.selection.generics; -import java.io.Serializable; - public class GenericTypeMapper { public T getWrapped(Wrapper source) { @@ -53,7 +51,4 @@ public class GenericTypeMapper { return (String) t.getWrapped(); } - public T getWildCardExtendsMBType(WildCardExtendsMBWrapper t) { - return t.getWrapped(); - } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Source.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Source.java index ce9947650..28c724e9a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Source.java @@ -17,10 +17,7 @@ public class Source { private Wrapper> fooNested; private UpperBoundWrapper fooUpperBoundCorrect; private WildCardExtendsWrapper fooWildCardExtendsString; - private WildCardExtendsWrapper fooWildCardExtendsTypeCCorrect; - private WildCardExtendsWrapper fooWildCardExtendsTypeBCorrect; private WildCardSuperWrapper fooWildCardSuperString; - private WildCardExtendsMBWrapper fooWildCardExtendsMBTypeCCorrect; private WildCardSuperWrapper fooWildCardSuperTypeBCorrect; public Wrapper getFooInteger() { @@ -87,22 +84,6 @@ public class Source { this.fooWildCardExtendsString = fooWildCardExtendsString; } - public void setFooWildCardExtendsTypeCCorrect(WildCardExtendsWrapper fooWildCardExtendsTypeCCorrect) { - this.fooWildCardExtendsTypeCCorrect = fooWildCardExtendsTypeCCorrect; - } - - public WildCardExtendsWrapper getFooWildCardExtendsTypeCCorrect() { - return fooWildCardExtendsTypeCCorrect; - } - - public WildCardExtendsWrapper getFooWildCardExtendsTypeBCorrect() { - return fooWildCardExtendsTypeBCorrect; - } - - public void setFooWildCardExtendsTypeBCorrect(WildCardExtendsWrapper fooWildCardExtendsTypeBCorrect) { - this.fooWildCardExtendsTypeBCorrect = fooWildCardExtendsTypeBCorrect; - } - public WildCardSuperWrapper getFooWildCardSuperString() { return fooWildCardSuperString; } @@ -111,14 +92,6 @@ public class Source { this.fooWildCardSuperString = fooWildCardSuperString; } - public WildCardExtendsMBWrapper getFooWildCardExtendsMBTypeCCorrect() { - return fooWildCardExtendsMBTypeCCorrect; - } - - public void setFooWildCardExtendsMBTypeCCorrect(WildCardExtendsMBWrapper fooWildCardExtendsMBTypeCCorrect) { - this.fooWildCardExtendsMBTypeCCorrect = fooWildCardExtendsMBTypeCCorrect; - } - public WildCardSuperWrapper getFooWildCardSuperTypeBCorrect() { return fooWildCardSuperTypeBCorrect; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Target.java b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Target.java index 85d672ca9..616935674 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/generics/Target.java @@ -17,10 +17,7 @@ public class Target { private BigDecimal fooNested; private TypeB fooUpperBoundCorrect; private String fooWildCardExtendsString; - private TypeC fooWildCardExtendsTypeCCorrect; - private TypeB fooWildCardExtendsTypeBCorrect; private String fooWildCardSuperString; - private TypeC fooWildCardExtendsMBTypeCCorrect; private TypeB fooWildCardSuperTypeBCorrect; public Integer getFooInteger() { @@ -87,22 +84,6 @@ public class Target { this.fooWildCardExtendsString = fooWildCardExtendsString; } - public TypeC getFooWildCardExtendsTypeCCorrect() { - return fooWildCardExtendsTypeCCorrect; - } - - public void setFooWildCardExtendsTypeCCorrect(TypeC fooWildCardExtendsTypeCCorrect) { - this.fooWildCardExtendsTypeCCorrect = fooWildCardExtendsTypeCCorrect; - } - - public TypeB getFooWildCardExtendsTypeBCorrect() { - return fooWildCardExtendsTypeBCorrect; - } - - public void setFooWildCardExtendsTypeBCorrect(TypeB fooWildCardExtendsTypeBCorrect) { - this.fooWildCardExtendsTypeBCorrect = fooWildCardExtendsTypeBCorrect; - } - public String getFooWildCardSuperString() { return fooWildCardSuperString; } @@ -111,14 +92,6 @@ public class Target { this.fooWildCardSuperString = fooWildCardSuperString; } - public TypeC getFooWildCardExtendsMBTypeCCorrect() { - return fooWildCardExtendsMBTypeCCorrect; - } - - public void setFooWildCardExtendsMBTypeCCorrect(TypeC fooWildCardExtendsMBTypeCCorrect) { - this.fooWildCardExtendsMBTypeCCorrect = fooWildCardExtendsMBTypeCCorrect; - } - public TypeB getFooWildCardSuperTypeBCorrect() { return fooWildCardSuperTypeBCorrect; } diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/BothParameterizedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/BothParameterizedMapper.java new file mode 100644 index 000000000..1c0ddfdc5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/BothParameterizedMapper.java @@ -0,0 +1,75 @@ +/* + * 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.methodgenerics.array; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface BothParameterizedMapper { + + BothParameterizedMapper INSTANCE = Mappers.getMapper( BothParameterizedMapper.class ); + + Target sourceToTarget(Source source); + + default GenericTargetWrapper map(GenericSourceWrapper in ) { + return new GenericTargetWrapper<>( in.getWrapped() ); + } + + class Source { + + private final GenericSourceWrapper prop; + + public Source(GenericSourceWrapper prop) { + this.prop = prop; + } + + public GenericSourceWrapper getProp() { + return prop; + } + + } + + class Target { + + private GenericTargetWrapper prop; + + public GenericTargetWrapper getProp() { + return prop; + } + + public void setProp(GenericTargetWrapper prop) { + this.prop = prop; + } + } + + class GenericTargetWrapper { + private final T wrapped; + + public GenericTargetWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } + + class GenericSourceWrapper { + private final T wrapped; + + public GenericSourceWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/GenericArrayTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/GenericArrayTest.java new file mode 100644 index 000000000..0b6dbc113 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/GenericArrayTest.java @@ -0,0 +1,64 @@ +/* + * 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.methodgenerics.array; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class GenericArrayTest { + + @Test + @WithClasses( ReturnTypeIsTypeVarArrayMapper.class ) + public void testGenericReturnTypeVar() { + + ReturnTypeIsTypeVarArrayMapper.GenericWrapper wrapper = + new ReturnTypeIsTypeVarArrayMapper.GenericWrapper<>( "test" ); + ReturnTypeIsTypeVarArrayMapper.Source source = new ReturnTypeIsTypeVarArrayMapper.Source( wrapper ); + + ReturnTypeIsTypeVarArrayMapper.Target target = ReturnTypeIsTypeVarArrayMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).containsExactly( "test" ); + } + + @Test + @WithClasses( SourceTypeIsTypeVarArrayMapper.class ) + public void testGenericSourceTypeVar() { + + SourceTypeIsTypeVarArrayMapper.Source source = + new SourceTypeIsTypeVarArrayMapper.Source( new String[] { "test" } ); + SourceTypeIsTypeVarArrayMapper.Target target = SourceTypeIsTypeVarArrayMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getWrapped() ).isEqualTo( "test" ); + + } + + @Test + @WithClasses( BothParameterizedMapper.class ) + public void testBothParameterized() { + + BothParameterizedMapper.GenericSourceWrapper wrapper = + new BothParameterizedMapper.GenericSourceWrapper<>( new String[] { "test" } ); + BothParameterizedMapper.Source source = new BothParameterizedMapper.Source( wrapper ); + BothParameterizedMapper.Target target = BothParameterizedMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getWrapped() ).containsExactly( "test" ); + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/ReturnTypeIsTypeVarArrayMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/ReturnTypeIsTypeVarArrayMapper.java new file mode 100644 index 000000000..76751f2cc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/ReturnTypeIsTypeVarArrayMapper.java @@ -0,0 +1,71 @@ +/* + * 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.methodgenerics.array; + +import java.lang.reflect.Array; +import java.util.Collections; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ReturnTypeIsTypeVarArrayMapper { + + ReturnTypeIsTypeVarArrayMapper INSTANCE = Mappers.getMapper( ReturnTypeIsTypeVarArrayMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings( "unchecked" ) + default T[] map(GenericWrapper in) { + return Collections.singletonList( in.getWrapped() ) + .toArray( (T[]) Array.newInstance( in.getWrapped().getClass(), 1 ) ); + } + + class Source { + + private GenericWrapper prop; + + public Source(GenericWrapper prop) { + this.prop = prop; + } + + public GenericWrapper getProp() { + return prop; + } + + public void setProp(GenericWrapper prop) { + this.prop = prop; + } + } + + class Target { + + private String[] prop; + + public String[] getProp() { + return prop; + } + + public void setProp(String[] prop) { + this.prop = prop; + } + } + + class GenericWrapper { + private final T wrapped; + + public GenericWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/SourceTypeIsTypeVarArrayMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/SourceTypeIsTypeVarArrayMapper.java new file mode 100644 index 000000000..4abeb4b59 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/array/SourceTypeIsTypeVarArrayMapper.java @@ -0,0 +1,67 @@ +/* + * 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.methodgenerics.array; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface SourceTypeIsTypeVarArrayMapper { + + SourceTypeIsTypeVarArrayMapper INSTANCE = Mappers.getMapper( SourceTypeIsTypeVarArrayMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings("unchecked") + default GenericWrapper map( T[] in ) { + if ( in.length > 0 ) { + return new GenericWrapper<>( in[0] ); + } + return null; + } + + class Source { + + private String[] prop; + + public Source(String[] prop) { + this.prop = prop; + } + + public String[] getProp() { + return prop; + } + + } + + class Target { + + private GenericWrapper prop; + + public GenericWrapper getProp() { + return prop; + } + + public void setProp(GenericWrapper prop) { + this.prop = prop; + } + } + + class GenericWrapper { + private final T wrapped; + + public GenericWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/BoundsTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/BoundsTest.java new file mode 100644 index 000000000..c181cfe0b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/BoundsTest.java @@ -0,0 +1,36 @@ +/* + * 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.methodgenerics.bounds; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith( AnnotationProcessorTestRunner.class ) +public class BoundsTest { + + @Test + @WithClasses( SourceTypeIsBoundedTypeVarMapper.class ) + public void testGenericSourceTypeVar() { + + SourceTypeIsBoundedTypeVarMapper.Source source = new SourceTypeIsBoundedTypeVarMapper.Source( "5", "test" ); + SourceTypeIsBoundedTypeVarMapper.Target target = + SourceTypeIsBoundedTypeVarMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp1() ).isEqualTo( 5L ); + assertThat( target.getProp2().getProp() ).isEqualTo( "test" ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/SourceTypeIsBoundedTypeVarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/SourceTypeIsBoundedTypeVarMapper.java new file mode 100644 index 000000000..4aa260a73 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/bounds/SourceTypeIsBoundedTypeVarMapper.java @@ -0,0 +1,87 @@ +/* + * 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.methodgenerics.bounds; + +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface SourceTypeIsBoundedTypeVarMapper { + + SourceTypeIsBoundedTypeVarMapper INSTANCE = Mappers.getMapper( SourceTypeIsBoundedTypeVarMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings( "unchecked" ) + default T map(String in, @TargetType Class clz) { + if ( clz == Nested.class ) { + return (T) new Nested( in ); + } + return null; + } + + class Source { + + private final String prop1; + private final String prop2; + + public Source(String prop1, String prop2) { + this.prop1 = prop1; + this.prop2 = prop2; + } + + public String getProp1() { + return prop1; + } + + public String getProp2() { + return prop2; + } + } + + class Target { + + private Long prop1; + private Nested prop2; + + public Long getProp1() { + return prop1; + } + + public void setProp1(Long prop1) { + this.prop1 = prop1; + } + + public Nested getProp2() { + return prop2; + } + + public void setProp2(Nested prop2) { + this.prop2 = prop2; + } + } + + class NestedBase { + } + + class Nested extends NestedBase { + + private String prop; + + public Nested(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/MultipleTypeVarTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/MultipleTypeVarTest.java new file mode 100644 index 000000000..b11162bb3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/MultipleTypeVarTest.java @@ -0,0 +1,73 @@ +/* + * 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.methodgenerics.multiple; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import java.util.Collections; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith( AnnotationProcessorTestRunner.class) +public class MultipleTypeVarTest { + + @Test + @WithClasses( ReturnTypeHasMultipleTypeVarOneGenericMapper.class ) + public void testGenericSourceTypeVarOneGeneric() { + + ReturnTypeHasMultipleTypeVarOneGenericMapper.Source src = + new ReturnTypeHasMultipleTypeVarOneGenericMapper.Source( 5L ); + ReturnTypeHasMultipleTypeVarOneGenericMapper.Target target = + ReturnTypeHasMultipleTypeVarOneGenericMapper.INSTANCE.toTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp() ).containsExactly( entry( "test", 5L ) ); + } + + @Test + @WithClasses( ReturnTypeHasMultipleTypeVarBothGenericMapper.class ) + public void testGenericReturnTypeVarBothGeneric() { + + ReturnTypeHasMultipleTypeVarBothGenericMapper.Pair pair + = new ReturnTypeHasMultipleTypeVarBothGenericMapper.Pair( "test", 5L ); + ReturnTypeHasMultipleTypeVarBothGenericMapper.Source src = + new ReturnTypeHasMultipleTypeVarBothGenericMapper.Source( pair ); + ReturnTypeHasMultipleTypeVarBothGenericMapper.Target target = + ReturnTypeHasMultipleTypeVarBothGenericMapper.INSTANCE.toTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp() ).containsExactly( entry( "test", 5L ) ); + } + + @Test + @WithClasses( SourceTypeHasMultipleTypeVarBothGenericMapper.class ) + public void testGenericSourceTypeVarBothGeneric() { + + Map map = Collections.singletonMap( "test", 5L ); + SourceTypeHasMultipleTypeVarBothGenericMapper.Source src = + new SourceTypeHasMultipleTypeVarBothGenericMapper.Source( map ); + SourceTypeHasMultipleTypeVarBothGenericMapper.Target target = + SourceTypeHasMultipleTypeVarBothGenericMapper.INSTANCE.toTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getFirst() ).isEqualTo( "test" ); + assertThat( target.getProp().getSecond() ).isEqualTo( 5L ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarBothGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarBothGenericMapper.java new file mode 100644 index 000000000..b31a190b6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarBothGenericMapper.java @@ -0,0 +1,76 @@ +/* + * 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.methodgenerics.multiple; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ReturnTypeHasMultipleTypeVarBothGenericMapper { + + ReturnTypeHasMultipleTypeVarBothGenericMapper INSTANCE = + Mappers.getMapper( ReturnTypeHasMultipleTypeVarBothGenericMapper.class ); + + Target toTarget(Source source); + + default HashMap toMap( Pair entry) { + HashMap result = new HashMap<>( ); + result.put( entry.first, entry.second ); + return result; + } + + class Source { + + private Pair prop; + + public Source(Pair prop) { + this.prop = prop; + } + + public Pair getProp() { + return prop; + } + } + + class Target { + + private Map prop; + + public Map getProp() { + return prop; + } + + public Target setProp(Map prop) { + this.prop = prop; + return this; + } + } + + class Pair { + private final T first; + private final U second; + + public Pair(T first, U second) { + this.first = first; + this.second = second; + } + + public T getFirst() { + return first; + } + + public U getSecond() { + return second; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarOneGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarOneGenericMapper.java new file mode 100644 index 000000000..f22741a87 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/ReturnTypeHasMultipleTypeVarOneGenericMapper.java @@ -0,0 +1,58 @@ +/* + * 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.methodgenerics.multiple; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ReturnTypeHasMultipleTypeVarOneGenericMapper { + + ReturnTypeHasMultipleTypeVarOneGenericMapper INSTANCE = + Mappers.getMapper( ReturnTypeHasMultipleTypeVarOneGenericMapper.class ); + + Target toTarget(Source source); + + default HashMap toMap( T entry) { + HashMap result = new HashMap<>( ); + result.put( "test", entry ); + return result; + } + + class Source { + + private Long prop; + + public Source(Long prop) { + this.prop = prop; + } + + public Long getProp() { + return prop; + } + } + + class Target { + + private Map prop; + + public Map getProp() { + return prop; + } + + public Target setProp(Map prop) { + this.prop = prop; + return this; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/SourceTypeHasMultipleTypeVarBothGenericMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/SourceTypeHasMultipleTypeVarBothGenericMapper.java new file mode 100644 index 000000000..09c427fd0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/multiple/SourceTypeHasMultipleTypeVarBothGenericMapper.java @@ -0,0 +1,86 @@ +/* + * 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.methodgenerics.multiple; + +import java.util.HashMap; +import java.util.Map; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface SourceTypeHasMultipleTypeVarBothGenericMapper { + + SourceTypeHasMultipleTypeVarBothGenericMapper INSTANCE = + Mappers.getMapper( SourceTypeHasMultipleTypeVarBothGenericMapper.class ); + + Target toTarget(Source source); + + default HashMap toMap( Pair entry) { + HashMap result = new HashMap<>( ); + result.put( entry.first, entry.second ); + return result; + } + + default Pair toPair( Map map) { + if ( !map.isEmpty() ) { + Map.Entry firstEntry = map.entrySet().iterator().next(); + return new Pair<>( firstEntry.getKey(), firstEntry.getValue() ); + } + return null; + } + + class Source { + + private final Map prop; + + public Source(Map prop) { + this.prop = prop; + } + + public Map getProp() { + return prop; + } + + } + + class Target { + + private Pair prop; + + public Target(Pair prop) { + this.prop = prop; + } + + public Pair getProp() { + return prop; + } + } + + class Pair { + + private final T first; + private final U second; + + public Pair(T first, U second) { + this.first = first; + this.second = second; + } + + public T getFirst() { + return first; + } + + public U getSecond() { + return second; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/NestedGenericsTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/NestedGenericsTest.java new file mode 100644 index 000000000..c177fb45e --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/NestedGenericsTest.java @@ -0,0 +1,49 @@ +/* + * 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.methodgenerics.nestedgenerics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith( AnnotationProcessorTestRunner.class) +public class NestedGenericsTest { + + @Test + @WithClasses( ReturnTypeHasNestedTypeVarMapper.class ) + public void testGenericReturnTypeVar() { + + ReturnTypeHasNestedTypeVarMapper.Source source = new ReturnTypeHasNestedTypeVarMapper.Source("test" ); + ReturnTypeHasNestedTypeVarMapper.Target target = ReturnTypeHasNestedTypeVarMapper.INSTANCE.toTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).hasSize( 1 ); + assertThat( target.getProp().get( 0 ) ).contains( "test" ); + } + + @Test + @WithClasses( SourceTypeHasNestedTypeVarMapper.class ) + public void testGenericSourceTypeVar() { + + SourceTypeHasNestedTypeVarMapper.Source src = + new SourceTypeHasNestedTypeVarMapper.Source( Collections.singletonList( Collections.singleton( "test" ) ) ); + SourceTypeHasNestedTypeVarMapper.Target target = SourceTypeHasNestedTypeVarMapper.INSTANCE.toTarget( src ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "test" ); + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/ReturnTypeHasNestedTypeVarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/ReturnTypeHasNestedTypeVarMapper.java new file mode 100644 index 000000000..891058d88 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/ReturnTypeHasNestedTypeVarMapper.java @@ -0,0 +1,57 @@ +/* + * 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.methodgenerics.nestedgenerics; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ReturnTypeHasNestedTypeVarMapper { + + ReturnTypeHasNestedTypeVarMapper INSTANCE = Mappers.getMapper( ReturnTypeHasNestedTypeVarMapper.class ); + + Target toTarget(Source source); + + default List> wrapAsSetInList(T entry) { + return Collections.singletonList( Collections.singleton( entry ) ); + } + + class Source { + + private final String prop; + + public Source(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + + } + + class Target { + + private List> prop; + + public List> getProp() { + return prop; + } + + public Target setProp(List> prop) { + this.prop = prop; + return this; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/SourceTypeHasNestedTypeVarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/SourceTypeHasNestedTypeVarMapper.java new file mode 100644 index 000000000..faeb5fe93 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/nestedgenerics/SourceTypeHasNestedTypeVarMapper.java @@ -0,0 +1,58 @@ +/* + * 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.methodgenerics.nestedgenerics; + +import java.util.List; +import java.util.Set; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface SourceTypeHasNestedTypeVarMapper { + + SourceTypeHasNestedTypeVarMapper INSTANCE = Mappers.getMapper( SourceTypeHasNestedTypeVarMapper.class ); + + Target toTarget(Source source); + + default T unwrapToOneElement(List> listOfSet) { + if ( !listOfSet.isEmpty() && listOfSet.get( 0 ).iterator().hasNext() ) { + return listOfSet.get( 0 ).iterator().next(); + } + return null; + } + + class Source { + + private final List> prop; + + public Source(List> prop) { + this.prop = prop; + } + + public List> getProp() { + return prop; + } + + } + + class Target { + + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/BothParameterizedMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/BothParameterizedMapper.java new file mode 100644 index 000000000..16b09a274 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/BothParameterizedMapper.java @@ -0,0 +1,75 @@ +/* + * 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.methodgenerics.plain; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface BothParameterizedMapper { + + BothParameterizedMapper INSTANCE = Mappers.getMapper( BothParameterizedMapper.class ); + + Target sourceToTarget(Source source); + + default GenericTargetWrapper map(GenericSourceWrapper in ) { + return new GenericTargetWrapper<>( in.getWrapped() ); + } + + class Source { + + private final GenericSourceWrapper prop; + + public Source(GenericSourceWrapper prop) { + this.prop = prop; + } + + public GenericSourceWrapper getProp() { + return prop; + } + + } + + class Target { + + private GenericTargetWrapper prop; + + public GenericTargetWrapper getProp() { + return prop; + } + + public void setProp(GenericTargetWrapper prop) { + this.prop = prop; + } + } + + class GenericTargetWrapper { + private final T wrapped; + + public GenericTargetWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } + + class GenericSourceWrapper { + private final T wrapped; + + public GenericSourceWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/PlainTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/PlainTest.java new file mode 100644 index 000000000..8f7c87881 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/PlainTest.java @@ -0,0 +1,75 @@ +/* + * 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.methodgenerics.plain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.util.Collections; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class PlainTest { + + @Test + @WithClasses( ReturnTypeIsTypeVarMapper.class ) + public void testGenericReturnTypeVar() { + + ReturnTypeIsTypeVarMapper.Source source = + new ReturnTypeIsTypeVarMapper.Source( new ReturnTypeIsTypeVarMapper.GenericWrapper<>( "test" ) ); + ReturnTypeIsTypeVarMapper.Target target = ReturnTypeIsTypeVarMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "test" ); + } + + @Test + @WithClasses( SourceTypeIsTypeVarMapper.class ) + public void testGenericSourceTypeVar() { + + SourceTypeIsTypeVarMapper.Source source = new SourceTypeIsTypeVarMapper.Source( "test" ); + SourceTypeIsTypeVarMapper.Target target = SourceTypeIsTypeVarMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getWrapped() ).isEqualTo( "test" ); + + } + + @Test + @WithClasses( BothParameterizedMapper.class ) + public void testBothParameterized() { + + BothParameterizedMapper.Source source = + new BothParameterizedMapper.Source( new BothParameterizedMapper.GenericSourceWrapper<>( "test" ) ); + BothParameterizedMapper.Target target = BothParameterizedMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getWrapped() ).isEqualTo( "test" ); + + } + + @Test + @WithClasses( ReturnTypeIsRawTypeMapper.class ) + public void testRaw() { + + ReturnTypeIsRawTypeMapper.Source source = new ReturnTypeIsRawTypeMapper.Source( Collections.singleton( 5 ) ); + + ReturnTypeIsRawTypeMapper.Target target = ReturnTypeIsRawTypeMapper.INSTANCE.sourceToTarget( source ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().iterator().next() ).isEqualTo( "5" ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsRawTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsRawTypeMapper.java new file mode 100644 index 000000000..24af41648 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsRawTypeMapper.java @@ -0,0 +1,50 @@ +/* + * 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.methodgenerics.plain; + +import java.util.Set; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ReturnTypeIsRawTypeMapper { + + ReturnTypeIsRawTypeMapper INSTANCE = Mappers.getMapper( ReturnTypeIsRawTypeMapper.class ); + + Target sourceToTarget(Source source); + + Set selectMe(Set integers); + + Set doNotSelectMe(Set strings); + + class Source { + + private final Set prop; + + public Source(Set prop) { + this.prop = prop; + } + + public Set getProp() { + return prop; + } + } + + class Target { + + private Set prop; + + public Set getProp() { + return prop; + } + + public Target setProp(Set prop) { + this.prop = prop; + return this; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsTypeVarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsTypeVarMapper.java new file mode 100644 index 000000000..137f00ca6 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/ReturnTypeIsTypeVarMapper.java @@ -0,0 +1,64 @@ +/* + * 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.methodgenerics.plain; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface ReturnTypeIsTypeVarMapper { + + ReturnTypeIsTypeVarMapper INSTANCE = Mappers.getMapper( ReturnTypeIsTypeVarMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings("unchecked") + default T map(GenericWrapper in) { + return in.getWrapped(); + } + + class Source { + + private final GenericWrapper prop; + + public Source(GenericWrapper prop) { + this.prop = prop; + } + + public GenericWrapper getProp() { + return prop; + } + + } + + class Target { + + private String prop; + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } + + class GenericWrapper { + private final T wrapped; + + public GenericWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/SourceTypeIsTypeVarMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/SourceTypeIsTypeVarMapper.java new file mode 100644 index 000000000..3f2c2a50c --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/plain/SourceTypeIsTypeVarMapper.java @@ -0,0 +1,63 @@ +/* + * 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.methodgenerics.plain; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface SourceTypeIsTypeVarMapper { + + SourceTypeIsTypeVarMapper INSTANCE = Mappers.getMapper( SourceTypeIsTypeVarMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings("unchecked") + default GenericWrapper map( T in ) { + return new GenericWrapper<>( in ); + } + + class Source { + + private final String prop; + + public Source(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + } + + class Target { + + private GenericWrapper prop; + + public GenericWrapper getProp() { + return prop; + } + + public void setProp(GenericWrapper prop) { + this.prop = prop; + } + } + + class GenericWrapper { + private final T wrapped; + + public GenericWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/NestedTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/NestedTargetTypeMapper.java new file mode 100644 index 000000000..665683f65 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/NestedTargetTypeMapper.java @@ -0,0 +1,75 @@ +/* + * 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.methodgenerics.targettype; + +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface NestedTargetTypeMapper { + + NestedTargetTypeMapper INSTANCE = Mappers.getMapper( NestedTargetTypeMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings("unchecked") + default T map(String string, @TargetType Class clazz) { + if ( clazz == GenericWrapper.class ) { + return (T) new GenericWrapper<>( string ); + } + + return null; + } + + class Source { + + private String prop; + + public Source(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } + + class Target { + + private GenericWrapper prop; + + public GenericWrapper getProp() { + return prop; + } + + public void setProp(GenericWrapper prop) { + this.prop = prop; + } + } + + class BaseType { + } + + class GenericWrapper extends BaseType { + private final T wrapped; + + public GenericWrapper(T someType) { + this.wrapped = someType; + } + + public T getWrapped() { + return wrapped; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/PlainTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/PlainTargetTypeMapper.java new file mode 100644 index 000000000..15e27da82 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/PlainTargetTypeMapper.java @@ -0,0 +1,66 @@ +/* + * 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.methodgenerics.targettype; + +import java.math.BigDecimal; + +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Sjaak Derksen + */ +@Mapper +public interface PlainTargetTypeMapper { + + PlainTargetTypeMapper INSTANCE = Mappers.getMapper( PlainTargetTypeMapper.class ); + + Target sourceToTarget(Source source); + + @SuppressWarnings("unchecked") + default T map(String string, @TargetType Class clazz) { + if ( clazz == BigDecimal.class ) { + return (T) new BigDecimal( string ); + } + + return null; + } + + class Source { + + private String prop; + + public Source(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + + public void setProp(String prop) { + this.prop = prop; + } + } + + class Target { + + private BigDecimal prop; + + public BigDecimal getProp() { + return prop; + } + + public void setProp(BigDecimal prop) { + this.prop = prop; + } + } + + class BaseType { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/TargetTypeTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/TargetTypeTest.java new file mode 100644 index 000000000..47037ec6b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/targettype/TargetTypeTest.java @@ -0,0 +1,43 @@ +/* + * 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.methodgenerics.targettype; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class TargetTypeTest { + + @Test + @WithClasses( PlainTargetTypeMapper.class ) + public void testPlain() { + + PlainTargetTypeMapper.Target target = + PlainTargetTypeMapper.INSTANCE.sourceToTarget( new PlainTargetTypeMapper.Source( "15" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp().toPlainString() ).isEqualTo( "15" ); + } + + @Test + @WithClasses( NestedTargetTypeMapper.class ) + public void testNestedTypeVar() { + NestedTargetTypeMapper.Target target = + NestedTargetTypeMapper.INSTANCE.sourceToTarget( new NestedTargetTypeMapper.Source( "test" ) ); + + assertThat( target ).isNotNull(); + assertThat( target.getProp().getWrapped() ).isEqualTo( "test" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/IntersectionMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/IntersectionMapper.java new file mode 100644 index 000000000..376acd0d5 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/IntersectionMapper.java @@ -0,0 +1,77 @@ +/* + * 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.methodgenerics.wildcards; + +import java.io.Serializable; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface IntersectionMapper { + + IntersectionMapper INSTANCE = Mappers.getMapper( IntersectionMapper.class ); + + Target map( Source source); + + default T unwrap(Wrapper t) { + return t.getWrapped(); + } + + class Source { + + private final Wrapper prop; + + public Source(Wrapper prop) { + this.prop = prop; + } + + public Wrapper getProp() { + return prop; + } + + } + + class Wrapper { + + private final T wrapped; + + public Wrapper(T wrapped) { + this.wrapped = wrapped; + } + + public T getWrapped() { + return wrapped; + } + + } + + class Target { + + private TypeC prop; + + public TypeC getProp() { + return prop; + } + + public void setProp(TypeC prop) { + this.prop = prop; + } + } + + /** + * TypeC must intersect both TypeB & Serializable + */ + class TypeC extends TypeB implements Serializable { + } + + class TypeB extends TypeA { + } + + class TypeA { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/SourceWildCardExtendsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/SourceWildCardExtendsMapper.java new file mode 100644 index 000000000..24c8b7737 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/SourceWildCardExtendsMapper.java @@ -0,0 +1,87 @@ +/* + * 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.methodgenerics.wildcards; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceWildCardExtendsMapper { + + SourceWildCardExtendsMapper INSTANCE = Mappers.getMapper( SourceWildCardExtendsMapper.class ); + + Target map( Source source); + + default T unwrap(Wrapper t) { + return t.getWrapped(); + } + + class Source { + + private final Wrapper propB; + private final Wrapper propC; + + public Source(Wrapper propB, Wrapper propC) { + this.propB = propB; + this.propC = propC; + } + + public Wrapper getPropB() { + return propB; + } + + public Wrapper getPropC() { + return propC; + } + + } + + class Wrapper { + + private final T wrapped; + + public Wrapper(T wrapped) { + this.wrapped = wrapped; + } + + public T getWrapped() { + return wrapped; + } + + } + + class Target { + + private TypeB propB; + private TypeC propC; + + public TypeB getPropB() { + return propB; + } + + public void setPropB(TypeB propB) { + this.propB = propB; + } + + public TypeC getPropC() { + return propC; + } + + public void setPropC(TypeC propC) { + this.propC = propC; + } + } + + class TypeC extends TypeB { + } + + class TypeB extends TypeA { + } + + class TypeA { + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java new file mode 100644 index 000000000..1df99d4d9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/methodgenerics/wildcards/WildCardTest.java @@ -0,0 +1,62 @@ +/* + * 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.methodgenerics.wildcards; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; +import org.mapstruct.ap.testutil.runner.Compiler; +import org.mapstruct.ap.testutil.runner.DisabledOnCompiler; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class WildCardTest { + + @Test + @WithClasses( SourceWildCardExtendsMapper.class ) + public void testExtendsRelation() { + + // prepare source + SourceWildCardExtendsMapper.TypeB typeB = new SourceWildCardExtendsMapper.TypeB(); + SourceWildCardExtendsMapper.Wrapper wrapperB = new SourceWildCardExtendsMapper.Wrapper( typeB ); + SourceWildCardExtendsMapper.TypeC typeC = new SourceWildCardExtendsMapper.TypeC(); + SourceWildCardExtendsMapper.Wrapper wrapperC = new SourceWildCardExtendsMapper.Wrapper( typeC ); + SourceWildCardExtendsMapper.Source source = new SourceWildCardExtendsMapper.Source( wrapperB, wrapperC ); + + // action + SourceWildCardExtendsMapper.Target target = SourceWildCardExtendsMapper.INSTANCE.map( source ); + + // verify target + assertThat( target ).isNotNull(); + assertThat( target.getPropB() ).isEqualTo( typeB ); + assertThat( target.getPropC() ).isEqualTo( typeC ); + } + + @Test + @WithClasses( IntersectionMapper.class ) + // Eclipse does not handle intersection types correctly (TODO: worthwhile to investigate?) + @DisabledOnCompiler( Compiler.ECLIPSE ) + public void testIntersectionRelation() { + + // prepare source + IntersectionMapper.TypeC typeC = new IntersectionMapper.TypeC(); + IntersectionMapper.Wrapper wrapper = new IntersectionMapper.Wrapper( typeC ); + IntersectionMapper.Source source = new IntersectionMapper.Source( wrapper ); + + // action + IntersectionMapper.Target target = IntersectionMapper.INSTANCE.map( source ); + + // verify target + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( typeC ); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/SourceWildCardExtendsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/SourceWildCardExtendsMapper.java new file mode 100644 index 000000000..feb49267d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/SourceWildCardExtendsMapper.java @@ -0,0 +1,66 @@ +/* + * 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.typegenerics; + +import java.math.BigInteger; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceWildCardExtendsMapper { + + SourceWildCardExtendsMapper INSTANCE = Mappers.getMapper( SourceWildCardExtendsMapper.class ); + + Target map( Source source); + + default String unwrap(Wrapper t) { + return t.getWrapped().toString(); + } + + class Source { + + private final Wrapper prop; + + public Source(Wrapper prop) { + + this.prop = prop; + } + + public Wrapper getProp() { + return prop; + } + } + + class Wrapper { + + private final T wrapped; + + public Wrapper(T wrapped) { + this.wrapped = wrapped; + } + + public T getWrapped() { + return wrapped; + } + + } + + class Target { + + private String prop; + + public String getProp() { + return prop; + } + + public Target setProp(String prop) { + this.prop = prop; + return this; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/WildCardTest.java new file mode 100644 index 000000000..362e00331 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/typegenerics/WildCardTest.java @@ -0,0 +1,41 @@ +/* + * 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.typegenerics; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class WildCardTest { + + @Test + @WithClasses( SourceWildCardExtendsMapper.class ) + public void testWildCard() { + + // prepare source + SourceWildCardExtendsMapper.Wrapper wrapper = + new SourceWildCardExtendsMapper.Wrapper<>( new BigInteger( "5" ) ); + SourceWildCardExtendsMapper.Source source = new SourceWildCardExtendsMapper.Source( wrapper ); + + // action + SourceWildCardExtendsMapper.Target target = SourceWildCardExtendsMapper.INSTANCE.map( source ); + + // verify target + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "5" ); + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/ReturnTypeWildCardExtendsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/ReturnTypeWildCardExtendsMapper.java new file mode 100644 index 000000000..3e4366e06 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/ReturnTypeWildCardExtendsMapper.java @@ -0,0 +1,70 @@ +/* + * 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.wildcards; + +import java.math.BigInteger; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface ReturnTypeWildCardExtendsMapper { + + ReturnTypeWildCardExtendsMapper INSTANCE = Mappers.getMapper( ReturnTypeWildCardExtendsMapper.class ); + + Target map(Source source); + + default Wrapper wrap(String in) { + return new Wrapper<>( new BigInteger( in ) ); + } + + class Source { + + private String prop; + + public Source(String prop) { + this.prop = prop; + } + + public String getProp() { + return prop; + } + + public Source setProp(String prop) { + this.prop = prop; + return this; + } + } + + class Target { + + private Wrapper prop; + + public Target setProp(Wrapper prop) { + this.prop = prop; + return this; + } + + public Wrapper getProp() { + return prop; + } + } + + class Wrapper { + + private final T wrapped; + + public Wrapper(T wrapped) { + this.wrapped = wrapped; + } + + public T getWrapped() { + return wrapped; + } + + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/SourceWildCardExtendsMapper.java b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/SourceWildCardExtendsMapper.java new file mode 100644 index 000000000..c82ae3ccc --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/SourceWildCardExtendsMapper.java @@ -0,0 +1,66 @@ +/* + * 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.wildcards; + +import java.math.BigInteger; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +@Mapper +public interface SourceWildCardExtendsMapper { + + SourceWildCardExtendsMapper INSTANCE = Mappers.getMapper( SourceWildCardExtendsMapper.class ); + + Target map( Source source); + + default String unwrap(Wrapper t) { + return t.getWrapped().toString(); + } + + class Source { + + private final Wrapper prop; + + public Source(Wrapper prop) { + + this.prop = prop; + } + + public Wrapper getProp() { + return prop; + } + } + + class Wrapper { + + private final T wrapped; + + public Wrapper(T wrapped) { + this.wrapped = wrapped; + } + + public T getWrapped() { + return wrapped; + } + + } + + class Target { + + private String prop; + + public String getProp() { + return prop; + } + + public Target setProp(String prop) { + this.prop = prop; + return this; + } + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/WildCardTest.java b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/WildCardTest.java new file mode 100644 index 000000000..405a22320 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/selection/wildcards/WildCardTest.java @@ -0,0 +1,58 @@ +/* + * 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.wildcards; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.math.BigInteger; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mapstruct.ap.testutil.WithClasses; +import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner; + +/** + * @author Sjaak Derksen + * + */ +@RunWith(AnnotationProcessorTestRunner.class) +public class WildCardTest { + + @Test + @WithClasses( SourceWildCardExtendsMapper.class ) + public void testWildCardAsSourceType() { + + // prepare source + SourceWildCardExtendsMapper.Wrapper wrapper = + new SourceWildCardExtendsMapper.Wrapper<>( new BigInteger( "5" ) ); + SourceWildCardExtendsMapper.Source source = new SourceWildCardExtendsMapper.Source( wrapper ); + + // action + SourceWildCardExtendsMapper.Target target = SourceWildCardExtendsMapper.INSTANCE.map( source ); + + // verify target + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isEqualTo( "5" ); + } + + @Test + @WithClasses( ReturnTypeWildCardExtendsMapper.class ) + public void testWildCardAsReturnType() { + + // prepare source + ReturnTypeWildCardExtendsMapper.Source source = new ReturnTypeWildCardExtendsMapper.Source( "5" ); + + // action + ReturnTypeWildCardExtendsMapper.Target target = ReturnTypeWildCardExtendsMapper.INSTANCE.map( source ); + + // verify target + assertThat( target ).isNotNull(); + assertThat( target.getProp() ).isNotNull(); + assertThat( target.getProp().getWrapped() ).isEqualTo( BigInteger.valueOf( 5 ) ); + + } + +}