diff --git a/core/src/main/java/org/mapstruct/Condition.java b/core/src/main/java/org/mapstruct/Condition.java index 37f1553be..1273deab0 100644 --- a/core/src/main/java/org/mapstruct/Condition.java +++ b/core/src/main/java/org/mapstruct/Condition.java @@ -11,26 +11,35 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * This annotation marks a method as a presence check method to check check for presence in beans. + * This annotation marks a method as a presence check method to check for presence in beans + * or it can be used to define additional check methods for something like source parameters. *
- * 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. *
* Presence check methods have to return {@code boolean}. * The following parameters are accepted for the presence check methods: *
+ *
* public class PresenceCheckUtils {
*
* @Condition
@@ -45,11 +54,10 @@ import java.lang.annotation.Target;
* MovieDto map(Movie movie);
* }
*
- *
+ *
* The following implementation of {@code MovieMapper} will be generated:
*
- *
- *
+ *
* public class MovieMapperImpl implements MovieMapper {
*
* @Override
@@ -67,14 +75,22 @@ import java.lang.annotation.Target;
* return movieDto;
* }
* }
- *
- *
+ *
+ *
+ * This annotation can also be used as a meta-annotation to define the condition strategy.
*
* @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;
+
}
diff --git a/core/src/main/java/org/mapstruct/ConditionStrategy.java b/core/src/main/java/org/mapstruct/ConditionStrategy.java
new file mode 100644
index 000000000..6b042017c
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/ConditionStrategy.java
@@ -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,
+}
diff --git a/core/src/main/java/org/mapstruct/SourceParameterCondition.java b/core/src/main/java/org/mapstruct/SourceParameterCondition.java
new file mode 100644
index 000000000..8bff97abc
--- /dev/null
+++ b/core/src/main/java/org/mapstruct/SourceParameterCondition.java
@@ -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 check method to check if a source parameter needs to be mapped.
+ *
+ * By default, source parameters are checked against {@code null}, unless they are primitives.
+ *
+ * Check methods have to return {@code boolean}.
+ * The following parameters are accepted for the presence check methods:
+ *
+ * - The mapping source parameter
+ * - {@code @}{@link Context} parameter
+ *
+ *
+ * Note: The usage of this annotation is mandatory
+ * for a method to be considered as a source check method.
+ *
+ *
+ * public class PresenceCheckUtils {
+ *
+ * @SourceParameterCondition
+ * public static boolean isDefined(Car car) {
+ * return car != null && car.getId() != null;
+ * }
+ * }
+ *
+ * @Mapper(uses = PresenceCheckUtils.class)
+ * public interface CarMapper {
+ *
+ * CarDto map(Car car);
+ * }
+ *
+ *
+ * The following implementation of {@code CarMapper} will be generated:
+ *
+ *
+ * public class CarMapperImpl implements CarMapper {
+ *
+ * @Override
+ * public CarDto map(Car car) {
+ * if ( !PresenceCheckUtils.isDefined( car ) ) {
+ * return null;
+ * }
+ *
+ * CarDto carDto = new CarDto();
+ *
+ * carDto.setId( car.getId() );
+ * // ...
+ *
+ * return carDto;
+ * }
+ * }
+ *
+ *
+ * @author Filip Hrisafov
+ * @since 1.6
+ * @see Condition @Condition
+ */
+@Target({ ElementType.METHOD })
+@Retention(RetentionPolicy.CLASS)
+@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+public @interface SourceParameterCondition {
+
+}
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 db13c0548..1e2bd133d 100644
--- a/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
+++ b/documentation/src/main/asciidoc/chapter-10-advanced-mapping-options.asciidoc
@@ -303,8 +303,10 @@ null check, regardless the value of the `NullValueCheckStrategy` to avoid additi
Conditional Mapping is a type of <>.
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:
@@ -484,6 +486,55 @@ Methods annotated with `@Condition` in addition to the value of the source prope
<> 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
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.java
new file mode 100644
index 000000000..adea4b4c1
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/gem/ConditionStrategyGem.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.internal.gem;
+
+/**
+ * @author Filip Hrisafov
+ */
+public enum ConditionStrategyGem {
+
+ PROPERTIES,
+ SOURCE_PARAMETERS
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
index dd3a86e2c..cf5179f9c 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/BeanMappingMethod.java
@@ -411,6 +411,26 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
}
+ Map 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(),
@@ -426,7 +446,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
afterMappingReferencesWithFinalizedReturnType,
finalizeMethod,
mappingReferences,
- subclasses
+ subclasses,
+ presenceChecksByParameter
);
}
@@ -1891,7 +1912,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
List afterMappingReferencesWithFinalizedReturnType,
MethodReference finalizerMethod,
MappingReferences mappingReferences,
- List subclassMappings) {
+ List subclassMappings,
+ Map presenceChecksByParameter) {
super(
method,
annotations,
@@ -1923,18 +1945,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// 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 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() ) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java
index a5c873743..5906db821 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/PresenceCheckMethodResolver.java
@@ -9,6 +9,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.source.Method;
@@ -18,6 +19,7 @@ import org.mapstruct.ap.internal.model.source.SourceMethod;
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
import org.mapstruct.ap.internal.model.source.selector.SelectionContext;
+import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
import org.mapstruct.ap.internal.util.Message;
/**
@@ -34,38 +36,12 @@ public final class PresenceCheckMethodResolver {
SelectionParameters selectionParameters,
MappingBuilderContext ctx
) {
- SelectedMethod matchingMethod = findMatchingPresenceCheckMethod(
+ List> matchingMethods = findMatchingMethods(
method,
- selectionParameters,
+ SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ),
ctx
);
- if ( matchingMethod == null ) {
- return null;
- }
-
- MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx );
-
- return new MethodReferencePresenceCheck( methodReference );
-
- }
-
- private static SelectedMethod findMatchingPresenceCheckMethod(
- Method method,
- SelectionParameters selectionParameters,
- MappingBuilderContext ctx
- ) {
- MethodSelectors selectors = new MethodSelectors(
- ctx.getTypeUtils(),
- ctx.getElementUtils(),
- ctx.getMessager()
- );
-
- List> matchingMethods = selectors.getMatchingMethods(
- getAllAvailableMethods( method, ctx.getSourceModel() ),
- SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() )
- );
-
if ( matchingMethods.isEmpty() ) {
return null;
}
@@ -84,7 +60,72 @@ public final class PresenceCheckMethodResolver {
return null;
}
- return matchingMethods.get( 0 );
+ SelectedMethod matchingMethod = matchingMethods.get( 0 );
+
+ MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx );
+
+ return new MethodReferencePresenceCheck( methodReference );
+
+ }
+
+ public static PresenceCheck getPresenceCheckForSourceParameter(
+ Method method,
+ SelectionParameters selectionParameters,
+ Parameter sourceParameter,
+ MappingBuilderContext ctx
+ ) {
+ List> matchingMethods = findMatchingMethods(
+ method,
+ SelectionContext.forSourceParameterPresenceCheckMethods(
+ method,
+ selectionParameters,
+ sourceParameter,
+ ctx.getTypeFactory()
+ ),
+ ctx
+ );
+
+ if ( matchingMethods.isEmpty() ) {
+ return null;
+ }
+
+ if ( matchingMethods.size() > 1 ) {
+ ctx.getMessager().printMessage(
+ method.getExecutable(),
+ Message.GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD,
+ sourceParameter.getType().describe(),
+ matchingMethods.stream()
+ .map( SelectedMethod::getMethod )
+ .map( Method::describe )
+ .collect( Collectors.joining( ", " ) )
+ );
+
+ return null;
+ }
+
+ SelectedMethod matchingMethod = matchingMethods.get( 0 );
+
+ MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx );
+
+ return new MethodReferencePresenceCheck( methodReference );
+
+ }
+
+ private static List> findMatchingMethods(
+ Method method,
+ SelectionContext selectionContext,
+ MappingBuilderContext ctx
+ ) {
+ MethodSelectors selectors = new MethodSelectors(
+ ctx.getTypeUtils(),
+ ctx.getElementUtils(),
+ ctx.getMessager()
+ );
+
+ return selectors.getMatchingMethods(
+ getAllAvailableMethods( method, ctx.getSourceModel(), selectionContext.getSelectionCriteria() ),
+ selectionContext
+ );
}
private static MethodReference getPresenceCheckMethodReference(
@@ -116,7 +157,8 @@ public final class PresenceCheckMethodResolver {
}
}
- private static List getAllAvailableMethods(Method method, List sourceModelMethods) {
+ private static List getAllAvailableMethods(Method method, List sourceModelMethods,
+ SelectionCriteria selectionCriteria) {
ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods();
if ( contextProvidedMethods.isEmpty() ) {
return sourceModelMethods;
@@ -129,9 +171,19 @@ public final class PresenceCheckMethodResolver {
new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() );
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
- // add only methods from context that do have the @Condition annotation
- if ( methodProvidedByParams.isPresenceCheck() ) {
- availableMethods.add( methodProvidedByParams );
+ if ( selectionCriteria.isPresenceCheckRequired() ) {
+ // add only methods from context that do have the @Condition for properties annotation
+ if ( methodProvidedByParams.getConditionOptions()
+ .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) {
+ availableMethods.add( methodProvidedByParams );
+ }
+ }
+ else if ( selectionCriteria.isSourceParameterCheckRequired() ) {
+ // add only methods from context that do have the @Condition for source parameters annotation
+ if ( methodProvidedByParams.getConditionOptions()
+ .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) {
+ availableMethods.add( methodProvidedByParams );
+ }
}
}
availableMethods.addAll( sourceModelMethods );
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 44ac0eb7f..aaab7f46c 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
@@ -133,6 +133,14 @@ public class Parameter extends ModelElement {
return varArgs;
}
+ public boolean isSourceParameter() {
+ return !isMappingTarget() &&
+ !isTargetType() &&
+ !isMappingContext() &&
+ !isSourcePropertyName() &&
+ !isTargetPropertyName();
+ }
+
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
@@ -224,12 +232,4 @@ public class Parameter extends ModelElement {
return parameters.stream().filter( Parameter::isTargetPropertyName ).findAny().orElse( null );
}
- private static boolean isSourceParameter( Parameter parameter ) {
- return !parameter.isMappingTarget() &&
- !parameter.isTargetType() &&
- !parameter.isMappingContext() &&
- !parameter.isSourcePropertyName() &&
- !parameter.isTargetPropertyName();
- }
-
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java b/processor/src/main/java/org/mapstruct/ap/internal/model/common/ParameterBinding.java
index 0791ee626..c1a594c73 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
@@ -6,7 +6,9 @@
package org.mapstruct.ap.internal.model.common;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.EnumSet;
import java.util.List;
import java.util.Set;
@@ -19,23 +21,14 @@ public class ParameterBinding {
private final Type type;
private final String variableName;
- private final boolean targetType;
- private final boolean mappingTarget;
- private final boolean mappingContext;
- private final boolean sourcePropertyName;
- private final boolean targetPropertyName;
private final SourceRHS sourceRHS;
+ private final Collection bindingTypes;
- private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
- boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName,
+ private ParameterBinding(Type parameterType, String variableName, Collection bindingTypes,
SourceRHS sourceRHS) {
this.type = parameterType;
this.variableName = variableName;
- this.targetType = targetType;
- this.mappingTarget = mappingTarget;
- this.mappingContext = mappingContext;
- this.sourcePropertyName = sourcePropertyName;
- this.targetPropertyName = targetPropertyName;
+ this.bindingTypes = bindingTypes;
this.sourceRHS = sourceRHS;
}
@@ -46,39 +39,47 @@ public class ParameterBinding {
return variableName;
}
+ public boolean isSourceParameter() {
+ return bindingTypes.contains( BindingType.PARAMETER );
+ }
+
/**
* @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter.
*/
public boolean isTargetType() {
- return targetType;
+ return bindingTypes.contains( BindingType.TARGET_TYPE );
}
/**
* @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter.
*/
public boolean isMappingTarget() {
- return mappingTarget;
+ return bindingTypes.contains( BindingType.MAPPING_TARGET );
}
/**
* @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter.
*/
public boolean isMappingContext() {
- return mappingContext;
+ return bindingTypes.contains( BindingType.CONTEXT );
+ }
+
+ public boolean isForSourceRhs() {
+ return bindingTypes.contains( BindingType.SOURCE_RHS );
}
/**
* @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter.
*/
public boolean isSourcePropertyName() {
- return sourcePropertyName;
+ return bindingTypes.contains( BindingType.SOURCE_PROPERTY_NAME );
}
/**
* @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter.
*/
public boolean isTargetPropertyName() {
- return targetPropertyName;
+ return bindingTypes.contains( BindingType.TARGET_PROPERTY_NAME );
}
/**
@@ -96,7 +97,7 @@ public class ParameterBinding {
}
public Set getImportTypes() {
- if ( targetType ) {
+ if ( isTargetType() ) {
return type.getImportTypes();
}
@@ -112,14 +113,31 @@ public class ParameterBinding {
* @return a parameter binding reflecting the given parameter as being used as argument for a method call
*/
public static ParameterBinding fromParameter(Parameter parameter) {
+ EnumSet bindingTypes = EnumSet.of( BindingType.PARAMETER );
+ if ( parameter.isMappingTarget() ) {
+ bindingTypes.add( BindingType.MAPPING_TARGET );
+ }
+
+ if ( parameter.isTargetType() ) {
+ bindingTypes.add( BindingType.TARGET_TYPE );
+ }
+
+ if ( parameter.isMappingContext() ) {
+ bindingTypes.add( BindingType.CONTEXT );
+ }
+
+ if ( parameter.isSourcePropertyName() ) {
+ bindingTypes.add( BindingType.SOURCE_PROPERTY_NAME );
+ }
+
+ if ( parameter.isTargetPropertyName() ) {
+ bindingTypes.add( BindingType.TARGET_PROPERTY_NAME );
+ }
+
return new ParameterBinding(
parameter.getType(),
parameter.getName(),
- parameter.isMappingTarget(),
- parameter.isTargetType(),
- parameter.isMappingContext(),
- parameter.isSourcePropertyName(),
- parameter.isTargetPropertyName(),
+ bindingTypes,
null
);
}
@@ -136,11 +154,7 @@ public class ParameterBinding {
return new ParameterBinding(
parameterType,
parameterName,
- false,
- false,
- false,
- false,
- false,
+ Collections.emptySet(),
null
);
}
@@ -150,21 +164,31 @@ public class ParameterBinding {
* @return a parameter binding representing a target type parameter
*/
public static ParameterBinding forTargetTypeBinding(Type classTypeOf) {
- return new ParameterBinding( classTypeOf, null, false, true, false, false, false, null );
+ return new ParameterBinding( classTypeOf, null, Collections.singleton( BindingType.TARGET_TYPE ), 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, false, true, null );
+ return new ParameterBinding(
+ classTypeOf,
+ null,
+ Collections.singleton( BindingType.TARGET_PROPERTY_NAME ),
+ null
+ );
}
/**
* @return a parameter binding representing a source property name parameter
*/
public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) {
- return new ParameterBinding( classTypeOf, null, false, false, false, true, false, null );
+ return new ParameterBinding(
+ classTypeOf,
+ null,
+ Collections.singleton( BindingType.SOURCE_PROPERTY_NAME ),
+ null
+ );
}
/**
@@ -172,7 +196,7 @@ public class ParameterBinding {
* @return a parameter binding representing a mapping target parameter
*/
public static ParameterBinding forMappingTargetBinding(Type resultType) {
- return new ParameterBinding( resultType, null, true, false, false, false, false, null );
+ return new ParameterBinding( resultType, null, Collections.singleton( BindingType.MAPPING_TARGET ), null );
}
/**
@@ -180,10 +204,27 @@ public class ParameterBinding {
* @return a parameter binding representing a mapping source type
*/
public static ParameterBinding forSourceTypeBinding(Type sourceType) {
- return new ParameterBinding( sourceType, null, false, false, false, false, false, null );
+ return new ParameterBinding( sourceType, null, Collections.singleton( BindingType.SOURCE_TYPE ), null );
}
public static ParameterBinding fromSourceRHS(SourceRHS sourceRHS) {
- return new ParameterBinding( sourceRHS.getSourceType(), null, false, false, false, false, false, sourceRHS );
+ return new ParameterBinding(
+ sourceRHS.getSourceType(),
+ null,
+ Collections.singleton( BindingType.SOURCE_RHS ),
+ sourceRHS
+ );
+ }
+
+ enum BindingType {
+ PARAMETER,
+ FROM_TYPE_AND_NAME,
+ TARGET_TYPE,
+ TARGET_PROPERTY_NAME,
+ SOURCE_PROPERTY_NAME,
+ MAPPING_TARGET,
+ CONTEXT,
+ SOURCE_TYPE,
+ SOURCE_RHS
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java
new file mode 100644
index 000000000..dfb0865ec
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionMethodOptions.java
@@ -0,0 +1,45 @@
+/*
+ * 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.model.source;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
+
+/**
+ * Encapsulates all options specific for a condition check method.
+ *
+ * @author Filip Hrisafov
+ */
+public class ConditionMethodOptions {
+
+ private static final ConditionMethodOptions EMPTY = new ConditionMethodOptions( Collections.emptyList() );
+
+ private final Collection conditionOptions;
+
+ public ConditionMethodOptions(Collection conditionOptions) {
+ this.conditionOptions = conditionOptions;
+ }
+
+ public boolean isStrategyApplicable(ConditionStrategyGem strategy) {
+ for ( ConditionOptions conditionOption : conditionOptions ) {
+ if ( conditionOption.getConditionStrategies().contains( strategy ) ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean isAnyStrategyApplicable() {
+ return !conditionOptions.isEmpty();
+ }
+
+ public static ConditionMethodOptions empty() {
+ return EMPTY;
+ }
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java
new file mode 100644
index 000000000..936d049af
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/ConditionOptions.java
@@ -0,0 +1,170 @@
+/*
+ * 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.model.source;
+
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.DeclaredType;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+
+import org.mapstruct.ap.internal.gem.ConditionGem;
+import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
+import org.mapstruct.ap.internal.model.common.Parameter;
+import org.mapstruct.ap.internal.util.FormattingMessager;
+import org.mapstruct.ap.internal.util.Message;
+
+/**
+ * @author Filip Hrisafov
+ */
+public class ConditionOptions {
+
+ private final Set conditionStrategies;
+
+ private ConditionOptions(Set conditionStrategies) {
+ this.conditionStrategies = conditionStrategies;
+ }
+
+ public Collection getConditionStrategies() {
+ return conditionStrategies;
+ }
+
+ public static ConditionOptions getInstanceOn(ConditionGem condition, ExecutableElement method,
+ List parameters,
+ FormattingMessager messager) {
+ if ( condition == null ) {
+ return null;
+ }
+
+ TypeMirror returnType = method.getReturnType();
+ TypeKind returnTypeKind = returnType.getKind();
+ // We only allow methods that return boolean or Boolean to be condition methods
+ if ( returnTypeKind != TypeKind.BOOLEAN ) {
+ if ( returnTypeKind != TypeKind.DECLARED ) {
+ return null;
+ }
+ DeclaredType declaredType = (DeclaredType) returnType;
+ TypeElement returnTypeElement = (TypeElement) declaredType.asElement();
+ if ( !returnTypeElement.getQualifiedName().contentEquals( Boolean.class.getCanonicalName() ) ) {
+ return null;
+ }
+ }
+
+ Set strategies = condition.appliesTo().get()
+ .stream()
+ .map( ConditionStrategyGem::valueOf )
+ .collect( Collectors.toCollection( () -> EnumSet.noneOf( ConditionStrategyGem.class ) ) );
+
+ if ( strategies.isEmpty() ) {
+ messager.printMessage(
+ method,
+ condition.mirror(),
+ condition.appliesTo().getAnnotationValue(),
+ Message.CONDITION_MISSING_APPLIES_TO_STRATEGY
+ );
+
+ return null;
+ }
+
+ boolean allStrategiesValid = true;
+
+ for ( ConditionStrategyGem strategy : strategies ) {
+ boolean isStrategyValid = isValid( strategy, condition, method, parameters, messager );
+ allStrategiesValid &= isStrategyValid;
+ }
+
+ return allStrategiesValid ? new ConditionOptions( strategies ) : null;
+ }
+
+ protected static boolean isValid(ConditionStrategyGem strategy, ConditionGem condition,
+ ExecutableElement method, List parameters,
+ FormattingMessager messager) {
+ if ( strategy == ConditionStrategyGem.SOURCE_PARAMETERS ) {
+ return hasValidStrategyForSourceProperties( condition, method, parameters, messager );
+ }
+ else if ( strategy == ConditionStrategyGem.PROPERTIES ) {
+ return hasValidStrategyForProperties( condition, method, parameters, messager );
+ }
+ else {
+ throw new IllegalStateException( "Invalid condition strategy: " + strategy );
+ }
+ }
+
+ protected static boolean hasValidStrategyForSourceProperties(ConditionGem condition, ExecutableElement method,
+ List parameters,
+ FormattingMessager messager) {
+ for ( Parameter parameter : parameters ) {
+ if ( parameter.isSourceParameter() ) {
+ // source parameter is a valid parameter for a source condition check
+ continue;
+ }
+
+ if ( parameter.isMappingContext() ) {
+ // mapping context parameter is a valid parameter for a source condition check
+ continue;
+ }
+
+ messager.printMessage(
+ method,
+ condition.mirror(),
+ Message.CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER,
+ parameter.describe()
+ );
+ return false;
+ }
+ return true;
+ }
+
+ protected static boolean hasValidStrategyForProperties(ConditionGem condition, ExecutableElement method,
+ List parameters,
+ FormattingMessager messager) {
+ for ( Parameter parameter : parameters ) {
+ if ( parameter.isSourceParameter() ) {
+ // source parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ if ( parameter.isMappingContext() ) {
+ // mapping context parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ if ( parameter.isTargetType() ) {
+ // target type parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ if ( parameter.isMappingTarget() ) {
+ // mapping target parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ if ( parameter.isSourcePropertyName() ) {
+ // source property name parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ if ( parameter.isTargetPropertyName() ) {
+ // target property name parameter is a valid parameter for a property condition check
+ continue;
+ }
+
+ messager.printMessage(
+ method,
+ condition.mirror(),
+ Message.CONDITION_PROPERTIES_INVALID_PARAMETER,
+ parameter
+ );
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java
index 0c60f4136..ad2882080 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/Method.java
@@ -90,14 +90,6 @@ public interface Method {
*/
boolean isObjectFactory();
- /**
- * Returns whether the method is designated as a presence check method
- * @return {@code true} if it is a presence check method
- */
- default boolean isPresenceCheck() {
- return false;
- }
-
/**
* Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType }
*
@@ -187,6 +179,10 @@ public interface Method {
*/
MappingMethodOptions getOptions();
+ default ConditionMethodOptions getConditionOptions() {
+ return ConditionMethodOptions.empty();
+ }
+
/**
*
* @return true when @MappingTarget annotated parameter is the same type as the return type. The method has
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 42c318ca4..7103fb285 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
@@ -15,7 +15,6 @@ import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
-import org.mapstruct.ap.internal.gem.ConditionGem;
import org.mapstruct.ap.internal.gem.ObjectFactoryGem;
import org.mapstruct.ap.internal.model.common.Accessibility;
import org.mapstruct.ap.internal.model.common.Parameter;
@@ -50,11 +49,11 @@ public class SourceMethod implements Method {
private final Parameter sourcePropertyNameParameter;
private final Parameter targetPropertyNameParameter;
private final boolean isObjectFactory;
- private final boolean isPresenceCheck;
private final Type returnType;
private final Accessibility accessibility;
private final List exceptionTypes;
private final MappingMethodOptions mappingMethodOptions;
+ private final ConditionMethodOptions conditionMethodOptions;
private final List prototypeMethods;
private final Type mapperToImplement;
@@ -95,6 +94,7 @@ public class SourceMethod implements Method {
private List valueMappings;
private EnumMappingOptions enumMappingOptions;
private ParameterProvidedMethods contextProvidedMethods;
+ private Set conditionOptions;
private List typeParameters;
private Set subclassMappings;
@@ -196,6 +196,11 @@ public class SourceMethod implements Method {
return this;
}
+ public Builder setConditionOptions(Set conditionOptions) {
+ this.conditionOptions = conditionOptions;
+ return this;
+ }
+
public Builder setVerboseLogging(boolean verboseLogging) {
this.verboseLogging = verboseLogging;
return this;
@@ -223,17 +228,22 @@ public class SourceMethod implements Method {
subclassValidator
);
+ ConditionMethodOptions conditionMethodOptions =
+ conditionOptions != null ? new ConditionMethodOptions( conditionOptions ) :
+ ConditionMethodOptions.empty();
+
this.typeParameters = this.executable.getTypeParameters()
.stream()
.map( Element::asType )
.map( typeFactory::getType )
.collect( Collectors.toList() );
- return new SourceMethod( this, mappingMethodOptions );
+ return new SourceMethod( this, mappingMethodOptions, conditionMethodOptions );
}
}
- private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions) {
+ private SourceMethod(Builder builder, MappingMethodOptions mappingMethodOptions,
+ ConditionMethodOptions conditionMethodOptions) {
this.declaringMapper = builder.declaringMapper;
this.executable = builder.executable;
this.parameters = builder.parameters;
@@ -242,6 +252,7 @@ public class SourceMethod implements Method {
this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() );
this.mappingMethodOptions = mappingMethodOptions;
+ this.conditionMethodOptions = conditionMethodOptions;
this.sourceParameters = Parameter.getSourceParameters( parameters );
this.contextParameters = Parameter.getContextParameters( parameters );
@@ -254,7 +265,6 @@ public class SourceMethod implements Method {
this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
this.isObjectFactory = determineIfIsObjectFactory();
- this.isPresenceCheck = determineIfIsPresenceCheck();
this.typeUtils = builder.typeUtils;
this.typeFactory = builder.typeFactory;
@@ -274,19 +284,6 @@ public class SourceMethod implements Method {
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
}
- private boolean determineIfIsPresenceCheck() {
- if ( returnType.isPrimitive() ) {
- if ( !returnType.getName().equals( "boolean" ) ) {
- return false;
- }
- }
- else if ( !returnType.getFullyQualifiedName().equals( Boolean.class.getCanonicalName() ) ) {
- return false;
- }
-
- return ConditionGem.instanceOn( executable ) != null;
- }
-
@Override
public Type getDeclaringMapper() {
return declaringMapper;
@@ -547,6 +544,11 @@ public class SourceMethod implements Method {
return mappingMethodOptions;
}
+ @Override
+ public ConditionMethodOptions getConditionOptions() {
+ return conditionMethodOptions;
+ }
+
@Override
public boolean isStatic() {
return executable.getModifiers().contains( Modifier.STATIC );
@@ -567,11 +569,6 @@ public class SourceMethod implements Method {
return Executables.isLifecycleCallbackMethod( getExecutable() );
}
- @Override
- public boolean isPresenceCheck() {
- return isPresenceCheck;
- }
-
public boolean isAfterMappingMethod() {
return Executables.isAfterMappingMethod( getExecutable() );
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java
index 03a671de5..b6e5ca0a5 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/CreateOrUpdateSelector.java
@@ -32,6 +32,7 @@ public class CreateOrUpdateSelector implements MethodSelector {
SelectionContext context) {
SelectionCriteria criteria = context.getSelectionCriteria();
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired()
+ || criteria.isSourceParameterCheckRequired()
|| criteria.isPresenceCheckRequired() ) {
return methods;
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
index d81269421..691199b49 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/MethodFamilySelector.java
@@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.model.source.selector;
import java.util.ArrayList;
import java.util.List;
+import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
import org.mapstruct.ap.internal.model.source.Method;
/**
@@ -25,9 +26,22 @@ public class MethodFamilySelector implements MethodSelector {
List> result = new ArrayList<>( methods.size() );
for ( SelectedMethod method : methods ) {
- if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired()
+ if ( criteria.isPresenceCheckRequired() ) {
+ if ( method.getMethod()
+ .getConditionOptions()
+ .isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) {
+ result.add( method );
+ }
+ }
+ else if ( criteria.isSourceParameterCheckRequired() ) {
+ if ( method.getMethod()
+ .getConditionOptions()
+ .isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) {
+ result.add( method );
+ }
+ }
+ else if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired()
&& method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired()
- && method.getMethod().isPresenceCheck() == criteria.isPresenceCheckRequired()
) {
result.add( method );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
index 800278a4c..84bd04d7d 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionContext.java
@@ -163,6 +163,45 @@ public class SelectionContext {
);
}
+ public static SelectionContext forSourceParameterPresenceCheckMethods(Method mappingMethod,
+ SelectionParameters selectionParameters,
+ Parameter sourceParameter,
+ TypeFactory typeFactory) {
+ SelectionCriteria criteria = SelectionCriteria.forSourceParameterCheckMethods( selectionParameters );
+ Type booleanType = typeFactory.getType( Boolean.class );
+ return new SelectionContext(
+ null,
+ criteria,
+ mappingMethod,
+ booleanType,
+ booleanType,
+ () -> getParameterBindingsForSourceParameterPresenceCheck(
+ mappingMethod,
+ booleanType,
+ sourceParameter,
+ typeFactory
+ )
+ );
+ }
+
+ private static List getParameterBindingsForSourceParameterPresenceCheck(Method method,
+ Type targetType,
+ Parameter sourceParameter,
+ TypeFactory typeFactory) {
+
+ List availableParams = new ArrayList<>( method.getParameters().size() + 3 );
+
+ availableParams.add( ParameterBinding.fromParameter( sourceParameter ) );
+ availableParams.add( ParameterBinding.forTargetTypeBinding( typeFactory.classTypeOf( targetType ) ) );
+ for ( Parameter parameter : method.getParameters() ) {
+ if ( !parameter.isSourceParameter( ) ) {
+ availableParams.add( ParameterBinding.fromParameter( parameter ) );
+ }
+ }
+
+ return availableParams;
+ }
+
private static List getAvailableParameterBindingsFromMethod(Method method, Type targetType,
SourceRHS sourceRHS,
TypeFactory typeFactory) {
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java
index 2d288dd56..fa5e1c29c 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/model/source/selector/SelectionCriteria.java
@@ -97,6 +97,13 @@ public class SelectionCriteria {
return type == Type.PRESENCE_CHECK;
}
+ /**
+ * @return {@code true} if source parameter check methods should be selected, {@code false} otherwise
+ */
+ public boolean isSourceParameterCheckRequired() {
+ return type == Type.SOURCE_PARAMETER_CHECK;
+ }
+
public void setIgnoreQualifiers(boolean ignoreQualifiers) {
this.ignoreQualifiers = ignoreQualifiers;
}
@@ -177,6 +184,10 @@ public class SelectionCriteria {
return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK );
}
+ public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) {
+ return new SelectionCriteria( selectionParameters, null, null, Type.SOURCE_PARAMETER_CHECK );
+ }
+
public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters,
MappingControl mappingControl) {
return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED );
@@ -187,6 +198,7 @@ public class SelectionCriteria {
OBJECT_FACTORY,
LIFECYCLE_CALLBACK,
PRESENCE_CHECK,
+ SOURCE_PARAMETER_CHECK,
SELF_NOT_ALLOWED,
}
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java
index a9099ea17..4bd2ed48a 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/MethodRetrievalProcessor.java
@@ -36,6 +36,7 @@ import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.Type;
import org.mapstruct.ap.internal.model.common.TypeFactory;
import org.mapstruct.ap.internal.model.source.BeanMappingOptions;
+import org.mapstruct.ap.internal.model.source.ConditionOptions;
import org.mapstruct.ap.internal.model.source.EnumMappingOptions;
import org.mapstruct.ap.internal.model.source.IterableMappingOptions;
import org.mapstruct.ap.internal.model.source.MapMappingOptions;
@@ -53,6 +54,7 @@ import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.Executables;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.Message;
+import org.mapstruct.ap.internal.util.MetaAnnotations;
import org.mapstruct.ap.internal.util.RepeatableAnnotations;
import org.mapstruct.ap.internal.util.TypeUtils;
import org.mapstruct.ap.spi.EnumTransformationStrategy;
@@ -73,6 +75,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor contextProvidedMethods = new ArrayList<>( contextParamMethods.size() );
for ( SourceMethod sourceMethod : contextParamMethods ) {
if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory()
- || sourceMethod.isPresenceCheck() ) {
+ || sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) {
contextProvidedMethods.add( sourceMethod );
}
}
@@ -393,6 +396,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor getConditionOptions(ExecutableElement method, List parameters) {
+ return new MetaConditions( parameters ).getProcessedAnnotations( method );
+ }
+
private class RepeatableMappings extends RepeatableAnnotations {
private BeanMappingOptions beanMappingOptions;
@@ -774,4 +790,32 @@ public class MethodRetrievalProcessor implements ModelElementProcessor {
+
+ protected final List parameters;
+
+ protected MetaConditions(List parameters) {
+ super( elementUtils, CONDITION_FQN );
+ this.parameters = parameters;
+ }
+
+ @Override
+ protected ConditionGem instanceOn(Element element) {
+ return ConditionGem.instanceOn( element );
+ }
+
+ @Override
+ protected void addInstance(ConditionGem gem, Element source, Set values) {
+ ConditionOptions options = ConditionOptions.getInstanceOn(
+ gem,
+ (ExecutableElement) source,
+ parameters,
+ messager
+ );
+ if ( options != null ) {
+ values.add( options );
+ }
+ }
+ }
}
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java
index e6a26ba04..d84ba974d 100755
--- a/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/processor/creation/MappingResolverImpl.java
@@ -470,7 +470,7 @@ public class MappingResolverImpl implements MappingResolver {
}
private boolean isCandidateForMapping(Method methodCandidate) {
- if ( methodCandidate.isPresenceCheck() ) {
+ if ( methodCandidate.getConditionOptions().isAnyStrategyApplicable() ) {
return false;
}
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
index 68c079cb9..5887ee07f 100644
--- a/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/Message.java
@@ -46,6 +46,10 @@ public enum Message {
BEANMAPPING_UNKNOWN_PROPERTY_IN_DEPENDS_ON( "\"%s\" is no property of the method return type." ),
BEANMAPPING_IGNORE_BY_DEFAULT_WITH_MAPPING_TARGET_THIS( "Using @BeanMapping( ignoreByDefault = true ) with @Mapping( target = \".\", ... ) is not allowed. You'll need to explicitly ignore the target properties that should be ignored instead." ),
+ CONDITION_MISSING_APPLIES_TO_STRATEGY("'appliesTo' has to have at least one value in @Condition" ),
+ CONDITION_SOURCE_PARAMETERS_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#SOURCE_PARAMETERS. Only source and @Context parameters are allowed for conditions applicable to source parameters." ),
+ CONDITION_PROPERTIES_INVALID_PARAMETER("Parameter \"%s\" cannot be used with the ConditionStrategy#PROPERTIES. Only source, @Context, @MappingTarget, @TargetType, @TargetPropertyName and @SourcePropertyName parameters are allowed for conditions applicable to properties." ),
+
PROPERTYMAPPING_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ),
PROPERTYMAPPING_SELECT_NOTE( "selecting property mapping: %s.", Diagnostic.Kind.NOTE ),
@@ -143,6 +147,7 @@ public enum Message {
GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
+ GENERAL_AMBIGUOUS_SOURCE_PARAMETER_CHECK_METHOD( "Ambiguous source parameter check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s: %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ),
GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ),
GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ),
diff --git a/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java
new file mode 100644
index 000000000..f2328a91c
--- /dev/null
+++ b/processor/src/main/java/org/mapstruct/ap/internal/util/MetaAnnotations.java
@@ -0,0 +1,85 @@
+/*
+ * 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.util;
+
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import javax.lang.model.element.AnnotationMirror;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+
+import org.mapstruct.tools.gem.Gem;
+
+/**
+ * @author Filip Hrisafov
+ */
+public abstract class MetaAnnotations {
+
+ private static final String JAVA_LANG_ANNOTATION_PGK = "java.lang.annotation";
+
+ private final ElementUtils elementUtils;
+ private final String annotationFqn;
+
+ protected MetaAnnotations(ElementUtils elementUtils, String annotationFqn) {
+ this.elementUtils = elementUtils;
+ this.annotationFqn = annotationFqn;
+ }
+
+ /**
+ * Retrieves the processed annotations.
+ *
+ * @param source The source element of interest
+ * @return The processed annotations for the given element
+ */
+ public Set getProcessedAnnotations(Element source) {
+ return getValues( source, source, new LinkedHashSet<>(), new HashSet<>() );
+ }
+
+ protected abstract G instanceOn(Element element);
+
+ protected abstract void addInstance(G gem, Element source, Set values);
+
+ /**
+ * Retrieves the processed annotations.
+ *
+ * @param source The source element of interest
+ * @param element Element of interest: method, or (meta) annotation
+ * @param values the set of values found so far
+ * @param handledElements The collection of already handled elements to handle recursion correctly.
+ * @return The processed annotations for the given element
+ */
+ private Set getValues(Element source, Element element, Set values, Set handledElements) {
+ for ( AnnotationMirror annotationMirror : element.getAnnotationMirrors() ) {
+ Element annotationElement = annotationMirror.getAnnotationType().asElement();
+ if ( isAnnotation( annotationElement, annotationFqn ) ) {
+ G gem = instanceOn( element );
+ addInstance( gem, source, values );
+ }
+ else if ( isNotJavaAnnotation( element ) && !handledElements.contains( annotationElement ) ) {
+ handledElements.add( annotationElement );
+ getValues( source, annotationElement, values, handledElements );
+ }
+ }
+ return values;
+ }
+
+ private boolean isNotJavaAnnotation(Element element) {
+ if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
+ return !elementUtils.getPackageOf( element ).getQualifiedName().contentEquals( JAVA_LANG_ANNOTATION_PGK );
+ }
+ return true;
+ }
+
+ private boolean isAnnotation(Element element, String annotationFqn) {
+ if ( ElementKind.ANNOTATION_TYPE == element.getKind() ) {
+ return ( (TypeElement) element ).getQualifiedName().contentEquals( annotationFqn );
+ }
+
+ return false;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java
index 57a68a952..ae309988c 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMappingTest.java
@@ -208,6 +208,84 @@ public class ConditionalMappingTest {
.containsExactly( "Test", "Test Vol. 2" );
}
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodForSourceBeanMapper.class
+ })
+ public void conditionalMethodForSourceBean() {
+ ConditionalMethodForSourceBeanMapper mapper = ConditionalMethodForSourceBeanMapper.INSTANCE;
+
+ ConditionalMethodForSourceBeanMapper.Employee employee = mapper.map(
+ new ConditionalMethodForSourceBeanMapper.EmployeeDto(
+ "1",
+ "Tester"
+ ) );
+
+ assertThat( employee ).isNotNull();
+ assertThat( employee.getId() ).isEqualTo( "1" );
+ assertThat( employee.getName() ).isEqualTo( "Tester" );
+
+ employee = mapper.map( null );
+
+ assertThat( employee ).isNull();
+
+ employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( null, "Tester" ) );
+
+ assertThat( employee ).isNull();
+
+ employee = mapper.map( new ConditionalMethodForSourceBeanMapper.EmployeeDto( "test-123", "Tester" ) );
+
+ assertThat( employee ).isNotNull();
+ assertThat( employee.getId() ).isEqualTo( "test-123" );
+ assertThat( employee.getName() ).isEqualTo( "Tester" );
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ConditionalMethodForSourceParameterAndPropertyMapper.class
+ })
+ public void conditionalMethodForSourceParameterAndProperty() {
+ ConditionalMethodForSourceParameterAndPropertyMapper mapper =
+ ConditionalMethodForSourceParameterAndPropertyMapper.INSTANCE;
+
+ ConditionalMethodForSourceParameterAndPropertyMapper.Employee employee = mapper.map(
+ new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto(
+ "1",
+ "Tester"
+ ) );
+
+ assertThat( employee ).isNotNull();
+ assertThat( employee.getId() ).isEqualTo( "1" );
+ assertThat( employee.getName() ).isEqualTo( "Tester" );
+ assertThat( employee.getManager() ).isNull();
+
+ employee = mapper.map( null );
+
+ assertThat( employee ).isNull();
+
+ employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto(
+ "1",
+ "Tester",
+ new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( null, "Manager" )
+ ) );
+
+ assertThat( employee ).isNotNull();
+ assertThat( employee.getManager() ).isNull();
+
+ employee = mapper.map( new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto(
+ "1",
+ "Tester",
+ new ConditionalMethodForSourceParameterAndPropertyMapper.EmployeeDto( "2", "Manager" )
+ ) );
+
+ assertThat( employee ).isNotNull();
+ assertThat( employee.getId() ).isEqualTo( "1" );
+ assertThat( employee.getName() ).isEqualTo( "Tester" );
+ assertThat( employee.getManager() ).isNotNull();
+ assertThat( employee.getManager().getId() ).isEqualTo( "2" );
+ assertThat( employee.getManager().getName() ).isEqualTo( "Manager" );
+ }
+
@ProcessorTest
@WithClasses({
OptionalLikeConditionalMapper.class
@@ -244,4 +322,124 @@ public class ConditionalMappingTest {
assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" );
}
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousConditionalWithoutAppliesToMethodMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousConditionalWithoutAppliesToMethodMapper.class,
+ line = 19,
+ message = "'appliesTo' has to have at least one value in @Condition"
+ )
+ }
+ )
+ public void emptyConditional() {
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousSourceParameterConditionalWithMappingTargetMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousSourceParameterConditionalWithMappingTargetMapper.class,
+ line = 21,
+ message = "Parameter \"@MappingTarget BasicEmployee employee\"" +
+ " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." +
+ " Only source and @Context parameters are allowed for conditions applicable to source parameters."
+ )
+ }
+ )
+ public void sourceParameterConditionalWithMappingTarget() {
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousSourceParameterConditionalWithTargetTypeMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousSourceParameterConditionalWithTargetTypeMapper.class,
+ line = 21,
+ message = "Parameter \"@TargetType Class> targetClass\"" +
+ " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." +
+ " Only source and @Context parameters are allowed for conditions applicable to source parameters."
+ )
+ }
+ )
+ public void sourceParameterConditionalWithTargetType() {
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.class,
+ line = 21,
+ message = "Parameter \"@TargetPropertyName String targetProperty\"" +
+ " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." +
+ " Only source and @Context parameters are allowed for conditions applicable to source parameters."
+ )
+ }
+ )
+ public void sourceParameterConditionalWithTargetPropertyName() {
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.class,
+ line = 21,
+ message = "Parameter \"@SourcePropertyName String sourceProperty\"" +
+ " cannot be used with the ConditionStrategy#SOURCE_PARAMETERS." +
+ " Only source and @Context parameters are allowed for conditions applicable to source parameters."
+ )
+ }
+ )
+ public void sourceParametersConditionalWithSourcePropertyName() {
+ }
+
+ @ProcessorTest
+ @WithClasses({
+ ErroneousAmbiguousSourceParameterConditionalMethodMapper.class
+ })
+ @ExpectedCompilationOutcome(
+ value = CompilationResult.FAILED,
+ diagnostics = {
+ @Diagnostic(
+ kind = javax.tools.Diagnostic.Kind.ERROR,
+ type = ErroneousAmbiguousSourceParameterConditionalMethodMapper.class,
+ line = 17,
+ message = "Ambiguous source parameter check methods found for checking BasicEmployeeDto: " +
+ "boolean hasName(BasicEmployeeDto value), " +
+ "boolean hasStrategy(BasicEmployeeDto value). " +
+ "See https://mapstruct.org/faq/#ambiguous for more info."
+ )
+ }
+ )
+ public void ambiguousSourceParameterConditionalMethod() {
+
+ }
}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java
new file mode 100644
index 000000000..dd2fe4ac9
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceBeanMapper.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.basic;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SourceParameterCondition;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ConditionalMethodForSourceBeanMapper {
+
+ ConditionalMethodForSourceBeanMapper INSTANCE = Mappers.getMapper( ConditionalMethodForSourceBeanMapper.class );
+
+ Employee map(EmployeeDto employee);
+
+ @SourceParameterCondition
+ default boolean canMapEmployeeDto(EmployeeDto employee) {
+ return employee != null && employee.getId() != null;
+ }
+
+ class EmployeeDto {
+
+ private final String id;
+ private final String name;
+
+ public EmployeeDto(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+
+ class Employee {
+
+ private final String id;
+ private final String name;
+
+ public Employee(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java
new file mode 100644
index 000000000..11a97b723
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ConditionalMethodForSourceParameterAndPropertyMapper.java
@@ -0,0 +1,85 @@
+/*
+ * 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.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.ConditionStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.factory.Mappers;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ConditionalMethodForSourceParameterAndPropertyMapper {
+
+ ConditionalMethodForSourceParameterAndPropertyMapper INSTANCE = Mappers.getMapper(
+ ConditionalMethodForSourceParameterAndPropertyMapper.class );
+
+ Employee map(EmployeeDto employee);
+
+ @Condition(appliesTo = {
+ ConditionStrategy.SOURCE_PARAMETERS,
+ ConditionStrategy.PROPERTIES
+ })
+ default boolean canMapEmployeeDto(EmployeeDto employee) {
+ return employee != null && employee.getId() != null;
+ }
+
+ class EmployeeDto {
+
+ private final String id;
+ private final String name;
+ private final EmployeeDto manager;
+
+ public EmployeeDto(String id, String name) {
+ this( id, name, null );
+ }
+
+ public EmployeeDto(String id, String name, EmployeeDto manager) {
+ this.id = id;
+ this.name = name;
+ this.manager = manager;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public EmployeeDto getManager() {
+ return manager;
+ }
+ }
+
+ class Employee {
+
+ private final String id;
+ private final String name;
+ private final Employee manager;
+
+ public Employee(String id, String name, Employee manager) {
+ this.id = id;
+ this.name = name;
+ this.manager = manager;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Employee getManager() {
+ return manager;
+ }
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java
new file mode 100644
index 000000000..39433e264
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousAmbiguousSourceParameterConditionalMethodMapper.java
@@ -0,0 +1,28 @@
+/*
+ * 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.basic;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.SourceParameterCondition;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousAmbiguousSourceParameterConditionalMethodMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @SourceParameterCondition
+ default boolean hasName(BasicEmployeeDto value) {
+ return value != null && value.getName() != null;
+ }
+
+ @SourceParameterCondition
+ default boolean hasStrategy(BasicEmployeeDto value) {
+ return value != null && value.getStrategy() != null;
+ }
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java
new file mode 100644
index 000000000..76f62845d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousConditionalWithoutAppliesToMethodMapper.java
@@ -0,0 +1,24 @@
+/*
+ * 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.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.Mapper;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousConditionalWithoutAppliesToMethodMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @Condition(appliesTo = {})
+ default boolean isNotBlank(String value) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java
new file mode 100644
index 000000000..b1206798d
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithMappingTargetMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.ConditionStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.MappingTarget;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousSourceParameterConditionalWithMappingTargetMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+ default boolean isNotBlank(String value, @MappingTarget BasicEmployee employee) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java
new file mode 100644
index 000000000..1bfd150af
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithSourcePropertyNameMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.ConditionStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.SourcePropertyName;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousSourceParameterConditionalWithSourcePropertyNameMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+ default boolean isNotBlank(String value, @SourcePropertyName String sourceProperty) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java
new file mode 100644
index 000000000..e9dac1ece
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetPropertyNameMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.ConditionStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetPropertyName;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousSourceParameterConditionalWithTargetPropertyNameMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+ default boolean isNotBlank(String value, @TargetPropertyName String targetProperty) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java
new file mode 100644
index 000000000..9ff55597a
--- /dev/null
+++ b/processor/src/test/java/org/mapstruct/ap/test/conditional/basic/ErroneousSourceParameterConditionalWithTargetTypeMapper.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright MapStruct Authors.
+ *
+ * Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
+ */
+package org.mapstruct.ap.test.conditional.basic;
+
+import org.mapstruct.Condition;
+import org.mapstruct.ConditionStrategy;
+import org.mapstruct.Mapper;
+import org.mapstruct.TargetType;
+
+/**
+ * @author Filip Hrisafov
+ */
+@Mapper
+public interface ErroneousSourceParameterConditionalWithTargetTypeMapper {
+
+ BasicEmployee map(BasicEmployeeDto employee);
+
+ @Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
+ default boolean isNotBlank(String value, @TargetType Class> targetClass) {
+ return value != null && !value.trim().isEmpty();
+ }
+
+}
diff --git a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java
index 0c92fc6bb..f46018065 100644
--- a/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java
+++ b/processor/src/test/java/org/mapstruct/ap/test/gem/EnumGemsTest.java
@@ -11,12 +11,14 @@ import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.mapstruct.CollectionMappingStrategy;
+import org.mapstruct.ConditionStrategy;
import org.mapstruct.InjectionStrategy;
import org.mapstruct.MappingInheritanceStrategy;
import org.mapstruct.NullValueCheckStrategy;
import org.mapstruct.NullValueMappingStrategy;
import org.mapstruct.ReportingPolicy;
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
+import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
import org.mapstruct.ap.internal.gem.InjectionStrategyGem;
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
@@ -67,6 +69,12 @@ public class EnumGemsTest {
namesOf( InjectionStrategyGem.values() ) );
}
+ @Test
+ public void conditionStrategyGemIsCorrect() {
+ assertThat( namesOf( ConditionStrategy.values() ) ).isEqualTo(
+ namesOf( ConditionStrategyGem.values() ) );
+ }
+
private static List namesOf(Enum>[] values) {
return Stream.of( values )
.map( Enum::name )