diff --git a/processor/src/main/java/org/mapstruct/ap/AnnotationProcessingException.java b/processor/src/main/java/org/mapstruct/ap/AnnotationProcessingException.java new file mode 100644 index 000000000..ec250f2c4 --- /dev/null +++ b/processor/src/main/java/org/mapstruct/ap/AnnotationProcessingException.java @@ -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; + } +} diff --git a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java index 2f50b97c7..32cb30b58 100644 --- a/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/MappingProcessor.java @@ -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; + } } } 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 acc8bcf01..6a5f3533a 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/BeanMappingMethod.java @@ -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> getPropertyMappingsByParameter() { + Map> mappingsByParameter = new HashMap>(); + + for ( Parameter sourceParameter : getSourceParameters() ) { + ArrayList mappingsOfParameter = new ArrayList(); + mappingsByParameter.put( sourceParameter.getName(), mappingsOfParameter ); + for ( PropertyMapping mapping : propertyMappings ) { + if ( mapping.getSourceBeanName().equals( sourceParameter.getName() ) ) { + mappingsOfParameter.add( mapping ); + } + } + } + + return mappingsByParameter; + } + @Override public Set getImportTypes() { Set types = super.getImportTypes(); 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 22cfd6e1f..c37dd5353 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/IterableMappingMethod.java @@ -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() 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 6086cbbab..f153f505e 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MapMappingMethod.java @@ -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; } 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 29086b461..10b203dec 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/model/MappingMethod.java @@ -37,14 +37,12 @@ public abstract class MappingMethod extends AbstractModelElement { private final String name; private final List 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 getSourceParameters() { + List sourceParameters = new ArrayList(); + + for ( Parameter parameter : parameters ) { + if ( !parameter.isMappingTarget() ) { + sourceParameters.add( parameter ); + } + } + + return sourceParameters; } public Type getResultType() { diff --git a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java index a69a5d9b0..66dc28168 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/PropertyMapping.java @@ -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; } diff --git a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java index d9762be2f..f2c205be5 100644 --- a/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java +++ b/processor/src/main/java/org/mapstruct/ap/model/source/Mapping.java @@ -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 fromMappingsPrism(MappingsPrism mappingsAnnotation) { + public static Map fromMappingsPrism(MappingsPrism mappingsAnnotation, Element element) { Map mappings = new HashMap(); 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 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 bbb2d8d85..c4df38a9f 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 @@ -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 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 getSourceParameters() { + List sourceParameters = new ArrayList(); + + for ( Parameter parameter : parameters ) { + if ( !parameter.isMappingTarget() ) { + sourceParameters.add( parameter ); + } + } + + return sourceParameters; + } + public List getParameterNames() { List parameterNames = new ArrayList( 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; + } } 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 4883b8c9c..79acbfed1 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MapperCreationProcessor.java @@ -181,7 +181,10 @@ public class MapperCreationProcessor implements ModelElementProcessor 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 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 methods, Method method, ReportingPolicy unmappedTargetPolicy) { List propertyMappings = new ArrayList(); Set mappedTargetProperties = new HashSet(); - Map mappings = method.getMappings(); + if ( !reportErrorIfMappedPropertiesDontExist( method ) ) { + return null; + } TypeElement resultTypeElement = elementUtils.getTypeElement( method.getResultType().getCanonicalName() ); - TypeElement parameterElement = elementUtils.getTypeElement( - method.getSingleSourceParameter() - .getType() - .getCanonicalName() - ); - - List sourceGetters = Filters.getterMethodsIn( - elementUtils.getAllMembers( parameterElement ) - ); List targetSetters = Filters.setterMethodsIn( elementUtils.getAllMembers( resultTypeElement ) ); - Set sourceProperties = executables.getPropertyNames( - Filters.getterMethodsIn( sourceGetters ) - ); - Set 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 targetProperties = executables.getPropertyNames( targetSetters ); + reportErrorForUnmappedTargetPropertiesIfRequired( method, unmappedTargetPolicy, @@ -303,21 +337,85 @@ public class MapperCreationProcessor implements ModelElementProcessor sourceProperties, - Set 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 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 targetSetters = Filters.setterMethodsIn( + elementUtils.getAllMembers( resultTypeElement ) + ); + + Set 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 methods, Method method, ExecutableElement getterMethod, - ExecutableElement setterMethod, String dateFormat) { + private PropertyMapping getPropertyMapping(List 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 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 methods, Method method) { - List sourceTypeParams = method.getSingleSourceParameter().getType().getTypeParameters(); + List 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 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 ); } } 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 3d5b3dcad..44612ded0 100644 --- a/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/processor/MethodRetrievalProcessor.java @@ -194,15 +194,6 @@ public class MethodRetrievalProcessor implements ModelElementProcessor 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 @Override public ${returnType.name} ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - if ( ${singleSourceParameter.name} == null ) { + if ( <#list sourceParameters as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && ) { return<#if returnType.name != "void"> null; } <#if !existingInstanceMapping> ${resultType.name} ${resultName} = new ${resultType.name}(); + + + + <#if (sourceParameters?size > 1)> + <#list sourceParameters as sourceParam> + if ( ${sourceParam.name} != null ) { + <#list propertyMappingsByParameter[sourceParam.name] as propertyMapping> + <@includeModel object=propertyMapping targetBeanName=resultName/> + + } + + <#else> + <#list propertyMappingsByParameter[sourceParameters[0].name] as propertyMapping> + <@includeModel object=propertyMapping targetBeanName=resultName/> + - <#list propertyMappings as propertyMapping> - <@includeModel object=propertyMapping sourceBeanName=singleSourceParameter.name targetBeanName=resultName/> - <#if returnType.name != "void"> 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 0d6920d67..5950ac918 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.IterableMappingMethod.ftl @@ -20,7 +20,7 @@ --> @Override public <@includeModel object=returnType/> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - if ( ${singleSourceParameter.name} == null ) { + if ( ${sourceParameter.name} == null ) { return<#if returnType.name != "void"> null; } @@ -31,7 +31,7 @@ <#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=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> 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 7a8c81a18..3b00bc91c 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.MapMappingMethod.ftl @@ -20,7 +20,7 @@ --> @Override public <@includeModel object=returnType /> ${name}(<#list parameters as param><@includeModel object=param/><#if param_has_next>, ) { - if ( ${singleSourceParameter.name} == null ) { + if ( ${sourceParameter.name} == null ) { return<#if returnType.name != "void"> null; } @@ -30,7 +30,7 @@ <@includeModel object=resultType /> ${resultName} = new <#if resultType.mapImplementationType??><@includeModel object=resultType.mapImplementationType /><#else><@includeModel object=resultType />(); - for ( Map.Entry<<#list singleSourceParameter.type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > ${entryVariableName} : ${singleSourceParameter.name}.entrySet() ) { + for ( Map.Entry<<#list sourceParameter.type.typeParameters as typeParameter><@includeModel object=typeParameter /><#if typeParameter_has_next>, > ${entryVariableName} : ${sourceParameter.name}.entrySet() ) { <#-- key --> <#if keyMappingMethod??> diff --git a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl index e42a3385b..8367291d2 100644 --- a/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl +++ b/processor/src/main/resources/org.mapstruct.ap.model.PropertyMapping.ftl @@ -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 targetType.elementType??><${targetType.elementType.name}>( ${ext.sourceBeanName}.${sourceAccessorName}() ) ); + if ( ${sourceBeanName}.${sourceAccessorName}() != null ) { + ${ext.targetBeanName}.${targetAccessorName}( new <#if targetType.collectionImplementationType??>${targetType.collectionImplementationType.name}<#else>${targetType.name}<#if targetType.elementType??><${targetType.elementType.name}>( ${sourceBeanName}.${sourceAccessorName}() ) ); } <#else> - ${ext.targetBeanName}.${targetAccessorName}( ${ext.sourceBeanName}.${sourceAccessorName}() ); + ${ext.targetBeanName}.${targetAccessorName}( ${sourceBeanName}.${sourceAccessorName}() ); <#macro applyConversion targetBeanName targetAccessorName conversion> diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/AnotherTarget.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/AnotherTarget.java index dabb0b342..6e77fa013 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/AnotherTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/AnotherTarget.java @@ -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; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java index 47d15e78a..ffb471f9f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMapper.java @@ -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); } diff --git a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java index 1a85e693c..2f337fa7b 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/erroneous/attributereference/ErroneousMappingsTest.java @@ -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() { diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/Address.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/Address.java new file mode 100644 index 000000000..1c3c11af3 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/Address.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/DeliveryAddress.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/DeliveryAddress.java new file mode 100644 index 000000000..deff01865 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/DeliveryAddress.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/ErroneousSourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/ErroneousSourceTargetMapper.java new file mode 100644 index 000000000..087c053a9 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/ErroneousSourceTargetMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/Person.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/Person.java new file mode 100644 index 000000000..454fcfc29 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/Person.java @@ -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; + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java new file mode 100644 index 000000000..a676d3f26 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SeveralSourceParametersTest.java @@ -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() { + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java new file mode 100644 index 000000000..c22fcbc81 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/severalsources/SourceTargetMapper.java @@ -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); +} diff --git a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java index a4a8c736d..faf52f2f8 100644 --- a/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java +++ b/processor/src/test/java/org/mapstruct/ap/testutil/compilation/model/DiagnosticDescriptor.java @@ -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; } }