Skip to content

Commit 8fa286f

Browse files
ivlcicfiliphr
authored andcommitted
#2688: Support accessing to the target property name
1 parent 62e7346 commit 8fa286f

28 files changed

+993
-13
lines changed

core/src/main/java/org/mapstruct/Condition.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
* e.g. the value given by calling {@code getName()} for the name property of the source bean</li>
2424
* <li>The mapping source parameter</li>
2525
* <li>{@code @}{@link Context} parameter</li>
26+
* <li>{@code @}{@link TargetPropertyName} parameter</li>
2627
* </ul>
2728
*
2829
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct;
7+
8+
import java.lang.annotation.ElementType;
9+
import java.lang.annotation.Retention;
10+
import java.lang.annotation.RetentionPolicy;
11+
import java.lang.annotation.Target;
12+
13+
/**
14+
* This annotation marks a <em>presence check method</em> parameter as a property name parameter.
15+
* <p>
16+
* This parameter enables conditional filtering based on target property name at run-time.
17+
* Parameter must be of type {@link String} and can be present only in {@link Condition} method.
18+
* </p>
19+
* @author Nikola Ivačič
20+
* @since 1.6
21+
*/
22+
@Target(ElementType.PARAMETER)
23+
@Retention(RetentionPolicy.CLASS)
24+
public @interface TargetPropertyName {
25+
}

documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,61 @@ public class CarMapperImpl implements CarMapper {
404404
----
405405
====
406406

407+
Additionally `@TargetPropertyName` of type `java.lang.String` can be used in custom condition check method:
408+
409+
.Mapper using custom condition check method with `@TargetPropertyName`
410+
====
411+
[source, java, linenums]
412+
[subs="verbatim,attributes"]
413+
----
414+
@Mapper
415+
public interface CarMapper {
416+
417+
CarDto carToCarDto(Car car, @MappingTarget CarDto carDto);
418+
419+
@Condition
420+
default boolean isNotEmpty(String value, @TargetPropertyName String name) {
421+
if ( name.equals( "owner" ) {
422+
return value != null
423+
&& !value.isEmpty()
424+
&& !value.equals( value.toLowerCase() );
425+
}
426+
return value != null && !value.isEmpty();
427+
}
428+
}
429+
----
430+
====
431+
432+
The generated mapper with `@TargetPropertyName` will look like:
433+
434+
.Custom condition check in generated implementation
435+
====
436+
[source, java, linenums]
437+
[subs="verbatim,attributes"]
438+
----
439+
// GENERATED CODE
440+
public class CarMapperImpl implements CarMapper {
441+
442+
@Override
443+
public CarDto carToCarDto(Car car, CarDto carDto) {
444+
if ( car == null ) {
445+
return carDto;
446+
}
447+
448+
if ( isNotEmpty( car.getOwner(), "owner" ) ) {
449+
carDto.setOwner( car.getOwner() );
450+
} else {
451+
carDto.setOwner( null );
452+
}
453+
454+
// Mapping of other properties
455+
456+
return carDto;
457+
}
458+
}
459+
----
460+
====
461+
407462
[IMPORTANT]
408463
====
409464
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
412467
[NOTE]
413468
====
414469
Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input.
470+
471+
`@TargetPropertyName` parameter can only be used in `@Condition` methods.
415472
====
416473

417474
<<selection-based-on-qualifiers>> is also valid for `@Condition` methods.

processor/src/main/java/org/mapstruct/ap/internal/gem/GemGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import org.mapstruct.Qualifier;
3131
import org.mapstruct.SubclassMapping;
3232
import org.mapstruct.SubclassMappings;
33+
import org.mapstruct.TargetPropertyName;
3334
import org.mapstruct.TargetType;
3435
import org.mapstruct.ValueMapping;
3536
import org.mapstruct.ValueMappings;
@@ -52,6 +53,7 @@
5253
@GemDefinition(SubclassMapping.class)
5354
@GemDefinition(SubclassMappings.class)
5455
@GemDefinition(TargetType.class)
56+
@GemDefinition(TargetPropertyName.class)
5557
@GemDefinition(MappingTarget.class)
5658
@GemDefinition(DecoratedWith.class)
5759
@GemDefinition(MapperConfig.class)

processor/src/main/java/org/mapstruct/ap/internal/model/common/Parameter.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import org.mapstruct.ap.internal.gem.ContextGem;
1616
import org.mapstruct.ap.internal.gem.MappingTargetGem;
1717
import org.mapstruct.ap.internal.gem.TargetTypeGem;
18+
import org.mapstruct.ap.internal.gem.TargetPropertyNameGem;
1819
import org.mapstruct.ap.internal.util.Collections;
1920

2021
/**
@@ -31,6 +32,7 @@ public class Parameter extends ModelElement {
3132
private final boolean mappingTarget;
3233
private final boolean targetType;
3334
private final boolean mappingContext;
35+
private final boolean targetPropertyName;
3436

3537
private final boolean varArgs;
3638

@@ -42,10 +44,12 @@ private Parameter(Element element, Type type, boolean varArgs) {
4244
this.mappingTarget = MappingTargetGem.instanceOn( element ) != null;
4345
this.targetType = TargetTypeGem.instanceOn( element ) != null;
4446
this.mappingContext = ContextGem.instanceOn( element ) != null;
47+
this.targetPropertyName = TargetPropertyNameGem.instanceOn( element ) != null;
4548
this.varArgs = varArgs;
4649
}
4750

4851
private Parameter(String name, Type type, boolean mappingTarget, boolean targetType, boolean mappingContext,
52+
boolean targetPropertyName,
4953
boolean varArgs) {
5054
this.element = null;
5155
this.name = name;
@@ -54,11 +58,12 @@ private Parameter(String name, Type type, boolean mappingTarget, boolean targetT
5458
this.mappingTarget = mappingTarget;
5559
this.targetType = targetType;
5660
this.mappingContext = mappingContext;
61+
this.targetPropertyName = targetPropertyName;
5762
this.varArgs = varArgs;
5863
}
5964

6065
public Parameter(String name, Type type) {
61-
this( name, type, false, false, false, false );
66+
this( name, type, false, false, false, false, false );
6267
}
6368

6469
public Element getElement() {
@@ -94,6 +99,7 @@ private String format() {
9499
return ( mappingTarget ? "@MappingTarget " : "" )
95100
+ ( targetType ? "@TargetType " : "" )
96101
+ ( mappingContext ? "@Context " : "" )
102+
+ ( targetPropertyName ? "@TargetPropertyName " : "" )
97103
+ "%s " + name;
98104
}
99105

@@ -110,6 +116,10 @@ public boolean isMappingContext() {
110116
return mappingContext;
111117
}
112118

119+
public boolean isTargetPropertyName() {
120+
return targetPropertyName;
121+
}
122+
113123
public boolean isVarArgs() {
114124
return varArgs;
115125
}
@@ -154,6 +164,7 @@ public static Parameter forForgedMappingTarget(Type parameterType) {
154164
true,
155165
false,
156166
false,
167+
false,
157168
false
158169
);
159170
}
@@ -195,8 +206,15 @@ public static Parameter getTargetTypeParameter(List<Parameter> parameters) {
195206
return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null );
196207
}
197208

209+
public static Parameter getTargetPropertyNameParameter(List<Parameter> parameters) {
210+
return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null );
211+
}
212+
198213
private static boolean isSourceParameter( Parameter parameter ) {
199-
return !parameter.isMappingTarget() && !parameter.isTargetType() && !parameter.isMappingContext();
214+
return !parameter.isMappingTarget() &&
215+
!parameter.isTargetType() &&
216+
!parameter.isMappingContext() &&
217+
!parameter.isTargetPropertyName();
200218
}
201219

202220
}

processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,17 @@ public class ParameterBinding {
2222
private final boolean targetType;
2323
private final boolean mappingTarget;
2424
private final boolean mappingContext;
25+
private final boolean targetPropertyName;
2526
private final SourceRHS sourceRHS;
2627

2728
private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
28-
boolean mappingContext, SourceRHS sourceRHS) {
29+
boolean mappingContext, boolean targetPropertyName, SourceRHS sourceRHS) {
2930
this.type = parameterType;
3031
this.variableName = variableName;
3132
this.targetType = targetType;
3233
this.mappingTarget = mappingTarget;
3334
this.mappingContext = mappingContext;
35+
this.targetPropertyName = targetPropertyName;
3436
this.sourceRHS = sourceRHS;
3537
}
3638

@@ -62,6 +64,13 @@ public boolean isMappingContext() {
6264
return mappingContext;
6365
}
6466

67+
/**
68+
* @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter.
69+
*/
70+
public boolean isTargetPropertyName() {
71+
return targetPropertyName;
72+
}
73+
6574
/**
6675
* @return the type of the parameter that is bound
6776
*/
@@ -99,6 +108,7 @@ public static ParameterBinding fromParameter(Parameter parameter) {
99108
parameter.isMappingTarget(),
100109
parameter.isTargetType(),
101110
parameter.isMappingContext(),
111+
parameter.isTargetPropertyName(),
102112
null
103113
);
104114
}
@@ -118,6 +128,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame
118128
false,
119129
false,
120130
false,
131+
false,
121132
null
122133
);
123134
}
@@ -127,26 +138,33 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame
127138
* @return a parameter binding representing a target type parameter
128139
*/
129140
public static ParameterBinding forTargetTypeBinding(Type classTypeOf) {
130-
return new ParameterBinding( classTypeOf, null, false, true, false, null );
141+
return new ParameterBinding( classTypeOf, null, false, true, false, false, null );
142+
}
143+
144+
/**
145+
* @return a parameter binding representing a target property name parameter
146+
*/
147+
public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) {
148+
return new ParameterBinding( classTypeOf, null, false, false, false, true, null );
131149
}
132150

133151
/**
134152
* @param resultType type of the mapping target
135153
* @return a parameter binding representing a mapping target parameter
136154
*/
137155
public static ParameterBinding forMappingTargetBinding(Type resultType) {
138-
return new ParameterBinding( resultType, null, true, false, false, null );
156+
return new ParameterBinding( resultType, null, true, false, false, false, null );
139157
}
140158

