#31 Providing support for bean mapping methods with several source parameters

This commit is contained in:
Gunnar Morling 2013-08-11 14:42:14 +02:00
parent b61f0ce915
commit 86a8e72692
25 changed files with 883 additions and 126 deletions

View File

@ -0,0 +1,64 @@
/**
* 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.ap;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
/**
* Indicates an error during annotation processing.
*
* @author Gunnar Morling
*/
@SuppressWarnings("serial")
public class AnnotationProcessingException extends RuntimeException {
private final Element element;
private final AnnotationMirror annotationMirror;
private final AnnotationValue annotationValue;
public AnnotationProcessingException(String message, Element element) {
this( message, element, null, null );
}
public AnnotationProcessingException(String message, Element element, AnnotationMirror annotationMirror) {
this( message, element, annotationMirror, null );
}
public AnnotationProcessingException(String message, Element element, AnnotationMirror annotationMirror,
AnnotationValue annotationValue) {
super( message );
this.element = element;
this.annotationMirror = annotationMirror;
this.annotationValue = annotationValue;
}
public Element getElement() {
return element;
}
public AnnotationMirror getAnnotationMirror() {
return annotationMirror;
}
public AnnotationValue getAnnotationValue() {
return annotationValue;
}
}

View File

@ -36,6 +36,7 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementKindVisitor6;
import javax.tools.Diagnostic.Kind;
import net.java.dev.hickory.prism.GeneratePrism;
import net.java.dev.hickory.prism.GeneratePrisms;
@ -152,7 +153,20 @@ public class MappingProcessor extends AbstractProcessor {
Object model = null;
for ( ModelElementProcessor<?, ?> processor : getProcessors() ) {
model = process( context, processor, mapperTypeElement, model );
try {
model = process( context, processor, mapperTypeElement, model );
}
catch ( AnnotationProcessingException e ) {
processingEnv.getMessager()
.printMessage(
Kind.ERROR,
e.getMessage(),
e.getElement(),
e.getAnnotationMirror(),
e.getAnnotationValue()
);
break;
}
}
}

View File

