From 4372e726eef8c10c58569f77e1f731687e8f6485 Mon Sep 17 00:00:00 2001 From: Andreas Gudian Date: Wed, 19 Feb 2014 21:49:08 +0100 Subject: [PATCH] #136 Introduce @TargetType to pass the target type of a custom property mapping method or factory method as parameter to the method --- .../java/org/mapstruct/MappingTarget.java | 3 - .../main/java/org/mapstruct/TargetType.java | 34 +++++++++ .../mapstruct/ap/model/MethodReference.java | 27 +++++++ .../mapstruct/ap/model/common/Parameter.java | 13 +++- .../org/mapstruct/ap/model/common/Type.java | 1 + .../ap/model/common/TypeFactory.java | 31 +++++++- .../org/mapstruct/ap/model/source/Method.java | 9 ++- .../ap/model/source/MethodMatcher.java | 47 +++++++++---- .../ap/model/source/SourceMethod.java | 65 ++++++++--------- .../model/source/builtin/BuiltInMethod.java | 7 +- .../source/selector/MethodSelectors.java | 28 +++++++- .../model/source/selector/TypeSelector.java | 11 ++- .../mapstruct/ap/prism/PrismGenerator.java | 2 + .../ap/processor/MapperCreationProcessor.java | 53 ++++++++------ .../processor/MethodRetrievalProcessor.java | 67 ++++++++++++++---- ...g.mapstruct.ap.model.BeanMappingMethod.ftl | 2 +- ...pstruct.ap.model.IterableMappingMethod.ftl | 2 +- ...rg.mapstruct.ap.model.MapMappingMethod.ftl | 4 +- ...org.mapstruct.ap.model.MethodReference.ftl | 6 +- ...org.mapstruct.ap.model.PropertyMapping.ftl | 4 +- .../typemismatch/ErroneousMapper.java | 3 + .../typemismatch/ErroneousMappingsTest.java | 15 ++-- .../org/mapstruct/ap/test/factories/Bar1.java | 2 +- .../ap/test/factories/FactoryCreatable.java | 27 +++++++ .../ap/test/factories/FactoryTest.java | 21 +++++- .../ap/test/factories/GenericFactory.java | 36 ++++++++++ .../SourceTargetMapperWithGenericFactory.java | 33 +++++++++ .../ap/test/references/BaseType.java | 27 +++++++ .../references/ReferencedCustomMapper.java | 24 +++++++ .../test/references/ReferencedMapperTest.java | 48 ++++++++++++- .../ap/test/references/SomeOtherType.java | 70 +++++++++++++++++++ .../ap/test/references/SomeType.java | 70 +++++++++++++++++++ .../mapstruct/ap/test/references/Source.java | 9 +++ .../test/references/SourceTargetMapper.java | 7 ++ .../SourceTargetMapperWithPrimitives.java | 50 +++++++++++++ .../test/references/SourceWithWrappers.java | 54 ++++++++++++++ .../mapstruct/ap/test/references/Target.java | 10 +++ .../test/references/TargetWithPrimitives.java | 53 ++++++++++++++ 38 files changed, 858 insertions(+), 117 deletions(-) create mode 100644 core/src/main/java/org/mapstruct/TargetType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/factories/FactoryCreatable.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/factories/GenericFactory.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperWithGenericFactory.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/BaseType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/SomeOtherType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/SomeType.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapperWithPrimitives.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/SourceWithWrappers.java create mode 100644 processor/src/test/java/org/mapstruct/ap/test/references/TargetWithPrimitives.java diff --git a/core/src/main/java/org/mapstruct/MappingTarget.java b/core/src/main/java/org/mapstruct/MappingTarget.java index 308488e0a..2a8c70e48 100644 --- a/core/src/main/java/org/mapstruct/MappingTarget.java +++ b/core/src/main/java/org/mapstruct/MappingTarget.java @@ -25,9 +25,6 @@ import java.lang.annotation.Target; * Declares a parameter of a mapping method to be the target of the mapping. *

* Not more than one parameter can be declared as {@code MappingTarget}. - *

