Skip to content
Merged
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
40 changes: 28 additions & 12 deletions core/src/main/java/org/mapstruct/Condition.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,35 @@
import java.lang.annotation.Target;

/**
* This annotation marks a method as a <em>presence check method</em> to check check for presence in beans.
* This annotation marks a method as a <em>presence check method</em> to check for presence in beans
* or it can be used to define additional check methods for something like source parameters.
* <p>
* By default bean properties are checked against {@code null} or using a presence check method in the source bean.
* By default, bean properties are checked against {@code null} or using a presence check method in the source bean.
* If a presence check method is available then it will be used instead.
* <p>
* Presence check methods have to return {@code boolean}.
* The following parameters are accepted for the presence check methods:
* <ul>
* <li>The parameter with the value of the source property.
* e.g. the value given by calling {@code getName()} for the name property of the source bean</li>
* e.g. the value given by calling {@code getName()} for the name property of the source bean
* - only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* <li>{@code @}{@link TargetPropertyName} parameter</li>
* <li>{@code @}{@link SourcePropertyName} parameter</li>
* <li>
* {@code @}{@link TargetPropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>
* {@code @}{@link SourcePropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
* for a method to be considered as a presence check method.
*
* <pre><code>
* <pre><code class='java'>
* public class PresenceCheckUtils {
*
* &#64;Condition
Expand All @@ -45,11 +54,10 @@
* MovieDto map(Movie movie);
* }
* </code></pre>
*
* <p>
* The following implementation of {@code MovieMapper} will be generated:
*
* <pre>
* <code>
* <pre><code class='java'>
* public class MovieMapperImpl implements MovieMapper {
*
* &#64;Override
Expand All @@ -67,14 +75,22 @@
* return movieDto;
* }
* }
* </code>
* </pre>
* </code></pre>
* <p>
* This annotation can also be used as a meta-annotation to define the condition strategy.
Comment thread
filiphr marked this conversation as resolved.
*
* @author Filip Hrisafov
* @see SourceParameterCondition
* @since 1.5
*/
@Target({ ElementType.METHOD })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Condition {

/**
* @return the places where the condition should apply to
* @since 1.6
*/
ConditionStrategy[] appliesTo() default ConditionStrategy.PROPERTIES;

}
23 changes: 23 additions & 0 deletions core/src/main/java/org/mapstruct/ConditionStrategy.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct;