@ -18,7 +18,10 @@
*/
package org.mapstruct.ap.model;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.mapstruct.ap.model.source.Method;
@ -43,6 +46,22 @@ public class BeanMappingMethod extends MappingMethod {
return propertyMappings;
}
public Map<String, List<PropertyMapping>> getPropertyMappingsByParameter() {
Map<String, List<PropertyMapping>> mappingsByParameter = new HashMap<String, List<PropertyMapping>>();
for ( Parameter sourceParameter : getSourceParameters() ) {
ArrayList<PropertyMapping> mappingsOfParameter = new ArrayList<PropertyMapping>();
mappingsByParameter.put( sourceParameter.getName(), mappingsOfParameter );
for ( PropertyMapping mapping : propertyMappings ) {
if ( mapping.getSourceBeanName().equals( sourceParameter.getName() ) ) {
mappingsOfParameter.add( mapping );
}
}
}
return mappingsByParameter;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> types = super.getImportTypes();

View File

@ -42,6 +42,16 @@ public class IterableMappingMethod extends MappingMethod {
this.conversion = conversion;
}
public Parameter getSourceParameter() {
for ( Parameter parameter : getParameters() ) {
if ( !parameter.isMappingTarget() ) {
return parameter;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
public MappingMethodReference getElementMappingMethod() {
return elementMappingMethod;
}
@ -64,7 +74,7 @@ public class IterableMappingMethod extends MappingMethod {
public String getLoopVariableName() {
return Strings.getSaveVariableName(
Introspector.decapitalize(
getSingleSourceParameter().getType()
getSourceParameter().getType()
.getTypeParameters()
.get( 0 )
.getName()

View File

@ -46,6 +46,16 @@ public class MapMappingMethod extends MappingMethod {
this.valueConversion = valueConversion;
}
public Parameter getSourceParameter() {
for ( Parameter parameter : getParameters() ) {
if ( !parameter.isMappingTarget() ) {
return parameter;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
public MappingMethodReference getKeyMappingMethod() {
return keyMappingMethod;
}

View File

@ -37,14 +37,12 @@ public abstract class MappingMethod extends AbstractModelElement {
private final String name;
private final List<Parameter> parameters;
private final Type returnType;
private final Parameter singleSourceParameter;
private final Parameter targetParameter;
public MappingMethod(Method method) {
this.name = method.getName();
this.parameters = method.getParameters();
this.returnType = method.getReturnType();
this.singleSourceParameter = method.getSingleSourceParameter();
this.targetParameter = method.getTargetParameter();
}
@ -56,8 +54,16 @@ public abstract class MappingMethod extends AbstractModelElement {
return parameters;
}
public Parameter getSingleSourceParameter() {
return singleSourceParameter;
public List<Parameter> getSourceParameters() {
List<Parameter> sourceParameters = new ArrayList<Parameter>();
for ( Parameter parameter : parameters ) {
if ( !parameter.isMappingTarget() ) {
sourceParameters.add( parameter );
}
}
return sourceParameters;
}
public Type getResultType() {

View File

@ -31,6 +31,7 @@ import java.util.Set;
*/
public class PropertyMapping extends AbstractModelElement {
private final String sourceBeanName;
private final String sourceName;
private final String sourceAccessorName;
private final Type sourceType;
@ -42,10 +43,11 @@ public class PropertyMapping extends AbstractModelElement {
private final MappingMethodReference mappingMethod;
private final TypeConversion conversion;
public PropertyMapping(String sourceName, String sourceAccessorName, Type sourceType, String targetName,
String targetAccessorName, Type targetType, MappingMethodReference mappingMethod,
TypeConversion conversion) {
public PropertyMapping(String sourceBeanName, String sourceName, String sourceAccessorName, Type sourceType,
String targetName, String targetAccessorName, Type targetType,
MappingMethodReference mappingMethod, TypeConversion conversion) {
this.sourceBeanName = sourceBeanName;
this.sourceName = sourceName;
this.sourceAccessorName = sourceAccessorName;
this.sourceType = sourceType;
@ -58,6 +60,10 @@ public class PropertyMapping extends AbstractModelElement {
this.conversion = conversion;
}
public String getSourceBeanName() {
return sourceBeanName;
}
public String getSourceName() {
return sourceName;
}

View File

@ -22,7 +22,9 @@ import java.util.HashMap;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import org.mapstruct.ap.AnnotationProcessingException;
import org.mapstruct.ap.MappingPrism;
import org.mapstruct.ap.MappingsPrism;
@ -34,25 +36,36 @@ import org.mapstruct.ap.MappingsPrism;
public class Mapping {
private final String sourceName;
private final String sourceParameterName;
private final String sourcePropertyName;
private final String targetName;
private final String dateFormat;
private final AnnotationMirror mirror;
private final AnnotationValue sourceAnnotationValue;
private final AnnotationValue targetAnnotationValue;
public static Map<String, Mapping> fromMappingsPrism(MappingsPrism mappingsAnnotation) {
public static Map<String, Mapping> fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element) {
Map<String, Mapping> mappings = new HashMap<String, Mapping>();
for ( MappingPrism mapping : mappingsAnnotation.value() ) {
mappings.put( mapping.source(), fromMappingPrism( mapping ) );
mappings.put( mapping.source(), fromMappingPrism( mapping, element ) );
}
return mappings;
}
public static Mapping fromMappingPrism(MappingPrism mapping) {
public static Mapping fromMappingPrism(MappingPrism mapping, Element element) {
String[] sourceNameParts = getSourceNameParts(
mapping.source(),
element,
mapping.mirror,
mapping.values.source()
);
return new Mapping(
mapping.source(),
sourceNameParts != null ? sourceNameParts[0] : null,
sourceNameParts != null ? sourceNameParts[1] : mapping.source(),
mapping.target(),
mapping.dateFormat(),
mapping.mirror,
@ -61,9 +74,31 @@ public class Mapping {
);
}
private Mapping(String sourceName, String targetName, String dateFormat, AnnotationMirror mirror,
AnnotationValue sourceAnnotationValue, AnnotationValue targetAnnotationValue) {
private static String[] getSourceNameParts(String sourceName, Element element, AnnotationMirror annotationMirror,
AnnotationValue annotationValue) {
if ( !sourceName.contains( "." ) ) {
return null;
}
String[] parts = sourceName.split( "\\." );
if ( parts.length != 2 ) {
throw new AnnotationProcessingException(
"Mapping of nested attributes not supported yet.",
element,
annotationMirror,
annotationValue
);
}
return parts;
}
private Mapping(String sourceName, String sourceParameterName, String sourcePropertyName, String targetName,
String dateFormat, AnnotationMirror mirror, AnnotationValue sourceAnnotationValue,
AnnotationValue targetAnnotationValue) {
this.sourceName = sourceName;
this.sourceParameterName = sourceParameterName;
this.sourcePropertyName = sourcePropertyName;
this.targetName = targetName.equals( "" ) ? sourceName : targetName;
this.dateFormat = dateFormat;
this.mirror = mirror;
@ -71,10 +106,34 @@ public class Mapping {
this.targetAnnotationValue = targetAnnotationValue;
}
/**
* Returns the complete source name of this mapping, either a qualified (e.g. {@code parameter1.foo}) or
* unqualified (e.g. {@code foo}) property reference.
*
* @return The complete source name of this mapping.
*/
public String getSourceName() {
return sourceName;
}
/**
* Returns the unqualified name of the source property (i.e. without the parameter name if given) of this mapping.
*
* @return The unqualified name of the source property of this mapping.
*/
public String getSourcePropertyName() {
return sourcePropertyName;
}
/**
* Returns the name of the source parameter of this mapping if the source name is qualified.
*
* @return The name of the source parameter of this mapping if given, {@code null} otherwise.
*/
public String getSourceParameterName() {
return sourceParameterName;
}
public String getTargetName() {
return targetName;
}
@ -96,7 +155,16 @@ public class Mapping {
}
public Mapping reverse() {
return new Mapping( targetName, sourceName, dateFormat, mirror, sourceAnnotationValue, targetAnnotationValue );
return new Mapping(
targetName,
null,
targetName,
sourceName,
dateFormat,
mirror,
sourceAnnotationValue,
targetAnnotationValue
);
}
@Override

View File

@ -45,7 +45,6 @@ public class Method {
private IterableMapping iterableMapping;
private MapMapping mapMapping;
private final Parameter singleSourceParameter;
private final Parameter targetParameter;
public static Method forMethodRequiringImplementation(ExecutableElement executable, List<Parameter> parameters,
@ -83,12 +82,10 @@ public class Method {
this.executable = executable;
this.parameters = parameters;
this.returnType = returnType;
this.mappings = mappings;
this.iterableMapping = iterableMapping;
this.mapMapping = mapMapping;
this.singleSourceParameter = determineSingleSourceParameter();
this.targetParameter = determineTargetParameter( parameters );
}
@ -102,16 +99,6 @@ public class Method {
return null;
}
private Parameter determineSingleSourceParameter() {
for ( Parameter parameter : parameters ) {
if ( !parameter.isMappingTarget() ) {
return parameter;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
/**
* Returns the mapper type declaring this method if it is not declared by
* the mapper interface currently processed but by another mapper imported
@ -135,6 +122,18 @@ public class Method {
return parameters;
}
public List<Parameter> getSourceParameters() {
List<Parameter> sourceParameters = new ArrayList<Parameter>();
for ( Parameter parameter : parameters ) {
if ( !parameter.isMappingTarget() ) {
sourceParameters.add( parameter );
}
}
return sourceParameters;
}
public List<String> getParameterNames() {
List<String> parameterNames = new ArrayList<String>( parameters.size() );
@ -179,12 +178,10 @@ public class Method {
public boolean reverses(Method method) {
return
equals( getSingleSourceParameter().getType(), method.getResultType() )
&& equals( getResultType(), method.getSingleSourceParameter().getType() );
}
public Parameter getSingleSourceParameter() {
return singleSourceParameter;
getSourceParameters().size() == 1 &&
method.getSourceParameters().size() == 1 &&
equals( getSourceParameters().iterator().next().getType(), method.getResultType() ) &&
equals( getResultType(), method.getSourceParameters().iterator().next().getType() );
}
public Parameter getTargetParameter() {
@ -192,11 +189,13 @@ public class Method {
}
public boolean isIterableMapping() {
return getSingleSourceParameter().getType().isIterableType() && getResultType().isIterableType();
return getSourceParameters().size() == 1 &&
getSourceParameters().iterator().next().getType().isIterableType() && getResultType().isIterableType();
}
public boolean isMapMapping() {
return getSingleSourceParameter().getType().isMapType() && getResultType().isMapType();
return getSourceParameters().size() == 1 && getSourceParameters().iterator().next().getType().isMapType() &&
getResultType().isMapType();
}
private boolean equals(Object o1, Object o2) {
@ -207,4 +206,24 @@ public class Method {
public String toString() {
return returnType + " " + getName() + "(" + Strings.join( parameters, ", " ) + ")";
}
public Mapping getMapping(String targetPropertyName) {
for ( Mapping mapping : mappings.values() ) {
if ( mapping.getTargetName().equals( targetPropertyName ) ) {
return mapping;
}
}
return null;
}
public Parameter getSourceParameter(String sourceParameterName) {
for ( Parameter parameter : getSourceParameters() ) {
if ( parameter.getName().equals( sourceParameterName ) ) {
return parameter;
}
}
return null;
}
}

View File

@ -181,7 +181,10 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
method.setMappings( reverse( reverseMappingMethod.getMappings() ) );
}
}
mappingMethods.add( getBeanMappingMethod( methods, method, unmappedTargetPolicy ) );
MappingMethod beanMappingMethod = getBeanMappingMethod( methods, method, unmappedTargetPolicy );
if ( beanMappingMethod != null ) {
mappingMethods.add( beanMappingMethod );
}
}
}
return mappingMethods;
@ -210,60 +213,91 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return reversed;
}
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, ExecutableElement setterMethod,
Parameter parameter) {
String targetPropertyName = executables.getPropertyName( setterMethod );
Mapping mapping = method.getMapping( targetPropertyName );
String dateFormat = mapping != null ? mapping.getDateFormat() : null;
String sourcePropertyName = mapping != null ? mapping.getSourcePropertyName() : targetPropertyName;
TypeElement parameterElement = elementUtils.getTypeElement( parameter.getType().getCanonicalName() );
List<ExecutableElement> sourceGetters = Filters.getterMethodsIn(
elementUtils.getAllMembers( parameterElement )
);
for ( ExecutableElement getter : sourceGetters ) {
Mapping sourceMapping = method.getMappings().get( sourcePropertyName );
boolean mapsToOtherTarget =
sourceMapping != null && !sourceMapping.getTargetName().equals( targetPropertyName );
if ( executables.getPropertyName( getter ).equals( sourcePropertyName ) && !mapsToOtherTarget ) {
return getPropertyMapping(
methods,
method,
parameter,
getter,
setterMethod,
dateFormat
);
}
}
return null;
}
private MappingMethod getBeanMappingMethod(List<Method> methods, Method method,
ReportingPolicy unmappedTargetPolicy) {
List<PropertyMapping> propertyMappings = new ArrayList<PropertyMapping>();
Set<String> mappedTargetProperties = new HashSet<String>();
Map<String, Mapping> mappings = method.getMappings();
if ( !reportErrorIfMappedPropertiesDontExist( method ) ) {
return null;
}
TypeElement resultTypeElement = elementUtils.getTypeElement( method.getResultType().getCanonicalName() );
TypeElement parameterElement = elementUtils.getTypeElement(
method.getSingleSourceParameter()
.getType()
.getCanonicalName()
);
List<ExecutableElement> sourceGetters = Filters.getterMethodsIn(
elementUtils.getAllMembers( parameterElement )
);
List<ExecutableElement> targetSetters = Filters.setterMethodsIn(
elementUtils.getAllMembers( resultTypeElement )
);
Set<String> sourceProperties = executables.getPropertyNames(
Filters.getterMethodsIn( sourceGetters )
);
Set<String> targetProperties = executables.getPropertyNames(
Filters.setterMethodsIn( targetSetters )
);
for ( ExecutableElement setterMethod : targetSetters ) {
String targetPropertyName = executables.getPropertyName( setterMethod );
reportErrorIfMappedPropertiesDontExist( method, sourceProperties, targetProperties );
Mapping mapping = method.getMapping( targetPropertyName );
for ( ExecutableElement getterMethod : sourceGetters ) {
String sourcePropertyName = executables.getPropertyName( getterMethod );
Mapping mapping = mappings.get( sourcePropertyName );
String dateFormat = mapping != null ? mapping.getDateFormat() : null;
PropertyMapping propertyMapping = null;
if ( mapping != null && mapping.getSourceParameterName() != null ) {
Parameter parameter = method.getSourceParameter( mapping.getSourceParameterName() );
propertyMapping = getPropertyMapping( methods, method, setterMethod, parameter );
}
for ( ExecutableElement setterMethod : targetSetters ) {
String targetPropertyName = executables.getPropertyName( setterMethod );
if ( targetPropertyName.equals( mapping != null ? mapping.getTargetName() : sourcePropertyName ) ) {
PropertyMapping property = getPropertyMapping(
if ( propertyMapping == null ) {
for ( Parameter sourceParameter : method.getSourceParameters() ) {
PropertyMapping newPropertyMapping = getPropertyMapping(
methods,
method,
getterMethod,
setterMethod,
dateFormat
sourceParameter
);
propertyMappings.add( property );
mappedTargetProperties.add( targetPropertyName );
if ( propertyMapping != null && newPropertyMapping != null ) {
messager.printMessage(
Kind.ERROR,
"Several possible source properties for target property \"" + targetPropertyName + "\".",
method.getExecutable()
);
break;
}
else if ( newPropertyMapping != null ) {
propertyMapping = newPropertyMapping;
}
}
}
if ( propertyMapping != null ) {
propertyMappings.add( propertyMapping );
mappedTargetProperties.add( targetPropertyName );
}
}
Set<String> targetProperties = executables.getPropertyNames( targetSetters );
reportErrorForUnmappedTargetPropertiesIfRequired(
method,
unmappedTargetPolicy,
@ -303,21 +337,85 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
return null;
}
private void reportErrorIfMappedPropertiesDontExist(Method method, Set<String> sourceProperties,
Set<String> targetProperties) {
private boolean hasSourceProperty(Method method, String propertyName) {
for ( Parameter parameter : method.getSourceParameters() ) {
if ( hasProperty( parameter, propertyName ) ) {
return true;
}
}
return false;
}
private boolean hasProperty(Parameter parameter, String propertyName) {
TypeElement parameterTypeElement = elementUtils.getTypeElement( parameter.getType().getCanonicalName() );
List<ExecutableElement> getters = Filters.setterMethodsIn(
elementUtils.getAllMembers( parameterTypeElement )
);
return executables.getPropertyNames( getters ).contains( propertyName );
}
private boolean reportErrorIfMappedPropertiesDontExist(Method method) {
TypeElement resultTypeElement = elementUtils.getTypeElement( method.getResultType().getCanonicalName() );
List<ExecutableElement> targetSetters = Filters.setterMethodsIn(
elementUtils.getAllMembers( resultTypeElement )
);
Set<String> targetProperties = executables.getPropertyNames( targetSetters );
boolean foundUnmappedProperty = false;
for ( Mapping mappedProperty : method.getMappings().values() ) {
if ( !sourceProperties.contains( mappedProperty.getSourceName() ) ) {
if ( mappedProperty.getSourceParameterName() != null ) {
Parameter sourceParameter = method.getSourceParameter( mappedProperty.getSourceParameterName() );
if ( sourceParameter == null ) {
messager.printMessage(
Kind.ERROR,
String.format(
"Method has no parameter named \"%s\".",
mappedProperty.getSourceParameterName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
else {
if ( !hasProperty( sourceParameter, mappedProperty.getSourcePropertyName() ) ) {
messager.printMessage(
Kind.ERROR,
String.format(
"The type of parameter \"%s\" has no property named \"%s\".",
mappedProperty.getSourceParameterName(),
mappedProperty.getSourcePropertyName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
}
}
else if ( !hasSourceProperty(
method,
mappedProperty.getSourcePropertyName()
) ) {
messager.printMessage(
Kind.ERROR,
String.format(
"Unknown property \"%s\" in parameter type %s.",
mappedProperty.getSourceName(),
method.getSingleSourceParameter().getType()
"No property named \"%s\" exists in source parameter(s).",
mappedProperty.getSourceName()
),
method.getExecutable(),
mappedProperty.getMirror(),
mappedProperty.getSourceAnnotationValue()
);
foundUnmappedProperty = true;
}
if ( !targetProperties.contains( mappedProperty.getTargetName() ) ) {
messager.printMessage(
@ -331,12 +429,16 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
mappedProperty.getMirror(),
mappedProperty.getTargetAnnotationValue()
);
foundUnmappedProperty = true;
}
}
return !foundUnmappedProperty;
}
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, ExecutableElement getterMethod,
ExecutableElement setterMethod, String dateFormat) {
private PropertyMapping getPropertyMapping(List<Method> methods, Method method, Parameter parameter,
ExecutableElement getterMethod, ExecutableElement setterMethod,
String dateFormat) {
Type sourceType = executables.retrieveReturnType( getterMethod );
Type targetType = executables.retrieveSingleParameter( setterMethod ).getType();
@ -345,11 +447,11 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
sourceType,
targetType,
dateFormat,
method.getSingleSourceParameter().getName() + "."
+ getterMethod.getSimpleName().toString() + "()"
parameter.getName() + "." + getterMethod.getSimpleName().toString() + "()"
);
PropertyMapping property = new PropertyMapping(
parameter.getName(),
executables.getPropertyName( getterMethod ),
getterMethod.getSimpleName().toString(),
sourceType,
@ -369,7 +471,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
}
private MappingMethod getIterableMappingMethod(List<Method> methods, Method method) {
Type sourceElementType = method.getSingleSourceParameter().getType().getTypeParameters().get( 0 );
Type sourceElementType = method.getSourceParameters().iterator().next().getType().getTypeParameters().get( 0 );
Type targetElementType = method.getResultType().getTypeParameters().get( 0 );
TypeConversion conversion = getConversion(
@ -390,7 +492,7 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
}
private MappingMethod getMapMappingMethod(List<Method> methods, Method method) {
List<Type> sourceTypeParams = method.getSingleSourceParameter().getType().getTypeParameters();
List<Type> sourceTypeParams = method.getSourceParameters().iterator().next().getType().getTypeParameters();
Type sourceKeyType = sourceTypeParams.get( 0 );
Type sourceValueType = sourceTypeParams.get( 1 );
@ -431,12 +533,16 @@ public class MapperCreationProcessor implements ModelElementProcessor<List<Metho
private MappingMethodReference getMappingMethodReference(Iterable<Method> methods, Type parameterType,
Type returnType) {
for ( Method oneMethod : methods ) {
Parameter singleSourceParam = oneMethod.getSingleSourceParameter();
for ( Method method : methods ) {
if ( method.getSourceParameters().size() > 1 ) {
continue;
}
Parameter singleSourceParam = method.getSourceParameters().iterator().next();
if ( singleSourceParam.getType().equals( parameterType ) &&
oneMethod.getResultType().equals( returnType ) ) {
return new MappingMethodReference( oneMethod );
method.getResultType().equals( returnType ) ) {
return new MappingMethodReference( method );
}
}

View File

@ -194,15 +194,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
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,
@ -274,11 +265,11 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
MappingsPrism mappingsAnnotation = MappingsPrism.getInstanceOn( method );
if ( mappingAnnotation != null ) {
mappings.put( mappingAnnotation.source(), Mapping.fromMappingPrism( mappingAnnotation ) );
mappings.put( mappingAnnotation.source(), Mapping.fromMappingPrism( mappingAnnotation, method ) );
}
if ( mappingsAnnotation != null ) {
mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation ) );
mappings.putAll( Mapping.fromMappingsPrism( mappingsAnnotation, method ) );
}
return mappings;

View File

@ -20,17 +20,29 @@
-->
@Override
public ${returnType.name} ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) {
if ( ${singleSourceParameter.name} == null ) {
if ( <#list sourceParameters as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
return<#if returnType.name != "void"> null</#if>;
}
<#if !existingInstanceMapping>
${resultType.name} ${resultName} = new ${resultType.name}();
</#if>
<#if (sourceParameters?size > 1)>
<#list sourceParameters as sourceParam>
if ( ${sourceParam.name} != null ) {
<#list propertyMappingsByParameter[sourceParam.name] as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName/>
</#list>
}
</#list>
<#else>
<#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName/>
</#list>
</#if>
<#list propertyMappings as propertyMapping>
<@includeModel object=propertyMapping sourceBeanName=singleSourceParameter.name targetBeanName=resultName/>
</#list>
<#if returnType.name != "void">
return ${resultName};

View File

@ -20,7 +20,7 @@
-->
@Override
public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) {
if ( ${singleSourceParameter.name} == null ) {
if ( ${sourceParameter.name} == null ) {
return<#if returnType.name != "void"> null</#if>;
}
@ -31,7 +31,7 @@
<#if resultType.name == "Iterable" && resultType.packageName == "java.lang">${resultType.iterableImplementationType.name}<#else>${resultType.name}</#if><<@includeModel object=resultType.typeParameters[0]/>> ${resultName} = new <#if resultType.iterableImplementationType??>${resultType.iterableImplementationType.name}<#else>${resultType.name}</#if><<@includeModel object=resultType.typeParameters[0]/>>();
</#if>
for ( <@includeModel object=singleSourceParameter.type.typeParameters[0]/> ${loopVariableName} : ${singleSourceParameter.name} ) {
for ( <@includeModel object=sourceParameter.type.typeParameters[0]/> ${loopVariableName} : ${sourceParameter.name} ) {
<#if elementMappingMethod??>
${resultName}.add( <@includeModel object=elementMappingMethod input="${loopVariableName}"/> );
<#else>

View File

@ -20,7 +20,7 @@
-->
@Override
public <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, </#if></#list>) {
if ( ${singleSourceParameter.name} == null ) {
if ( ${sourceParameter.name} == null ) {
return<#if returnType.name != "void"> null</#if>;
}
@ -30,7 +30,7 @@
<@includeModel object=resultType /> ${resultName} = new <#if resultType.mapImplementationType??><@includeModel object=resultType.mapImplementationType /><#else><@includeModel object=resultType /></#if>();
</#if>
for ( Map.Entry<<#list singleSourceParameter.type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, </#if></#list>> ${entryVariableName} : ${singleSourceParameter.name}.entrySet() ) {
for ( Map.Entry<<#list sourceParameter.type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, </#if></#list>> ${entryVariableName} : ${sourceParameter.name}.entrySet() ) {
<#-- key -->
<#if keyMappingMethod??>

View File

@ -20,11 +20,11 @@
-->
<#-- a) invoke mapping method -->
<#if mappingMethod??>
${ext.targetBeanName}.${targetAccessorName}( <@includeModel object=mappingMethod input="${ext.sourceBeanName}.${sourceAccessorName}()"/> );
${ext.targetBeanName}.${targetAccessorName}( <@includeModel object=mappingMethod input="${sourceBeanName}.${sourceAccessorName}()"/> );
<#-- b) simple conversion -->
<#elseif conversion??>
<#if sourceType.primitive == false>
if ( ${ext.sourceBeanName}.${sourceAccessorName}() != null ) {
if ( ${sourceBeanName}.${sourceAccessorName}() != null ) {
<@applyConversion targetBeanName=ext.targetBeanName targetAccessorName=targetAccessorName conversion=conversion/>
}
<#else>
@ -33,11 +33,11 @@
<#-- c) simply set -->
<#else>
<#if targetType.collectionType == true>
if ( ${ext.sourceBeanName}.${sourceAccessorName}() != null ) {
${ext.targetBeanName}.${targetAccessorName}( new <#if targetType.collectionImplementationType??>${targetType.collectionImplementationType.name}<#else>${targetType.name}</#if><#if targetType.elementType??><${targetType.elementType.name}></#if>( ${ext.sourceBeanName}.${sourceAccessorName}() ) );
if ( ${sourceBeanName}.${sourceAccessorName}() != null ) {
${ext.targetBeanName}.${targetAccessorName}( new <#if targetType.collectionImplementationType??>${targetType.collectionImplementationType.name}<#else>${targetType.name}</#if><#if targetType.elementType??><${targetType.elementType.name}></#if>( ${sourceBeanName}.${sourceAccessorName}() ) );
}
<#else>
${ext.targetBeanName}.${targetAccessorName}( ${ext.sourceBeanName}.${sourceAccessorName}() );
${ext.targetBeanName}.${targetAccessorName}( ${sourceBeanName}.${sourceAccessorName}() );
</#if>
</#if>
<#macro applyConversion targetBeanName targetAccessorName conversion>

View File

@ -20,13 +20,13 @@ package org.mapstruct.ap.test.erroneous.attributereference;
public class AnotherTarget {
private int foo;
private int bar;
public int getFoo() {
return foo;
public int getBar() {
return bar;
}
public void setFoo(int foo) {
this.foo = foo;
public void setBar(int bar) {
this.bar = bar;
}
}

View File

@ -20,13 +20,18 @@ package org.mapstruct.ap.test.erroneous.attributereference;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
@Mapper
public interface ErroneousMapper {
@Mapping(source = "foo", target = "bar")
@Mappings({
@Mapping(source = "bar", target = "foo"),
@Mapping(source = "source1.foo", target = "foo"),
@Mapping(source = "foo", target = "bar"),
@Mapping(source = "source.foobar", target = "foo")
})
Target sourceToTarget(Source source);
@Mapping(source = "bar", target = "foo")
AnotherTarget sourceToAnotherTarget(Source source);
}

View File

@ -43,16 +43,24 @@ public class ErroneousMappingsTest extends MapperTestBase {
diagnostics = {
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.ERROR,
line = 27,
messageRegExp = "Unknown property \"bar\" in return type"),
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.WARNING,
line = 28,
messageRegExp = "Unmapped target property: \"foo\""),
line = 29,
messageRegExp = "No property named \"bar\" exists in source parameter\\(s\\)"),
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.ERROR,
line = 30,
messageRegExp = "Unknown property \"bar\" in parameter type")
messageRegExp = "Method has no parameter named \"source1\""),
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.ERROR,
line = 31,
messageRegExp = "Unknown property \"bar\" in return type"),
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.ERROR,
line = 32,
messageRegExp = "The type of parameter \"source\" has no property named \"foobar\""),
@Diagnostic(type = ErroneousMapper.class,
kind = Kind.WARNING,
line = 36,
messageRegExp = "Unmapped target property: \"bar\""),
}
)
public void shouldFailToGenerateMappings() {

View File

@ -0,0 +1,66 @@
/**
* 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.ap.test.severalsources;
public class Address {
private String street;
private int zipCode;
private int houseNo;
private String description;
public Address(String street, int zipCode, int houseNo, String description) {
this.street = street;
this.zipCode = zipCode;
this.houseNo = houseNo;
this.description = description;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getZipCode() {
return zipCode;
}
public void setZipCode(int zipCode) {
this.zipCode = zipCode;
}
public int getHouseNo() {
return houseNo;
}
public void setHouseNo(int houseNo) {
this.houseNo = houseNo;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,101 @@
/**
* 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.ap.test.severalsources;
public class DeliveryAddress {
private String firstName;
private String lastName;
private int height;
private String street;
private int zipCode;
private int houseNumber;
private String description;
public DeliveryAddress() {
}
public DeliveryAddress(String firstName, String lastName, int height, String street, int zipCode, int houseNumber,
String description) {
this.firstName = firstName;
this.lastName = lastName;
this.height = height;
this.street = street;
this.zipCode = zipCode;
this.houseNumber = houseNumber;
this.description = description;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getZipCode() {
return zipCode;
}
public void setZipCode(int zipCode) {
this.zipCode = zipCode;
}
public int getHouseNumber() {
return houseNumber;
}
public void setHouseNumber(int houseNumber) {
this.houseNumber = houseNumber;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,30 @@
/**
* 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.ap.test.severalsources;
import org.mapstruct.Mapper;
import org.mapstruct.Mappers;
@Mapper
public interface ErroneousSourceTargetMapper {
ErroneousSourceTargetMapper INSTANCE = Mappers.getMapper( ErroneousSourceTargetMapper.class );
DeliveryAddress addressAndAddressToDeliveryAddress(Address address1, Address address2);
}

View File

@ -0,0 +1,66 @@
/**
* 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.ap.test.severalsources;
public class Person {
private String firstName;
private String lastName;
private int height;
private String description;
public Person(String firstName, String lastName, int height, String description) {
this.firstName = firstName;
this.lastName = lastName;
this.height = height;
this.description = description;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}

View File

@ -0,0 +1,112 @@
/**
* 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.ap.test.severalsources;
import javax.tools.Diagnostic.Kind;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.MapperTestBase;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.assertThat;
/**
* Test for propagation of attribute without setter in source and getter in
* target.
*
* @author Gunnar Morling
*/
@IssueKey("31")
public class SeveralSourceParametersTest extends MapperTestBase {
@Test
@WithClasses({ Person.class, Address.class, DeliveryAddress.class, SourceTargetMapper.class })
public void shouldMapSeveralSourceAttributesToCombinedTarget() {
Person person = new Person( "Bob", "Garner", 181, "An actor" );
Address address = new Address( "Main street", 12345, 42, "His address" );
DeliveryAddress deliveryAddress = SourceTargetMapper.INSTANCE
.personAndAddressToDeliveryAddress( person, address );
assertThat( deliveryAddress ).isNotNull();
assertThat( deliveryAddress.getLastName() ).isEqualTo( "Garner" );
assertThat( deliveryAddress.getZipCode() ).isEqualTo( 12345 );
assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 42 );
assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" );
}
@Test
@WithClasses({ Person.class, Address.class, DeliveryAddress.class, SourceTargetMapper.class })
public void shouldMapSeveralSourceAttributesToCombinedTargetWithTargetParameter() {
Person person = new Person( "Bob", "Garner", 181, "An actor" );
Address address = new Address( "Main street", 12345, 42, "His address" );
DeliveryAddress deliveryAddress = new DeliveryAddress();
SourceTargetMapper.INSTANCE.personAndAddressToDeliveryAddress( person, address, deliveryAddress );
assertThat( deliveryAddress.getLastName() ).isEqualTo( "Garner" );
assertThat( deliveryAddress.getZipCode() ).isEqualTo( 12345 );
assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 42 );
assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" );
}
@Test
@WithClasses({ Person.class, Address.class, DeliveryAddress.class, SourceTargetMapper.class })
public void shouldSetAttributesFromNonNullParameters() {
Person person = new Person( "Bob", "Garner", 181, "An actor" );
DeliveryAddress deliveryAddress = SourceTargetMapper.INSTANCE
.personAndAddressToDeliveryAddress( person, null );
assertThat( deliveryAddress ).isNotNull();
assertThat( deliveryAddress.getLastName() ).isEqualTo( "Garner" );
assertThat( deliveryAddress.getDescription() ).isEqualTo( "An actor" );
assertThat( deliveryAddress.getHouseNumber() ).isEqualTo( 0 );
assertThat( deliveryAddress.getStreet() ).isNull();
}
@Test
@WithClasses({ Person.class, Address.class, DeliveryAddress.class, SourceTargetMapper.class })
public void shouldReturnNullIfAllParametersAreNull() {
DeliveryAddress deliveryAddress = SourceTargetMapper.INSTANCE
.personAndAddressToDeliveryAddress( null, null );
assertThat( deliveryAddress ).isNull();
}
@Test
@WithClasses({ ErroneousSourceTargetMapper.class, Address.class, DeliveryAddress.class })
@ProcessorOption(name = "unmappedTargetPolicy", value = "IGNORE")
@ExpectedCompilationOutcome(
value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousSourceTargetMapper.class,
kind = Kind.ERROR,
line = 29,
messageRegExp = "Several possible source properties for target property \"street\".")
}
)
public void shouldFailToGenerateMappingsForAmbigiousSourceProperty() {
}
}

View File

@ -0,0 +1,44 @@
/**
* 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.ap.test.severalsources;
import org.mapstruct.Mapper;
import org.mapstruct.Mappers;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;
@Mapper
public interface SourceTargetMapper {
SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
@Mappings({
@Mapping(source = "houseNo", target = "houseNumber"),
@Mapping(source = "person.description", target = "description")
})
DeliveryAddress personAndAddressToDeliveryAddress(Person person, Address address);
@Mappings({
@Mapping(source = "houseNo", target = "houseNumber"),
@Mapping(source = "person.description", target = "description")
})
void personAndAddressToDeliveryAddress(Person person, Address address,
@MappingTarget DeliveryAddress deliveryAddress);
}

View File

@ -167,8 +167,8 @@ public class DiagnosticDescriptor {
@Override
public String toString() {
String sourceFileName = this.sourceFileName
.substring( this.sourceFileName.lastIndexOf( File.separatorChar ) + 1 );
return "DiagnosticDescriptor: " + kind + " " + sourceFileName + ":" + line + " " + message;
String fileName = sourceFileName != null ?
sourceFileName.substring( this.sourceFileName.lastIndexOf( File.separatorChar ) + 1 ) : null;
return "DiagnosticDescriptor: " + kind + " " + fileName + ":" + line + " " + message;
}
}