diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java
index ec1bf06a8..148ec4879 100644
--- a/core/src/main/java/org/mapstruct/Condition.java
+++ b/core/src/main/java/org/mapstruct/Condition.java
@@ -23,6 +23,7 @@ import java.lang.annotation.Target;
* e.g. the value given by calling {@code getName()} for the name property of the source bean
*
The mapping source parameter
* {@code @}{@link Context} parameter
+ * {@code @}{@link TargetPropertyName} parameter
*
*
* Note: The usage of this annotation is mandatory
diff --git a/core/src/main/java/org/mapstruct/TargetPropertyName.java b/core/src/main/java/org/mapstruct/TargetPropertyName.java
new file mode 100644
index 000000000..17af966fa
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/TargetPropertyName.java
@@ -0,0 +1,25 @@
+/*
+ * 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 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.
+ *
+ * @author Nikola Ivačič
+ * @since 1.6
+ */
+@Target(ElementType.PARAMETER)
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetPropertyName {
+}
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 1cc19fcc3..decee792d 100644
--- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
@@ -404,6 +404,61 @@ public class CarMapperImpl implements CarMapper {
----
====
+Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method:
+
+.Mapper using custom condition check method with `@TargetPropertyName`
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+@Mapper
+public interface CarMapper {
+
+ CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
+
+ @Condition
+ default boolean isNotEmpty(String value, @TargetPropertyName String name) {
+ if ( name.equals( "owner" ) {
+ return value != null
+ && !value.isEmpty()
+ && !value.equals( value.toLowerCase() );
+ }
+ return value != null && !value.isEmpty();
+ }
+}
+----
+====
+
+The generated mapper with `@TargetPropertyName` will look like:
+
+.Custom condition check in generated implementation
+====
+[source, java, linenums]
+[subs="verbatim,attributes"]
+----
+// GENERATED CODE
+public class CarMapperImpl implements CarMapper {
+
+ @Override
+ public CarDto carToCarDto(Car car, CarDto carDto) {
+ if ( car == null ) {
+ return carDto;
+ }
+
+ if ( isNotEmpty( car.getOwner(), "owner" ) ) {
+ carDto.setOwner( car.getOwner() );
+ } else {
+ carDto.setOwner( null );
+ }
+
+ // Mapping of other properties
+
+ return carDto;
+ }
+}
+----
+====
+
[IMPORTANT]
====
If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself.
@@ -412,6 +467,8 @@ If there is a custom `@Condition` method applicable for the property it will hav
[NOTE]
====
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.
====
<> 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 e0491cbc4..70098c952 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
@@ -30,6 +30,7 @@ import org.mapstruct.ObjectFactory;
import org.mapstruct.Qualifier;
import org.mapstruct.SubclassMapping;
import org.mapstruct.SubclassMappings;
+import org.mapstruct.TargetPropertyName;
import org.mapstruct.TargetType;
import org.mapstruct.ValueMapping;
import org.mapstruct.ValueMappings;
@@ -52,6 +53,7 @@ import org.mapstruct.tools.gem.GemDefinition;
@GemDefinition(SubclassMapping.class)
@GemDefinition(SubclassMappings.class)
@GemDefinition(TargetType.class)
+@GemDefinition(TargetPropertyName.class)
@GemDefinition(MappingTarget.class)
@GemDefinition(DecoratedWith.class)
@GemDefinition(MapperConfig.class)
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 dc62018be..600c8ac33 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
@@ -15,6 +15,7 @@ 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.TargetPropertyNameGem;
import org.mapstruct.ap.internal.util.Collections;
/**
@@ -31,6 +32,7 @@ public class Parameter extends ModelElement {
private final boolean mappingTarget;
private final boolean targetType;
private final boolean mappingContext;
+ private final boolean targetPropertyName;
private final boolean varArgs;
@@ -42,10 +44,12 @@ public class Parameter extends ModelElement {
this.mappingTarget = MappingTargetGem.instanceOn( element ) != null;
this.targetType = TargetTypeGem.instanceOn( element ) != null;
this.mappingContext = ContextGem.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 varArgs) {
this.element = null;
this.name = name;
@@ -54,11 +58,12 @@ public class Parameter extends ModelElement {
this.mappingTarget = mappingTarget;
this.targetType = targetType;
this.mappingContext = mappingContext;
+ this.targetPropertyName = targetPropertyName;
this.varArgs = varArgs;
}
public Parameter(String name, Type type) {
- this( name, type, false, false, false, false );
+ this( name, type, false, false, false, false, false );
}
public Element getElement() {
@@ -94,6 +99,7 @@ public class Parameter extends ModelElement {
return ( mappingTarget ? "@MappingTarget " : "" )
+ ( targetType ? "@TargetType " : "" )
+ ( mappingContext ? "@Context " : "" )
+ + ( targetPropertyName ? "@TargetPropertyName " : "" )
+ "%s " + name;
}
@@ -110,6 +116,10 @@ public class Parameter extends ModelElement {
return mappingContext;
}
+ public boolean isTargetPropertyName() {
+ return targetPropertyName;
+ }
+
public boolean isVarArgs() {
return varArgs;
}
@@ -154,6 +164,7 @@ public class Parameter extends ModelElement {
true,
false,
false,
+ false,
false
);
}
@@ -195,8 +206,15 @@ public class Parameter extends ModelElement {
return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null );
}
+ public static Parameter getTargetPropertyNameParameter(List parameters) {
+ return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null );
+ }
+
private static boolean isSourceParameter( Parameter parameter ) {
- return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext();
+ return !parameter.isMappingTarget() &&
+ !parameter.isTargetType() &&
+ !parameter.isMappingContext() &&
+ !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 36e4d3e86..18274e549 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,15 +22,17 @@ public class ParameterBinding {
private final boolean targetType;
private final boolean mappingTarget;
private final boolean mappingContext;
+ private final boolean targetPropertyName;
private final SourceRHS sourceRHS;
private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
- boolean mappingContext, SourceRHS sourceRHS) {
+ boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) {
this.type = parameterType;
this.variableName = variableName;
this.targetType = targetType;
this.mappingTarget = mappingTarget;
this.mappingContext = mappingContext;
+ this.targetPropertyName = targetPropertyName;
this.sourceRHS = sourceRHS;
}
@@ -62,6 +64,13 @@ public class ParameterBinding {
return mappingContext;
}
+ /**
+ * @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter.
+ */
+ public boolean isTargetPropertyName() {
+ return targetPropertyName;
+ }
+
/**
* @return the type of the parameter that is bound
*/
@@ -99,6 +108,7 @@ public class ParameterBinding {
parameter.isMappingTarget(),
parameter.isTargetType(),
parameter.isMappingContext(),
+ parameter.isTargetPropertyName(),
null
);
}
@@ -118,6 +128,7 @@ public class ParameterBinding {
false,
false,
false,
+ false,
null
);
}
@@ -127,7 +138,14 @@ 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, null );
+ return new ParameterBinding( classTypeOf, null, false, true, 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 );
}
/**
@@ -135,7 +153,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, null );
+ return new ParameterBinding( resultType, null, true, false, false, false, null );
}
/**
@@ -143,10 +161,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, null );
+ return new ParameterBinding( sourceType, null, false, false, false, false, null );
}
public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) {
- return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, sourceRHS );
+ return new ParameterBinding( sourceRHS.getSourceType(), null, 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 92180bca8..37b57d902 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 targetPropertyNameParameter;
private final boolean isObjectFactory;
private final boolean isPresenceCheck;
private final Type returnType;
@@ -248,6 +249,7 @@ public class SourceMethod implements Method {
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
+ this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
this.isObjectFactory = determineIfIsObjectFactory();
this.isPresenceCheck = determineIfIsPresenceCheck();
@@ -263,8 +265,9 @@ public class SourceMethod implements Method {
private boolean determineIfIsObjectFactory() {
boolean hasNoSourceParameters = getSourceParameters().isEmpty();
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
+ boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null;
return !isLifecycleCallbackMethod() && !returnType.isVoid()
- && hasNoMappingTargetParam
+ && hasNoMappingTargetParam && hasNoTargetPropertyNameParam
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
}
@@ -379,6 +382,10 @@ public class SourceMethod implements Method {
return targetTypeParameter;
}
+ public Parameter getTargetPropertyNameParameter() {
+ return targetPropertyNameParameter;
+ }
+
public boolean isIterableMapping() {
if ( isIterableMapping == null ) {
isIterableMapping = getSourceParameters().size() == 1
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 74e518c84..8acdd327c 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
@@ -96,7 +96,7 @@ public class TypeSelector implements MethodSelector {
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
}
- addMappingTargetAndTargetTypeBindings( availableParams, targetType );
+ addTargetRelevantBindings( availableParams, targetType );
return availableParams;
}
@@ -116,7 +116,7 @@ public class TypeSelector implements MethodSelector {
}
}
- addMappingTargetAndTargetTypeBindings( availableParams, targetType );
+ addTargetRelevantBindings( availableParams, targetType );
return availableParams;
}
@@ -127,9 +127,10 @@ public class TypeSelector implements MethodSelector {
* @param availableParams Already available params, new entries will be added to this list
* @param targetType Target type
*/
- private void addMappingTargetAndTargetTypeBindings(List availableParams, Type targetType) {
+ private void addTargetRelevantBindings(List availableParams, Type targetType) {
boolean mappingTargetAvailable = false;
boolean targetTypeAvailable = false;
+ boolean targetPropertyNameAvailable = false;
// search available parameter bindings if mapping-target and/or target-type is available
for ( ParameterBinding pb : availableParams ) {
@@ -139,6 +140,9 @@ public class TypeSelector implements MethodSelector {
else if ( pb.isTargetType() ) {
targetTypeAvailable = true;
}
+ else if ( pb.isTargetPropertyName() ) {
+ targetPropertyNameAvailable = true;
+ }
}
if ( !mappingTargetAvailable ) {
@@ -147,6 +151,9 @@ public class TypeSelector implements MethodSelector {
if ( !targetTypeAvailable ) {
availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
}
+ if ( !targetPropertyNameAvailable ) {
+ availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) );
+ }
}
private SelectedMethod getMatchingParameterBinding(Type returnType,
@@ -301,7 +308,8 @@ public class TypeSelector implements MethodSelector {
for ( ParameterBinding candidate : candidateParameters ) {
if ( parameter.isTargetType() == candidate.isTargetType()
&& parameter.isMappingTarget() == candidate.isMappingTarget()
- && parameter.isMappingContext() == candidate.isMappingContext() ) {
+ && parameter.isMappingContext() == candidate.isMappingContext()
+ && parameter.isTargetPropertyName() == candidate.isTargetPropertyName()) {
result.add( candidate );
}
}
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 6ccdf2687..80f893f1a 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><#if ext.targetReadAccessorName??>.${ext.targetReadAccessorName}#if><#t>
<#elseif param.mappingContext>
${param.variableName}<#t>
+ <#elseif param.targetPropertyName>
+ "${ext.targetPropertyName}"<#t>
<#elseif param.sourceRHS??>
<@_assignment assignmentToUse=param.sourceRHS/><#t>
<#elseif assignment??>
@@ -66,5 +68,6 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=singleSourceParameterType/>
#macro>
\ 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 9a2837a02..24f871e21 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
@@ -7,4 +7,5 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" -->
<@includeModel object=methodReference
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
\ No newline at end of file
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 20e6f1734..e6aef4cec 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,5 +11,6 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=targetReadAccessorName
targetWriteAccessorName=targetWriteAccessorName
+ 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 370fe7c97..4a9d356ce 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
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
#macro>
@compress>
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl
index 2795dac7b..ce1fcdcd6 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/Java8FunctionWrapper.ftl
@@ -34,5 +34,6 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
#macro>
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl
index 53e987e47..97ea856ec 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/LocalVarWrapper.ftl
@@ -25,5 +25,6 @@
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
#macro>
\ No newline at end of file
diff --git a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl
index 9f46f3604..9917dbacf 100644
--- a/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl
+++ b/processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/ReturnWrapper.ftl
@@ -13,5 +13,6 @@ return <@_assignment/>;
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
#macro>
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 6d05ddd13..e5fb97060 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
@@ -16,6 +16,7 @@
<#macro handleSourceReferenceNullCheck>
<#if sourcePresenceCheckerReference??>
if ( <@includeModel object=sourcePresenceCheckerReference
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/> ) {
<#nested>
}
@@ -58,7 +59,8 @@
-->
<#macro handleLocalVarNullCheck needs_explicit_local_var>
<#if sourcePresenceCheckerReference??>
- if ( <@includeModel object=sourcePresenceCheckerReference /> ) {
+ if ( <@includeModel object=sourcePresenceCheckerReference
+ targetPropertyName=ext.targetPropertyName/> ) {
<#if needs_explicit_local_var>
<@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>;
<#nested>
@@ -113,6 +115,7 @@ Performs a standard assignment.
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>
#macro>
<#--
@@ -124,6 +127,7 @@ Performs a default assignment with a default value.
existingInstanceMapping=ext.existingInstanceMapping
targetReadAccessorName=ext.targetReadAccessorName
targetWriteAccessorName=ext.targetWriteAccessorName
+ targetPropertyName=ext.targetPropertyName
targetType=ext.targetType
defaultValue=ext.defaultValue/>
#macro>
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/targetpropertyname/Address.java
new file mode 100644
index 000000000..162ed118b
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Address.java
@@ -0,0 +1,21 @@
+/*
+ * 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.targetpropertyname;
+
+/**
+ * @author Nikola Ivačič
+ */
+public class Address implements DomainModel {
+ private String street;
+
+ public String getStreet() {
+ return street;
+ }
+
+ public void setStreet(String street) {
+ this.street = street;
+ }
+}
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/targetpropertyname/AddressDto.java
new file mode 100644
index 000000000..f4cbc7191
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/AddressDto.java
@@ -0,0 +1,21 @@
+/*
+ * 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.targetpropertyname;
+
+/**
+ * @author Nikola Ivačič
+ */
+public class AddressDto implements DomainModel {
+ private final String street;
+
+ public AddressDto(String street) {
+ this.street = street;
+ }
+
+ public String getStreet() {
+ return street;
+ }
+}
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/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java
new file mode 100644
index 000000000..71baa0942
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodForCollectionMapperWithTargetPropertyName.java
@@ -0,0 +1,41 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+import org.mapstruct.factory.Mappers;
+
+import java.util.Collection;
+
+/**
+ * @author Nikola Ivačič
+ */
+@Mapper
+public interface ConditionalMethodForCollectionMapperWithTargetPropertyName {
+
+ ConditionalMethodForCollectionMapperWithTargetPropertyName INSTANCE
+ = Mappers.getMapper( ConditionalMethodForCollectionMapperWithTargetPropertyName.class );
+
+ Employee map(EmployeeDto employee);
+
+ @Condition
+ default boolean isNotEmpty(Collection collection, @TargetPropertyName String propName) {
+ if ( "addresses".equalsIgnoreCase( propName ) ) {
+ return false;
+ }
+ return collection != null && !collection.isEmpty();
+ }
+
+ @Condition
+ default boolean isNotBlank(String value, @TargetPropertyName String propName) {
+ if ( propName.equalsIgnoreCase( "lastName" ) ) {
+ return false;
+ }
+ return value != null && !value.trim().isEmpty();
+ }
+}
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/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java
new file mode 100644
index 000000000..430303d06
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllExceptTarget.java
@@ -0,0 +1,46 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Context;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+import org.mapstruct.factory.Mappers;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author Filip Hrisafov
+ * @author Nikola Ivačič
+ */
+@Mapper
+public interface ConditionalMethodInMapperWithAllExceptTarget {
+
+ ConditionalMethodInMapperWithAllExceptTarget INSTANCE
+ = Mappers.getMapper( ConditionalMethodInMapperWithAllExceptTarget.class );
+
+ class PresenceUtils {
+ Set visited = new LinkedHashSet<>();
+ Set visitedSources = new LinkedHashSet<>();
+ }
+
+ Employee map(EmployeeDto employee, @Context PresenceUtils utils);
+
+ @Condition
+ default boolean isNotBlank(String value,
+ DomainModel source,
+ @TargetPropertyName 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/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java
new file mode 100644
index 000000000..a18ba0db7
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithAllOptions.java
@@ -0,0 +1,52 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Context;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+import org.mapstruct.TargetPropertyName;
+import org.mapstruct.factory.Mappers;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author Filip Hrisafov
+ * @author Nikola Ivačič
+ */
+@Mapper
+public interface ConditionalMethodInMapperWithAllOptions {
+
+ ConditionalMethodInMapperWithAllOptions INSTANCE
+ = Mappers.getMapper( ConditionalMethodInMapperWithAllOptions.class );
+
+ class PresenceUtils {
+ Set visited = new LinkedHashSet<>();
+ Set visitedSources = new LinkedHashSet<>();
+ Set visitedTargets = new LinkedHashSet<>();
+ }
+
+ void map(EmployeeDto employeeDto,
+ @MappingTarget Employee employee,
+ @Context PresenceUtils utils);
+
+ @Condition
+ default boolean isNotBlank(String value,
+ DomainModel source,
+ @MappingTarget DomainModel target,
+ @TargetPropertyName String propName,
+ @Context PresenceUtils utils) {
+ utils.visited.add( propName );
+ utils.visitedSources.add( source.getClass().getSimpleName() );
+ utils.visitedTargets.add( target.getClass().getSimpleName() );
+ if ( propName.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/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java
new file mode 100644
index 000000000..6d27bc903
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInMapperWithTargetPropertyName.java
@@ -0,0 +1,32 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ * @author Nikola Ivačič
+ */
+@Mapper
+public interface ConditionalMethodInMapperWithTargetPropertyName {
+
+ ConditionalMethodInMapperWithTargetPropertyName INSTANCE
+ = Mappers.getMapper( ConditionalMethodInMapperWithTargetPropertyName.class );
+
+ Employee map(EmployeeDto employee);
+
+ @Condition
+ default boolean isNotBlank(String value, @TargetPropertyName String propName) {
+ if ( propName.equalsIgnoreCase( "lastName" ) ) {
+ return false;
+ }
+ return value != null && !value.trim().isEmpty();
+ }
+}
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/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java
new file mode 100644
index 000000000..7c526884e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodInUsesMapperWithTargetPropertyName.java
@@ -0,0 +1,36 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ * @author Nikola Ivačič
+ */
+@Mapper(uses = ConditionalMethodInUsesMapperWithTargetPropertyName.PresenceUtils.class)
+public interface ConditionalMethodInUsesMapperWithTargetPropertyName {
+
+ ConditionalMethodInUsesMapperWithTargetPropertyName INSTANCE
+ = Mappers.getMapper( ConditionalMethodInUsesMapperWithTargetPropertyName.class );
+
+ Employee map(EmployeeDto employee);
+
+ class PresenceUtils {
+
+ @Condition
+ public boolean isNotBlank(String value, @TargetPropertyName String propName) {
+ if ( propName.equalsIgnoreCase( "lastName" ) ) {
+ return false;
+ }
+ return value != null && !value.trim().isEmpty();
+ }
+
+ }
+}
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/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java
new file mode 100644
index 000000000..a7cde220c
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/ConditionalMethodWithTargetPropertyNameInContextMapper.java
@@ -0,0 +1,89 @@
+/*
+ * 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.targetpropertyname;
+
+import org.mapstruct.AfterMapping;
+import org.mapstruct.BeforeMapping;
+import org.mapstruct.Condition;
+import org.mapstruct.Context;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+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č
+ */
+@Mapper
+public interface ConditionalMethodWithTargetPropertyNameInContextMapper {
+
+ ConditionalMethodWithTargetPropertyNameInContextMapper INSTANCE
+ = Mappers.getMapper( ConditionalMethodWithTargetPropertyNameInContextMapper.class );
+
+ 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, @TargetPropertyName String propName) {
+ visited.add( propName );
+ return value != null && !value.trim().isEmpty();
+ }
+ }
+
+ Employee map(EmployeeDto employee, @Context PresenceUtilsAllProps utils);
+
+ Address map(AddressDto addressDto, @Context PresenceUtilsAllProps utils);
+
+ class PresenceUtilsAllProps {
+ Set visited = new LinkedHashSet<>();
+
+ @Condition
+ public boolean collect(@TargetPropertyName String propName) {
+ visited.add( propName );
+ return true;
+ }
+ }
+
+ 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(@Context PresenceUtilsAllPropsWithSource utils) {
+ utils.path.pollLast();
+ }
+
+ class PresenceUtilsAllPropsWithSource {
+ Deque visitedSegments = new LinkedList<>();
+ Deque visited = new LinkedList<>();
+ Deque path = new LinkedList<>();
+
+ @Condition
+ public boolean collect(@TargetPropertyName 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/targetpropertyname/DomainModel.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java
new file mode 100644
index 000000000..4d2c716a9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/DomainModel.java
@@ -0,0 +1,15 @@
+/*
+ * 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.targetpropertyname;
+
+/**
+ * Target Property Name test entities
+ *
+ * @author Nikola Ivačič
+ */
+public interface DomainModel {
+
+}
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/targetpropertyname/Employee.java
new file mode 100644
index 000000000..59ee3426e
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/Employee.java
@@ -0,0 +1,99 @@
+/*
+ * 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.targetpropertyname;
+
+import java.util.List;
+
+/**
+ * @author Nikola Ivačič
+ */
+public class Employee implements DomainModel {
+
+ private String firstName;
+ private String lastName;
+ private String title;
+ private String country;
+ private boolean active;
+ private int age;
+
+ private Employee boss;
+
+ private Address primaryAddress;
+
+ private List addresses;
+
+ 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 String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public Employee getBoss() {
+ return boss;
+ }
+
+ public void setBoss(Employee boss) {
+ this.boss = boss;
+ }
+
+ public Address getPrimaryAddress() {
+ return primaryAddress;
+ }
+
+ public void setPrimaryAddress(Address primaryAddress) {
+ this.primaryAddress = primaryAddress;
+ }
+
+ public List getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List addresses) {
+ this.addresses = addresses;
+ }
+}
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/targetpropertyname/EmployeeDto.java
new file mode 100644
index 000000000..5d81a9334
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/EmployeeDto.java
@@ -0,0 +1,98 @@
+/*
+ * 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.targetpropertyname;
+
+import java.util.List;
+
+/**
+ * @author Nikola Ivačič
+ */
+public class EmployeeDto implements DomainModel {
+
+ private String firstName;
+ private String lastName;
+ private String title;
+ private String country;
+ private boolean active;
+ private int age;
+
+ private EmployeeDto boss;
+
+ private AddressDto primaryAddress;
+ private List addresses;
+
+ 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 String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getCountry() {
+ return country;
+ }
+
+ public void setCountry(String country) {
+ this.country = country;
+ }
+
+ public boolean isActive() {
+ return active;
+ }
+
+ public void setActive(boolean active) {
+ this.active = active;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public EmployeeDto getBoss() {
+ return boss;
+ }
+
+ public void setBoss(EmployeeDto boss) {
+ this.boss = boss;
+ }
+
+ public AddressDto getPrimaryAddress() {
+ return primaryAddress;
+ }
+
+ public void setPrimaryAddress(AddressDto primaryAddress) {
+ this.primaryAddress = primaryAddress;
+ }
+
+ public List getAddresses() {
+ return addresses;
+ }
+
+ public void setAddresses(List addresses) {
+ this.addresses = addresses;
+ }
+}
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/targetpropertyname/TargetPropertyNameTest.java
new file mode 100644
index 000000000..3d96e6666
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/targetpropertyname/TargetPropertyNameTest.java
@@ -0,0 +1,281 @@
+/*
+ * 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.targetpropertyname;
+
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mapstruct.ap.testutil.IssueKey;
+import org.mapstruct.ap.testutil.ProcessorTest;
+import org.mapstruct.ap.testutil.WithClasses;
+import org.mapstruct.ap.testutil.runner.GeneratedSource;
+
+import java.util.Collections;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Filip Hrisafov
+ * @author Nikola Ivačič
+ */
+@IssueKey("2051")
+@WithClasses({
+ Address.class,
+ AddressDto.class,
+ Employee.class,
+ EmployeeDto.class,
+ DomainModel.class
+})
+public class TargetPropertyNameTest {
+
+ @RegisterExtension
+ final GeneratedSource generatedSource = new GeneratedSource();
+
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodInMapperWithTargetPropertyName.class
+ })
+ public void conditionalMethodInMapperWithTargetPropertyName() {
+ ConditionalMethodInMapperWithTargetPropertyName mapper
+ = ConditionalMethodInMapperWithTargetPropertyName.INSTANCE;
+
+ EmployeeDto employeeDto = new EmployeeDto();
+ employeeDto.setFirstName( " " );
+ employeeDto.setLastName( "Testirovich" );
+ employeeDto.setCountry( "US" );
+ employeeDto.setAddresses(
+ Collections.singletonList( new AddressDto( "Testing St. 6" ) )
+ );
+
+ Employee employee = mapper.map( employeeDto );
+ assertThat( employee.getLastName() ).isNull();
+ assertThat( employee.getFirstName() ).isNull();
+ assertThat( employee.getCountry() ).isEqualTo( "US" );
+ assertThat( employee.getAddresses() )
+ .extracting( Address::getStreet )
+ .containsExactly( "Testing St. 6" );
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodForCollectionMapperWithTargetPropertyName.class
+ })
+ public void conditionalMethodForCollectionMapperWithTargetPropertyName() {
+ ConditionalMethodForCollectionMapperWithTargetPropertyName mapper
+ = ConditionalMethodForCollectionMapperWithTargetPropertyName.INSTANCE;
+
+ EmployeeDto employeeDto = new EmployeeDto();
+ employeeDto.setFirstName( " " );
+ employeeDto.setLastName( "Testirovich" );
+ employeeDto.setCountry( "US" );
+ employeeDto.setAddresses(
+ Collections.singletonList( new AddressDto( "Testing St. 6" ) )
+ );
+
+ Employee employee = mapper.map( employeeDto );
+ assertThat( employee.getLastName() ).isNull();
+ assertThat( employee.getFirstName() ).isNull();
+ assertThat( employee.getCountry() ).isEqualTo( "US" );
+ assertThat( employee.getAddresses() ).isNull();
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodInUsesMapperWithTargetPropertyName.class
+ })
+ public void conditionalMethodInUsesMapperWithTargetPropertyName() {
+ ConditionalMethodInUsesMapperWithTargetPropertyName mapper
+ = ConditionalMethodInUsesMapperWithTargetPropertyName.INSTANCE;
+
+ EmployeeDto employeeDto = new EmployeeDto();
+ employeeDto.setFirstName( " " );
+ employeeDto.setLastName( "Testirovich" );
+ employeeDto.setCountry( "US" );
+ employeeDto.setAddresses(
+ Collections.singletonList( new AddressDto( "Testing St. 6" ) )
+ );
+
+ Employee employee = mapper.map( employeeDto );
+ assertThat( employee.getLastName() ).isNull();
+ assertThat( employee.getFirstName() ).isNull();
+ assertThat( employee.getCountry() ).isEqualTo( "US" );
+ 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.setCountry( "US" );
+ employeeDto.setAddresses(
+ 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.visited )
+ .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.setCountry( "US" );
+ employeeDto.setAddresses(
+ 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", "country", "street" );
+ assertThat( utils.visitedSources ).containsExactlyInAnyOrder( "EmployeeDto", "AddressDto" );
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodWithTargetPropertyNameInContextMapper.class
+ })
+ public void conditionalMethodWithTargetPropertyNameInUsesContextMapper() {
+ ConditionalMethodWithTargetPropertyNameInContextMapper mapper
+ = ConditionalMethodWithTargetPropertyNameInContextMapper.INSTANCE;
+
+ ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils utils =
+ new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtils();
+
+ EmployeeDto employeeDto = new EmployeeDto();
+ employeeDto.setLastName( " " );
+ employeeDto.setCountry( "US" );
+ employeeDto.setAddresses(
+ 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", "country", "street" );
+
+ ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps allPropsUtils =
+ new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllProps();
+
+ employeeDto = new EmployeeDto();
+ employeeDto.setLastName( "Tester" );
+ employeeDto.setCountry( "US" );
+ employeeDto.setAddresses(
+ 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",
+ "country",
+ "active",
+ "age",
+ "boss",
+ "primaryAddress",
+ "addresses",
+ "street"
+ );
+
+ ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource allPropsUtilsWithSource =
+ new ConditionalMethodWithTargetPropertyNameInContextMapper.PresenceUtilsAllPropsWithSource();
+
+ EmployeeDto bossEmployeeDto = new EmployeeDto();
+ bossEmployeeDto.setLastName( "Boss Tester" );
+ bossEmployeeDto.setCountry( "US" );
+ bossEmployeeDto.setAddresses( Collections.singletonList( new AddressDto(
+ "Testing St. 10" ) ) );
+
+ employeeDto = new EmployeeDto();
+ employeeDto.setLastName( "Tester" );
+ employeeDto.setCountry( "US" );
+ employeeDto.setBoss( bossEmployeeDto );
+ employeeDto.setAddresses(
+ 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(
+ "firstName",
+ "lastName",
+ "title",
+ "country",
+ "active",
+ "age",
+ "boss",
+ "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"
+ );
+ }
+}