diff --git a/core/src/main/java/org/mapstruct/MappingTarget.java b/core/src/main/java/org/mapstruct/MappingTarget.java new file mode 100644 index 000000000..6d689b39f --- /dev/null +++ b/core/src/main/java/org/mapstruct/MappingTarget.java @@ -0,0 +1,36 @@ +/** + * Copyright 2012-2013 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 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 + */ +@Target( ElementType.PARAMETER ) +public @interface MappingTarget { +} diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 0ad4cec92..2f50b97c7 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -43,6 +43,7 @@ import org.mapstruct.IterableMapping; import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import org.mapstruct.Mappings; import org.mapstruct.ap.model.Options; import org.mapstruct.ap.model.ReportingPolicy; @@ -76,7 +77,8 @@ import org.mapstruct.ap.processor.ModelElementProcessor.ProcessorContext; @GeneratePrism(value = Mapping.class, publicAccess = true), @GeneratePrism(value = Mappings.class, publicAccess = true), @GeneratePrism(value = IterableMapping.class, publicAccess = true), - @GeneratePrism(value = MapMapping.class, publicAccess = true) + @GeneratePrism(value = MapMapping.class, publicAccess = true), + @GeneratePrism(value = MappingTarget.class, publicAccess = true) }) @SupportedOptions({ MappingProcessor.SUPPRESS_GENERATOR_TIMESTAMP, diff --git a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java index 1cd7134c9..3ac1cdf77 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -21,6 +21,8 @@ package org.mapstruct.ap.model; import java.util.List; import java.util.Set; +import org.mapstruct.ap.model.source.Parameter; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one * bean type to another, optionally configured by one or more @@ -32,9 +34,10 @@ public class BeanMappingMethod extends MappingMethod { private final List propertyMappings; - public BeanMappingMethod(String name, String parameterName, Type sourceType, Type targetType, + public BeanMappingMethod(String name, List parameters, List sourceParameters, + Type resultType, String resultName, Type returnType, List propertyMappings) { - super( name, parameterName, sourceType, targetType ); + super( name, parameters, sourceParameters, resultType, resultName, returnType ); this.propertyMappings = propertyMappings; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java index 1d43483d9..a14ad5aa1 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -18,8 +18,11 @@ */ package org.mapstruct.ap.model; +import java.util.List; import java.util.Set; +import org.mapstruct.ap.model.source.Parameter; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one iterable type to another. The collection * elements are mapped either by a {@link TypeConversion} or another mapping method. @@ -31,9 +34,10 @@ public class IterableMappingMethod extends MappingMethod { private final MappingMethodReference elementMappingMethod; private final TypeConversion conversion; - public IterableMappingMethod(String name, String parameterName, Type sourceType, Type targetType, + public IterableMappingMethod(String name, List parameters, List sourceParameters, + Type resultType, String resultName, Type returnType, MappingMethodReference elementMappingMethod, TypeConversion conversion) { - super( name, parameterName, sourceType, targetType ); + super( name, parameters, sourceParameters, resultType, resultName, returnType ); this.elementMappingMethod = elementMappingMethod; this.conversion = conversion; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java index ea06a614f..e127765bb 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java @@ -18,8 +18,11 @@ */ package org.mapstruct.ap.model; +import java.util.List; import java.util.Set; +import org.mapstruct.ap.model.source.Parameter; + /** * A {@link MappingMethod} implemented by a {@link Mapper} class which maps one {@code Map} type to another. Keys and * values are mapped either by a {@link TypeConversion} or another mapping method if required. @@ -33,10 +36,11 @@ public class MapMappingMethod extends MappingMethod { private final MappingMethodReference valueMappingMethod; private final TypeConversion valueConversion; - public MapMappingMethod(String name, String parameterName, Type sourceType, Type targetType, + public MapMappingMethod(String name, List parameters, List sourceParameters, Type resultType, + String resultName, Type returnType, MappingMethodReference keyMappingMethod, TypeConversion keyConversion, MappingMethodReference valueMappingMethod, TypeConversion valueConversion) { - super( name, parameterName, sourceType, targetType ); + super( name, parameters, sourceParameters, resultType, resultName, returnType ); this.keyMappingMethod = keyMappingMethod; this.keyConversion = keyConversion; @@ -68,8 +72,4 @@ public class MapMappingMethod extends MappingMethod { return types; } - - public String getReturnValueName() { - return "map"; - } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java index 5a57d02d3..2aeb14ace 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java @@ -19,8 +19,11 @@ package org.mapstruct.ap.model; import java.util.HashSet; +import java.util.List; import java.util.Set; +import org.mapstruct.ap.model.source.Parameter; + /** * A method implemented or referenced by a {@link Mapper} class. * @@ -29,38 +32,62 @@ import java.util.Set; public abstract class MappingMethod extends AbstractModelElement { private final String name; - private final String parameterName; - private final Type sourceType; - private final Type targetType; + private final List parameters; + private final List sourceParameters; + private final Type resultType; + private final String resultName; + private final Type returnType; + private final boolean existingInstanceMapping; - protected MappingMethod(String name, String parameterName, Type sourceType, Type targetType) { + protected MappingMethod(String name, List parameters, List sourceParameters, Type resultType, + String resultName, Type returnType) { this.name = name; - this.parameterName = parameterName; - this.sourceType = sourceType; - this.targetType = targetType; + this.parameters = parameters; + this.sourceParameters = sourceParameters; + this.resultType = resultType; + this.resultName = resultName; + this.returnType = returnType; + this.existingInstanceMapping = + ( null != parameters && null != sourceParameters && parameters.size() > sourceParameters.size() ); } public String getName() { return name; } - public String getParameterName() { - return parameterName; + public List getParameters() { + return parameters; } - public Type getSourceType() { - return sourceType; + public List getSourceParameters() { + return sourceParameters; } - public Type getTargetType() { - return targetType; + public Type getResultType() { + return resultType; + } + + public String getResultName() { + return resultName; + } + + public Type getReturnType() { + return returnType; + } + + public boolean isExistingInstanceMapping() { + return existingInstanceMapping; } @Override public Set getImportTypes() { Set types = new HashSet(); - types.add( getSourceType() ); - types.add( getTargetType() ); + + for ( Parameter param : getParameters() ) { + types.add( param.getType() ); + } + + types.add( getReturnType() ); return types; } @@ -69,7 +96,7 @@ public abstract class MappingMethod extends AbstractModelElement { public String toString() { return "MappingMethod {" + "\n name='" + name + "\'," + - "\n parameterName='" + parameterName + "\'," + + "\n parameters='" + parameters + "\'," + "\n}"; } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/MappingMethodReference.java b/processor/src/main/java/org/mapstruct/ap/model/MappingMethodReference.java index 311546cf2..874b4462e 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MappingMethodReference.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingMethodReference.java @@ -30,9 +30,8 @@ public class MappingMethodReference extends MappingMethod { private final Type declaringMapper; - public MappingMethodReference(Type declaringMapper, String name, String parameterName, Type sourceType, - Type targetType) { - super( name, parameterName, sourceType, targetType ); + public MappingMethodReference(Type declaringMapper, String name) { + super( name, null, null, null, null, null ); this.declaringMapper = declaringMapper; } @@ -41,10 +40,6 @@ public class MappingMethodReference extends MappingMethod { } public Set getReferencedTypes() { - Set types = new HashSet(); - types.add( getSourceType() ); - types.add( getTargetType() ); - - return types; + return new HashSet(); } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/Options.java b/processor/src/main/java/org/mapstruct/ap/model/Options.java index 6af2b2084..2101960b3 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/Options.java +++ b/processor/src/main/java/org/mapstruct/ap/model/Options.java @@ -22,7 +22,7 @@ package org.mapstruct.ap.model; * The options passed to the code generator. * * @author Andreas Gudian - * @autor Gunnar Morling + * @author Gunnar Morling */ public class Options { private final boolean suppressGeneratorTimestamp; diff --git a/processor/src/main/java/org/mapstruct/ap/model/Type.java b/processor/src/main/java/org/mapstruct/ap/model/Type.java index dd8f8ea23..b0c2ccb13 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/Type.java +++ b/processor/src/main/java/org/mapstruct/ap/model/Type.java @@ -38,6 +38,10 @@ import org.mapstruct.ap.util.Strings; * @author Gunnar Morling */ public class Type extends AbstractModelElement implements Comparable { + /** + * Type representing {@code void} + */ + public static final Type VOID = new Type( "void" ); private static final Set PRIMITIVE_TYPE_NAMES = new HashSet( Arrays.asList( "boolean", "char", "byte", "short", "int", "long", "float", "double" ) @@ -61,6 +65,7 @@ public class Type extends AbstractModelElement implements Comparable { DEFAULT_MAP_IMPLEMENTATION_TYPES.put( Map.class.getName(), forClass( HashMap.class ) ); } + private final String canonicalName; private final String packageName; private final String name; private final List typeParameters; @@ -77,6 +82,7 @@ public class Type extends AbstractModelElement implements Comparable { if ( pakkage != null ) { return new Type( + clazz.getCanonicalName(), pakkage.getName(), clazz.getSimpleName(), clazz.isEnum(), @@ -92,15 +98,16 @@ public class Type extends AbstractModelElement implements Comparable { } public Type(String name) { - this( null, name, false, false, false, false, Collections.emptyList() ); + this( name, null, name, false, false, false, false, Collections.emptyList() ); } public Type(String packageName, String name) { - this( packageName, name, false, false, false, false, Collections.emptyList() ); + this( packageName + "." + name, packageName, name, false, false, false, false, Collections.emptyList() ); } - public Type(String packageName, String name, boolean isEnumType, boolean isCollectionType, + public Type(String canonicalName, String packageName, String name, boolean isEnumType, boolean isCollectionType, boolean isIterableType, boolean isMapType, List typeParameters) { + this.canonicalName = canonicalName; this.packageName = packageName; this.name = name; this.isEnumType = isEnumType; @@ -126,6 +133,7 @@ public class Type extends AbstractModelElement implements Comparable { if ( isMapType ) { Type mapType = DEFAULT_MAP_IMPLEMENTATION_TYPES.get( packageName + "." + name ); mapImplementationType = mapType != null ? new Type( + mapType.getPackageName() + "." + mapType.getName(), mapType.getPackageName(), mapType.getName(), mapType.isEnumType(), @@ -140,6 +148,10 @@ public class Type extends AbstractModelElement implements Comparable { } } + public String getCanonicalName() { + return canonicalName; + } + public String getPackageName() { return packageName; } 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 3887b71d2..505a1793a 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 @@ -18,7 +18,10 @@ */ package org.mapstruct.ap.model.source; +import java.util.Arrays; import java.util.Collections; +import java.util.Iterator; +import java.util.List; import java.util.Map; import javax.lang.model.element.ExecutableElement; @@ -34,23 +37,28 @@ public class Method { private final Type declaringMapper; private final ExecutableElement executable; - private final String parameterName; - private final Type sourceType; - private final Type targetType; + private final List parameters; + private final List sourceParameters; + private final String resultName; + private final Type resultType; + private final Type returnType; private Map mappings; private IterableMapping iterableMapping; private MapMapping mapMapping; - public static Method forMethodRequiringImplementation(ExecutableElement executable, String parameterName, - Type sourceType, Type targetType, + public static Method forMethodRequiringImplementation(ExecutableElement executable, List parameters, + List sourceParameters, Type resultType, + String resultName, Type targetType, Map mappings, IterableMapping iterableMapping, MapMapping mapMapping) { return new Method( null, executable, - parameterName, - sourceType, + parameters, + sourceParameters, + resultType, + resultName, targetType, mappings, iterableMapping, @@ -64,8 +72,10 @@ public class Method { return new Method( declaringMapper, executable, - parameterName, - sourceType, + Arrays.asList( new Parameter( parameterName, sourceType ) ), + Arrays.asList( new Parameter( parameterName, sourceType ) ), + targetType, + null, targetType, Collections.emptyMap(), null, @@ -73,14 +83,18 @@ public class Method { ); } - private Method(Type declaringMapper, ExecutableElement executable, String parameterName, Type sourceType, - Type targetType, Map mappings, IterableMapping iterableMapping, + private Method(Type declaringMapper, ExecutableElement executable, List parameters, + List sourceParameters, Type resultType, String resultName, + Type returnType, + Map mappings, IterableMapping iterableMapping, MapMapping mapMapping) { this.declaringMapper = declaringMapper; this.executable = executable; - this.parameterName = parameterName; - this.sourceType = sourceType; - this.targetType = targetType; + this.parameters = parameters; + this.sourceParameters = sourceParameters; + this.resultType = resultType; + this.resultName = resultName; + this.returnType = returnType; this.mappings = mappings; this.iterableMapping = iterableMapping; this.mapMapping = mapMapping; @@ -105,16 +119,24 @@ public class Method { return executable.getSimpleName().toString(); } - public String getParameterName() { - return parameterName; + public List getParameters() { + return parameters; } - public Type getSourceType() { - return sourceType; + public String getResultName() { + return resultName; } - public Type getTargetType() { - return targetType; + public List getSourceParameters() { + return sourceParameters; + } + + public Type getResultType() { + return resultType; + } + + public Type getReturnType() { + return returnType; } public Map getMappings() { @@ -143,16 +165,20 @@ public class Method { public boolean reverses(Method method) { return - equals( sourceType, method.getTargetType() ) && - equals( targetType, method.getSourceType() ); + equals( getSingleSourceType(), method.getReturnType() ) + && equals( returnType, method.getSingleSourceType() ); + } + + public Type getSingleSourceType() { + return sourceParameters.size() == 1 ? sourceParameters.get( 0 ).getType() : null; } public boolean isIterableMapping() { - return sourceType.isIterableType() && targetType.isIterableType(); + return getSingleSourceType().isIterableType() && resultType.isIterableType(); } public boolean isMapMapping() { - return sourceType.isMapType() && targetType.isMapType(); + return getSingleSourceType().isMapType() && resultType.isMapType(); } private boolean equals(Object o1, Object o2) { @@ -161,6 +187,19 @@ public class Method { @Override public String toString() { - return targetType + " " + getName() + "(" + sourceType + " " + parameterName + ")"; + return returnType + " " + getName() + "(" + getParamsList() + ")"; + } + + private String getParamsList() { + StringBuilder sb = new StringBuilder(); + for ( Iterator it = parameters.iterator(); it.hasNext(); ) { + Parameter param = it.next(); + sb.append( param.getType() ).append( " " ).append( param.getName() ); + if ( it.hasNext() ) { + sb.append( ", " ); + } + } + + return sb.toString(); } } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Parameter.java b/processor/src/main/java/org/mapstruct/ap/model/source/Parameter.java index 7406b0ac4..34db914e4 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Parameter.java @@ -29,10 +29,16 @@ public class Parameter { private final String name; private final Type type; + private final boolean mappingTarget; - public Parameter(String name, Type type) { + public Parameter(String name, Type type, boolean mappingTarget) { this.name = name; this.type = type; + this.mappingTarget = mappingTarget; + } + + public Parameter(String name, Type type) { + this( name, type, false ); } public String getName() { @@ -43,8 +49,12 @@ public class Parameter { return type; } + public boolean isMappingTarget() { + return mappingTarget; + } + @Override public String toString() { - return type.toString() + " " + name; + return ( mappingTarget ? "@MappingTarget " : "" ) + type.toString() + " " + name; } } 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 093df31a8..47643f996 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -54,6 +54,7 @@ import org.mapstruct.ap.model.Type; import org.mapstruct.ap.model.TypeConversion; import org.mapstruct.ap.model.source.Mapping; import org.mapstruct.ap.model.source.Method; +import org.mapstruct.ap.model.source.Parameter; import org.mapstruct.ap.util.Executables; import org.mapstruct.ap.util.Filters; import org.mapstruct.ap.util.Strings; @@ -200,19 +201,14 @@ public class MapperCreationProcessor implements ModelElementProcessor mappings = method.getMappings(); - TypeElement returnTypeElement = (TypeElement) typeUtils.asElement( method.getExecutable().getReturnType() ); - TypeElement parameterElement = (TypeElement) typeUtils.asElement( - method.getExecutable() - .getParameters() - .get( 0 ) - .asType() - ); + TypeElement resultTypeElement = elementUtils.getTypeElement( method.getResultType().getCanonicalName() ); + TypeElement parameterElement = elementUtils.getTypeElement( method.getSingleSourceType().getCanonicalName() ); List sourceGetters = Filters.getterMethodsIn( elementUtils.getAllMembers( parameterElement ) ); List targetSetters = Filters.setterMethodsIn( - elementUtils.getAllMembers( returnTypeElement ) + elementUtils.getAllMembers( resultTypeElement ) ); Set sourceProperties = executables.getPropertyNames( @@ -257,9 +253,11 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Method method, ExecutableElement getterMethod, ExecutableElement setterMethod, String dateFormat) { Type sourceType = executables.retrieveReturnType( getterMethod ); - Type targetType = executables.retrieveParameter( setterMethod ).getType(); + Type targetType = executables.retrieveSingleParameter( setterMethod ).getType(); MappingMethodReference propertyMappingMethod = getMappingMethodReference( methods, sourceType, targetType ); TypeConversion conversion = getConversion( sourceType, targetType, dateFormat, - method.getParameterName() + "." + getterMethod.getSimpleName().toString() + "()" + method.getSourceParameters().get( 0 ).getName() + "." + + getterMethod.getSimpleName().toString() + "()" ); PropertyMapping property = new PropertyMapping( - method.getParameterName(), - Introspector.decapitalize( method.getTargetType().getName() ), + method.getSourceParameters().get( 0 ).getName(), + method.getResultName(), executables.getPropertyName( getterMethod ), getterMethod.getSimpleName().toString(), sourceType, @@ -360,8 +359,8 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Method method) { - Type sourceElementType = method.getSourceType().getTypeParameters().get( 0 ); - Type targetElementType = method.getTargetType().getTypeParameters().get( 0 ); + Type sourceElementType = method.getSourceParameters().get( 0 ).getType().getTypeParameters().get( 0 ); + Type targetElementType = method.getResultType().getTypeParameters().get( 0 ); TypeConversion conversion = getConversion( sourceElementType, @@ -372,19 +371,24 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Method method) { - Type sourceKeyType = method.getSourceType().getTypeParameters().get( 0 ); - Type sourceValueType = method.getSourceType().getTypeParameters().get( 1 ); - Type targetKeyType = method.getTargetType().getTypeParameters().get( 0 ); - Type targetValueType = method.getTargetType().getTypeParameters().get( 1 ); + List sourceTypeParams = method.getSourceParameters().get( 0 ).getType().getTypeParameters(); + Type sourceKeyType = sourceTypeParams.get( 0 ); + Type sourceValueType = sourceTypeParams.get( 1 ); + + List resultTypeParams = method.getResultType().getTypeParameters(); + Type targetKeyType = resultTypeParams.get( 0 ); + Type targetValueType = resultTypeParams.get( 1 ); String keyDateFormat = method.getMapMapping() != null ? method.getMapMapping().getKeyFormat() : null; String valueDateFormat = method.getMapMapping() != null ? method.getMapMapping().getValueFormat() : null; @@ -406,9 +410,11 @@ public class MapperCreationProcessor implements ModelElementProcessor methods, Type parameterType, Type returnType) { for ( Method oneMethod : methods ) { - if ( oneMethod.getSourceType().equals( parameterType ) && oneMethod.getTargetType().equals( returnType ) ) { + Parameter singleSourceParam = oneMethod.getSourceParameters().get( 0 ); + + if ( singleSourceParam.getType().equals( parameterType ) && + oneMethod.getReturnType().equals( returnType ) ) { return new MappingMethodReference( oneMethod.getDeclaringMapper(), - oneMethod.getName(), - oneMethod.getParameterName(), - oneMethod.getSourceType(), - oneMethod.getTargetType() + oneMethod.getName() ); } } 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 2d9f34615..0c3d496f2 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -18,6 +18,7 @@ */ package org.mapstruct.ap.processor; +import java.beans.Introspector; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -115,19 +116,26 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters = executables.retrieveParameters( method ); Type returnType = executables.retrieveReturnType( method ); //add method with property mappings if an implementation needs to be generated if ( implementationRequired ) { - boolean isValid = checkParameterAndReturnType( method, parameter.getType(), returnType ); + List sourceParameters = extractSourceParameters( parameters ); + Parameter targetParameter = extractTargetParameter( parameters ); + Type resultType = selectResultType( returnType, targetParameter ); + + boolean isValid = + checkParameterAndReturnType( method, sourceParameters, targetParameter, resultType, returnType ); if ( isValid ) { return Method.forMethodRequiringImplementation( method, - parameter.getName(), - parameter.getType(), + parameters, + sourceParameters, + resultType, + selectResultName( targetParameter, resultType ), returnType, getMappings( method ), IterableMapping.fromPrism( IterableMappingPrism.getInstanceOn( method ) ), @@ -139,20 +147,102 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters) { + for ( Parameter param : parameters ) { + if ( param.isMappingTarget() ) { + return param; + } + } + + return null; + } + + private List extractSourceParameters(List parameters) { + List sourceParameters = new ArrayList( parameters.size() ); + for ( Parameter param : parameters ) { + if ( !param.isMappingTarget() ) { + sourceParameters.add( param ); + } + } + + return sourceParameters; + } + + private String selectResultName(Parameter targetParameter, Type resultType) { + if ( null != targetParameter ) { + return targetParameter.getName(); + } + else { + return Introspector.decapitalize( resultType.getName() ); + } + } + + private Type selectResultType(Type returnType, Parameter targetParameter) { + if ( null != targetParameter ) { + return targetParameter.getType(); + } + else { + return returnType; + } + } + + private boolean checkParameterAndReturnType(ExecutableElement method, List sourceParameters, + Parameter targetParameter, Type resultType, Type returnType) { + if ( sourceParameters.isEmpty() ) { + messager.printMessage( Kind.ERROR, "Can't generate mapping method with no input arguments.", method ); + return false; + } + + if ( sourceParameters.size() > 1 ) { + messager.printMessage( + Kind.ERROR, + "Mappings from more than one source objects are not yet supported.", + method + ); + return false; + } + + if ( targetParameter != null && ( sourceParameters.size() + 1 != method.getParameters().size() ) ) { + messager.printMessage( + Kind.ERROR, + "Can't generate mapping method with more than one @MappingTarget parameter.", + method + ); + return false; + } + + if ( resultType == Type.VOID ) { + messager.printMessage( Kind.ERROR, "Can't generate mapping method with return type void.", method ); + return false; + } + + if ( returnType != Type.VOID && !typeUtil.isAssignable( resultType, returnType ) ) { + messager.printMessage( + Kind.ERROR, + "The result type is not assignable to the the return type.", + method + ); + return false; + } + + Type parameterType = sourceParameters.get( 0 ).getType(); + + if ( parameterType.isIterableType() && !resultType.isIterableType() ) { messager.printMessage( Kind.ERROR, "Can't generate mapping method from iterable type to non-iterable type.", @@ -161,7 +251,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters = method.getParameters(); if ( parameters.size() != 1 ) { @@ -117,10 +120,42 @@ public class Executables { return new Parameter( parameter.getSimpleName().toString(), - typeUtil.retrieveType( parameter.asType() ) + typeUtil.retrieveType( parameter.asType() ), + false ); } + public List retrieveParameters(ExecutableElement method) { + List parameters = method.getParameters(); + List result = new ArrayList( parameters.size() ); + + boolean mappingTargetDefined = false; + for ( Iterator it = parameters.iterator(); it.hasNext(); ) { + VariableElement parameter = it.next(); + + boolean isExplicitMappingTarget = null != MappingTargetPrism.getInstanceOn( parameter ); + mappingTargetDefined |= isExplicitMappingTarget; + + result + .add( + new Parameter( + parameter.getSimpleName().toString(), + typeUtil.retrieveType( parameter.asType() ), + // the parameter is a mapping target, if it was either defined explicitly or if if this is the + // last parameter in a multi-argument void method + isExplicitMappingTarget + || ( !mappingTargetDefined && isMultiArgVoidMethod( method ) && !it.hasNext() ) + ) + ); + } + + return result; + } + + public boolean isMultiArgVoidMethod(ExecutableElement method) { + return method.getParameters().size() > 1 && Type.VOID == retrieveReturnType( method ); + } + public Type retrieveReturnType(ExecutableElement method) { return typeUtil.retrieveType( method.getReturnType() ); } diff --git a/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java b/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java index 7c67c75e6..3d3b072d7 100644 --- a/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java +++ b/processor/src/main/java/org/mapstruct/ap/util/TypeUtil.java @@ -23,6 +23,7 @@ import java.util.Collection; import java.util.List; import java.util.Map; import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -56,6 +57,7 @@ public class TypeUtil { } return new Type( + ( (TypeElement) type.asElement() ).getQualifiedName().toString(), elementUtils.getPackageOf( type.asElement() ).toString(), type.asElement().getSimpleName().toString(), type.asElement().getKind() == ElementKind.ENUM, @@ -85,8 +87,42 @@ public class TypeUtil { else if ( mirror.getKind() == TypeKind.DECLARED ) { return getType( ( (DeclaredType) mirror ) ); } + else if ( mirror.getKind() == TypeKind.VOID ) { + return Type.VOID; + } else { return new Type( mirror.toString() ); } } + + /** + * @param type1 first type + * @param type2 second type + * + * @return {@code true} if and only if the first type is assignable to the second + */ + public boolean isAssignable(Type type1, Type type2) { + if ( type1.equals( type2 ) ) { + return true; + } + + TypeMirror mirror1 = toTypeMirror( type1 ); + TypeMirror mirror2 = toTypeMirror( type2 ); + return null != mirror1 && null != mirror2 && typeUtils.isAssignable( mirror1, mirror2 ); + } + + private TypeMirror toTypeMirror(Type type) { + TypeElement rawType = elementUtils.getTypeElement( type.getCanonicalName() ); + + if ( null == rawType ) { + return null; + } + + TypeMirror[] parameters = new TypeMirror[type.getTypeParameters().size()]; + for ( int i = 0; i < type.getTypeParameters().size(); i++ ) { + parameters[i] = toTypeMirror( type.getTypeParameters().get( i ) ); + } + + return typeUtils.getDeclaredType( rawType, parameters ); + } } diff --git a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl index 11acaeb51..b88298d17 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.BeanMappingMethod.ftl @@ -19,16 +19,19 @@ --> @Override - public ${targetType.name} ${name}(${sourceType.name} ${parameterName}) { - if ( ${parameterName} == null ) { - return null; + public ${returnType.name} ${name}(<#list parameters as param>${param.type.name} ${param.name}<#if param_has_next>, ) { + if ( ${sourceParameters[0].name} == null ) { + return<#if returnType.name != "void"> null; } - - ${targetType.name} ${targetType.name?uncap_first} = new ${targetType.name}(); + <#if !existingInstanceMapping> + ${resultType.name} ${resultName} = new ${resultType.name}(); + <#list propertyMappings as propertyMapping> <@includeModel object=propertyMapping/> + <#if returnType.name != "void"> - return ${targetType.name?uncap_first}; + return ${resultName}; + } 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 0916185e1..7407abae0 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl @@ -19,23 +19,27 @@ --> @Override - public <@includeModel object=targetType/> ${name}(<@includeModel object=sourceType/> ${parameterName}) { - if ( ${parameterName} == null ) { - return null; + public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param.type/> ${param.name}<#if param_has_next>, ) { + if ( ${sourceParameters[0].name} == null ) { + return<#if returnType.name != "void"> null; } + <#if existingInstanceMapping> + ${resultName}.clear(); + <#else> <#-- Use the interface type on the left side, except it is java.lang.Iterable; use the implementation type - if present - on the right side --> - <#if targetType.name == "Iterable" && targetType.packageName == "java.lang">${targetType.iterableImplementationType.name}<#else>${targetType.name}<<@includeModel object=targetType.typeParameters[0]/>> ${targetType.name?uncap_first} = new <#if targetType.iterableImplementationType??>${targetType.iterableImplementationType.name}<#else>${targetType.name}<<@includeModel object=targetType.typeParameters[0]/>>(); + <#if resultType.name == "Iterable" && resultType.packageName == "java.lang">${resultType.iterableImplementationType.name}<#else>${resultType.name}<<@includeModel object=resultType.typeParameters[0]/>> ${resultName} = new <#if resultType.iterableImplementationType??>${resultType.iterableImplementationType.name}<#else>${resultType.name}<<@includeModel object=resultType.typeParameters[0]/>>(); + - for ( <@includeModel object=sourceType.typeParameters[0]/> ${sourceType.typeParameters[0].name?uncap_first} : ${parameterName} ) { + for ( <@includeModel object=sourceParameters[0].type.typeParameters[0]/> ${sourceParameters[0].type.typeParameters[0].name?uncap_first} : ${sourceParameters[0].name} ) { <#if elementMappingMethod??> - ${targetType.name?uncap_first}.add( <@includeModel object=elementMappingMethod input="${sourceType.typeParameters[0].name?uncap_first}"/> ); + ${resultName}.add( <@includeModel object=elementMappingMethod input="${sourceParameters[0].type.typeParameters[0].name?uncap_first}"/> ); <#else> <#if (conversion.exceptionTypes?size == 0) > - ${targetType.name?uncap_first}.add( <@includeModel object=conversion/> ); + ${resultName}.add( <@includeModel object=conversion/> ); <#else> try { - ${targetType.name?uncap_first}.add( <@includeModel object=conversion/> ); + ${resultName}.add( <@includeModel object=conversion/> ); } <#list conversion.exceptionTypes as exceptionType> catch( ${exceptionType.name} e ) { @@ -45,6 +49,8 @@ } + <#if returnType.name != "void"> - return ${targetType.name?uncap_first}; + return ${resultName}; + } 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 07ce9c77e..e20de89a6 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl @@ -19,23 +19,27 @@ --> @Override - public <@includeModel object=targetType /> ${name}(<@includeModel object=sourceType /> ${parameterName}) { - if ( ${parameterName} == null ) { - return null; + public <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param.type/> ${param.name}<#if param_has_next>, ) { + if ( ${sourceParameters[0].name} == null ) { + return<#if returnType.name != "void"> null; } - <@includeModel object=targetType /> ${returnValueName} = new <#if targetType.mapImplementationType??><@includeModel object=targetType.mapImplementationType /><#else><@includeModel object=targetType />(); + <#if existingInstanceMapping> + ${resultName}.clear(); + <#else> + <@includeModel object=resultType /> ${resultName} = new <#if resultType.mapImplementationType??><@includeModel object=resultType.mapImplementationType /><#else><@includeModel object=resultType />(); + - for ( Map.Entry<<#list sourceType.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > entry : ${parameterName}.entrySet() ) { + for ( Map.Entry<<#list sourceParameters[0].type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > entry : ${sourceParameters[0].name}.entrySet() ) { <#-- key --> <#if keyMappingMethod??> - <@includeModel object=targetType.typeParameters[0]/> key = <@includeModel object=keyMappingMethod input="entry.getKey()"/>; + <@includeModel object=resultType.typeParameters[0]/> key = <@includeModel object=keyMappingMethod input="entry.getKey()"/>; <#elseif keyConversion??> <#if (keyConversion.exceptionTypes?size == 0) > - <@includeModel object=targetType.typeParameters[0]/> key = <@includeModel object=keyConversion/>; + <@includeModel object=resultType.typeParameters[0]/> key = <@includeModel object=keyConversion/>; <#else> - <@includeModel object=targetType.typeParameters[0]/> key; + <@includeModel object=resultType.typeParameters[0]/> key; try { key = <@includeModel object=keyConversion/>; } @@ -46,16 +50,16 @@ <#else> - <@includeModel object=targetType.typeParameters[0]/> key = entry.getKey(); + <@includeModel object=resultType.typeParameters[0]/> key = entry.getKey(); <#-- value --> <#if valueMappingMethod??> - <@includeModel object=targetType.typeParameters[1]/> value = <@includeModel object=valueMappingMethod input="entry.getValue()"/>; + <@includeModel object=resultType.typeParameters[1]/> value = <@includeModel object=valueMappingMethod input="entry.getValue()"/>; <#elseif valueConversion??> <#if (valueConversion.exceptionTypes?size == 0) > - <@includeModel object=targetType.typeParameters[1]/> value = <@includeModel object=valueConversion/>; + <@includeModel object=resultType.typeParameters[1]/> value = <@includeModel object=valueConversion/>; <#else> - <@includeModel object=targetType.typeParameters[1]/> value; + <@includeModel object=resultType.typeParameters[1]/> value; try { value = <@includeModel object=valueConversion/>; } @@ -66,11 +70,13 @@ <#else> - <@includeModel object=targetType.typeParameters[1]/> value = entry.getValue(); + <@includeModel object=resultType.typeParameters[1]/> value = entry.getValue(); - ${returnValueName}.put( key, value ); + ${resultName}.put( key, value ); } + <#if returnType.name != "void"> - return ${returnValueName}; + return ${resultName}; + } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java index 45b20f1e8..b025484fc 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/DefaultCollectionImplementationTest.java @@ -18,8 +18,10 @@ */ package org.mapstruct.ap.test.collection.defaultimplementation; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.MapperTestBase; @@ -41,7 +43,7 @@ public class DefaultCollectionImplementationTest extends MapperTestBase { @IssueKey("6") public void shouldUseDefaultImplementationForList() { Source source = new Source(); - source.setFooList( Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ) ); + source.setFooList( createSourceFooList() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); @@ -52,7 +54,7 @@ public class DefaultCollectionImplementationTest extends MapperTestBase { @IssueKey("6") public void shouldUseDefaultImplementationForSet() { Source source = new Source(); - source.setFooSet( new HashSet( Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ) ) ); + source.setFooSet( new HashSet( createSourceFooList() ) ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); @@ -63,7 +65,7 @@ public class DefaultCollectionImplementationTest extends MapperTestBase { @IssueKey("6") public void shouldUseDefaultImplementationForCollection() { Source source = new Source(); - source.setFooCollection( Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ) ); + source.setFooCollection( createSourceFooList() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); @@ -74,10 +76,49 @@ public class DefaultCollectionImplementationTest extends MapperTestBase { @IssueKey("6") public void shouldUseDefaultImplementationForIterable() { Source source = new Source(); - source.setFooIterable( Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ) ); + source.setFooIterable( createSourceFooList() ); Target target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); assertThat( target ).isNotNull(); - assertThat( target.getFooIterable() ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + Iterable fooIterable = target.getFooIterable(); + assertResultList( fooIterable ); + } + + private void assertResultList(Iterable fooIterable) { + assertThat( fooIterable ).isNotNull(); + assertThat( fooIterable ).containsOnly( new TargetFoo( "Bob" ), new TargetFoo( "Alice" ) ); + } + + private List createSourceFooList() { + return Arrays.asList( new SourceFoo( "Bob" ), new SourceFoo( "Alice" ) ); + } + + @Test + @IssueKey("19") + public void existingMapping1() { + List target = new ArrayList(); + SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos1( createSourceFooList(), target ); + + assertResultList( target ); + } + + @Test + @IssueKey("19") + public void existingMapping2() { + List target = new ArrayList(); + SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos2( target, createSourceFooList() ); + + assertResultList( target ); + } + + @Test + @IssueKey("19") + public void existingMapping3() { + List target = new ArrayList(); + Iterable result = + SourceTargetMapper.INSTANCE.sourceFoosToTargetFoos3( createSourceFooList(), target ); + + assertThat( target == result ).isTrue(); + assertResultList( target ); } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java index c0bb111a8..20cc46392 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/defaultimplementation/SourceTargetMapper.java @@ -24,6 +24,7 @@ import java.util.Set; import org.mapstruct.Mapper; import org.mapstruct.Mappers; +import org.mapstruct.MappingTarget; @Mapper public interface SourceTargetMapper { @@ -41,4 +42,11 @@ public interface SourceTargetMapper { Collection sourceFoosToTargetFoos(Collection foos); Iterable sourceFoosToTargetFoos(Iterable foos); + + void sourceFoosToTargetFoos1(Iterable sourceFoos, List targetFoos); + + void sourceFoosToTargetFoos2(@MappingTarget List targetFoos, Iterable sourceFoos); + + Iterable sourceFoosToTargetFoos3(Iterable sourceFoos, + @MappingTarget List targetFoos); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java index 412ff4093..c6f3c5dc4 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/MapMappingTest.java @@ -58,12 +58,55 @@ public class MapMappingTest extends MapperTestBase { @Test public void shouldCreateReverseMapMethodImplementation() { - Map values = new HashMap(); - values.put( "42", "01.01.1980" ); - values.put( "121", "20.07.2013" ); + Map values = createStringStringMap(); Map target = SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap( values ); + assertResult( target ); + } + + @Test + @IssueKey("19") + public void shouldCreateReverseMapMethodImplementation1() { + Map values = createStringStringMap(); + + Map target = new HashMap(); + target.put( 66L, new GregorianCalendar( 2013, 7, 16 ).getTime() ); + + SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap( values, target ); + + assertResult( target ); + } + + @Test + @IssueKey("19") + public void shouldCreateReverseMapMethodImplementation2() { + Map values = createStringStringMap(); + + Map target = new HashMap(); + target.put( 66L, new GregorianCalendar( 2013, 7, 16 ).getTime() ); + + SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap2( target, values ); + + assertResult( target ); + } + + @Test + @IssueKey("19") + public void shouldCreateReverseMapMethodImplementation3() { + Map values = createStringStringMap(); + + Map target = new HashMap(); + target.put( 66L, new GregorianCalendar( 2013, 7, 16 ).getTime() ); + + Map returnedTarget = SourceTargetMapper.INSTANCE.stringStringMapToLongDateMap3( values, target ); + + assertThat( target ).isSameAs( returnedTarget ); + + assertResult( target ); + } + + private void assertResult(Map target) { assertThat( target ).isNotNull(); assertThat( target ).hasSize( 2 ); assertThat( target ).includes( @@ -72,6 +115,13 @@ public class MapMappingTest extends MapperTestBase { ); } + private Map createStringStringMap() { + Map values = new HashMap(); + values.put( "42", "01.01.1980" ); + values.put( "121", "20.07.2013" ); + return values; + } + @Test public void shouldInvokeMapMethodImplementationForMapTypedProperty() { Map values = new HashMap(); @@ -94,9 +144,7 @@ public class MapMappingTest extends MapperTestBase { @Test public void shouldInvokeReverseMapMethodImplementationForMapTypedProperty() { - Map values = new HashMap(); - values.put( "42", "01.01.1980" ); - values.put( "121", "20.07.2013" ); + Map values = createStringStringMap(); Target target = new Target(); target.setValues( values ); diff --git a/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java index edb491793..4f1100306 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/collection/map/SourceTargetMapper.java @@ -24,17 +24,27 @@ import java.util.Map; import org.mapstruct.MapMapping; import org.mapstruct.Mapper; import org.mapstruct.Mappers; +import org.mapstruct.MappingTarget; @Mapper(uses = CustomNumberMapper.class) public interface SourceTargetMapper { SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class ); - @MapMapping( valueDateFormat = "dd.MM.yyyy" ) + @MapMapping(valueDateFormat = "dd.MM.yyyy") Map longDateMapToStringStringMap(Map source); Map stringStringMapToLongDateMap(Map source); + @MapMapping(valueDateFormat = "dd.MM.yyyy") + void stringStringMapToLongDateMap(Map source, Map target); + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + void stringStringMapToLongDateMap2(@MappingTarget Map target, Map source); + + @MapMapping(valueDateFormat = "dd.MM.yyyy") + Map stringStringMapToLongDateMap3(Map source, @MappingTarget Map target); + Target sourceToTarget(Source source); Source targetToSource(Target target); diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java index 799868247..e898c7f5b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/InheritanceTest.java @@ -36,17 +36,61 @@ public class InheritanceTest extends MapperTestBase { @Test @IssueKey("17") public void shouldMapAttributeFromSuperType() { - SourceExt source = new SourceExt(); - source.setFoo( 42 ); - source.setBar( 23L ); + SourceExt source = createSource(); TargetExt target = SourceTargetMapper.INSTANCE.sourceToTarget( source ); + assertResult( target ); + } + + @Test + @IssueKey("19") + public void existingMapping1() { + SourceExt source = createSource(); + + TargetExt target = new TargetExt(); + SourceTargetMapper.INSTANCE.sourceToTarget1( source, target ); + + assertResult( target ); + } + + @Test + @IssueKey("19") + public void existingMapping2() { + SourceExt source = createSource(); + + TargetExt target = new TargetExt(); + SourceTargetMapper.INSTANCE.sourceToTarget2( target, source ); + + assertResult( target ); + } + + @Test + @IssueKey("19") + public void existingMapping3() { + SourceExt source = createSource(); + + TargetExt target = new TargetExt(); + TargetBase result = SourceTargetMapper.INSTANCE.sourceToTarget3( source, target ); + + assertThat( target ).isSameAs( result ); + + assertResult( target ); + } + + private void assertResult(TargetExt target) { assertThat( target ).isNotNull(); assertThat( target.getFoo() ).isEqualTo( Long.valueOf( 42 ) ); assertThat( target.getBar() ).isEqualTo( 23 ); } + private SourceExt createSource() { + SourceExt source = new SourceExt(); + source.setFoo( 42 ); + source.setBar( 23L ); + return source; + } + @Test @IssueKey("17") public void shouldReverseMapAttributeFromSuperType() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/inheritance/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/inheritance/SourceTargetMapper.java index 738f51d88..bfed071be 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/inheritance/SourceTargetMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/inheritance/SourceTargetMapper.java @@ -20,6 +20,7 @@ package org.mapstruct.ap.test.inheritance; import org.mapstruct.Mapper; import org.mapstruct.Mappers; +import org.mapstruct.MappingTarget; @Mapper public interface SourceTargetMapper { @@ -28,5 +29,11 @@ public interface SourceTargetMapper { TargetExt sourceToTarget(SourceExt source); + void sourceToTarget1(SourceExt source, TargetExt target); + + void sourceToTarget2(@MappingTarget TargetExt target, SourceExt source); + + TargetBase sourceToTarget3(SourceExt source, @MappingTarget TargetExt target); + SourceExt targetToSource(TargetExt target); }