- * For methods with return type {@code void}, the last parameter of the method is regarded as {@code MappingTarget}, - * unless another parameter carries this annotation. * * @author Andreas Gudian */ diff --git a/core/src/main/java/org/mapstruct/TargetType.java b/core/src/main/java/org/mapstruct/TargetType.java new file mode 100644 index 000000000..34d44671e --- /dev/null +++ b/core/src/main/java/org/mapstruct/TargetType.java @@ -0,0 +1,34 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Target; + +/** + * Declares a parameter of a custom mapping method to be populated with the target type of the mapping. + *

+ * Not more than one parameter can be declared as {@code TargetType} and that parameter needs to be of type + * {@link Class} (may be parameterized), or a super-type of it. + * + * @author Andreas Gudian + */ +@Target( ElementType.PARAMETER ) +public @interface TargetType { +} diff --git a/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java index c3809e381..cf359bc55 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MethodReference.java @@ -21,7 +21,9 @@ package org.mapstruct.ap.model; import java.util.Set; import org.mapstruct.ap.model.common.ConversionContext; +import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.SourceMethod; import org.mapstruct.ap.model.source.builtin.BuiltInMethod; @@ -33,6 +35,7 @@ import org.mapstruct.ap.model.source.builtin.BuiltInMethod; public class MethodReference extends MappingMethod { private final MapperReference declaringMapper; + private final Type importType; /** * A reference to another mapping method in case this is a two-step mapping, e.g. from {@code JAXBElement} to @@ -52,12 +55,21 @@ public class MethodReference extends MappingMethod { super( method ); this.declaringMapper = declaringMapper; this.contextParam = null; + this.importType = null; } public MethodReference(BuiltInMethod method, ConversionContext contextParam) { super( method ); this.declaringMapper = null; this.contextParam = method.getContextParameter( contextParam ); + this.importType = null; + } + + public MethodReference(Method method, MapperReference declaringMapper, Type importType) { + super( method ); + this.declaringMapper = declaringMapper; + this.contextParam = null; + this.importType = importType; } public MapperReference getDeclaringMapper() { @@ -72,6 +84,18 @@ public class MethodReference extends MappingMethod { return contextParam; } + /** + * @return the type of the single source parameter that is not the {@code @TargetType} parameter + */ + public Type getSingleSourceParameterType() { + for ( Parameter parameter : getSourceParameters() ) { + if ( !parameter.isTargetType() ) { + return parameter.getType(); + } + } + return null; + } + public void setMethodRefChild(MethodReference methodRefChild) { this.methodRefChild = methodRefChild; } @@ -83,6 +107,9 @@ public class MethodReference extends MappingMethod { @Override public Set getImportTypes() { Set imported = super.getImportTypes(); + if ( importType != null ) { + imported.add( importType ); + } if ( methodRefChild != null ) { imported.addAll( methodRefChild.getImportTypes() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java index d54a72c92..8079d6449 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/Parameter.java @@ -32,15 +32,17 @@ public class Parameter extends ModelElement { private final String name; private final Type type; private final boolean mappingTarget; + private final boolean targetType; - public Parameter(String name, Type type, boolean mappingTarget) { + public Parameter(String name, Type type, boolean mappingTarget, boolean targetType) { this.name = name; this.type = type; this.mappingTarget = mappingTarget; + this.targetType = targetType; } public Parameter(String name, Type type) { - this( name, type, false ); + this( name, type, false, false ); } public String getName() { @@ -57,11 +59,16 @@ public class Parameter extends ModelElement { @Override public String toString() { - return ( mappingTarget ? "@MappingTarget " : "" ) + type.toString() + " " + name; + return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + + type.toString() + " " + name; } @Override public Set getImportTypes() { return Collections.asSet( type ); } + + public boolean isTargetType() { + return targetType; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java index 298d69aba..c1eb9df79 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/Type.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; + import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; diff --git a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java index 19da1fb4f..ba00d7f77 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java +++ b/processor/src/main/java/org/mapstruct/ap/model/common/TypeFactory.java @@ -41,6 +41,7 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; 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.util.Elements; @@ -48,6 +49,7 @@ import javax.lang.model.util.SimpleElementVisitor6; import javax.lang.model.util.Types; import org.mapstruct.ap.prism.MappingTargetPrism; +import org.mapstruct.ap.prism.TargetTypePrism; import org.mapstruct.ap.util.AnnotationProcessingException; /** @@ -185,6 +187,29 @@ public class TypeFactory { ); } + /** + * Returns the Type that represents the declared Class type of the given type. For primitive types, the boxed class + * will be used.
+ * Examples:
+ * If type represents {@code java.lang.Integer}, it will return the type that represents {@code Class}. + *
+ * If type represents {@code int}, it will return the type that represents {@code Class}. + * + * @param type the type to return the declared class type for + * @return the type representing {@code Class}. + */ + public Type classTypeOf(Type type) { + TypeMirror typeToUse; + if ( type.isPrimitive() ) { + typeToUse = typeUtils.boxedClass( (PrimitiveType) type.getTypeMirror() ).asType(); + } + else { + typeToUse = type.getTypeMirror(); + } + + return getType( typeUtils.getDeclaredType( elementUtils.getTypeElement( "java.lang.Class" ), typeToUse ) ); + } + public Parameter getSingleParameter(ExecutableElement method) { List parameters = method.getParameters(); @@ -197,8 +222,7 @@ public class TypeFactory { return new Parameter( parameter.getSimpleName().toString(), - getType( parameter.asType() ), - false + getType( parameter.asType() ) ); } @@ -212,7 +236,8 @@ public class TypeFactory { new Parameter( parameter.getSimpleName().toString(), getType( parameter.asType() ), - MappingTargetPrism.getInstanceOn( parameter ) != null + MappingTargetPrism.getInstanceOn( parameter ) != null, + TargetTypePrism.getInstanceOn( parameter ) != null ) ); } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java index 7ad65143a..3a2ad1fc3 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Method.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Method.java @@ -36,15 +36,14 @@ import org.mapstruct.ap.model.source.builtin.BuiltInMethod; public interface Method { /** - * Checks whether the provided sourceType and provided targetType match with the parameter respectively - * return type of the method. The check also should incorporate wild card and generic type variables + * Checks whether the provided sourceType and provided targetType match with the parameter respectively return type + * of the method. The check also should incorporate wild card and generic type variables * - * @param sourceType the sourceType to match to the parameter + * @param sourceTypes the sourceTypes to match to the parameter * @param targetType the targetType to match to the returnType - * * @return true when match */ - boolean matches(Type sourceType, Type targetType); + boolean matches(List sourceTypes, Type targetType); /** * Returns the mapper type declaring this method if it is not declared by the mapper interface currently processed diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java index b488774ce..30f1e48eb 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/MethodMatcher.java @@ -21,6 +21,7 @@ package org.mapstruct.ap.model.source; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; @@ -61,10 +62,8 @@ import org.mapstruct.ap.model.common.Type; */ public class MethodMatcher { - private final SourceMethod candidateMethod; private final Types typeUtils; - private final Map genericTypesMap = new HashMap(); MethodMatcher(Types typeUtils, SourceMethod candidateMethod) { this.typeUtils = typeUtils; @@ -72,38 +71,56 @@ public class MethodMatcher { } /** - * Whether the given source and target type are matched by this matcher's candidate method. + * Whether the given source and target types are matched by this matcher's candidate method. * - * @param sourceType the source type + * @param sourceTypes the source types * @param targetType the target type * - * @return {@code true} when both, source type and target type match the signature of this matcher's method; + * @return {@code true} when both, source type and target types match the signature of this matcher's method; * {@code false} otherwise. */ - boolean matches(Type sourceType, Type targetType) { + boolean matches(List sourceTypes, Type targetType) { // check & collect generic types. List candidateParameters = candidateMethod.getExecutable().getParameters(); - if ( candidateParameters.size() != 1 ) { + if ( candidateParameters.size() != sourceTypes.size() ) { return false; } - TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM ); - if ( !parameterMatcher.visit( candidateParameters.iterator().next().asType(), sourceType.getTypeMirror() ) ) { - return false; + Map genericTypesMap = new HashMap(); + + int i = 0; + for ( VariableElement candidateParameter : candidateParameters ) { + TypeMatcher parameterMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_FROM, genericTypesMap ); + if ( !parameterMatcher.visit( candidateParameter.asType(), sourceTypes.get( i++ ).getTypeMirror() ) ) { + return false; + } } // check return type TypeMirror candidateReturnType = candidateMethod.getExecutable().getReturnType(); - TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO ); + TypeMatcher returnTypeMatcher = new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + if ( !returnTypeMatcher.visit( candidateReturnType, targetType.getTypeMirror() ) ) { - return false; + if ( targetType.isPrimitive() ) { + TypeMirror boxedType = typeUtils.boxedClass( (PrimitiveType) targetType.getTypeMirror() ).asType(); + TypeMatcher boxedReturnTypeMatcher = + new TypeMatcher( Assignability.VISITED_ASSIGNABLE_TO, genericTypesMap ); + + if ( !boxedReturnTypeMatcher.visit( candidateReturnType, boxedType ) ) { + return false; + } + } + else { + return false; + } } // check if all type parameters are indeed mapped - if ( candidateMethod.getExecutable().getTypeParameters().size() != this.genericTypesMap.size() ) { + if ( candidateMethod.getExecutable().getTypeParameters().size() != genericTypesMap.size() ) { return false; } + // check if all entries are in the bounds for ( Map.Entry entry : genericTypesMap.entrySet() ) { if ( !isWithinBounds( entry.getValue(), getTypeParamFromCandidate( entry.getKey() ) ) ) { @@ -120,10 +137,12 @@ public class MethodMatcher { private class TypeMatcher extends SimpleTypeVisitor6 { private final Assignability assignability; + private final Map genericTypesMap; - public TypeMatcher(Assignability assignability) { + public TypeMatcher(Assignability assignability, Map genericTypesMap) { super( Boolean.FALSE ); // default value this.assignability = assignability; + this.genericTypesMap = genericTypesMap; } @Override diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java index 245f6b9fd..a79117616 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/SourceMethod.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; + import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.util.Types; @@ -58,11 +59,9 @@ public class SourceMethod implements Method { private boolean configuredByReverseMappingMethod = false; public static SourceMethod forMethodRequiringImplementation(ExecutableElement executable, - List parameters, - Type returnType, Map> mappings, - IterableMapping iterableMapping, - MapMapping mapMapping, + List parameters, Type returnType, + Map> mappings, + IterableMapping iterableMapping, MapMapping mapMapping, Types typeUtils) { return new SourceMethod( @@ -73,51 +72,40 @@ public class SourceMethod implements Method { mappings, iterableMapping, mapMapping, - typeUtils - ); + typeUtils ); } - public static SourceMethod forReferencedMethod(Type declaringMapper, - ExecutableElement executable, - List parameters, - Type returnType, - Types typeUtils) { + public static SourceMethod forReferencedMethod(Type declaringMapper, ExecutableElement executable, + List parameters, Type returnType, Types typeUtils) { return new SourceMethod( declaringMapper, executable, parameters, returnType, - Collections.>emptyMap(), + Collections.> emptyMap(), null, null, - typeUtils - ); + typeUtils ); } - public static SourceMethod forFactoryMethod(Type declaringMapper, ExecutableElement executable, - Type returnType, Types typeUtils) { + public static SourceMethod forFactoryMethod(Type declaringMapper, ExecutableElement executable, Type returnType, + Types typeUtils) { return new SourceMethod( declaringMapper, executable, - Collections.emptyList(), + Collections. emptyList(), returnType, - Collections.>emptyMap(), + Collections.> emptyMap(), null, null, - typeUtils - ); + typeUtils ); } - private SourceMethod(Type declaringMapper, - ExecutableElement executable, - List parameters, - Type returnType, - Map> mappings, - IterableMapping iterableMapping, - MapMapping mapMapping, - Types typeUtils) { + private SourceMethod(Type declaringMapper, ExecutableElement executable, List parameters, + Type returnType, Map> mappings, IterableMapping iterableMapping, + MapMapping mapMapping, Types typeUtils) { this.declaringMapper = declaringMapper; this.executable = executable; this.parameters = parameters; @@ -178,7 +166,7 @@ public class SourceMethod implements Method { List sourceParameters = new ArrayList(); for ( Parameter parameter : parameters ) { - if ( !parameter.isMappingTarget() ) { + if ( !parameter.isMappingTarget() && !parameter.isTargetType() ) { sourceParameters.add( parameter ); } } @@ -336,9 +324,22 @@ public class SourceMethod implements Method { * {@inheritDoc} {@link Method} */ @Override - public boolean matches(Type sourceType, Type targetType) { + public boolean matches(List sourceTypes, Type targetType) { MethodMatcher matcher = new MethodMatcher( typeUtils, this ); - return matcher.matches( sourceType, targetType ); + return matcher.matches( sourceTypes, targetType ); } + /** + * @param parameters the parameter list to check + * @return true, iff the parameter list contains a parameter annotated with {@code @TargetType} + */ + public static boolean containsTargetTypeParameter(List parameters) { + for ( Parameter param : parameters ) { + if ( param.isTargetType() ) { + return true; + } + } + + return false; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java b/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java index 95588492a..e140bcf4b 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/builtin/BuiltInMethod.java @@ -68,7 +68,12 @@ public abstract class BuiltInMethod implements Method { * excluding generic type variables. When the implementor sees a need for this, this method can be overridden. */ @Override - public boolean matches(Type sourceType, Type targetType) { + public boolean matches(List sourceTypes, Type targetType) { + if ( sourceTypes.size() > 1 ) { + return false; + } + Type sourceType = sourceTypes.iterator().next(); + if ( targetType.erasure().isAssignableTo( getReturnType().erasure() ) && sourceType.erasure().isAssignableTo( getParameter().getType().erasure() ) ) { return doTypeVarsMatch( sourceType, targetType ); diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java index 83dee2f9f..16904a78d 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/MethodSelectors.java @@ -24,7 +24,9 @@ import java.util.List; import javax.lang.model.util.Types; +import org.mapstruct.ap.model.common.Parameter; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.SourceMethod; @@ -37,10 +39,10 @@ public class MethodSelectors implements MethodSelector { private final List selectors; - public MethodSelectors(Types typeUtils) { + public MethodSelectors(Types typeUtils, TypeFactory typeFactory) { selectors = Arrays.asList( - new TypeSelector(), + new TypeSelector( typeFactory ), new InheritanceSelector(), new XmlElementDeclSelector( typeUtils ) ); @@ -64,4 +66,26 @@ public class MethodSelectors implements MethodSelector { } return candidates; } + + /** + * @param typeFactory the type factory to use + * @param parameters the parameters to map the types for + * @param sourceType the source type + * @param returnType the return type + * @return the list of actual parameter types + */ + public static List getParameterTypes(TypeFactory typeFactory, List parameters, Type sourceType, + Type returnType) { + List result = new ArrayList( parameters.size() ); + for ( Parameter param : parameters ) { + if ( param.isTargetType() ) { + result.add( typeFactory.classTypeOf( returnType ) ); + } + else { + result.add( sourceType ); + } + } + + return result; + } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java index d2d631149..7afc6572a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/selector/TypeSelector.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import org.mapstruct.ap.model.common.Type; +import org.mapstruct.ap.model.common.TypeFactory; import org.mapstruct.ap.model.source.Method; import org.mapstruct.ap.model.source.MethodMatcher; import org.mapstruct.ap.model.source.SourceMethod; @@ -34,6 +35,12 @@ import org.mapstruct.ap.model.source.SourceMethod; */ public class TypeSelector implements MethodSelector { + private final TypeFactory typeFactory; + + public TypeSelector(TypeFactory typeFactory) { + this.typeFactory = typeFactory; + } + @Override public List getMatchingMethods(SourceMethod mappingMethod, List methods, Type parameterType, Type returnType, @@ -45,7 +52,9 @@ public class TypeSelector implements MethodSelector { continue; } - if ( method.matches( parameterType, returnType ) ) { + List parameterTypes = + MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), parameterType, returnType ); + if ( method.matches( parameterTypes, returnType ) ) { result.add( method ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java b/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java index 08d81108c..5e72094ee 100644 --- a/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/prism/PrismGenerator.java @@ -27,6 +27,7 @@ import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.mapstruct.TargetType; import org.mapstruct.Mappings; /** @@ -40,6 +41,7 @@ import org.mapstruct.Mappings; @GeneratePrism(value = Mappings.class, publicAccess = true), @GeneratePrism(value = IterableMapping.class, publicAccess = true), @GeneratePrism(value = MapMapping.class, publicAccess = true), + @GeneratePrism(value = TargetType.class, publicAccess = true), @GeneratePrism(value = MappingTarget.class, publicAccess = true), // external types diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java index db0c89b05..e858b29db 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -106,7 +106,7 @@ public class MapperCreationProcessor implements ModelElementProcessor(); - this.methodSelectors = new MethodSelectors( typeUtils ); + this.methodSelectors = new MethodSelectors( typeUtils, typeFactory ); return getMapper( mapperTypeElement, sourceModel ); } @@ -257,18 +257,21 @@ public class MapperCreationProcessor implements ModelElementProcessor paramterTypes = + MethodSelectors.getParameterTypes( typeFactory, method.getParameters(), null, returnType ); + + if ( method.matches( paramterTypes, returnType ) ) { + if ( result == null ) { + MapperReference mapperReference = findMapperReference( mapperReferences, method ); + + result = + new MethodReference( + method, + mapperReference, + SourceMethod.containsTargetTypeParameter( method.getParameters() ) + ? returnType : null ); } else { messager.printMessage( @@ -1012,7 +1015,7 @@ public class MapperCreationProcessor implements ModelElementProcessor T getBestMatch(SourceMethod mappingMethod, String mappedElement, List methods, - Type parameterType, + Type sourceType, Type returnType, String targetPropertyName) { List candidates = methodSelectors.getMatchingMethods( mappingMethod, methods, - parameterType, + sourceType, returnType, targetPropertyName ); @@ -1130,7 +1133,7 @@ public class MapperCreationProcessor implements ModelElementProcessor mapperReferences) { - MapperReference mapperReference = null; + private MethodReference getMappingMethodReference(SourceMethod method, List mapperReferences, + Type targetType) { + MapperReference mapperReference = findMapperReference( mapperReferences, method ); + + return new MethodReference( + method, + mapperReference, + SourceMethod.containsTargetTypeParameter( method.getParameters() ) ? targetType : null ); + } + + private MapperReference findMapperReference(List mapperReferences, SourceMethod method) { for ( MapperReference ref : mapperReferences ) { if ( ref.getMapperType().equals( method.getDeclaringMapper() ) ) { - mapperReference = ref; - break; + return ref; } } - return new MethodReference( method, mapperReference ); + return null; } private MethodReference getMappingMethodReference(BuiltInMethod method, Type returnType, String dateFormat) { diff --git a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java index e9e620c76..ac5a76b6b 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -18,10 +18,13 @@ */ package org.mapstruct.ap.processor; +import static javax.lang.model.util.ElementFilter.methodsIn; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.annotation.processing.Messager; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; @@ -46,8 +49,6 @@ import org.mapstruct.ap.prism.MappingPrism; import org.mapstruct.ap.prism.MappingsPrism; import org.mapstruct.ap.util.AnnotationProcessingException; -import static javax.lang.model.util.ElementFilter.methodsIn; - /** * A {@link ModelElementProcessor} which retrieves a list of {@link SourceMethod}s * representing all the mapping methods of the given bean mapper type as well as @@ -125,6 +126,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor sourceParameters = extractSourceParameters( parameters ); @@ -132,7 +134,13 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters) { + return isValidReferencedOrFactoryMethod( 1, parameters ); + } + + private boolean isValidFactoryMethod(List parameters) { + return isValidReferencedOrFactoryMethod( 0, parameters ); + } + + private boolean isValidReferencedOrFactoryMethod(int sourceParamCount, List parameters) { + int validSourceParameters = 0; + int targetParameters = 0; + int targetTypeParameters = 0; + + for ( Parameter param : parameters ) { + if ( param.isMappingTarget() ) { + targetParameters++; + } + + if ( param.isTargetType() ) { + targetTypeParameters++; + } + + if ( !param.isMappingTarget() && !param.isTargetType() ) { + validSourceParameters++; + } + } + return validSourceParameters == sourceParamCount && targetParameters == 0 && targetTypeParameters <= 1 + && parameters.size() == validSourceParameters + targetParameters + targetTypeParameters; + } + private Parameter extractTargetParameter(List parameters) { for ( Parameter param : parameters ) { if ( param.isMappingTarget() ) { @@ -207,7 +235,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor sourceParameters, - Parameter targetParameter, Type resultType, Type returnType) { + Parameter targetParameter, Type resultType, Type returnType, + boolean containsTargetTypeParameter) { if ( sourceParameters.isEmpty() ) { messager.printMessage( Kind.ERROR, "Can't generate mapping method with no input arguments.", method ); return false; @@ -248,6 +277,14 @@ public class MethodRetrievalProcessor implements ModelElementProcessor null; } - <#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod/><#else>new <@includeModel object=resultType/>(); + <#if !existingInstanceMapping><@includeModel object=resultType/> ${resultName} = <#if factoryMethod??><@includeModel object=factoryMethod targetType="${resultType.name}"/><#else>new <@includeModel object=resultType/>(); <#if (sourceParameters?size > 1)> <#list sourceParameters as sourceParam> if ( ${sourceParam.name} != null ) { diff --git a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl index 43fbdac3e..055ebb664 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl @@ -33,7 +33,7 @@ for ( <@includeModel object=sourceParameter.type.typeParameters[0]/> ${loopVariableName} : ${sourceParameter.name} ) { <#if elementMappingMethod??> - ${resultName}.add( <@includeModel object=elementMappingMethod input="${loopVariableName}"/> ); + ${resultName}.add( <@includeModel object=elementMappingMethod input="${loopVariableName}" targetType="${resultType.typeParameters[0].name}"/> ); <#elseif conversion??> <#if (conversion.exceptionTypes?size == 0) > ${resultName}.add( <@includeModel object=conversion/> ); diff --git a/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl index 98f169976..31fd1006a 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl @@ -35,7 +35,7 @@ <#-- key --> <#if keyMappingMethod??> - <@includeModel object=resultType.typeParameters[0]/> ${keyVariableName} = <@includeModel object=keyMappingMethod input="entry.getKey()"/>; + <@includeModel object=resultType.typeParameters[0]/> ${keyVariableName} = <@includeModel object=keyMappingMethod input="entry.getKey()" targetType="${resultType.typeParameters[0].name}"/>; <#elseif keyConversion??> <#if (keyConversion.exceptionTypes?size == 0) > <@includeModel object=resultType.typeParameters[0]/> ${keyVariableName} = <@includeModel object=keyConversion/>; @@ -55,7 +55,7 @@ <#-- value --> <#if valueMappingMethod??> - <@includeModel object=resultType.typeParameters[1]/> ${valueVariableName} = <@includeModel object=valueMappingMethod input="entry.getValue()"/>; + <@includeModel object=resultType.typeParameters[1]/> ${valueVariableName} = <@includeModel object=valueMappingMethod input="entry.getValue()" targetType="${resultType.typeParameters[1].name}"/>; <#elseif valueConversion??> <#if (valueConversion.exceptionTypes?size == 0) > <@includeModel object=resultType.typeParameters[1]/> ${valueVariableName} = <@includeModel object=valueConversion/>; diff --git a/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl b/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl index 9de44aa8d..485aa58bb 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.MethodReference.ftl @@ -18,4 +18,8 @@ limitations under the License. --> -<#if declaringMapper??>${mapperVariableName}.${name}<#if ext.input??>( <#if methodRefChild??><@includeModel object=methodRefChild input=ext.input/><#if contextParam??>, ${contextParam}<#else>${ext.input}<#if contextParam??>, ${contextParam} )<#else>() \ No newline at end of file +<#if methodRefChild??> + <#if declaringMapper??>${mapperVariableName}.${name}(<#list parameters as param> <#if param.targetType>${ext.targetType}.class<#else><@includeModel object=methodRefChild input=ext.input targetType=singleSourceParameterType.name/><#if param_has_next>,<#else> <#if contextParam??>, ${contextParam})<#t> +<#else> + <#if declaringMapper??>${mapperVariableName}.${name}(<#list parameters as param> <#if param.targetType>${ext.targetType}.class<#else>${ext.input}<#if param_has_next>,<#else> <#if contextParam??>, ${contextParam})<#t> + \ No newline at end of file diff --git a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl index 785feaeb7..759ad5a56 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl @@ -21,10 +21,10 @@ <#-- a) invoke mapping method --> <#if mappingMethod??> <#if targetAccessorSetter> -${ext.targetBeanName}.${targetAccessorName}( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()"/> ); +${ext.targetBeanName}.${targetAccessorName}( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()" targetType="${targetType.name}"/> ); <#elseif targetType.collectionType> if ( ${sourceBeanName}.${sourceAccessorName}() != null && ${ext.targetBeanName}.${targetAccessorName}() != null ) { - ${ext.targetBeanName}.${targetAccessorName}().addAll( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()"/> ); + ${ext.targetBeanName}.${targetAccessorName}().addAll( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()" targetType="${targetType.name}"/> ); } <#-- b) simple conversion --> diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMapper.java index 9636407bc..d869c746e 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMapper.java @@ -19,6 +19,7 @@ package org.mapstruct.ap.test.erroneous.typemismatch; import org.mapstruct.Mapper; +import org.mapstruct.TargetType; @Mapper public interface ErroneousMapper { @@ -30,4 +31,6 @@ public interface ErroneousMapper { long sourceToLong(Source source); Source longToSource(long id); + + Target sourceToTargetWithMappingTargetType(Source source, @TargetType Class clazz); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java index 7b72f2f9a..5690d03c3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/typemismatch/ErroneousMappingsTest.java @@ -43,20 +43,25 @@ public class ErroneousMappingsTest extends MapperTestBase { diagnostics = { @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 26, + line = 27, messageRegExp = "Can't map property \"boolean foo\" to \"int foo\"\\."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 28, + line = 29, messageRegExp = "Can't map property \"int foo\" to \"boolean foo\"\\."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 30, + line = 31, messageRegExp = "Can't generate mapping method with primitive return type\\."), @Diagnostic(type = ErroneousMapper.class, kind = Kind.ERROR, - line = 32, - messageRegExp = "Can't generate mapping method with primitive parameter type\\.") + line = 33, + messageRegExp = "Can't generate mapping method with primitive parameter type\\."), + @Diagnostic(type = ErroneousMapper.class, + kind = Kind.ERROR, + line = 35, + messageRegExp = + "Can't generate mapping method that has a parameter annotated with @TargetType\\.") } ) public void shouldFailToGenerateMappings() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/Bar1.java b/processor/src/test/java/org/mapstruct/ap/test/factories/Bar1.java index 9d4f32a58..2a69af0d2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/Bar1.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/Bar1.java @@ -21,7 +21,7 @@ package org.mapstruct.ap.test.factories; /** * @author Sjaak Derksen */ -public class Bar1 { +public class Bar1 implements FactoryCreatable { private String prop; private final String someTypeProp; diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryCreatable.java b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryCreatable.java new file mode 100644 index 000000000..ad7edbbec --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryCreatable.java @@ -0,0 +1,27 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.factories; + +/** + * @author Andreas Gudian + * + */ +public interface FactoryCreatable { + void setProp(String prop); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java index 38c85b61c..c67c158ad 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/FactoryTest.java @@ -18,6 +18,8 @@ */ package org.mapstruct.ap.test.factories; +import static org.fest.assertions.Assertions.assertThat; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -29,8 +31,6 @@ import org.mapstruct.ap.testutil.MapperTestBase; import org.mapstruct.ap.testutil.WithClasses; import org.testng.annotations.Test; -import static org.fest.assertions.Assertions.assertThat; - /** * @author Sjaak Derksen */ @@ -38,7 +38,7 @@ import static org.fest.assertions.Assertions.assertThat; @WithClasses({ Bar1.class, Foo1.class, Bar2.class, Foo2.class, Bar3.class, Foo3.class, BarFactory.class, org.mapstruct.ap.test.factories.b.BarFactory.class, Source.class, SourceTargetMapperAndBar2Factory.class, - Target.class, CustomList.class, CustomListImpl.class, CustomMap.class, CustomMapImpl.class + Target.class, CustomList.class, CustomListImpl.class, CustomMap.class, CustomMapImpl.class, FactoryCreatable.class }) public class FactoryTest extends MapperTestBase { @Test @@ -87,4 +87,19 @@ public class FactoryTest extends MapperTestBase { source.setPropMap( fooMap ); return source; } + + @Test + @IssueKey( "136" ) + @WithClasses( { GenericFactory.class, SourceTargetMapperWithGenericFactory.class } ) + public void shouldUseGenericFactory() { + SourceTargetMapperWithGenericFactory mapper = SourceTargetMapperWithGenericFactory.INSTANCE; + + Foo1 foo1 = new Foo1(); + foo1.setProp( "foo1" ); + Bar1 bar1 = mapper.fromFoo1( foo1 ); + + assertThat( bar1 ).isNotNull(); + assertThat( bar1.getSomeTypeProp() ).isEqualTo( "created by GenericFactory" ); + assertThat( bar1.getProp() ).isEqualTo( "foo1" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/GenericFactory.java b/processor/src/test/java/org/mapstruct/ap/test/factories/GenericFactory.java new file mode 100644 index 000000000..d9592e3b9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/GenericFactory.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.factories; + +import org.mapstruct.TargetType; + +/** + * @author Andreas Gudian + * + */ +public class GenericFactory { + @SuppressWarnings( "unchecked" ) + public T createNew(@TargetType Class clazz) { + if ( clazz == Bar1.class ) { + return (T) new Bar1( "created by GenericFactory" ); + } + + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperWithGenericFactory.java b/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperWithGenericFactory.java new file mode 100644 index 000000000..d9ab7ae3d --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/factories/SourceTargetMapperWithGenericFactory.java @@ -0,0 +1,33 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.factories; + +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper( uses = { GenericFactory.class } ) +public interface SourceTargetMapperWithGenericFactory { + SourceTargetMapperWithGenericFactory INSTANCE = Mappers.getMapper( SourceTargetMapperWithGenericFactory.class ); + + Bar1 fromFoo1(Foo1 foo1); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/BaseType.java b/processor/src/test/java/org/mapstruct/ap/test/references/BaseType.java new file mode 100644 index 000000000..3f1c7f920 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/BaseType.java @@ -0,0 +1,27 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +/** + * @author Andreas Gudian + * + */ +public class BaseType { + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedCustomMapper.java b/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedCustomMapper.java index 57ff5620b..00e11d9ab 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedCustomMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedCustomMapper.java @@ -18,6 +18,10 @@ */ package org.mapstruct.ap.test.references; +import java.util.Map; + +import org.mapstruct.TargetType; + /** * @author Andreas Gudian * @@ -26,4 +30,24 @@ public class ReferencedCustomMapper { public long incrementingIntToLong(int source) { return source + 1; } + + @SuppressWarnings( "unchecked" ) + public T convert(String string, @TargetType Class clazz) { + if ( clazz == SomeType.class ) { + return (T) new SomeType( string ); + } + else if ( clazz == SomeOtherType.class ) { + return (T) new SomeOtherType( string ); + } + + return null; + } + + /** + * This method should not be chosen for the mapping, as our types are never within the bounds of + * {@code T extends Map} + */ + public > T unused(String string, @TargetType Class clazz) { + throw new RuntimeException( "should never be called" ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedMapperTest.java b/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedMapperTest.java index 9233a492c..6c8486511 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedMapperTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/ReferencedMapperTest.java @@ -19,6 +19,12 @@ package org.mapstruct.ap.test.references; import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.MapAssert.entry; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.MapperTestBase; @@ -31,7 +37,7 @@ import org.testng.annotations.Test; */ @IssueKey( "82" ) @WithClasses( { Bar.class, Foo.class, FooMapper.class, ReferencedCustomMapper.class, Source.class, - SourceTargetMapper.class, Target.class } ) + SourceTargetMapper.class, Target.class, BaseType.class, SomeType.class, SomeOtherType.class } ) public class ReferencedMapperTest extends MapperTestBase { @Test public void referencedMappersAreInstatiatedCorrectly() { @@ -41,6 +47,7 @@ public class ReferencedMapperTest extends MapperTestBase { assertThat( target.getProp1() ).isEqualTo( 43 ); assertThat( target.getProp2() ).isNotNull(); assertThat( target.getProp2().getProp1() ).isEqualTo( "foo" ); + assertThat( target.getProp3().getValue() ).isEqualTo( "prop3" ); } private Source createSource() { @@ -50,7 +57,46 @@ public class ReferencedMapperTest extends MapperTestBase { Foo prop2 = new Foo(); prop2.setProp1( "foo" ); source.setProp2( prop2 ); + source.setProp3( "prop3" ); return source; } + + @Test + @IssueKey( "136" ) + public void shouldUseGenericFactoryForIterable() { + List result = SourceTargetMapper.INSTANCE.fromStringList( Arrays.asList( "foo1", "foo2" ) ); + + assertThat( result ).onProperty( "value" ).containsExactly( "foo1", "foo2" ); + } + + @Test + @IssueKey( "136" ) + public void shouldUseGenericFactoryForMap() { + Map source = new HashMap(); + source.put( "foo1", "bar1" ); + source.put( "foo2", "bar2" ); + Map result = SourceTargetMapper.INSTANCE.fromStringMap( source ); + + assertThat( result ).hasSize( 2 ); + assertThat( result ).includes( + entry( new SomeType( "foo1" ), new SomeOtherType( "bar1" ) ), + entry( new SomeType( "foo2" ), new SomeOtherType( "bar2" ) ) ); + } + + @Test + @IssueKey( "136" ) + @WithClasses( { SourceTargetMapperWithPrimitives.class, SourceWithWrappers.class, TargetWithPrimitives.class } ) + public void shouldMapPrimitivesWithCustomMapper() { + SourceWithWrappers source = new SourceWithWrappers(); + source.setProp1( new SomeType( "42" ) ); + source.setProp2( new SomeType( "1701" ) ); + source.setProp3( new SomeType( "true" ) ); + + TargetWithPrimitives result = SourceTargetMapperWithPrimitives.INSTANCE.sourceToTarget( source ); + + assertThat( result.getProp1() ).isEqualTo( 42 ); + assertThat( result.getProp2() ).isEqualTo( 1701 ); + assertThat( result.isProp3() ).isEqualTo( true ); + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/SomeOtherType.java b/processor/src/test/java/org/mapstruct/ap/test/references/SomeOtherType.java new file mode 100644 index 000000000..dd0bba24b --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/SomeOtherType.java @@ -0,0 +1,70 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +/** + * @author Andreas Gudian + * + */ +public class SomeOtherType extends BaseType { + private String value; + + public SomeOtherType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( value == null ) ? 0 : value.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + SomeOtherType other = (SomeOtherType) obj; + if ( value == null ) { + if ( other.value != null ) { + return false; + } + } + else if ( !value.equals( other.value ) ) { + return false; + } + return true; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/SomeType.java b/processor/src/test/java/org/mapstruct/ap/test/references/SomeType.java new file mode 100644 index 000000000..072e23e15 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/SomeType.java @@ -0,0 +1,70 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +/** + * @author Andreas Gudian + * + */ +public class SomeType extends BaseType { + private String value; + + public SomeType(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ( ( value == null ) ? 0 : value.hashCode() ); + return result; + } + + @Override + public boolean equals(Object obj) { + if ( this == obj ) { + return true; + } + if ( obj == null ) { + return false; + } + if ( getClass() != obj.getClass() ) { + return false; + } + SomeType other = (SomeType) obj; + if ( value == null ) { + if ( other.value != null ) { + return false; + } + } + else if ( !value.equals( other.value ) ) { + return false; + } + return true; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/Source.java b/processor/src/test/java/org/mapstruct/ap/test/references/Source.java index 9051df79e..b3b11793f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/Source.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/Source.java @@ -25,6 +25,7 @@ package org.mapstruct.ap.test.references; public class Source { private int prop1; private Foo prop2; + private String prop3; public int getProp1() { return prop1; @@ -41,4 +42,12 @@ public class Source { public void setProp2(Foo prop2) { this.prop2 = prop2; } + + public String getProp3() { + return prop3; + } + + public void setProp3(String prop3) { + this.prop3 = prop3; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapper.java index ee66db22a..e47333b02 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapper.java @@ -18,6 +18,9 @@ */ package org.mapstruct.ap.test.references; +import java.util.List; +import java.util.Map; + import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; @@ -30,4 +33,8 @@ public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); Target sourceToTarget(Source source); + + List fromStringList(List stringList); + + Map fromStringMap(Map stringStringMap); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapperWithPrimitives.java b/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapperWithPrimitives.java new file mode 100644 index 000000000..e3189b0c9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/SourceTargetMapperWithPrimitives.java @@ -0,0 +1,50 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +import org.mapstruct.Mapper; +import org.mapstruct.TargetType; +import org.mapstruct.factory.Mappers; + +/** + * @author Andreas Gudian + * + */ +@Mapper +public abstract class SourceTargetMapperWithPrimitives { + public static final SourceTargetMapperWithPrimitives INSTANCE = + Mappers.getMapper( SourceTargetMapperWithPrimitives.class ); + + public abstract TargetWithPrimitives sourceToTarget(SourceWithWrappers source); + + @SuppressWarnings( "unchecked" ) + public T convert(@TargetType Class clazz, SomeType wrapper) { + if ( clazz == int.class ) { + return (T) Integer.valueOf( wrapper.getValue() ); + } + else if ( clazz == long.class ) { + return (T) Long.valueOf( wrapper.getValue() ); + } + else if ( clazz == boolean.class ) { + return (T) Boolean.valueOf( wrapper.getValue() ); + } + + return null; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/SourceWithWrappers.java b/processor/src/test/java/org/mapstruct/ap/test/references/SourceWithWrappers.java new file mode 100644 index 000000000..4b9a81968 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/SourceWithWrappers.java @@ -0,0 +1,54 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +/** + * @author Andreas Gudian + * + */ +public class SourceWithWrappers { + private SomeType prop1; + private SomeType prop2; + private SomeType prop3; + + public SomeType getProp1() { + return prop1; + } + + public void setProp1(SomeType prop1) { + this.prop1 = prop1; + } + + public SomeType getProp2() { + return prop2; + } + + public void setProp2(SomeType prop2) { + this.prop2 = prop2; + } + + public SomeType getProp3() { + return prop3; + } + + public void setProp3(SomeType prop3) { + this.prop3 = prop3; + } + +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/Target.java b/processor/src/test/java/org/mapstruct/ap/test/references/Target.java index 0aa12b397..de4406bb7 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/references/Target.java +++ b/processor/src/test/java/org/mapstruct/ap/test/references/Target.java @@ -18,6 +18,7 @@ */ package org.mapstruct.ap.test.references; + /** * @author Andreas Gudian * @@ -25,6 +26,7 @@ package org.mapstruct.ap.test.references; public class Target { private long prop1; private Bar prop2; + private SomeType prop3; public long getProp1() { return prop1; @@ -41,4 +43,12 @@ public class Target { public void setProp2(Bar prop2) { this.prop2 = prop2; } + + public SomeType getProp3() { + return prop3; + } + + public void setProp3(SomeType prop3) { + this.prop3 = prop3; + } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/references/TargetWithPrimitives.java b/processor/src/test/java/org/mapstruct/ap/test/references/TargetWithPrimitives.java new file mode 100644 index 000000000..12fb466c0 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/references/TargetWithPrimitives.java @@ -0,0 +1,53 @@ +/** + * Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/) + * and/or other contributors as indicated by the @authors tag. See the + * copyright.txt file in the distribution for a full listing of all + * contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.mapstruct.ap.test.references; + +/** + * @author Andreas Gudian + * + */ +public class TargetWithPrimitives { + private int prop1; + private long prop2; + private boolean prop3; + + public int getProp1() { + return prop1; + } + + public void setProp1(int prop1) { + this.prop1 = prop1; + } + + public long getProp2() { + return prop2; + } + + public void setProp2(long prop2) { + this.prop2 = prop2; + } + + public boolean isProp3() { + return prop3; + } + + public void setProp3(boolean prop3) { + this.prop3 = prop3; + } +}