/**
* Strategy for defining what to what a condition (check) method is applied to
*
* @author Filip Hrisafov
* @since 1.6
*/
public enum ConditionStrategy {
/**
* The condition method should be applied whether a property should be mapped.
*/
PROPERTIES,
/**
* The condition method should be applied to check if a source parameters should be mapped.
*/
SOURCE_PARAMETERS,
}
74 changes: 74 additions & 0 deletions core/src/main/java/org/mapstruct/SourceParameterCondition.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 method as a <em>check method</em> to check if a source parameter needs to be mapped.
* <p>
* By default, source parameters are checked against {@code null}, unless they are primitives.
* <p>
* Check methods have to return {@code boolean}.
* The following parameters are accepted for the presence check methods:
* <ul>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
* for a method to be considered as a source check method.
*
* <pre><code class='java'>
* public class PresenceCheckUtils {
*
* &#64;SourceParameterCondition
* public static boolean isDefined(Car car) {
* return car != null &#38;&#38; car.getId() != null;
* }
* }
*
* &#64;Mapper(uses = PresenceCheckUtils.class)
* public interface CarMapper {
*
* CarDto map(Car car);
* }
* </code></pre>
*
* The following implementation of {@code CarMapper} will be generated:
*
* <pre><code class='java'>
* public class CarMapperImpl implements CarMapper {
*
* &#64;Override
* public CarDto map(Car car) {
* if ( !PresenceCheckUtils.isDefined( car ) ) {
* return null;
* }
*
* CarDto carDto = new CarDto();
*
* carDto.setId( car.getId() );
* // ...
*
* return carDto;
* }
* }
* </code></pre>
*
* @author Filip Hrisafov
* @since 1.6
* @see Condition @Condition
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.CLASS)
@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
public @interface SourceParameterCondition {
Comment thread
filiphr marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,10 @@ null check, regardless the value of the `NullValueCheckStrategy` to avoid additi

Conditional Mapping is a type of <<source-presence-check>>.
The difference is that it allows users to write custom condition methods that will be invoked to check if a property needs to be mapped or not.
Conditional mapping can also be used to check if a source parameter should be mapped or not.

A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
A custom condition method for properties is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
A custom condition method for source parameters is annotated with `org.mapstruct.SourceParameterCondition`, `org.mapstruct.Condition(appliesTo = org.mapstruct.ConditionStrategy#SOURCE_PARAMETERS)` or meta-annotated with `Condition(appliesTo = ConditionStrategy#SOURCE_PARAMETERS)`

e.g. if you only want to map a String property when it is not `null`, and it is not empty then you can do something like:

Expand Down Expand Up @@ -484,6 +486,55 @@ Methods annotated with `@Condition` in addition to the value of the source prope
<<selection-based-on-qualifiers>> is also valid for `@Condition` methods.
In order to use a more specific condition method you will need to use one of `Mapping#conditionQualifiedByName` or `Mapping#conditionQualifiedBy`.

If we want to only map cars that have an id provided then we can do something like:


.Mapper using custom condition source parameter check method
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
@Mapper
public interface CarMapper {

CarDto carToCarDto(Car car);

@SourceParameterCondition
default boolean hasCar(Car car) {
return car != null && car.getId() != null;
}
}
----
====

The generated mapper will look like:

.Custom condition source parameter check generated implementation
====
[source, java, linenums]
[subs="verbatim,attributes"]
----
// GENERATED CODE
public class CarMapperImpl implements CarMapper {

@Override
public CarDto carToCarDto(Car car) {
if ( !hasCar( car ) ) {
return null;
}

CarDto carDto = new CarDto();

carDto.setOwner( car.getOwner() );

// Mapping of other properties

return carDto;
}
}
----
====

[[exceptions]]
=== Exceptions

Expand Down
Original file line number Diff line number Diff line change
@@ -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.internal.gem;

/**
* @author Filip Hrisafov
*/
public enum ConditionStrategyGem {

PROPERTIES,
SOURCE_PARAMETERS
}
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,26 @@ else if ( !method.isUpdateMethod() ) {
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
}

Map<String, PresenceCheck> presenceChecksByParameter = new LinkedHashMap<>();
for ( Parameter sourceParameter : method.getSourceParameters() ) {
PresenceCheck parameterPresenceCheck = PresenceCheckMethodResolver.getPresenceCheckForSourceParameter(
method,
selectionParameters,
sourceParameter,
ctx
);
if ( parameterPresenceCheck != null ) {
presenceChecksByParameter.put( sourceParameter.getName(), parameterPresenceCheck );
}
else if ( !sourceParameter.getType().isPrimitive() ) {
presenceChecksByParameter.put(
sourceParameter.getName(),
new NullPresenceCheck( sourceParameter.getName() )
);
}
}


return new BeanMappingMethod(
method,
getMethodAnnotations(),
Expand All @@ -426,7 +446,8 @@ else if ( !method.isUpdateMethod() ) {
afterMappingReferencesWithFinalizedReturnType,
finalizeMethod,
mappingReferences,
subclasses
subclasses,
presenceChecksByParameter
);
}

Expand Down Expand Up @@ -1891,7 +1912,8 @@ private BeanMappingMethod(Method method,
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
MethodReference finalizerMethod,
MappingReferences mappingReferences,
List<SubclassMapping> subclassMappings) {
List<SubclassMapping> subclassMappings,
Map<String, PresenceCheck> presenceChecksByParameter) {
super(
method,
annotations,
Expand Down Expand Up @@ -1923,18 +1945,12 @@ private BeanMappingMethod(Method method,
// parameter mapping.
this.mappingsByParameter = new HashMap<>();
this.constantMappings = new ArrayList<>( propertyMappings.size() );
this.presenceChecksByParameter = new LinkedHashMap<>();
this.presenceChecksByParameter = presenceChecksByParameter;
this.constructorMappingsByParameter = new LinkedHashMap<>();
this.constructorConstantMappings = new ArrayList<>();
Set<String> sourceParameterNames = new HashSet<>();
for ( Parameter sourceParameter : getSourceParameters() ) {
sourceParameterNames.add( sourceParameter.getName() );
if ( !sourceParameter.getType().isPrimitive() ) {
presenceChecksByParameter.put(
sourceParameter.getName(),
new NullPresenceCheck( sourceParameter.getName() )
);
}
}
for ( PropertyMapping mapping : propertyMappings ) {
if ( mapping.isConstructorMapping() ) {
Expand Down
Loading