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;
|
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>
|
* <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.
|
* If a presence check method is available then it will be used instead.
|
||||||
* <p>
|
* <p>
|
||||||
* Presence check methods have to return {@code boolean}.
|
* Presence check methods have to return {@code boolean}.
|
||||||
* The following parameters are accepted for the presence check methods:
|
* The following parameters are accepted for the presence check methods:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>The parameter with the value of the source property.
|
* <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>The mapping source parameter</li>
|
||||||
* <li>{@code @}{@link Context} parameter</li>
|
* <li>{@code @}{@link Context} parameter</li>
|
||||||
* <li>{@code @}{@link TargetPropertyName} parameter</li>
|
* <li>
|
||||||
* <li>{@code @}{@link SourcePropertyName} parameter</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>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
|
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
|
||||||
* for a method to be considered as a presence check method.
|
* for a method to be considered as a presence check method.
|
||||||
*
|
*
|
||||||
* <pre><code>
|
* <pre><code class='java'>
|
||||||
* public class PresenceCheckUtils {
|
* public class PresenceCheckUtils {
|
||||||
*
|
*
|
||||||
* @Condition
|
* @Condition
|
||||||
@ -45,11 +54,10 @@ import java.lang.annotation.Target;
|
|||||||
* MovieDto map(Movie movie);
|
* MovieDto map(Movie movie);
|
||||||
* }
|
* }
|
||||||
* </code></pre>
|
* </code></pre>
|
||||||
*
|
* <p>
|
||||||
* The following implementation of {@code MovieMapper} will be generated:
|
* The following implementation of {@code MovieMapper} will be generated:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre><code class='java'>
|
||||||
* <code>
|
|
||||||
* public class MovieMapperImpl implements MovieMapper {
|
* public class MovieMapperImpl implements MovieMapper {
|
||||||
*
|
*
|
||||||
* @Override
|
* @Override
|
||||||
@ -67,14 +75,22 @@ import java.lang.annotation.Target;
|
|||||||
* return movieDto;
|
* return movieDto;
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* </code>
|
* </code></pre>
|
||||||
* </pre>
|
* <p>
|
||||||
|
* This annotation can also be used as a meta-annotation to define the condition strategy.
|
||||||
*
|
*
|
||||||
* @author Filip Hrisafov
|
* @author Filip Hrisafov
|
||||||
|
* @see SourceParameterCondition
|
||||||
* @since 1.5
|
* @since 1.5
|
||||||
*/
|
*/
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||||
@Retention(RetentionPolicy.CLASS)
|
@Retention(RetentionPolicy.CLASS)
|
||||||
public @interface Condition {
|
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>>.
|
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.
|
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:
|
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.
|
<<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`.
|
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]]
|
||||||
=== 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 );
|
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(
|
return new BeanMappingMethod(
|
||||||
method,
|
method,
|
||||||
getMethodAnnotations(),
|
getMethodAnnotations(),
|
||||||
@ -426,7 +446,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
afterMappingReferencesWithFinalizedReturnType,
|
afterMappingReferencesWithFinalizedReturnType,
|
||||||
finalizeMethod,
|
finalizeMethod,
|
||||||
mappingReferences,
|
mappingReferences,
|
||||||
subclasses
|
subclasses,
|
||||||
|
presenceChecksByParameter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1891,7 +1912,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
|
List<LifecycleCallbackMethodReference> afterMappingReferencesWithFinalizedReturnType,
|
||||||
MethodReference finalizerMethod,
|
MethodReference finalizerMethod,
|
||||||
MappingReferences mappingReferences,
|
MappingReferences mappingReferences,
|
||||||
List<SubclassMapping> subclassMappings) {
|
List<SubclassMapping> subclassMappings,
|
||||||
|
Map<String, PresenceCheck> presenceChecksByParameter) {
|
||||||
super(
|
super(
|
||||||
method,
|
method,
|
||||||
annotations,
|
annotations,
|
||||||
@ -1923,18 +1945,12 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
|||||||
// parameter mapping.
|
// parameter mapping.
|
||||||
this.mappingsByParameter = new HashMap<>();
|
this.mappingsByParameter = new HashMap<>();
|
||||||
this.constantMappings = new ArrayList<>( propertyMappings.size() );
|
this.constantMappings = new ArrayList<>( propertyMappings.size() );
|
||||||
this.presenceChecksByParameter = new LinkedHashMap<>();
|
this.presenceChecksByParameter = presenceChecksByParameter;
|
||||||
this.constructorMappingsByParameter = new LinkedHashMap<>();
|
this.constructorMappingsByParameter = new LinkedHashMap<>();
|
||||||
this.constructorConstantMappings = new ArrayList<>();
|
this.constructorConstantMappings = new ArrayList<>();
|
||||||
Set<String> sourceParameterNames = new HashSet<>();
|
Set<String> sourceParameterNames = new HashSet<>();
|
||||||
for ( Parameter sourceParameter : getSourceParameters() ) {
|
for ( Parameter sourceParameter : getSourceParameters() ) {
|
||||||
sourceParameterNames.add( sourceParameter.getName() );
|
sourceParameterNames.add( sourceParameter.getName() );
|
||||||
if ( !sourceParameter.getType().isPrimitive() ) {
|
|
||||||
presenceChecksByParameter.put(
|
|
||||||
sourceParameter.getName(),
|
|
||||||
new NullPresenceCheck( sourceParameter.getName() )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for ( PropertyMapping mapping : propertyMappings ) {
|
for ( PropertyMapping mapping : propertyMappings ) {
|
||||||
if ( mapping.isConstructorMapping() ) {
|
if ( mapping.isConstructorMapping() ) {
|
||||||
|
@ -9,6 +9,7 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
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.Parameter;
|
||||||
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||||
import org.mapstruct.ap.internal.model.source.Method;
|
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.MethodSelectors;
|
||||||
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
|
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.SelectionContext;
|
||||||
|
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
|
||||||
import org.mapstruct.ap.internal.util.Message;
|
import org.mapstruct.ap.internal.util.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,38 +36,12 @@ public final class PresenceCheckMethodResolver {
|
|||||||
SelectionParameters selectionParameters,
|
SelectionParameters selectionParameters,
|
||||||
MappingBuilderContext ctx
|
MappingBuilderContext ctx
|
||||||
) {
|
) {
|
||||||
SelectedMethod<SourceMethod> matchingMethod = findMatchingPresenceCheckMethod(
|
List<SelectedMethod<SourceMethod>> matchingMethods = findMatchingMethods(
|
||||||
method,
|
method,
|
||||||
selectionParameters,
|
SelectionContext.forPresenceCheckMethods( method, selectionParameters, ctx.getTypeFactory() ),
|
||||||
ctx
|
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() ) {
|
if ( matchingMethods.isEmpty() ) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -84,7 +60,72 @@ public final class PresenceCheckMethodResolver {
|
|||||||
return null;
|
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(
|
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();
|
ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods();
|
||||||
if ( contextProvidedMethods.isEmpty() ) {
|
if ( contextProvidedMethods.isEmpty() ) {
|
||||||
return sourceModelMethods;
|
return sourceModelMethods;
|
||||||
@ -129,9 +171,19 @@ public final class PresenceCheckMethodResolver {
|
|||||||
new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() );
|
new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() );
|
||||||
|
|
||||||
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
|
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
|
||||||
// add only methods from context that do have the @Condition annotation
|
if ( selectionCriteria.isPresenceCheckRequired() ) {
|
||||||
if ( methodProvidedByParams.isPresenceCheck() ) {
|
// add only methods from context that do have the @Condition for properties annotation
|
||||||
availableMethods.add( methodProvidedByParams );
|
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 );
|
availableMethods.addAll( sourceModelMethods );
|
||||||
|
@ -133,6 +133,14 @@ public class Parameter extends ModelElement {
|
|||||||
return varArgs;
|
return varArgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSourceParameter() {
|
||||||
|
return !isMappingTarget() &&
|
||||||
|
!isTargetType() &&
|
||||||
|
!isMappingContext() &&
|
||||||
|
!isSourcePropertyName() &&
|
||||||
|
!isTargetPropertyName();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = name != null ? name.hashCode() : 0;
|
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 );
|
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;
|
package org.mapstruct.ap.internal.model.common;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@ -19,23 +21,14 @@ public class ParameterBinding {
|
|||||||
|
|
||||||
private final Type type;
|
private final Type type;
|
||||||
private final String variableName;
|
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 SourceRHS sourceRHS;
|
||||||
|
private final Collection<BindingType> bindingTypes;
|
||||||
|
|
||||||
private ParameterBinding(Type parameterType, String variableName, boolean mappingTarget, boolean targetType,
|
private ParameterBinding(Type parameterType, String variableName, Collection<BindingType> bindingTypes,
|
||||||
boolean mappingContext, boolean sourcePropertyName, boolean targetPropertyName,
|
|
||||||
SourceRHS sourceRHS) {
|
SourceRHS sourceRHS) {
|
||||||
this.type = parameterType;
|
this.type = parameterType;
|
||||||
this.variableName = variableName;
|
this.variableName = variableName;
|
||||||
this.targetType = targetType;
|
this.bindingTypes = bindingTypes;
|
||||||
this.mappingTarget = mappingTarget;
|
|
||||||
this.mappingContext = mappingContext;
|
|
||||||
this.sourcePropertyName = sourcePropertyName;
|
|
||||||
this.targetPropertyName = targetPropertyName;
|
|
||||||
this.sourceRHS = sourceRHS;
|
this.sourceRHS = sourceRHS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,39 +39,47 @@ public class ParameterBinding {
|
|||||||
return variableName;
|
return variableName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isSourceParameter() {
|
||||||
|
return bindingTypes.contains( BindingType.PARAMETER );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter.
|
* @return {@code true}, if the parameter being bound is a {@code @TargetType} parameter.
|
||||||
*/
|
*/
|
||||||
public boolean isTargetType() {
|
public boolean isTargetType() {
|
||||||
return targetType;
|
return bindingTypes.contains( BindingType.TARGET_TYPE );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter.
|
* @return {@code true}, if the parameter being bound is a {@code @MappingTarget} parameter.
|
||||||
*/
|
*/
|
||||||
public boolean isMappingTarget() {
|
public boolean isMappingTarget() {
|
||||||
return mappingTarget;
|
return bindingTypes.contains( BindingType.MAPPING_TARGET );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter.
|
* @return {@code true}, if the parameter being bound is a {@code @MappingContext} parameter.
|
||||||
*/
|
*/
|
||||||
public boolean isMappingContext() {
|
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.
|
* @return {@code true}, if the parameter being bound is a {@code @SourcePropertyName} parameter.
|
||||||
*/
|
*/
|
||||||
public boolean isSourcePropertyName() {
|
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.
|
* @return {@code true}, if the parameter being bound is a {@code @TargetPropertyName} parameter.
|
||||||
*/
|
*/
|
||||||
public boolean isTargetPropertyName() {
|
public boolean isTargetPropertyName() {
|
||||||
return targetPropertyName;
|
return bindingTypes.contains( BindingType.TARGET_PROPERTY_NAME );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -96,7 +97,7 @@ public class ParameterBinding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Set<Type> getImportTypes() {
|
public Set<Type> getImportTypes() {
|
||||||
if ( targetType ) {
|
if ( isTargetType() ) {
|
||||||
return type.getImportTypes();
|
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
|
* @return a parameter binding reflecting the given parameter as being used as argument for a method call
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding fromParameter(Parameter parameter) {
|
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(
|
return new ParameterBinding(
|
||||||
parameter.getType(),
|
parameter.getType(),
|
||||||
parameter.getName(),
|
parameter.getName(),
|
||||||
parameter.isMappingTarget(),
|
bindingTypes,
|
||||||
parameter.isTargetType(),
|
|
||||||
parameter.isMappingContext(),
|
|
||||||
parameter.isSourcePropertyName(),
|
|
||||||
parameter.isTargetPropertyName(),
|
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -136,11 +154,7 @@ public class ParameterBinding {
|
|||||||
return new ParameterBinding(
|
return new ParameterBinding(
|
||||||
parameterType,
|
parameterType,
|
||||||
parameterName,
|
parameterName,
|
||||||
false,
|
Collections.emptySet(),
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -150,21 +164,31 @@ public class ParameterBinding {
|
|||||||
* @return a parameter binding representing a target type parameter
|
* @return a parameter binding representing a target type parameter
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding forTargetTypeBinding(Type classTypeOf) {
|
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
|
* @return a parameter binding representing a target property name parameter
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding forTargetPropertyNameBinding(Type classTypeOf) {
|
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
|
* @return a parameter binding representing a source property name parameter
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding forSourcePropertyNameBinding(Type classTypeOf) {
|
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
|
* @return a parameter binding representing a mapping target parameter
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding forMappingTargetBinding(Type resultType) {
|
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
|
* @return a parameter binding representing a mapping source type
|
||||||
*/
|
*/
|
||||||
public static ParameterBinding forSourceTypeBinding(Type sourceType) {
|
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) {
|
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();
|
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 }
|
* Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType }
|
||||||
*
|
*
|
||||||
@ -187,6 +179,10 @@ public interface Method {
|
|||||||
*/
|
*/
|
||||||
MappingMethodOptions getOptions();
|
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
|
* @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.ExecutableElement;
|
||||||
import javax.lang.model.element.Modifier;
|
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.gem.ObjectFactoryGem;
|
||||||
import org.mapstruct.ap.internal.model.common.Accessibility;
|
import org.mapstruct.ap.internal.model.common.Accessibility;
|
||||||
import org.mapstruct.ap.internal.model.common.Parameter;
|
import org.mapstruct.ap.internal.model.common.Parameter;
|
||||||
@ -50,11 +49,11 @@ public class SourceMethod implements Method {
|
|||||||
private final Parameter sourcePropertyNameParameter;
|
private final Parameter sourcePropertyNameParameter;
|
||||||
private final Parameter targetPropertyNameParameter;
|
private final Parameter targetPropertyNameParameter;
|
||||||
private final boolean isObjectFactory;
|
private final boolean isObjectFactory;
|
||||||
private final boolean isPresenceCheck;
|
|
||||||
private final Type returnType;
|
private final Type returnType;
|
||||||
private final Accessibility accessibility;
|
private final Accessibility accessibility;
|
||||||
private final List<Type> exceptionTypes;
|
private final List<Type> exceptionTypes;
|
||||||
private final MappingMethodOptions mappingMethodOptions;
|
private final MappingMethodOptions mappingMethodOptions;
|
||||||
|
private final ConditionMethodOptions conditionMethodOptions;
|
||||||
private final List<SourceMethod> prototypeMethods;
|
private final List<SourceMethod> prototypeMethods;
|
||||||
private final Type mapperToImplement;
|
private final Type mapperToImplement;
|
||||||
|
|
||||||
@ -95,6 +94,7 @@ public class SourceMethod implements Method {
|
|||||||
private List<ValueMappingOptions> valueMappings;
|
private List<ValueMappingOptions> valueMappings;
|
||||||
private EnumMappingOptions enumMappingOptions;
|
private EnumMappingOptions enumMappingOptions;
|
||||||
private ParameterProvidedMethods contextProvidedMethods;
|
private ParameterProvidedMethods contextProvidedMethods;
|
||||||
|
private Set<ConditionOptions> conditionOptions;
|
||||||
private List<Type> typeParameters;
|
private List<Type> typeParameters;
|
||||||
private Set<SubclassMappingOptions> subclassMappings;
|
private Set<SubclassMappingOptions> subclassMappings;
|
||||||
|
|
||||||
@ -196,6 +196,11 @@ public class SourceMethod implements Method {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder setConditionOptions(Set<ConditionOptions> conditionOptions) {
|
||||||
|
this.conditionOptions = conditionOptions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder setVerboseLogging(boolean verboseLogging) {
|
public Builder setVerboseLogging(boolean verboseLogging) {
|
||||||
this.verboseLogging = verboseLogging;
|
this.verboseLogging = verboseLogging;
|
||||||
return this;
|
return this;
|
||||||
@ -223,17 +228,22 @@ public class SourceMethod implements Method {
|
|||||||
subclassValidator
|
subclassValidator
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ConditionMethodOptions conditionMethodOptions =
|
||||||
|
conditionOptions != null ? new ConditionMethodOptions( conditionOptions ) :
|
||||||
|
ConditionMethodOptions.empty();
|
||||||
|
|
||||||
this.typeParameters = this.executable.getTypeParameters()
|
this.typeParameters = this.executable.getTypeParameters()
|
||||||
.stream()
|
.stream()
|
||||||
.map( Element::asType )
|
.map( Element::asType )
|
||||||
.map( typeFactory::getType )
|
.map( typeFactory::getType )
|
||||||
.collect( Collectors.toList() );
|
.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.declaringMapper = builder.declaringMapper;
|
||||||
this.executable = builder.executable;
|
this.executable = builder.executable;
|
||||||
this.parameters = builder.parameters;
|
this.parameters = builder.parameters;
|
||||||
@ -242,6 +252,7 @@ public class SourceMethod implements Method {
|
|||||||
this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() );
|
this.accessibility = Accessibility.fromModifiers( builder.executable.getModifiers() );
|
||||||
|
|
||||||
this.mappingMethodOptions = mappingMethodOptions;
|
this.mappingMethodOptions = mappingMethodOptions;
|
||||||
|
this.conditionMethodOptions = conditionMethodOptions;
|
||||||
|
|
||||||
this.sourceParameters = Parameter.getSourceParameters( parameters );
|
this.sourceParameters = Parameter.getSourceParameters( parameters );
|
||||||
this.contextParameters = Parameter.getContextParameters( parameters );
|
this.contextParameters = Parameter.getContextParameters( parameters );
|
||||||
@ -254,7 +265,6 @@ public class SourceMethod implements Method {
|
|||||||
this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
|
this.targetPropertyNameParameter = Parameter.getTargetPropertyNameParameter( parameters );
|
||||||
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
|
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
|
||||||
this.isObjectFactory = determineIfIsObjectFactory();
|
this.isObjectFactory = determineIfIsObjectFactory();
|
||||||
this.isPresenceCheck = determineIfIsPresenceCheck();
|
|
||||||
|
|
||||||
this.typeUtils = builder.typeUtils;
|
this.typeUtils = builder.typeUtils;
|
||||||
this.typeFactory = builder.typeFactory;
|
this.typeFactory = builder.typeFactory;
|
||||||
@ -274,19 +284,6 @@ public class SourceMethod implements Method {
|
|||||||
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
|
&& ( 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
|
@Override
|
||||||
public Type getDeclaringMapper() {
|
public Type getDeclaringMapper() {
|
||||||
return declaringMapper;
|
return declaringMapper;
|
||||||
@ -547,6 +544,11 @@ public class SourceMethod implements Method {
|
|||||||
return mappingMethodOptions;
|
return mappingMethodOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ConditionMethodOptions getConditionOptions() {
|
||||||
|
return conditionMethodOptions;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isStatic() {
|
public boolean isStatic() {
|
||||||
return executable.getModifiers().contains( Modifier.STATIC );
|
return executable.getModifiers().contains( Modifier.STATIC );
|
||||||
@ -567,11 +569,6 @@ public class SourceMethod implements Method {
|
|||||||
return Executables.isLifecycleCallbackMethod( getExecutable() );
|
return Executables.isLifecycleCallbackMethod( getExecutable() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isPresenceCheck() {
|
|
||||||
return isPresenceCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAfterMappingMethod() {
|
public boolean isAfterMappingMethod() {
|
||||||
return Executables.isAfterMappingMethod( getExecutable() );
|
return Executables.isAfterMappingMethod( getExecutable() );
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ public class CreateOrUpdateSelector implements MethodSelector {
|
|||||||
SelectionContext context) {
|
SelectionContext context) {
|
||||||
SelectionCriteria criteria = context.getSelectionCriteria();
|
SelectionCriteria criteria = context.getSelectionCriteria();
|
||||||
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired()
|
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired()
|
||||||
|
|| criteria.isSourceParameterCheckRequired()
|
||||||
|| criteria.isPresenceCheckRequired() ) {
|
|| criteria.isPresenceCheckRequired() ) {
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ package org.mapstruct.ap.internal.model.source.selector;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
|
||||||
import org.mapstruct.ap.internal.model.source.Method;
|
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() );
|
List<SelectedMethod<T>> result = new ArrayList<>( methods.size() );
|
||||||
for ( SelectedMethod<T> method : methods ) {
|
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().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired()
|
||||||
&& method.getMethod().isPresenceCheck() == criteria.isPresenceCheckRequired()
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
result.add( method );
|
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,
|
private static List<ParameterBinding> getAvailableParameterBindingsFromMethod(Method method, Type targetType,
|
||||||
SourceRHS sourceRHS,
|
SourceRHS sourceRHS,
|
||||||
TypeFactory typeFactory) {
|
TypeFactory typeFactory) {
|
||||||
|
@ -97,6 +97,13 @@ public class SelectionCriteria {
|
|||||||
return type == Type.PRESENCE_CHECK;
|
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) {
|
public void setIgnoreQualifiers(boolean ignoreQualifiers) {
|
||||||
this.ignoreQualifiers = ignoreQualifiers;
|
this.ignoreQualifiers = ignoreQualifiers;
|
||||||
}
|
}
|
||||||
@ -177,6 +184,10 @@ public class SelectionCriteria {
|
|||||||
return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK );
|
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,
|
public static SelectionCriteria forSubclassMappingMethods(SelectionParameters selectionParameters,
|
||||||
MappingControl mappingControl) {
|
MappingControl mappingControl) {
|
||||||
return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED );
|
return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED );
|
||||||
@ -187,6 +198,7 @@ public class SelectionCriteria {
|
|||||||
OBJECT_FACTORY,
|
OBJECT_FACTORY,
|
||||||
LIFECYCLE_CALLBACK,
|
LIFECYCLE_CALLBACK,
|
||||||
PRESENCE_CHECK,
|
PRESENCE_CHECK,
|
||||||
|
SOURCE_PARAMETER_CHECK,
|
||||||
SELF_NOT_ALLOWED,
|
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.Type;
|
||||||
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
import org.mapstruct.ap.internal.model.common.TypeFactory;
|
||||||
import org.mapstruct.ap.internal.model.source.BeanMappingOptions;
|
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.EnumMappingOptions;
|
||||||
import org.mapstruct.ap.internal.model.source.IterableMappingOptions;
|
import org.mapstruct.ap.internal.model.source.IterableMappingOptions;
|
||||||
import org.mapstruct.ap.internal.model.source.MapMappingOptions;
|
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.Executables;
|
||||||
import org.mapstruct.ap.internal.util.FormattingMessager;
|
import org.mapstruct.ap.internal.util.FormattingMessager;
|
||||||
import org.mapstruct.ap.internal.util.Message;
|
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.RepeatableAnnotations;
|
||||||
import org.mapstruct.ap.internal.util.TypeUtils;
|
import org.mapstruct.ap.internal.util.TypeUtils;
|
||||||
import org.mapstruct.ap.spi.EnumTransformationStrategy;
|
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 SUB_CLASS_MAPPINGS_FQN = "org.mapstruct.SubclassMappings";
|
||||||
private static final String VALUE_MAPPING_FQN = "org.mapstruct.ValueMapping";
|
private static final String VALUE_MAPPING_FQN = "org.mapstruct.ValueMapping";
|
||||||
private static final String VALUE_MAPPINGS_FQN = "org.mapstruct.ValueMappings";
|
private static final String VALUE_MAPPINGS_FQN = "org.mapstruct.ValueMappings";
|
||||||
|
private static final String CONDITION_FQN = "org.mapstruct.Condition";
|
||||||
private FormattingMessager messager;
|
private FormattingMessager messager;
|
||||||
private TypeFactory typeFactory;
|
private TypeFactory typeFactory;
|
||||||
private AccessorNamingUtils accessorNaming;
|
private AccessorNamingUtils accessorNaming;
|
||||||
@ -358,7 +361,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
|||||||
List<SourceMethod> contextProvidedMethods = new ArrayList<>( contextParamMethods.size() );
|
List<SourceMethod> contextProvidedMethods = new ArrayList<>( contextParamMethods.size() );
|
||||||
for ( SourceMethod sourceMethod : contextParamMethods ) {
|
for ( SourceMethod sourceMethod : contextParamMethods ) {
|
||||||
if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory()
|
if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory()
|
||||||
|| sourceMethod.isPresenceCheck() ) {
|
|| sourceMethod.getConditionOptions().isAnyStrategyApplicable() ) {
|
||||||
contextProvidedMethods.add( sourceMethod );
|
contextProvidedMethods.add( sourceMethod );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -393,6 +396,7 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
|||||||
.setExceptionTypes( exceptionTypes )
|
.setExceptionTypes( exceptionTypes )
|
||||||
.setTypeUtils( typeUtils )
|
.setTypeUtils( typeUtils )
|
||||||
.setTypeFactory( typeFactory )
|
.setTypeFactory( typeFactory )
|
||||||
|
.setConditionOptions( getConditionOptions( method, parameters ) )
|
||||||
.setVerboseLogging( options.isVerbose() )
|
.setVerboseLogging( options.isVerbose() )
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
@ -633,6 +637,18 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
|||||||
.getProcessedAnnotations( method );
|
.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 class RepeatableMappings extends RepeatableAnnotations<MappingGem, MappingsGem, MappingOptions> {
|
||||||
private BeanMappingOptions beanMappingOptions;
|
private BeanMappingOptions beanMappingOptions;
|
||||||
|
|
||||||
@ -774,4 +790,32 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
|||||||
ValueMappingOptions.fromMappingsGem( gems, (ExecutableElement) source, messager, mappings );
|
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) {
|
private boolean isCandidateForMapping(Method methodCandidate) {
|
||||||
if ( methodCandidate.isPresenceCheck() ) {
|
if ( methodCandidate.getConditionOptions().isAnyStrategyApplicable() ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return isCreateMethodForMapping( methodCandidate ) || isUpdateMethodForMapping( methodCandidate );
|
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_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." ),
|
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_MAPPING_NOTE( "mapping property: %s to: %s.", Diagnostic.Kind.NOTE ),
|
||||||
PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ),
|
PROPERTYMAPPING_CREATE_NOTE( "creating property mapping: %s.", Diagnostic.Kind.NOTE ),
|
||||||
PROPERTYMAPPING_SELECT_NOTE( "selecting 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_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_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_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_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_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" ),
|
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" );
|
.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
|
@ProcessorTest
|
||||||
@WithClasses({
|
@WithClasses({
|
||||||
OptionalLikeConditionalMapper.class
|
OptionalLikeConditionalMapper.class
|
||||||
@ -244,4 +322,124 @@ public class ConditionalMappingTest {
|
|||||||
|
|
||||||
assertThat( targetEmployee.getName() ).isEqualTo( "CurrentName" );
|
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.junit.jupiter.api.Test;
|
||||||
import org.mapstruct.CollectionMappingStrategy;
|
import org.mapstruct.CollectionMappingStrategy;
|
||||||
|
import org.mapstruct.ConditionStrategy;
|
||||||
import org.mapstruct.InjectionStrategy;
|
import org.mapstruct.InjectionStrategy;
|
||||||
import org.mapstruct.MappingInheritanceStrategy;
|
import org.mapstruct.MappingInheritanceStrategy;
|
||||||
import org.mapstruct.NullValueCheckStrategy;
|
import org.mapstruct.NullValueCheckStrategy;
|
||||||
import org.mapstruct.NullValueMappingStrategy;
|
import org.mapstruct.NullValueMappingStrategy;
|
||||||
import org.mapstruct.ReportingPolicy;
|
import org.mapstruct.ReportingPolicy;
|
||||||
import org.mapstruct.ap.internal.gem.CollectionMappingStrategyGem;
|
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.InjectionStrategyGem;
|
||||||
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
|
import org.mapstruct.ap.internal.gem.MappingInheritanceStrategyGem;
|
||||||
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
|
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
|
||||||
@ -67,6 +69,12 @@ public class EnumGemsTest {
|
|||||||
namesOf( InjectionStrategyGem.values() ) );
|
namesOf( InjectionStrategyGem.values() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void conditionStrategyGemIsCorrect() {
|
||||||
|
assertThat( namesOf( ConditionStrategy.values() ) ).isEqualTo(
|
||||||
|
namesOf( ConditionStrategyGem.values() ) );
|
||||||
|
}
|
||||||
|
|
||||||
private static List<String> namesOf(Enum<?>[] values) {
|
private static List<String> namesOf(Enum<?>[] values) {
|
||||||
return Stream.of( values )
|
return Stream.of( values )
|
||||||
.map( Enum::name )
|
.map( Enum::name )
|
||||||
|
Loading…
x
Reference in New Issue
Block a user