mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
#2610 Add support for conditions on source parameters + fix incorrect use of source parameter in presence check method (#3543)
The new `@SourceParameterCondition` is also going to cover the problems in #3270 and #3459. The changes in the `MethodFamilySelector` are also fixing #3561
This commit is contained in:
parent
0a935c67a7
commit
0a2a0aa526
@ -11,26 +11,35 @@ import java.lang.annotation.RetentionPolicy;
|
||||
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 {
|
||||
*
|
||||
* @Condition
|
||||
@ -45,11 +54,10 @@ import java.lang.annotation.Target;
|
||||
* 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 {
|
||||
*
|
||||
* @Override
|
||||
@ -67,14 +75,22 @@ import java.lang.annotation.Target;
|
||||
* return movieDto;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* 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;
|
||||
|
||||
}
|
||||
|
23
core/src/main/java/org/mapstruct/ConditionStrategy.java
Normal file
23
core/src/main/java/org/mapstruct/ConditionStrategy.java
Normal file
@ -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,
|
||||
}
|
@ -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 {
|
||||
*
|
||||
* @SourceParameterCondition
|
||||
* public static boolean isDefined(Car car) {
|
||||
* return car != null && car.getId() != null;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @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 {
|
||||
*
|
||||
* @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 {
|
||||
|
||||
}
|
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
@ -411,6 +411,26 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
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(),
|
||||
@ -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<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
|
||||
MethodReference finalizerMethod,
|
||||
MappingReferences mappingReferences,
|
||||
List<SubclassMapping> subclassMappings) {
|
||||
List<SubclassMapping> subclassMappings,
|
||||
Map<String, PresenceCheck> 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<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() ) {
|
||||
|
@ -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<SourceMethod> matchingMethod = findMatchingPresenceCheckMethod(
|
||||
List<SelectedMethod<SourceMethod>> 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<SourceMethod> findMatchingPresenceCheckMethod(
|
||||
Method method,
|
||||
SelectionParameters selectionParameters,
|
||||
MappingBuilderContext ctx
|
||||
) {
|
||||
MethodSelectors selectors = new MethodSelectors(
|
||||
ctx.getTypeUtils(),
|
||||
ctx.getElementUtils(),
|
||||
ctx.getMessager()
|
||||
);
|
||||
|
||||
List<SelectedMethod<SourceMethod>> 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<SourceMethod> 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<SelectedMethod<SourceMethod>> 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<SourceMethod> matchingMethod = matchingMethods.get( 0 );
|
||||
|
||||
MethodReference methodReference = getPresenceCheckMethodReference( method, matchingMethod, ctx );
|
||||
|
||||
return new MethodReferencePresenceCheck( methodReference );
|
||||
|
||||
}
|
||||
|
||||
private static List<SelectedMethod<SourceMethod>> 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<SourceMethod> getAllAvailableMethods(Method method, List<SourceMethod> sourceModelMethods) {
|
||||
private static List<SourceMethod> getAllAvailableMethods(Method method, List<SourceMethod> 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 );
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<BindingType> bindingTypes;
|
||||
|
||||
private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
|
||||
boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName,
|
||||
private ParameterBinding(Type parameterType, String variableName, Collection<BindingType> 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<Type> 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<BindingType> 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
|
||||
}
|
||||
}
|
||||
|
@ -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> conditionOptions;
|
||||
|
||||
public ConditionMethodOptions(Collection<ConditionOptions> 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;
|
||||
}
|
||||
}
|
@ -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<ConditionStrategyGem> conditionStrategies;
|
||||
|
||||
private ConditionOptions(Set<ConditionStrategyGem> conditionStrategies) {
|
||||
this.conditionStrategies = conditionStrategies;
|
||||
}
|
||||
|
||||
public Collection<ConditionStrategyGem> getConditionStrategies() {
|
||||
return conditionStrategies;
|
||||
}
|
||||
|
||||
public static ConditionOptions getInstanceOn(ConditionGem condition, ExecutableElement method,
|
||||
List<Parameter> 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<ConditionStrategyGem> 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<Parameter> 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<Parameter> 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<Parameter> 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;
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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<Type> exceptionTypes;
|
||||
private final MappingMethodOptions mappingMethodOptions;
|
||||
private final ConditionMethodOptions conditionMethodOptions;
|
||||
private final List<SourceMethod> prototypeMethods;
|
||||
private final Type mapperToImplement;
|
||||
|
||||
@ -95,6 +94,7 @@ public class SourceMethod implements Method {
|
||||
private List<ValueMappingOptions> valueMappings;
|
||||
private EnumMappingOptions enumMappingOptions;
|
||||
private ParameterProvidedMethods contextProvidedMethods;
|
||||
private Set<ConditionOptions> conditionOptions;
|
||||
private List<Type> typeParameters;
|
||||
private Set<SubclassMappingOptions> subclassMappings;
|
||||
|
||||
@ -196,6 +196,11 @@ public class SourceMethod implements Method {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setConditionOptions(Set<ConditionOptions> 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() );
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<SelectedMethod<T>> result = new ArrayList<>( methods.size() );
|
||||
for ( SelectedMethod<T> 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 );
|
||||
|
@ -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<ParameterBinding> getParameterBindingsForSourceParameterPresenceCheck(Method method,
|
||||
Type targetType,
|
||||
Parameter sourceParameter,
|
||||
TypeFactory typeFactory) {
|
||||
|
||||
List<ParameterBinding> 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<ParameterBinding> getAvailableParameterBindingsFromMethod(Method method, Type targetType,
|
||||
SourceRHS sourceRHS,
|
||||
TypeFactory typeFactory) {
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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<Void, Lis
|
||||
private static final String SUB_CLASS_MAPPINGS_FQN = "org.mapstruct.SubclassMappings";
|
||||
private static final String VALUE_MAPPING_FQN = "org.mapstruct.ValueMapping";
|
||||
private static final String VALUE_MAPPINGS_FQN = "org.mapstruct.ValueMappings";
|
||||
private static final String CONDITION_FQN = "org.mapstruct.Condition";
|
||||
private FormattingMessager messager;
|
||||
private TypeFactory typeFactory;
|
||||
private AccessorNamingUtils accessorNaming;
|
||||
@ -358,7 +361,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
List<SourceMethod> 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<Void, Lis
|
||||
.setExceptionTypes( exceptionTypes )
|
||||
.setTypeUtils( typeUtils )
|
||||
.setTypeFactory( typeFactory )
|
||||
.setConditionOptions( getConditionOptions( method, parameters ) )
|
||||
.setVerboseLogging( options.isVerbose() )
|
||||
.build();
|
||||
}
|
||||
@ -633,6 +637,18 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
.getProcessedAnnotations( method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the conditions configured via {@code @Condition} from the given method.
|
||||
*
|
||||
* @param method The method of interest
|
||||
* @param parameters
|
||||
* @return The condition options for the given method
|
||||
*/
|
||||
|
||||
private Set<ConditionOptions> getConditionOptions(ExecutableElement method, List<Parameter> parameters) {
|
||||
return new MetaConditions( parameters ).getProcessedAnnotations( method );
|
||||
}
|
||||
|
||||
private class RepeatableMappings extends RepeatableAnnotations<MappingGem, MappingsGem, MappingOptions> {
|
||||
private BeanMappingOptions beanMappingOptions;
|
||||
|
||||
@ -774,4 +790,32 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings );
|
||||
}
|
||||
}
|
||||
|
||||
private class MetaConditions extends MetaAnnotations<ConditionGem, ConditionOptions> {
|
||||
|
||||
protected final List<Parameter> parameters;
|
||||
|
||||
protected MetaConditions(List<Parameter> 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<ConditionOptions> values) {
|
||||
ConditionOptions options = ConditionOptions.getInstanceOn(
|
||||
gem,
|
||||
(ExecutableElement) source,
|
||||
parameters,
|
||||
messager
|
||||
);
|
||||
if ( options != null ) {
|
||||
values.add( options );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
|
@ -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" ),
|
||||
|
@ -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<G extends Gem, V> {
|
||||
|
||||
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<V> 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<V> 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<V> getValues(Element source, Element element, Set<V> values, Set<Element> 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;
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<String> namesOf(Enum<?>[] values) {
|
||||
return Stream.of( values )
|
||||
.map( Enum::name )
|
||||
|
Loading…
x
Reference in New Issue
Block a user