diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 148ec4879..37f1553be 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -24,6 +24,7 @@ import java.lang.annotation.Target; *
  • The mapping source parameter
  • *
  • {@code @}{@link Context} parameter
  • *
  • {@code @}{@link TargetPropertyName} parameter
  • + *
  • {@code @}{@link SourcePropertyName} parameter
  • * * * Note: The usage of this annotation is mandatory diff --git a/core/src/main/java/org/mapstruct/SourcePropertyName.java b/core/src/main/java/org/mapstruct/SourcePropertyName.java new file mode 100644 index 000000000..a9d036d5d --- /dev/null +++ b/core/src/main/java/org/mapstruct/SourcePropertyName.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation marks a presence check method parameter as a source property name parameter. + *

    + * This parameter enables conditional filtering based on source property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + *

    + * + * @author Oliver Erhart + * @since 1.6 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.CLASS) +public @interface SourcePropertyName { +} diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java index 17af966fa..c7ab8d957 100644 --- a/core/src/main/java/org/mapstruct/TargetPropertyName.java +++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java @@ -11,10 +11,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation marks a presence check method parameter as a property name parameter. + * This annotation marks a presence check method parameter as a target property name parameter. *

    - * This parameter enables conditional filtering based on target property name at run-time. - * Parameter must be of type {@link String} and can be present only in {@link Condition} method. + * This parameter enables conditional filtering based on target property name at run-time. + * Parameter must be of type {@link String} and can be present only in {@link Condition} method. *

    * @author Nikola Ivačič * @since 1.6 diff --git a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc index 9d6c93f21..db13c0548 100644 --- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc +++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc @@ -406,9 +406,9 @@ public class CarMapperImpl implements CarMapper { ---- ==== -Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method: +Additionally `@TargetPropertyName` or `@SourcePropertyName` of type `java.lang.String` can be used in custom condition check method: -.Mapper using custom condition check method with `@TargetPropertyName` +.Mapper using custom condition check method with `@TargetPropertyName` and `@SourcePropertyName` ==== [source, java, linenums] [subs="verbatim,attributes"] @@ -416,11 +416,19 @@ Additionally `@TargetPropertyName` of type `java.lang.String` can be used in cus @Mapper public interface CarMapper { + @Mapping(target = "owner", source = "ownerName") CarDto carToCarDto(Car car, @MappingTarget CarDto carDto); @Condition - default boolean isNotEmpty(String value, @TargetPropertyName String name) { - if ( name.equals( "owner" ) { + default boolean isNotEmpty( + String value, + @TargetPropertyName String targetPropertyName, + @SourcePropertyName String sourcePropertyName + ) { + + if ( targetPropertyName.equals( "owner" ) + && sourcePropertyName.equals( "ownerName" ) ) { + return value != null && !value.isEmpty() && !value.equals( value.toLowerCase() ); @@ -431,7 +439,7 @@ public interface CarMapper { ---- ==== -The generated mapper with `@TargetPropertyName` will look like: +The generated mapper with `@TargetPropertyName` and `@SourcePropertyName` will look like: .Custom condition check in generated implementation ==== @@ -447,7 +455,7 @@ public class CarMapperImpl implements CarMapper { return carDto; } - if ( isNotEmpty( car.getOwner(), "owner" ) ) { + if ( isNotEmpty( car.getOwner(), "owner", "ownerName" ) ) { carDto.setOwner( car.getOwner() ); } else { carDto.setOwner( null ); @@ -470,7 +478,7 @@ If there is a custom `@Condition` method applicable for the property it will hav ==== Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input. -`@TargetPropertyName` parameter can only be used in `@Condition` methods. +`@TargetPropertyName` and `@SourcePropertyName` parameters can only be used in `@Condition` methods. ==== <> is also valid for `@Condition` methods. diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java index 9ac13184c..2ed9bd9a0 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java @@ -31,6 +31,7 @@ import org.mapstruct.Mappings; import org.mapstruct.Named; import org.mapstruct.ObjectFactory; import org.mapstruct.Qualifier; +import org.mapstruct.SourcePropertyName; import org.mapstruct.SubclassMapping; import org.mapstruct.SubclassMappings; import org.mapstruct.TargetPropertyName; @@ -57,6 +58,7 @@ import org.mapstruct.tools.gem.GemDefinition; @GemDefinition(BeanMapping.class) @GemDefinition(EnumMapping.class) @GemDefinition(MapMapping.class) +@GemDefinition(SourcePropertyName.class) @GemDefinition(SubclassMapping.class) @GemDefinition(SubclassMappings.class) @GemDefinition(TargetType.class) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java index ee347ef35..be7203507 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java @@ -1522,6 +1522,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod { MappingReferences mappingRefs = extractMappingReferences( targetPropertyName, false ); PropertyMapping propertyMapping = new PropertyMappingBuilder().mappingContext( ctx ) .sourceMethod( method ) + .sourcePropertyName( targetPropertyName ) .target( targetPropertyName, targetPropertyReadAccessor, targetPropertyWriteAccessor ) .sourceReference( sourceRef ) .existingVariableNames( existingVariableNames ) diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java index 50f23ecbf..2f3095796 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java @@ -70,6 +70,7 @@ import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.D public class PropertyMapping extends ModelElement { private final String name; + private final String sourcePropertyName; private final String sourceBeanName; private final String targetWriteAccessorName; private final ReadAccessor targetReadAccessorProvider; @@ -286,6 +287,7 @@ public class PropertyMapping extends ModelElement { } return new PropertyMapping( + sourcePropertyName, targetPropertyName, rightHandSide.getSourceParameterName(), targetWriteAccessor.getSimpleName(), @@ -1099,16 +1101,17 @@ public class PropertyMapping extends ModelElement { ReadAccessor targetReadAccessorProvider, Type targetType, Assignment propertyAssignment, Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { - this( name, null, targetWriteAccessorName, targetReadAccessorProvider, + this( name, null, null, targetWriteAccessorName, targetReadAccessorProvider, targetType, propertyAssignment, dependsOn, defaultValueAssignment, constructorMapping ); } - private PropertyMapping(String name, String sourceBeanName, String targetWriteAccessorName, - ReadAccessor targetReadAccessorProvider, Type targetType, - Assignment assignment, - Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + private PropertyMapping(String sourcePropertyName, String name, String sourceBeanName, + String targetWriteAccessorName, ReadAccessor targetReadAccessorProvider, Type targetType, + Assignment assignment, + Set dependsOn, Assignment defaultValueAssignment, boolean constructorMapping) { + this.sourcePropertyName = sourcePropertyName; this.name = name; this.sourceBeanName = sourceBeanName; this.targetWriteAccessorName = targetWriteAccessorName; @@ -1128,6 +1131,10 @@ public class PropertyMapping extends ModelElement { return name; } + public String getSourcePropertyName() { + return sourcePropertyName; + } + public String getSourceBeanName() { return sourceBeanName; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java index 600c8ac33..44ac0eb7f 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java @@ -14,8 +14,9 @@ import javax.lang.model.element.VariableElement; import org.mapstruct.ap.internal.gem.ContextGem; import org.mapstruct.ap.internal.gem.MappingTargetGem; -import org.mapstruct.ap.internal.gem.TargetTypeGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; +import org.mapstruct.ap.internal.gem.TargetTypeGem; import org.mapstruct.ap.internal.util.Collections; /** @@ -32,6 +33,7 @@ public class Parameter extends ModelElement { private final boolean mappingTarget; private final boolean targetType; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final boolean varArgs; @@ -44,12 +46,13 @@ public class Parameter extends ModelElement { this.mappingTarget = MappingTargetGem.instanceOn( element ) != null; this.targetType = TargetTypeGem.instanceOn( element ) != null; this.mappingContext = ContextGem.instanceOn( element ) != null; + this.sourcePropertyName = SourcePropertyNameGem.instanceOn( element ) != null; this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null; this.varArgs = varArgs; } private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext, - boolean targetPropertyName, + boolean sourcePropertyName, boolean targetPropertyName, boolean varArgs) { this.element = null; this.name = name; @@ -58,12 +61,13 @@ public class Parameter extends ModelElement { this.mappingTarget = mappingTarget; this.targetType = targetType; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.varArgs = varArgs; } public Parameter(String name, Type type) { - this( name, type, false, false, false, false, false ); + this( name, type, false, false, false, false, false, false ); } public Element getElement() { @@ -99,6 +103,7 @@ public class Parameter extends ModelElement { return ( mappingTarget ? "@MappingTarget " : "" ) + ( targetType ? "@TargetType " : "" ) + ( mappingContext ? "@Context " : "" ) + + ( sourcePropertyName ? "@SourcePropertyName " : "" ) + ( targetPropertyName ? "@TargetPropertyName " : "" ) + "%s " + name; } @@ -120,6 +125,10 @@ public class Parameter extends ModelElement { return targetPropertyName; } + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + public boolean isVarArgs() { return varArgs; } @@ -165,6 +174,7 @@ public class Parameter extends ModelElement { false, false, false, + false, false ); } @@ -206,6 +216,10 @@ public class Parameter extends ModelElement { return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null ); } + public static Parameter getSourcePropertyNameParameter(List parameters) { + return parameters.stream().filter( Parameter::isSourcePropertyName ).findAny().orElse( null ); + } + public static Parameter getTargetPropertyNameParameter(List parameters) { return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null ); } @@ -214,6 +228,7 @@ public class Parameter extends ModelElement { return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext() && + !parameter.isSourcePropertyName() && !parameter.isTargetPropertyName(); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java index 18274e549..0791ee626 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java @@ -22,16 +22,19 @@ public class ParameterBinding { private final boolean targetType; private final boolean mappingTarget; private final boolean mappingContext; + private final boolean sourcePropertyName; private final boolean targetPropertyName; private final SourceRHS sourceRHS; private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType, - boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) { + boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName, + SourceRHS sourceRHS) { this.type = parameterType; this.variableName = variableName; this.targetType = targetType; this.mappingTarget = mappingTarget; this.mappingContext = mappingContext; + this.sourcePropertyName = sourcePropertyName; this.targetPropertyName = targetPropertyName; this.sourceRHS = sourceRHS; } @@ -64,11 +67,18 @@ public class ParameterBinding { return mappingContext; } + /** + * @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter. + */ + public boolean isSourcePropertyName() { + return sourcePropertyName; + } + /** * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter. */ public boolean isTargetPropertyName() { - return targetPropertyName; + return targetPropertyName; } /** @@ -108,6 +118,7 @@ public class ParameterBinding { parameter.isMappingTarget(), parameter.isTargetType(), parameter.isMappingContext(), + parameter.isSourcePropertyName(), parameter.isTargetPropertyName(), null ); @@ -129,6 +140,7 @@ public class ParameterBinding { false, false, false, + false, null ); } @@ -138,14 +150,21 @@ public class ParameterBinding { * @return a parameter binding representing a target type parameter */ public static ParameterBinding forTargetTypeBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, true, false, false, null ); + return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null ); } /** * @return a parameter binding representing a target property name parameter */ public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) { - return new ParameterBinding( classTypeOf, null, false, false, false, true, null ); + return new ParameterBinding( classTypeOf, null, false, false, false, false, true, null ); + } + + /** + * @return a parameter binding representing a source property name parameter + */ + public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) { + return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null ); } /** @@ -153,7 +172,7 @@ public class ParameterBinding { * @return a parameter binding representing a mapping target parameter */ public static ParameterBinding forMappingTargetBinding(Type resultType) { - return new ParameterBinding( resultType, null, true, false, false, false, null ); + return new ParameterBinding( resultType, null, true, false, false, false, false, null ); } /** @@ -161,10 +180,10 @@ public class ParameterBinding { * @return a parameter binding representing a mapping source type */ public static ParameterBinding forSourceTypeBinding(Type sourceType) { - return new ParameterBinding( sourceType, null, false, false, false, false, null ); + return new ParameterBinding( sourceType, null, false, false, false, false, false, null ); } public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) { - return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS ); + return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS ); } } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java index 37b57d902..42c318ca4 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java @@ -47,6 +47,7 @@ public class SourceMethod implements Method { private final List parameters; private final Parameter mappingTargetParameter; private final Parameter targetTypeParameter; + private final Parameter sourcePropertyNameParameter; private final Parameter targetPropertyNameParameter; private final boolean isObjectFactory; private final boolean isPresenceCheck; @@ -249,6 +250,7 @@ public class SourceMethod implements Method { this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters ); this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters ); + this.sourcePropertyNameParameter = Parameter.getSourcePropertyNameParameter( parameters ); this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters ); this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null; this.isObjectFactory = determineIfIsObjectFactory(); @@ -265,9 +267,10 @@ public class SourceMethod implements Method { private boolean determineIfIsObjectFactory() { boolean hasNoSourceParameters = getSourceParameters().isEmpty(); boolean hasNoMappingTargetParam = getMappingTargetParameter() == null; + boolean hasNoSourcePropertyNameParam = getSourcePropertyNameParameter() == null; boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null; return !isLifecycleCallbackMethod() && !returnType.isVoid() - && hasNoMappingTargetParam && hasNoTargetPropertyNameParam + && hasNoMappingTargetParam && hasNoSourcePropertyNameParam && hasNoTargetPropertyNameParam && ( hasObjectFactoryAnnotation || hasNoSourceParameters ); } @@ -382,6 +385,10 @@ public class SourceMethod implements Method { return targetTypeParameter; } + public Parameter getSourcePropertyNameParameter() { + return sourcePropertyNameParameter; + } + public Parameter getTargetPropertyNameParameter() { return targetPropertyNameParameter; } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java index 9e82dae25..800278a4c 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java @@ -171,6 +171,7 @@ public class SelectionContext { if ( sourceRHS != null ) { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) ); + addSourcePropertyNameBindings( availableParams, sourceRHS.getSourceType(), typeFactory ); } else { availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) ); @@ -189,6 +190,7 @@ public class SelectionContext { List availableParams = new ArrayList<>(); availableParams.add( ParameterBinding.forSourceTypeBinding( sourceType ) ); + addSourcePropertyNameBindings( availableParams, sourceType, typeFactory ); for ( Parameter param : mappingMethod.getParameters() ) { if ( param.isMappingContext() ) { @@ -201,6 +203,22 @@ public class SelectionContext { return availableParams; } + private static void addSourcePropertyNameBindings(List availableParams, Type sourceType, + TypeFactory typeFactory) { + + boolean sourcePropertyNameAvailable = false; + for ( ParameterBinding pb : availableParams ) { + if ( pb.isSourcePropertyName() ) { + sourcePropertyNameAvailable = true; + break; + } + } + if ( !sourcePropertyNameAvailable ) { + availableParams.add( ParameterBinding.forSourcePropertyNameBinding( typeFactory.getType( String.class ) ) ); + } + + } + /** * Adds default parameter bindings for the mapping-target and target-type if not already available. * diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java index 24275f594..50f834d43 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java @@ -215,6 +215,7 @@ public class TypeSelector implements MethodSelector { if ( parameter.isTargetType() == candidate.isTargetType() && parameter.isMappingTarget() == candidate.isMappingTarget() && parameter.isMappingContext() == candidate.isMappingContext() + && parameter.isSourcePropertyName() == candidate.isSourcePropertyName() && parameter.isTargetPropertyName() == candidate.isTargetPropertyName()) { result.add( candidate ); } diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java index faf7a786f..a9099ea17 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java @@ -26,6 +26,7 @@ import org.mapstruct.ap.internal.gem.MapMappingGem; import org.mapstruct.ap.internal.gem.MappingGem; import org.mapstruct.ap.internal.gem.MappingsGem; import org.mapstruct.ap.internal.gem.ObjectFactoryGem; +import org.mapstruct.ap.internal.gem.SourcePropertyNameGem; import org.mapstruct.ap.internal.gem.SubclassMappingGem; import org.mapstruct.ap.internal.gem.SubclassMappingsGem; import org.mapstruct.ap.internal.gem.TargetPropertyNameGem; @@ -415,6 +416,16 @@ public class MethodRetrievalProcessor implements ModelElementProcessor parameters, Type returnType) { for ( Parameter param : parameters ) { + + if ( param.isSourcePropertyName() && !param.getType().isString() ) { + messager.printMessage( + param.getElement(), + SourcePropertyNameGem.instanceOn( param.getElement() ).mirror(), + Message.RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE + ); + return false; + } + if ( param.isTargetPropertyName() && !param.getType().isString() ) { messager.printMessage( param.getElement(), diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java index 4b4331539..d8f27791e 100644 --- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java +++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java @@ -177,6 +177,7 @@ public enum Message { RETRIEVAL_MAPPER_USES_CYCLE( "The mapper %s is referenced itself in Mapper#uses.", Diagnostic.Kind.WARNING ), RETRIEVAL_AFTER_METHOD_NOT_IMPLEMENTED( "@AfterMapping can only be applied to an implemented method." ), RETRIEVAL_BEFORE_METHOD_NOT_IMPLEMENTED( "@BeforeMapping can only be applied to an implemented method." ), + RETRIEVAL_SOURCE_PROPERTY_NAME_WRONG_TYPE( "@SourcePropertyName can only by applied to a String parameter." ), RETRIEVAL_TARGET_PROPERTY_NAME_WRONG_TYPE( "@TargetPropertyName can only by applied to a String parameter." ), INHERITINVERSECONFIGURATION_DUPLICATES( "Several matching inverse methods exist: %s(). Specify a name explicitly." ), diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl index 4b45643dc..63f983df4 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReference.ftl @@ -44,6 +44,8 @@ <#if ext.targetBeanName??>${ext.targetBeanName}<#else>${param.variableName}<#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}<#t> <#elseif param.mappingContext> ${param.variableName}<#t> + <#elseif param.sourcePropertyName> + "${ext.sourcePropertyName}"<#t> <#elseif param.targetPropertyName> "${ext.targetPropertyName}"<#t> <#elseif param.sourceRHS??> @@ -60,7 +62,7 @@ <#-- macro: assignment - purpose: note: takes its targetyType from the singleSourceParameterType + purpose: note: takes its targetType from the singleSourceParameterType --> <#macro _assignment assignmentToUse> <@includeModel object=assignmentToUse @@ -69,6 +71,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=singleSourceParameterType/> - \ No newline at end of file + diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl index 0452e1699..68b05d84e 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/MethodReferencePresenceCheck.ftl @@ -8,5 +8,6 @@ <#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" --> <#if isNegate()>!<@includeModel object=methodReference presenceCheck=true + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName - targetType=ext.targetType/> \ No newline at end of file + targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl index e6aef4cec..f45659cb5 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/PropertyMapping.ftl @@ -11,6 +11,7 @@ existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=targetReadAccessorName targetWriteAccessorName=targetWriteAccessorName + sourcePropertyName=sourcePropertyName targetPropertyName=name targetType=targetType defaultValueAssignment=defaultValueAssignment /> \ No newline at end of file diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl index 4a9d356ce..a5e5798d1 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/TypeConversion.ftl @@ -14,6 +14,7 @@ ${openExpression}<@_assignment/>${closeExpression} existingInstanceMapping=ext.existingInstanceMapping targetReadAccessorName=ext.targetReadAccessorName targetWriteAccessorName=ext.targetWriteAccessorName + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName targetType=ext.targetType/> diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl index bce28ebe1..278b441aa 100644 --- a/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl +++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl @@ -17,6 +17,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetPropertyName=ext.targetPropertyName + sourcePropertyName=ext.sourcePropertyName targetType=ext.targetType/> ) { <#nested> } @@ -61,6 +62,7 @@ <#if sourcePresenceCheckerReference??> if ( <@includeModel object=sourcePresenceCheckerReference targetType=ext.targetType + sourcePropertyName=ext.sourcePropertyName targetPropertyName=ext.targetPropertyName /> ) { <#if needs_explicit_local_var> <@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java index 162ed118b..339af7594 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Address.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java similarity index 86% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java index f4cbc7191..c6a63065d 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/AddressDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * @author Nikola Ivačič diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java similarity index 80% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java index 4d2c716a9..8e5fa8695 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/DomainModel.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; /** * Target Property Name test entities diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java similarity index 96% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java index 59ee3426e..497717a59 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/Employee.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java similarity index 75% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java index 5d81a9334..f49f25e4a 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/EmployeeDto.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname; import java.util.List; @@ -15,14 +15,14 @@ public class EmployeeDto implements DomainModel { private String firstName; private String lastName; private String title; - private String country; + private String originCountry; private boolean active; private int age; private EmployeeDto boss; private AddressDto primaryAddress; - private List addresses; + private List originAddresses; public String getFirstName() { return firstName; @@ -48,12 +48,12 @@ public class EmployeeDto implements DomainModel { this.title = title; } - public String getCountry() { - return country; + public String getOriginCountry() { + return originCountry; } - public void setCountry(String country) { - this.country = country; + public void setOriginCountry(String originCountry) { + this.originCountry = originCountry; } public boolean isActive() { @@ -88,11 +88,11 @@ public class EmployeeDto implements DomainModel { this.primaryAddress = primaryAddress; } - public List getAddresses() { - return addresses; + public List getOriginAddresses() { + return originAddresses; } - public void setAddresses(List addresses) { - this.addresses = addresses; + public void setOriginAddresses(List originAddresses) { + this.originAddresses = originAddresses; } } diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java new file mode 100644 index 000000000..944fe5d14 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodForCollectionMapperWithSourcePropertyName.java @@ -0,0 +1,48 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collection; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodForCollectionMapperWithSourcePropertyName { + + ConditionalMethodForCollectionMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodForCollectionMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotEmpty(Collection collection, @SourcePropertyName String propName) { + if ( "originAddresses".equalsIgnoreCase( propName ) ) { + return false; + } + return collection != null && !collection.isEmpty(); + } + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java new file mode 100644 index 000000000..ea6a77d94 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -0,0 +1,54 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllExceptTarget { + + ConditionalMethodInMapperWithAllExceptTarget INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class ); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @SourcePropertyName String propName, + @Context PresenceUtils utils) { + utils.visited.add( propName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + if ( propName.equalsIgnoreCase( "firstName" ) ) { + return true; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java new file mode 100644 index 000000000..ee3be5764 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -0,0 +1,64 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithAllOptions { + + ConditionalMethodInMapperWithAllOptions INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); + + class PresenceUtils { + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); + Set visitedSources = new LinkedHashSet<>(); + Set visitedTargets = new LinkedHashSet<>(); + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + void map(EmployeeDto employeeDto, + @MappingTarget Employee employee, + @Context PresenceUtils utils); + + @Condition + default boolean isNotBlank(String value, + DomainModel source, + @MappingTarget DomainModel target, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, + @Context PresenceUtils utils) { + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); + utils.visitedSources.add( source.getClass().getSimpleName() ); + utils.visitedTargets.add( target.getClass().getSimpleName() ); + if ( sourcePropName.equalsIgnoreCase( "lastName" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java new file mode 100644 index 000000000..41ff6c526 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInMapperWithSourcePropertyName.java @@ -0,0 +1,39 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodInMapperWithSourcePropertyName { + + ConditionalMethodInMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java new file mode 100644 index 000000000..8ba222512 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodInUsesMapperWithSourcePropertyName.java @@ -0,0 +1,43 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper(uses = ConditionalMethodInUsesMapperWithSourcePropertyName.PresenceUtils.class) +public interface ConditionalMethodInUsesMapperWithSourcePropertyName { + + ConditionalMethodInUsesMapperWithSourcePropertyName INSTANCE + = Mappers.getMapper( ConditionalMethodInUsesMapperWithSourcePropertyName.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + class PresenceUtils { + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + if ( propName.equalsIgnoreCase( "originCountry" ) ) { + return false; + } + return value != null && !value.trim().isEmpty(); + } + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java new file mode 100644 index 000000000..64b6fcbba --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ConditionalMethodWithSourcePropertyNameInContextMapper.java @@ -0,0 +1,108 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.mapstruct.AfterMapping; +import org.mapstruct.BeforeMapping; +import org.mapstruct.Condition; +import org.mapstruct.Context; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.factory.Mappers; + +/** + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@Mapper +public interface ConditionalMethodWithSourcePropertyNameInContextMapper { + + ConditionalMethodWithSourcePropertyNameInContextMapper INSTANCE + = Mappers.getMapper( ConditionalMethodWithSourcePropertyNameInContextMapper.class ); + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtils utils); + + Address map(AddressDto addressDto, @Context PresenceUtils utils); + + class PresenceUtils { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean isNotBlank(String value, @SourcePropertyName String propName) { + visited.add( propName ); + return value != null && !value.trim().isEmpty(); + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); + + class PresenceUtilsAllProps { + Set visited = new LinkedHashSet<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visited.add( propName ); + return true; + } + } + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); + + Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); + + @BeforeMapping + default void before(DomainModel source, @Context PresenceUtilsAllPropsWithSource utils) { + String lastProp = utils.visitedSegments.peekLast(); + if ( lastProp != null && source != null ) { + utils.path.offerLast( lastProp ); + } + } + + @AfterMapping + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } + } + + class PresenceUtilsAllPropsWithSource { + Deque visitedSegments = new LinkedList<>(); + Deque visited = new LinkedList<>(); + Deque path = new LinkedList<>(); + + @Condition + public boolean collect(@SourcePropertyName String propName) { + visitedSegments.offerLast( propName ); + path.offerLast( propName ); + visited.offerLast( String.join( ".", path ) ); + path.pollLast(); + return true; + } + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java new file mode 100644 index 000000000..5ed118676 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/ErroneousNonStringSourcePropertyNameParameter.java @@ -0,0 +1,26 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import org.mapstruct.Condition; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.SourcePropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; + +@Mapper +public interface ErroneousNonStringSourcePropertyNameParameter { + + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") + Employee map(EmployeeDto employee); + + @Condition + default boolean isNotBlank(String value, @SourcePropertyName int propName) { + return value != null && !value.trim().isEmpty(); + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java new file mode 100644 index 000000000..2471ed647 --- /dev/null +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/sourcepropertyname/SourcePropertyNameTest.java @@ -0,0 +1,312 @@ +/* + * Copyright MapStruct Authors. + * + * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 + */ +package org.mapstruct.ap.test.conditional.propertyname.sourcepropertyname; + +import java.util.Collections; + +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; +import org.mapstruct.ap.testutil.IssueKey; +import org.mapstruct.ap.testutil.ProcessorTest; +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.runner.GeneratedSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Filip Hrisafov + * @author Nikola Ivačič + * @author Mohammad Al Zouabi + * @author Oliver Erhart + */ +@IssueKey("3323") +@WithClasses({ + Address.class, + AddressDto.class, + Employee.class, + EmployeeDto.class, + DomainModel.class +}) +public class SourcePropertyNameTest { + + @RegisterExtension + final GeneratedSource generatedSource = new GeneratedSource(); + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithSourcePropertyName.class + }) + public void conditionalMethodInMapperWithSourcePropertyName() { + ConditionalMethodInMapperWithSourcePropertyName mapper + = ConditionalMethodInMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodForCollectionMapperWithSourcePropertyName.class + }) + public void conditionalMethodForCollectionMapperWithSourcePropertyName() { + ConditionalMethodForCollectionMapperWithSourcePropertyName mapper + = ConditionalMethodForCollectionMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ).isNull(); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInUsesMapperWithSourcePropertyName.class + }) + public void conditionalMethodInUsesMapperWithSourcePropertyName() { + ConditionalMethodInUsesMapperWithSourcePropertyName mapper + = ConditionalMethodInUsesMapperWithSourcePropertyName.INSTANCE; + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isNull(); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllOptions.class + }) + public void conditionalMethodInMapperWithAllOptions() { + ConditionalMethodInMapperWithAllOptions mapper + = ConditionalMethodInMapperWithAllOptions.INSTANCE; + + ConditionalMethodInMapperWithAllOptions.PresenceUtils utils = + new ConditionalMethodInMapperWithAllOptions.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = new Employee(); + mapper.map( employeeDto, employee, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getFirstName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); + assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); + assertThat( utils.visitedTargets ).containsExactly( "Employee" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodInMapperWithAllExceptTarget.class + }) + public void conditionalMethodInMapperWithAllExceptTarget() { + ConditionalMethodInMapperWithAllExceptTarget mapper + = ConditionalMethodInMapperWithAllExceptTarget.INSTANCE; + + ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils utils = + new ConditionalMethodInMapperWithAllExceptTarget.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setFirstName( " " ); + employeeDto.setLastName( "Testirovich" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isEqualTo( "Testirovich" ); + assertThat( employee.getFirstName() ).isEqualTo( " " ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" ); + } + + @ProcessorTest + @WithClasses({ + ConditionalMethodWithSourcePropertyNameInContextMapper.class + }) + public void conditionalMethodWithSourcePropertyNameInUsesContextMapper() { + ConditionalMethodWithSourcePropertyNameInContextMapper mapper + = ConditionalMethodWithSourcePropertyNameInContextMapper.INSTANCE; + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils utils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtils(); + + EmployeeDto employeeDto = new EmployeeDto(); + employeeDto.setLastName( " " ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + Employee employee = mapper.map( employeeDto, utils ); + assertThat( employee.getLastName() ).isNull(); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( utils.visited ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry", "street" ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllProps(); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtils ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 6" ); + assertThat( allPropsUtils.visited ) + .containsExactlyInAnyOrder( + "firstName", + "lastName", + "title", + "originCountry", + "active", + "age", + "boss", + "primaryAddress", + "originAddresses", + "street" + ); + + ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource = + new ConditionalMethodWithSourcePropertyNameInContextMapper.PresenceUtilsAllPropsWithSource(); + + EmployeeDto bossEmployeeDto = new EmployeeDto(); + bossEmployeeDto.setLastName( "Boss Tester" ); + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( + "Testing St. 10" ) ) ); + + employeeDto = new EmployeeDto(); + employeeDto.setLastName( "Tester" ); + employeeDto.setOriginCountry( "US" ); + employeeDto.setBoss( bossEmployeeDto ); + employeeDto.setOriginAddresses( + Collections.singletonList( new AddressDto( "Testing St. 6" ) ) + ); + + employee = mapper.map( employeeDto, allPropsUtilsWithSource ); + assertThat( employee.getLastName() ).isEqualTo( "Tester" ); + assertThat( employee.getCountry() ).isEqualTo( "US" ); + assertThat( employee.getAddresses() ).isNotEmpty(); + assertThat( employee.getAddresses().get( 0 ).getStreet() ).isEqualTo( "Testing St. 6" ); + assertThat( employee.getBoss() ).isNotNull(); + assertThat( employee.getBoss().getCountry() ).isEqualTo( "US" ); + assertThat( employee.getBoss().getLastName() ).isEqualTo( "Boss Tester" ); + assertThat( employee.getBoss().getAddresses() ) + .extracting( Address::getStreet ) + .containsExactly( "Testing St. 10" ); + assertThat( allPropsUtilsWithSource.visited ) + .containsExactly( + "originCountry", + "originAddresses", + "originAddresses.street", + "firstName", + "lastName", + "title", + "active", + "age", + "boss", + "boss.originCountry", + "boss.originAddresses", + "boss.originAddresses.street", + "boss.firstName", + "boss.lastName", + "boss.title", + "boss.active", + "boss.age", + "boss.boss", + "boss.primaryAddress", + "primaryAddress" + ); + } + + @ProcessorTest + @WithClasses({ + ErroneousNonStringSourcePropertyNameParameter.class + }) + @ExpectedCompilationOutcome( + value = CompilationResult.FAILED, + diagnostics = { + @Diagnostic( + kind = javax.tools.Diagnostic.Kind.ERROR, + type = ErroneousNonStringSourcePropertyNameParameter.class, + line = 23, + message = "@SourcePropertyName can only by applied to a String parameter." + ) + } + ) + public void nonStringSourcePropertyNameParameter() { + + } +} diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java index 71baa0942..a51a318eb 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java @@ -3,15 +3,18 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collection; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Collection; - /** * @author Nikola Ivačič */ @@ -21,6 +24,8 @@ public interface ConditionalMethodForCollectionMapperWithTargetPropertyName { ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java similarity index 76% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java index 430303d06..1d0dd3fa5 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java @@ -3,17 +3,21 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -29,6 +33,8 @@ public interface ConditionalMethodInMapperWithAllExceptTarget { Set visitedSources = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java similarity index 61% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java index a18ba0db7..d3ccb9ba2 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java @@ -3,18 +3,23 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.LinkedHashSet; +import java.util.Set; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; +import org.mapstruct.SourcePropertyName; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.LinkedHashSet; -import java.util.Set; - /** * @author Filip Hrisafov * @author Nikola Ivačič @@ -26,11 +31,14 @@ public interface ConditionalMethodInMapperWithAllOptions { = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class ); class PresenceUtils { - Set visited = new LinkedHashSet<>(); + Set visitedSourceNames = new LinkedHashSet<>(); + Set visitedTargetNames = new LinkedHashSet<>(); Set visitedSources = new LinkedHashSet<>(); Set visitedTargets = new LinkedHashSet<>(); } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") void map(EmployeeDto employeeDto, @MappingTarget Employee employee, @Context PresenceUtils utils); @@ -39,12 +47,14 @@ public interface ConditionalMethodInMapperWithAllOptions { default boolean isNotBlank(String value, DomainModel source, @MappingTarget DomainModel target, - @TargetPropertyName String propName, + @SourcePropertyName String sourcePropName, + @TargetPropertyName String targetPropName, @Context PresenceUtils utils) { - utils.visited.add( propName ); + utils.visitedSourceNames.add( sourcePropName ); + utils.visitedTargetNames.add( targetPropName ); utils.visitedSources.add( source.getClass().getSimpleName() ); utils.visitedTargets.add( target.getClass().getSimpleName() ); - if ( propName.equalsIgnoreCase( "lastName" ) ) { + if ( targetPropName.equalsIgnoreCase( "lastName" ) ) { return false; } return value != null && !value.trim().isEmpty(); diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java index 6d27bc903..d5dc378f3 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInMapperWithTargetPropertyName { ConditionalMethodInMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java similarity index 74% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java index 7c526884e..381b99d4f 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java @@ -3,11 +3,14 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; /** @@ -20,6 +23,8 @@ public interface ConditionalMethodInUsesMapperWithTargetPropertyName { ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); class PresenceUtils { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java similarity index 70% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java index a7cde220c..44bc26243 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java @@ -3,21 +3,29 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Deque; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; import org.mapstruct.AfterMapping; import org.mapstruct.BeforeMapping; import org.mapstruct.Condition; import org.mapstruct.Context; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.TargetType; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.factory.Mappers; -import java.util.Deque; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.Set; - /** * @author Nikola Ivačič */ @@ -27,6 +35,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class ); + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtils utils); Address map(AddressDto addressDto, @Context PresenceUtils utils); @@ -41,6 +51,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils); @@ -55,6 +67,8 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { } } + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee, @Context PresenceUtilsAllPropsWithSource utils); Address map(AddressDto addressDto, @Context PresenceUtilsAllPropsWithSource utils); @@ -68,8 +82,11 @@ public interface ConditionalMethodWithTargetPropertyNameInContextMapper { } @AfterMapping - default void after(@Context PresenceUtilsAllPropsWithSource utils) { - utils.path.pollLast(); + default void after(@TargetType Class targetClass, @Context PresenceUtilsAllPropsWithSource utils) { + // intermediate method for collection mapping must not change the path + if (targetClass != List.class) { + utils.path.pollLast(); + } } class PresenceUtilsAllPropsWithSource { diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java similarity index 59% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java index ec545a058..d56277abf 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/ErroneousNonStringTargetPropertyNameParameter.java @@ -3,15 +3,20 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; import org.mapstruct.Condition; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; import org.mapstruct.TargetPropertyName; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; @Mapper public interface ErroneousNonStringTargetPropertyNameParameter { + @Mapping(target = "country", source = "originCountry") + @Mapping(target = "addresses", source = "originAddresses") Employee map(EmployeeDto employee); @Condition diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java similarity index 88% rename from processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java rename to processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java index 91e4b77de..bb90c0b06 100644 --- a/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java +++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/propertyname/targetpropertyname/TargetPropertyNameTest.java @@ -3,9 +3,16 @@ * * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0 */ -package org.mapstruct.ap.test.conditional.targetpropertyname; +package org.mapstruct.ap.test.conditional.propertyname.targetpropertyname; + +import java.util.Collections; import org.junit.jupiter.api.extension.RegisterExtension; +import org.mapstruct.ap.test.conditional.propertyname.Address; +import org.mapstruct.ap.test.conditional.propertyname.AddressDto; +import org.mapstruct.ap.test.conditional.propertyname.DomainModel; +import org.mapstruct.ap.test.conditional.propertyname.Employee; +import org.mapstruct.ap.test.conditional.propertyname.EmployeeDto; import org.mapstruct.ap.testutil.IssueKey; import org.mapstruct.ap.testutil.ProcessorTest; import org.mapstruct.ap.testutil.WithClasses; @@ -14,8 +21,6 @@ import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic; import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome; import org.mapstruct.ap.testutil.runner.GeneratedSource; -import java.util.Collections; - import static org.assertj.core.api.Assertions.assertThat; /** @@ -46,8 +51,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -71,8 +76,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -94,8 +99,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -122,8 +127,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -135,7 +140,9 @@ public class TargetPropertyNameTest { assertThat( employee.getAddresses() ) .extracting( Address::getStreet ) .containsExactly( "Testing St. 6" ); - assertThat( utils.visited ) + assertThat( utils.visitedSourceNames ) + .containsExactlyInAnyOrder( "firstName", "lastName", "title", "originCountry" ); + assertThat( utils.visitedTargetNames ) .containsExactlyInAnyOrder( "firstName", "lastName", "title", "country" ); assertThat( utils.visitedSources ).containsExactly( "EmployeeDto" ); assertThat( utils.visitedTargets ).containsExactly( "Employee" ); @@ -155,8 +162,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setFirstName( " " ); employeeDto.setLastName( "Testirovich" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -185,8 +192,8 @@ public class TargetPropertyNameTest { EmployeeDto employeeDto = new EmployeeDto(); employeeDto.setLastName( " " ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -204,8 +211,8 @@ public class TargetPropertyNameTest { employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); - employeeDto.setAddresses( + employeeDto.setOriginCountry( "US" ); + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -234,15 +241,15 @@ public class TargetPropertyNameTest { EmployeeDto bossEmployeeDto = new EmployeeDto(); bossEmployeeDto.setLastName( "Boss Tester" ); - bossEmployeeDto.setCountry( "US" ); - bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto( + bossEmployeeDto.setOriginCountry( "US" ); + bossEmployeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 10" ) ) ); employeeDto = new EmployeeDto(); employeeDto.setLastName( "Tester" ); - employeeDto.setCountry( "US" ); + employeeDto.setOriginCountry( "US" ); employeeDto.setBoss( bossEmployeeDto ); - employeeDto.setAddresses( + employeeDto.setOriginAddresses( Collections.singletonList( new AddressDto( "Testing St. 6" ) ) ); @@ -259,26 +266,26 @@ public class TargetPropertyNameTest { .containsExactly( "Testing St. 10" ); assertThat( allPropsUtilsWithSource.visited ) .containsExactly( + "country", + "addresses", + "addresses.street", "firstName", "lastName", "title", - "country", "active", "age", "boss", + "boss.country", + "boss.addresses", + "boss.addresses.street", "boss.firstName", "boss.lastName", "boss.title", - "boss.country", "boss.active", "boss.age", "boss.boss", "boss.primaryAddress", - "boss.addresses", - "boss.addresses.street", - "primaryAddress", - "addresses", - "addresses.street" + "primaryAddress" ); } @@ -293,7 +300,7 @@ public class TargetPropertyNameTest { @Diagnostic( kind = javax.tools.Diagnostic.Kind.ERROR, type = ErroneousNonStringTargetPropertyNameParameter.class, - line = 18, + line = 23, message = "@TargetPropertyName can only by applied to a String parameter." ) }