Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/src/main/java/org/mapstruct/Condition.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
* e.g. the value given by calling {@code getName()} for the name property of the source bean</li>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* <li>{@code @}{@link TargetPropertyName} parameter</li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
Expand Down
25 changes: 25 additions & 0 deletions core/src/main/java/org/mapstruct/TargetPropertyName.java
Original file line number Diff line number Diff line change
@@ -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 <em>presence check method</em> parameter as a property name parameter.
* <p>
* 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.
* </p>
* @author Nikola Ivačič
* @since 1.6
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.CLASS)
public @interface TargetPropertyName {
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
====

<<selection-based-on-qualifiers>> is also valid for `@Condition` methods.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
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;
Expand All @@ -52,6 +53,7 @@
@GemDefinition(SubclassMapping.class)
@GemDefinition(SubclassMappings.class)
@GemDefinition(TargetType.class)
@GemDefinition(TargetPropertyName.class)
@GemDefinition(MappingTarget.class)
@GemDefinition(DecoratedWith.class)
@GemDefinition(MapperConfig.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
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;

/**
Expand All @@ -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;

Expand All @@ -42,23 +44,25 @@ private Parameter(Element element, Type type, boolean varArgs) {
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 varArgs) {
boolean targetPropertyName, boolean varArgs) {
this.element = null;
this.name = name;
this.originalName = name;
this.type = type;
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() {
Expand Down Expand Up @@ -94,6 +98,7 @@ private String format() {
return ( mappingTarget ? "@MappingTarget " : "" )
+ ( targetType ? "@TargetType " : "" )
+ ( mappingContext ? "@Context " : "" )
+ ( targetPropertyName ? "@TargetPropertyName " : "" )
+ "%s " + name;
}

Expand All @@ -110,7 +115,11 @@ public boolean isMappingContext() {
return mappingContext;
}

public boolean isVarArgs() {
public boolean isTargetPropertyName() {
return targetPropertyName;
}

public boolean isVarArgs() {
return varArgs;
}

Expand Down Expand Up @@ -154,6 +163,7 @@ public static Parameter forForgedMappingTarget(Type parameterType) {
true,
false,
false,
false,
false
);
}
Expand Down Expand Up @@ -195,8 +205,15 @@ public static Parameter getTargetTypeParameter(List<Parameter> parameters) {
return parameters.stream().filter( Parameter::isTargetType ).findAny().orElse( null );
}

public static Parameter getTargetPropertyNameParameter(List<Parameter> 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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -62,6 +64,13 @@ public boolean isMappingContext() {
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
*/
Expand Down Expand Up @@ -99,6 +108,7 @@ public static ParameterBinding fromParameter(Parameter parameter) {
parameter.isMappingTarget(),
parameter.isTargetType(),
parameter.isMappingContext(),
parameter.isTargetPropertyName(),
null
);
}
Expand All @@ -118,6 +128,7 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame
false,
false,
false,
false,
null
);
}
Expand All @@ -127,26 +138,33 @@ public static ParameterBinding fromTypeAndName(Type parameterType, String parame
* @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 );
}

/**
* @param resultType type of the mapping target
* @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 );
}

/**
* @param sourceType type of the parameter
* @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 );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public class SourceMethod implements Method {
private final List<Parameter> 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;
Expand Down Expand Up @@ -248,6 +249,7 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions)

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();
Expand All @@ -263,8 +265,9 @@ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions)
private boolean determineIfIsObjectFactory() {
boolean hasNoSourceParameters = getSourceParameters().isEmpty();
boolean hasNoMappingTargetParam = getMappingTargetParameter() == null;
boolean hasNoTargetPropertyNameParam = getTargetPropertyNameParameter() == null;
return !isLifecycleCallbackMethod() && !returnType.isVoid()
&& hasNoMappingTargetParam
&& hasNoMappingTargetParam && hasNoTargetPropertyNameParam
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
}

Expand Down Expand Up @@ -379,6 +382,10 @@ public Parameter getTargetTypeParameter() {
return targetTypeParameter;
}

public Parameter getTargetPropertyNameParameter() {
return targetPropertyNameParameter;
}

public boolean isIterableMapping() {
if ( isIterableMapping == null ) {
isIterableMapping = getSourceParameters().size() == 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ private List<ParameterBinding> getAvailableParameterBindingsFromMethod(Method me
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
}

addTargetPropertyNameBindings( availableParams );
addMappingTargetAndTargetTypeBindings( availableParams, targetType );

return availableParams;
Expand Down Expand Up @@ -149,6 +150,25 @@ else if ( pb.isTargetType() ) {
}
}

/**
* Adds default parameter bindings for the target-property-name if not already available
*
* @param availableParams Already available params, new entries will be added to this list
*/
private <T extends Method> void addTargetPropertyNameBindings(List<ParameterBinding> availableParams) {
boolean targetPropertyNameAvailable = false;
// search available parameter bindings if target-property-name is available
for ( ParameterBinding pb : availableParams ) {
if ( pb.isTargetPropertyName() ) {
targetPropertyNameAvailable = true;
break;
}
}
if ( !targetPropertyNameAvailable ) {
availableParams.add( ParameterBinding.forTargetPropertyNameBinding( typeFactory.getType( String.class ) ) );
}
}

private <T extends Method> SelectedMethod<T> getMatchingParameterBinding(Type returnType,
Method mappingMethod, SelectedMethod<T> selectedMethodInfo,
List<List<ParameterBinding>> parameterAssignmentVariants) {
Expand Down Expand Up @@ -301,7 +321,8 @@ private static List<ParameterBinding> findCandidateBindingsForParameter(List<Par
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 );
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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??>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@

-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" -->
<@includeModel object=methodReference/>
<@includeModel object=methodReference
targetPropertyName=ext.targetPropertyName/>
Loading