141159
/**
142160
* @param sourceType type of the parameter
143161
* @return a parameter binding representing a mapping source type
144162
*/
145163
public static ParameterBinding forSourceTypeBinding(Type sourceType) {
146-
return new ParameterBinding( sourceType, null, false, false, false, null );
164+
return new ParameterBinding( sourceType, null, false, false, false, false, null );
147165
}
148166

149167
public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) {
150-
return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, sourceRHS );
168+
return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, sourceRHS );
151169
}
152170
}

processor/src/main/java/org/mapstruct/ap/internal/model/source/SourceMethod.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class SourceMethod implements Method {
4747
private final List<Parameter> parameters;
4848
private final Parameter mappingTargetParameter;
4949
private final Parameter targetTypeParameter;
50+
private final Parameter targetPropertyNameParameter;
5051
private final boolean isObjectFactory;
5152
private final boolean isPresenceCheck;
5253
private final Type returnType;
@@ -248,6 +249,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions)
248249

249250
this.mappingTargetParameter = Parameter.getMappingTargetParameter( parameters );
250251
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
252+
this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
251253
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
252254
this.isObjectFactory = determineIfIsObjectFactory();
253255
this.isPresenceCheck = determineIfIsPresenceCheck();
@@ -263,8 +265,9 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions)
263265
private boolean determineIfIsObjectFactory() {
264266
boolean hasNoSourceParameters = getSourceParameters().isEmpty();
265267
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
268+
boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null;
266269
return !isLifecycleCallbackMethod() && !returnType.isVoid()
267-
&& hasNoMappingTargetParam
270+
&& hasNoMappingTargetParam && hasNoTargetPropertyNameParam
268271
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
269272
}
270273

@@ -379,6 +382,10 @@ public Parameter getTargetTypeParameter() {
379382
return targetTypeParameter;
380383
}
381384

385+
public Parameter getTargetPropertyNameParameter() {
386+
return targetPropertyNameParameter;
387+
}
388+
382389
public boolean isIterableMapping() {
383390
if ( isIterableMapping == null ) {
384391
isIterableMapping = getSourceParameters().size() == 1

processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/TypeSelector.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ private List<ParameterBinding> getAvailableParameterBindingsFromMethod(Method me
9696
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
9797
}
9898

99-
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
99+
addTargetRelevantBindings( availableParams, targetType );
100100

101101
return availableParams;
102102
}
@@ -116,7 +116,7 @@ private List<ParameterBinding> getAvailableParameterBindingsFromSourceTypes(List
116116
}
117117
}
118118

119-
addMappingTargetAndTargetTypeBindings( availableParams, targetType );
119+
addTargetRelevantBindings( availableParams, targetType );
120120

121121
return availableParams;
122122
}
@@ -127,9 +127,10 @@ private List<ParameterBinding> getAvailableParameterBindingsFromSourceTypes(List
127127
* @param availableParams Already available params, new entries will be added to this list
128128
* @param targetType Target type
129129
*/
130-
private void addMappingTargetAndTargetTypeBindings(List<ParameterBinding> availableParams, Type targetType) {
130+
private void addTargetRelevantBindings(List<ParameterBinding> availableParams, Type targetType) {
131131
boolean mappingTargetAvailable = false;
132132
boolean targetTypeAvailable = false;
133+
boolean targetPropertyNameAvailable = false;
133134

134135
// search available parameter bindings if mapping-target and/or target-type is available
135136
for ( ParameterBinding pb : availableParams ) {
@@ -139,6 +140,9 @@ private void addMappingTargetAndTargetTypeBindings(List<ParameterBinding> availa
139140
else if ( pb.isTargetType() ) {
140141
targetTypeAvailable = true;
141142
}
143+
else if ( pb.isTargetPropertyName() ) {
144+
targetPropertyNameAvailable = true;
145+
}
142146
}
143147

144148
if ( !mappingTargetAvailable ) {
@@ -147,6 +151,9 @@ else if ( pb.isTargetType() ) {
147151
if ( !targetTypeAvailable ) {
148152
availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
149153
}
154+
if ( !targetPropertyNameAvailable ) {
155+
availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) );
156+
}
150157
}
151158

152159
private <T extends Method> SelectedMethod<T> getMatchingParameterBinding(Type returnType,
@@ -301,7 +308,8 @@ private static List<ParameterBinding> findCandidateBindingsForParameter(List<Par
301308
for ( ParameterBinding candidate : candidateParameters ) {
302309
if ( parameter.isTargetType() == candidate.isTargetType()
303310
&& parameter.isMappingTarget() == candidate.isMappingTarget()
304-
&& parameter.isMappingContext() == candidate.isMappingContext() ) {
311+
&& parameter.isMappingContext() == candidate.isMappingContext()
312+
&& parameter.isTargetPropertyName() == candidate.isTargetPropertyName()) {
305313
result.add( candidate );
306314
}
307315
}

0 commit comments

Comments
 (0)