#2239 matching generics (#2320)

This commit is contained in:
Sjaak Derksen 2021-03-28 17:34:59 +02:00 committed by GitHub
parent e7f6813d9a
commit 1187e357c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 2683 additions and 521 deletions

View File

@ -340,6 +340,11 @@ public class ForgedMethod implements Method {
return basedOn.getOptions();
}
@Override
public List<Type> getTypeParameters() {
return Collections.emptyList();
}
@Override
public String describe() {
// the name of the forged method is never fully qualified, so no need to distinguish

View File

@ -128,6 +128,11 @@ public abstract class HelperMethod implements Method {
return false;
}
@Override
public List<Type> 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.

View File

@ -141,6 +141,7 @@ public final class LifecycleMethodResolver {
callbackMethods,
Collections.emptyList(),
targetType,
method.getReturnType(),
SelectionCriteria.forLifecycleMethods( selectionParameters ) );
return toLifecycleCallbackMethodRefs(

View File

@ -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:
* <ol>

View File

@ -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 ) {

View File

@ -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<Type> {
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<Type> {
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<Type> {
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<Type> {
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 super Number> 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 extends Number> 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<Type> {
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<Type> {
}
/**
* 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<Type> {
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> {
}
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<Type> {
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<Type> 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<Type> {
}
/**
* 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<String>
* parameterizedType: JAXBElement<T>
* result: String
*
* @return the matching declared type.
*
* this: T, T[] or ? extends T,
* declaredType: E.g. Callable<? extends T>
* parameterizedType: Callable<BigDecimal>
* 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<Type, Type> {
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<ResolvedPair, Type> {
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<ResolvedPair> 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<T> 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;
}
/**

View File

@ -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 super Number> T map (T in);
if ( typeVariableType.getLowerBound() != null ) {
return typeVariableType.getLowerBound();
}
}
return typeMirror;

View File

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

View File

@ -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<Type> sourceTypes, Type resultType) {
boolean matches(List<Type> sourceTypes, Type targetType) {
// check & collect generic types.
Map<TypeVariable, TypeMirror> 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<Type> sourceTypes;
private Type targetType;
GenericAnalyser(TypeFactory typeFactory, TypeUtils typeUtils, Method candidateMethod,
List<Type> 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<Type> candidateParTypes;
// check if all entries are in the bounds
for ( Map.Entry<TypeVariable, TypeMirror> 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<TypeVariable, TypeMirror> 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<Type, TypeVarCandidate> 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<Type, Type> 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, U extends Number> 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<Type, TypeVarCandidate> 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<TypeVariable, TypeMirror> 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<Boolean, TypeMirror> {
private final Assignability assignability;
private final Map<TypeVariable, TypeMirror> genericTypesMap;
private final TypeMatcher inverse;
TypeMatcher(Assignability assignability, Map<TypeVariable, TypeMirror> genericTypesMap) {
super( Boolean.FALSE ); // default value
this.assignability = assignability;
this.genericTypesMap = genericTypesMap;
this.inverse = new TypeMatcher( this, genericTypesMap );
}
TypeMatcher(TypeMatcher inverse, Map<TypeVariable, TypeMirror> 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<E> 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> T fromOptional(Optional<T> optional)
// T resolves to Collection<Integer>
// We know what T resolves to, so we should treat it as if the method signature was
// Collection<Integer> fromOptional(Optional<Collection<Integer> 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 extends String & Serializable> T method(? extends T)
// this can be done the directly by checking: ? extends String & Serializable
// this checks the part? <T extends String & Serializable>
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: <type, superType] (so type not included) so we need to check sameType also.
return typeUtils.isSubtypeErased( superBound, p ) || typeUtils.isSameType( p, superBound );
case TYPEVAR:
TypeParameterElement typeParameter = getTypeParamFromCandidate( superBound );
// for example method: <T extends String & Serializable> T method(? super T)
if ( !isWithinBounds( p, typeParameter ) ) {
// this checks the part? <T extends String & Serializable>
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: <type, superType] (so type not included) so we need to check sameType also.
TypeMirror superBoundAsDeclared = typeParameter.getBounds().get( 0 );
return ( typeUtils.isSubtypeErased( superBoundAsDeclared, p ) || typeUtils.isSameType(
p,
superBoundAsDeclared ) );
default:
// does this situation occur?
return Boolean.FALSE;
}
}
return Boolean.TRUE;
}
}
/**
* Looks through the list of type parameters of the candidate method for a match
*
* @param t type parameter to match
*
* @return matching type parameter
*/
private TypeParameterElement getTypeParamFromCandidate(TypeMirror t) {
for ( TypeParameterElement candidateTypeParam : candidateMethod.getExecutable().getTypeParameters() ) {
if ( typeUtils.isSameType( candidateTypeParam.asType(), t ) ) {
return candidateTypeParam;
}
}
return null;
}
/**
* checks whether a type t is in bounds of the typeParameter tpe
*
* @return true if within bounds
*/
private boolean isWithinBounds(TypeMirror t, TypeParameterElement tpe) {
List<? extends TypeMirror> 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<Type, TypeVarCandidate> 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. <T> String method( T par );
// typeFromCandidateMethod is a generic arrayType e.g. <T> String method( T[] par );
// typeFromCandidateMethod is embedded in another type e.g. <T> String method( Callable<T> par );
// typeFromCandidateMethod is a wildcard, bounded by a typeVar
// e.g. <T> String method( List<? extends T> 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 extends Number> T map( List<T[]> 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 extends Number> T map( List<? super T> 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
* @<code><T, U extends Callable<T> U map( T in )</code>. 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<Type, TypeVarCandidate> methodParCandidates ) {
for ( Map.Entry<Type, TypeVarCandidate> 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<Type, Type> 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, T> ), 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<Type.ResolvedPair> pairs = new ArrayList<>();
}
}

View File

@ -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<Parameter> sourceParameters;
private final List<Parameter> contextParameters;
private final ParameterProvidedMethods contextProvidedMethods;
private final List<Type> typeParameters;
private List<String> parameterNames;
@ -89,6 +91,8 @@ public class SourceMethod implements Method {
private List<ValueMappingOptions> valueMappings;
private EnumMappingOptions enumMappingOptions;
private ParameterProvidedMethods contextProvidedMethods;
private List<Type> 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<Type> getTypeParameters() {
return this.typeParameters;
}
@Override
public String describe() {
if ( verboseLogging ) {

View File

@ -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<Type> getTypeParameters() {
return Collections.emptyList();
}
/**
* hashCode based on class
*

View File

@ -30,9 +30,11 @@ public class CreateOrUpdateSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) {
return methods;

View File

@ -22,10 +22,10 @@ public class FactoryParameterSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type>sourceTypes,
Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType, Type returnType,
SelectionCriteria criteria) {
if ( !criteria.isObjectFactoryRequired() || methods.size() <= 1 ) {
return methods;
}

View File

@ -23,10 +23,10 @@ public class InheritanceSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType, Type returnType,
SelectionCriteria criteria) {
if ( sourceTypes.size() != 1 ) {
return methods;

View File

@ -21,9 +21,11 @@ public class MethodFamilySelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type targetType, SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> result = new ArrayList<>( methods.size() );
for ( SelectedMethod<T> method : methods ) {

View File

@ -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
*/
<T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> candidates,
List<Type> sourceTypes,
Type targetType, SelectionCriteria criteria);
List<SelectedMethod<T>> candidates,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria);
}

View File

@ -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 <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod, List<T> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<T> methods,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> 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;

View File

@ -50,9 +50,11 @@ public class QualifierSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
int numberOfQualifiersToMatch = 0;

View File

@ -32,9 +32,11 @@ public class TargetTypeSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
TypeMirror qualifyingTypeMirror = criteria.getQualifyingResultType();
if ( qualifyingTypeMirror != null && !criteria.isLifecycleCallbackRequired() ) {

View File

@ -39,9 +39,11 @@ public class TypeSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> methods,
List<Type> 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<T> method : methods ) {
@ -68,7 +74,7 @@ public class TypeSelector implements MethodSelector {
if ( parameterBindingPermutations != null ) {
SelectedMethod<T> 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 <T extends Method> SelectedMethod<T> getMatchingParameterBinding(Type targetType,
private <T extends Method> SelectedMethod<T> getMatchingParameterBinding(Type returnType,
Method mappingMethod, SelectedMethod<T> selectedMethodInfo,
List<List<ParameterBinding>> 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() ) {

View File

@ -46,7 +46,9 @@ public class XmlElementDeclSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
List<SelectedMethod<T>> methods,
List<Type> sourceTypes, Type targetType,
List<Type> sourceTypes,
Type mappingTargetType,
Type returnType,
SelectionCriteria criteria) {
List<SelectedMethod<T>> nameMatches = new ArrayList<>();

View File

@ -513,14 +513,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
messager.printMessage( method, Message.RETRIEVAL_TYPE_VAR_RESULT );
return false;
}
if ( typeParameter.isWildCardExtendsBound() ) {
if ( typeParameter.hasExtendsBound() ) {
messager.printMessage( method, Message.RETRIEVAL_WILDCARD_EXTENDS_BOUND_RESULT );
return false;
}
}
for ( Type typeParameter : parameterType.getTypeParameters() ) {
if ( typeParameter.isWildCardSuperBound() ) {
if ( typeParameter.hasSuperBound() ) {
messager.printMessage( method, Message.RETRIEVAL_WILDCARD_SUPER_BOUND_SOURCE );
return false;
}

View File

@ -460,6 +460,7 @@ public class MappingResolverImpl implements MappingResolver {
methods,
singletonList( source ),
target,
target,
selectionCriteria
);
}
@ -758,7 +759,7 @@ public class MappingResolverImpl implements MappingResolver {
attempt.selectionCriteria.setPreferUpdateMapping( false );
for ( T2 yCandidate : yMethods ) {
Type ySourceType = yCandidate.getMappingSourceType();
ySourceType = ySourceType.resolveTypeVarToType( targetType, yCandidate.getResultType() );
ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch();
Type yTargetType = yCandidate.getResultType();
if ( ySourceType == null
|| !yTargetType.isRawAssignableTo( targetType )
@ -876,7 +877,7 @@ public class MappingResolverImpl implements MappingResolver {
for ( T yCandidate : methods ) {
Type ySourceType = yCandidate.getMappingSourceType();
ySourceType = ySourceType.resolveTypeVarToType( targetType, yCandidate.getResultType() );
ySourceType = ySourceType.resolveParameterToType( targetType, yCandidate.getResultType() ).getMatch();
Type yTargetType = yCandidate.getResultType();
if ( ySourceType == null
|| !yTargetType.isRawAssignableTo( targetType )
@ -993,7 +994,7 @@ public class MappingResolverImpl implements MappingResolver {
for ( T xCandidate : methods ) {
Type xTargetType = xCandidate.getReturnType();
Type xSourceType = xCandidate.getMappingSourceType();
xTargetType = xTargetType.resolveTypeVarToType( sourceType, xSourceType );
xTargetType = xTargetType.resolveParameterToType( sourceType, xSourceType ).getMatch();
if ( xTargetType == null
|| xCandidate.isUpdateMethod()
|| !sourceType.isRawAssignableTo( xSourceType )

View File

@ -6,10 +6,49 @@
package org.mapstruct.ap.internal.util;
import javax.annotation.processing.ProcessingEnvironment;
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.Types;
public class EclipseTypeUtilsDecorator extends AbstractTypeUtilsDecorator {
private final Types delegate;
EclipseTypeUtilsDecorator(ProcessingEnvironment processingEnv) {
super( processingEnv );
this.delegate = processingEnv.getTypeUtils();
}
@Override
public boolean contains(TypeMirror t1, TypeMirror t2) {
if ( TypeKind.TYPEVAR == t2.getKind() ) {
return containsType( t1, ( (TypeVariable) t2 ).getLowerBound() );
}
else {
return containsType( t1, t2 );
}
}
private boolean containsType(TypeMirror t1, TypeMirror t2) {
boolean result = false;
if ( TypeKind.DECLARED == t2.getKind() ) {
if ( TypeKind.WILDCARD == t1.getKind() ) {
WildcardType wct = (WildcardType) t1;
if ( wct.getExtendsBound() != null ) {
result = isAssignable( t2, wct.getExtendsBound() );
}
else if ( wct.getSuperBound() != null ) {
result = isAssignable( wct.getSuperBound(), t2 );
}
else {
result = isAssignable( t2, wct );
}
}
}
return result;
}
}

View File

@ -7,9 +7,9 @@
-->
<#-- @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></#if><#if (!ext.raw?? && typeParameters?size > 0) ><<#list typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, </#if></#list>>

View File

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

View File

@ -20,9 +20,9 @@ public abstract class TargetSourceMapper {
@Mapping(target = "wrapper", source = "bigDecimal")
abstract Source2 map(Target target);
protected <T extends Enum<T>> Enum<T> map(String in, @TargetType Class<T>clz ) {
protected <T extends Enum<T>> T map(String in, @TargetType Class<T>clz ) {
if ( clz.isAssignableFrom( SourceEnum.class )) {
return (Enum<T>) SourceEnum.valueOf( in );
return (T) SourceEnum.valueOf( in );
}
return null;
}

View File

@ -37,8 +37,6 @@ public abstract class SourceTargetMapper {
@InheritConfiguration
public abstract Target sourceToTargetTwoArg(Source source, @MappingTarget Target target);
public abstract Set<String> integerSetToStringSet(Set<Integer> integers);
@InheritInverseConfiguration
public abstract Set<Integer> stringSetToIntegerSet(Set<String> strings);

View File

@ -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<TypeC> " +
"fooWildCardSuperTypeCFailure\" to \"TypeC fooWildCardSuperTypeCFailure\". " +
"Consider to declare/implement a mapping method: \"TypeC map(WildCardSuperWrapper<TypeC> value)\".")
})
public void shouldFailOnSuperBounds2() {
}
@Test
@WithClasses({
ErroneousSource6.class,

View File

@ -5,8 +5,6 @@
*/
package org.mapstruct.ap.test.selection.generics;
import java.io.Serializable;
public class GenericTypeMapper {
public <T> T getWrapped(Wrapper<T> source) {
@ -53,7 +51,4 @@ public class GenericTypeMapper {
return (String) t.getWrapped();
}
public <T extends TypeB & Serializable> T getWildCardExtendsMBType(WildCardExtendsMBWrapper<? extends T> t) {
return t.getWrapped();
}
}

View File

@ -17,10 +17,7 @@ public class Source {
private Wrapper<Wrapper<BigDecimal>> fooNested;
private UpperBoundWrapper<TypeB> fooUpperBoundCorrect;
private WildCardExtendsWrapper<String> fooWildCardExtendsString;
private WildCardExtendsWrapper<TypeC> fooWildCardExtendsTypeCCorrect;
private WildCardExtendsWrapper<TypeB> fooWildCardExtendsTypeBCorrect;
private WildCardSuperWrapper<String> fooWildCardSuperString;
private WildCardExtendsMBWrapper<TypeC> fooWildCardExtendsMBTypeCCorrect;
private WildCardSuperWrapper<TypeB> fooWildCardSuperTypeBCorrect;
public Wrapper<Integer> getFooInteger() {
@ -87,22 +84,6 @@ public class Source {
this.fooWildCardExtendsString = fooWildCardExtendsString;
}
public void setFooWildCardExtendsTypeCCorrect(WildCardExtendsWrapper<TypeC> fooWildCardExtendsTypeCCorrect) {
this.fooWildCardExtendsTypeCCorrect = fooWildCardExtendsTypeCCorrect;
}
public WildCardExtendsWrapper<TypeC> getFooWildCardExtendsTypeCCorrect() {
return fooWildCardExtendsTypeCCorrect;
}
public WildCardExtendsWrapper<TypeB> getFooWildCardExtendsTypeBCorrect() {
return fooWildCardExtendsTypeBCorrect;
}
public void setFooWildCardExtendsTypeBCorrect(WildCardExtendsWrapper<TypeB> fooWildCardExtendsTypeBCorrect) {
this.fooWildCardExtendsTypeBCorrect = fooWildCardExtendsTypeBCorrect;
}
public WildCardSuperWrapper<String> getFooWildCardSuperString() {
return fooWildCardSuperString;
}
@ -111,14 +92,6 @@ public class Source {
this.fooWildCardSuperString = fooWildCardSuperString;
}
public WildCardExtendsMBWrapper<TypeC> getFooWildCardExtendsMBTypeCCorrect() {
return fooWildCardExtendsMBTypeCCorrect;
}
public void setFooWildCardExtendsMBTypeCCorrect(WildCardExtendsMBWrapper<TypeC> fooWildCardExtendsMBTypeCCorrect) {
this.fooWildCardExtendsMBTypeCCorrect = fooWildCardExtendsMBTypeCCorrect;
}
public WildCardSuperWrapper<TypeB> getFooWildCardSuperTypeBCorrect() {
return fooWildCardSuperTypeBCorrect;
}

View File

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

View File

@ -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 <T> GenericTargetWrapper<T[]> map(GenericSourceWrapper<T[]> in ) {
return new GenericTargetWrapper<>( in.getWrapped() );
}
class Source {
private final GenericSourceWrapper<String[]> prop;
public Source(GenericSourceWrapper<String[]> prop) {
this.prop = prop;
}
public GenericSourceWrapper<String[]> getProp() {
return prop;
}
}
class Target {
private GenericTargetWrapper<String[]> prop;
public GenericTargetWrapper<String[]> getProp() {
return prop;
}
public void setProp(GenericTargetWrapper<String[]> prop) {
this.prop = prop;
}
}
class GenericTargetWrapper<T> {
private final T wrapped;
public GenericTargetWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
class GenericSourceWrapper<T> {
private final T wrapped;
public GenericSourceWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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<String> 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<String[]> 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" );
}
}

View File

@ -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> T[] map(GenericWrapper<T> in) {
return Collections.singletonList( in.getWrapped() )
.toArray( (T[]) Array.newInstance( in.getWrapped().getClass(), 1 ) );
}
class Source {
private GenericWrapper<String> prop;
public Source(GenericWrapper<String> prop) {
this.prop = prop;
}
public GenericWrapper<String> getProp() {
return prop;
}
public void setProp(GenericWrapper<String> prop) {
this.prop = prop;
}
}
class Target {
private String[] prop;
public String[] getProp() {
return prop;
}
public void setProp(String[] prop) {
this.prop = prop;
}
}
class GenericWrapper<T> {
private final T wrapped;
public GenericWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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 <T> GenericWrapper<T> 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<String> prop;
public GenericWrapper<String> getProp() {
return prop;
}
public void setProp(GenericWrapper<String> prop) {
this.prop = prop;
}
}
class GenericWrapper<T> {
private final T wrapped;
public GenericWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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" );
}
}

View File

@ -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 extends NestedBase> T map(String in, @TargetType Class<T> 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;
}
}
}

View File

@ -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<String, Long> 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 );
}
}

View File

@ -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 <T, U> HashMap<T, U> toMap( Pair<T, U> entry) {
HashMap<T, U> result = new HashMap<>( );
result.put( entry.first, entry.second );
return result;
}
class Source {
private Pair<String, Long> prop;
public Source(Pair<String, Long> prop) {
this.prop = prop;
}
public Pair<String, Long> getProp() {
return prop;
}
}
class Target {
private Map<String, Long> prop;
public Map<String, Long> getProp() {
return prop;
}
public Target setProp(Map<String, Long> prop) {
this.prop = prop;
return this;
}
}
class Pair<T, U> {
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;
}
}
}

View File

@ -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 <T> HashMap<String, T> toMap( T entry) {
HashMap<String, T> 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<String, Long> prop;
public Map<String, Long> getProp() {
return prop;
}
public Target setProp(Map<String, Long> prop) {
this.prop = prop;
return this;
}
}
}

View File

@ -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 <T, U> HashMap<T, U> toMap( Pair<T, U> entry) {
HashMap<T, U> result = new HashMap<>( );
result.put( entry.first, entry.second );
return result;
}
default <T, U> Pair<T, U> toPair( Map<T, U> map) {
if ( !map.isEmpty() ) {
Map.Entry<T, U> firstEntry = map.entrySet().iterator().next();
return new Pair<>( firstEntry.getKey(), firstEntry.getValue() );
}
return null;
}
class Source {
private final Map<String, Long> prop;
public Source(Map<String, Long> prop) {
this.prop = prop;
}
public Map<String, Long> getProp() {
return prop;
}
}
class Target {
private Pair<String, Long> prop;
public Target(Pair<String, Long> prop) {
this.prop = prop;
}
public Pair<String, Long> getProp() {
return prop;
}
}
class Pair<T, U> {
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;
}
}
}

View File

@ -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" );
}
}

View File

@ -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 <T> List<Set<T>> 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<Set<String>> prop;
public List<Set<String>> getProp() {
return prop;
}
public Target setProp(List<Set<String>> prop) {
this.prop = prop;
return this;
}
}
}

View File

@ -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> T unwrapToOneElement(List<Set<T>> listOfSet) {
if ( !listOfSet.isEmpty() && listOfSet.get( 0 ).iterator().hasNext() ) {
return listOfSet.get( 0 ).iterator().next();
}
return null;
}
class Source {
private final List<Set<String>> prop;
public Source(List<Set<String>> prop) {
this.prop = prop;
}
public List<Set<String>> getProp() {
return prop;
}
}
class Target {
private String prop;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
}
}

View File

@ -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 <T> GenericTargetWrapper<T> map(GenericSourceWrapper<T> in ) {
return new GenericTargetWrapper<>( in.getWrapped() );
}
class Source {
private final GenericSourceWrapper<String> prop;
public Source(GenericSourceWrapper<String> prop) {
this.prop = prop;
}
public GenericSourceWrapper<String> getProp() {
return prop;
}
}
class Target {
private GenericTargetWrapper<String> prop;
public GenericTargetWrapper<String> getProp() {
return prop;
}
public void setProp(GenericTargetWrapper<String> prop) {
this.prop = prop;
}
}
class GenericTargetWrapper<T> {
private final T wrapped;
public GenericTargetWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
class GenericSourceWrapper<T> {
private final T wrapped;
public GenericSourceWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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" );
}
}

View File

@ -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<String> selectMe(Set<Integer> integers);
Set<Integer> doNotSelectMe(Set<String> strings);
class Source {
private final Set<Integer> prop;
public Source(Set<Integer> prop) {
this.prop = prop;
}
public Set<Integer> getProp() {
return prop;
}
}
class Target {
private Set prop;
public Set getProp() {
return prop;
}
public Target setProp(Set prop) {
this.prop = prop;
return this;
}
}
}

View File

@ -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> T map(GenericWrapper<T> in) {
return in.getWrapped();
}
class Source {
private final GenericWrapper<String> prop;
public Source(GenericWrapper<String> prop) {
this.prop = prop;
}
public GenericWrapper<String> getProp() {
return prop;
}
}
class Target {
private String prop;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
}
class GenericWrapper<T> {
private final T wrapped;
public GenericWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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 <T> GenericWrapper<T> 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<String> prop;
public GenericWrapper<String> getProp() {
return prop;
}
public void setProp(GenericWrapper<String> prop) {
this.prop = prop;
}
}
class GenericWrapper<T> {
private final T wrapped;
public GenericWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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 extends BaseType> T map(String string, @TargetType Class<T> 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<String> prop;
public GenericWrapper<String> getProp() {
return prop;
}
public void setProp(GenericWrapper<String> prop) {
this.prop = prop;
}
}
class BaseType {
}
class GenericWrapper<T> extends BaseType {
private final T wrapped;
public GenericWrapper(T someType) {
this.wrapped = someType;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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> T map(String string, @TargetType Class<T> 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 {
}
}

View File

@ -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" );
}
}

View File

@ -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 extends TypeB & Serializable> T unwrap(Wrapper<? extends T> t) {
return t.getWrapped();
}
class Source {
private final Wrapper<TypeC> prop;
public Source(Wrapper<TypeC> prop) {
this.prop = prop;
}
public Wrapper<TypeC> getProp() {
return prop;
}
}
class Wrapper<T> {
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 {
}
}

View File

@ -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 extends TypeB> T unwrap(Wrapper<? extends T> t) {
return t.getWrapped();
}
class Source {
private final Wrapper<TypeC> propB;
private final Wrapper<TypeC> propC;
public Source(Wrapper<TypeC> propB, Wrapper<TypeC> propC) {
this.propB = propB;
this.propC = propC;
}
public Wrapper<TypeC> getPropB() {
return propB;
}
public Wrapper<TypeC> getPropC() {
return propC;
}
}
class Wrapper<T> {
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 {
}
}

View File

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

View File

@ -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<? extends Number> t) {
return t.getWrapped().toString();
}
class Source {
private final Wrapper<BigInteger> prop;
public Source(Wrapper<BigInteger> prop) {
this.prop = prop;
}
public Wrapper<BigInteger> getProp() {
return prop;
}
}
class Wrapper<T> {
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;
}
}
}

View File

@ -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<BigInteger> 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" );
}
}

View File

@ -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<BigInteger> 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<? super BigInteger> prop;
public Target setProp(Wrapper<? super BigInteger> prop) {
this.prop = prop;
return this;
}
public Wrapper<? super BigInteger> getProp() {
return prop;
}
}
class Wrapper<T> {
private final T wrapped;
public Wrapper(T wrapped) {
this.wrapped = wrapped;
}
public T getWrapped() {
return wrapped;
}
}
}

View File

@ -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<? extends Number> t) {
return t.getWrapped().toString();
}
class Source {
private final Wrapper<BigInteger> prop;
public Source(Wrapper<BigInteger> prop) {
this.prop = prop;
}
public Wrapper<BigInteger> getProp() {
return prop;
}
}
class Wrapper<T> {
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;
}
}
}

View File

@ -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<BigInteger> 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 ) );
}
}