mirror of
https://github.com/mapstruct/mapstruct.git
synced 2025-07-12 00:00:08 +08:00
This commit is contained in:
parent
a2e1404b93
commit
51cdbd67e3
78
core/src/main/java/org/mapstruct/Condition.java
Normal file
78
core/src/main/java/org/mapstruct/Condition.java
Normal file
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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>presence check method</em> to check check for presence in beans.
|
||||
* <p>
|
||||
* By default bean properties are checked against {@code null} or using a presence check method in the source bean.
|
||||
* If a presence check method is available then it will be used instead.
|
||||
* <p>
|
||||
* Presence check methods have to return {@code boolean}.
|
||||
* The following parameters are accepted for the presence check methods:
|
||||
* <ul>
|
||||
* <li>The parameter with the value of the source property.
|
||||
* e.g. the value given by calling {@code getName()} for the name property of the source bean</li>
|
||||
* <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 presence check method.
|
||||
*
|
||||
* <pre><code>
|
||||
* public class PresenceCheckUtils {
|
||||
*
|
||||
* @Condition
|
||||
* public static boolean isNotEmpty(String value) {
|
||||
* return value != null && !value.isEmpty();
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @Mapper(uses = PresenceCheckUtils.class)
|
||||
* public interface MovieMapper {
|
||||
*
|
||||
* MovieDto map(Movie movie);
|
||||
* }
|
||||
* </code></pre>
|
||||
*
|
||||
* The following implementation of {@code MovieMapper} will be generated:
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* public class MovieMapperImpl implements MovieMapper {
|
||||
*
|
||||
* @Override
|
||||
* public MovieDto map(Movie movie) {
|
||||
* if ( movie == null ) {
|
||||
* return null;
|
||||
* }
|
||||
*
|
||||
* MovieDto movieDto = new MovieDto();
|
||||
*
|
||||
* if ( PresenceCheckUtils.isNotEmpty( movie.getTitle() ) ) {
|
||||
* movieDto.setTitle( movie.getTitle() );
|
||||
* }
|
||||
*
|
||||
* return movieDto;
|
||||
* }
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Filip Hrisafov
|
||||
* @since 1.5
|
||||
*/
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
public @interface Condition {
|
||||
|
||||
}
|
@ -316,6 +316,74 @@ public @interface Mapping {
|
||||
*/
|
||||
String[] qualifiedByName() default { };
|
||||
|
||||
/**
|
||||
* A qualifier can be specified to aid the selection process of a suitable presence check method.
|
||||
* This is useful in case multiple presence check methods qualify and thus would result in an
|
||||
* 'Ambiguous presence check methods found' error.
|
||||
* A qualifier is a custom annotation and can be placed on a hand written mapper class or a method.
|
||||
* This is similar to the {@link #qualifiedBy()}, but it is only applied for {@link Condition} methods.
|
||||
*
|
||||
* @return the qualifiers
|
||||
* @see Qualifier
|
||||
* @see #qualifiedBy()
|
||||
* @since 1.5
|
||||
*/
|
||||
Class<? extends Annotation>[] conditionQualifiedBy() default { };
|
||||
|
||||
/**
|
||||
* String-based form of qualifiers for condition / presence check methods;
|
||||
* When looking for a suitable presence check method for a given property, MapStruct will
|
||||
* only consider those methods carrying directly or indirectly (i.e. on the class-level) a {@link Named} annotation
|
||||
* for each of the specified qualifier names.
|
||||
*
|
||||
* This is similar like {@link #qualifiedByName()} but it is only applied for {@link Condition} methods.
|
||||
* <p>
|
||||
* Note that annotation-based qualifiers are generally preferable as they allow more easily to find references and
|
||||
* are safe for refactorings, but name-based qualifiers can be a less verbose alternative when requiring a large
|
||||
* number of qualifiers as no custom annotation types are needed.
|
||||
* </p>
|
||||
*
|
||||
*
|
||||
* @return One or more qualifier name(s)
|
||||
* @see #conditionQualifiedBy()
|
||||
* @see #qualifiedByName()
|
||||
* @see Named
|
||||
* @since 1.5
|
||||
*/
|
||||
String[] conditionQualifiedByName() default { };
|
||||
|
||||
/**
|
||||
* A conditionExpression {@link String} based on which the specified property is to be checked
|
||||
* whether it is present or not.
|
||||
* <p>
|
||||
* Currently, Java is the only supported "expression language" and expressions must be given in form of Java
|
||||
* expressions using the following format: {@code java(<EXPRESSION>)}. For instance the mapping:
|
||||
* <pre><code>
|
||||
* @Mapping(
|
||||
* target = "someProp",
|
||||
* conditionExpression = "java(s.getAge() < 18)"
|
||||
* )
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* will cause the following target property assignment to be generated:
|
||||
* <pre><code>
|
||||
* if (s.getAge() < 18) {
|
||||
* targetBean.setSomeProp( s.getSomeProp() );
|
||||
* }
|
||||
* </code></pre>
|
||||
* <p>
|
||||
* <p>
|
||||
* Any types referenced in expressions must be given via their fully-qualified name. Alternatively, types can be
|
||||
* imported via {@link Mapper#imports()}.
|
||||
* <p>
|
||||
* This attribute can not be used together with {@link #expression()} or {@link #constant()}.
|
||||
*
|
||||
* @return An expression specifying a condition check for the designated property
|
||||
*
|
||||
* @since 1.5
|
||||
*/
|
||||
String conditionExpression() default "";
|
||||
|
||||
/**
|
||||
* Specifies the result type of the mapping method to be used in case multiple mapping methods qualify.
|
||||
*
|
||||
|
@ -229,6 +229,78 @@ The source presence checker name can be changed in the MapStruct service provide
|
||||
Some types of mappings (collections, maps), in which MapStruct is instructed to use a getter or adder as target accessor see `CollectionMappingStrategy`, MapStruct will always generate a source property
|
||||
null check, regardless the value of the `NullValueCheckStrategy` to avoid addition of `null` to the target collection or map.
|
||||
====
|
||||
|
||||
[[conditional-mapping]]
|
||||
=== Conditional Mapping
|
||||
|
||||
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.
|
||||
|
||||
A custom condition method is a method that is annotated with `org.mapstruct.Condition` and returns `boolean`.
|
||||
|
||||
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:
|
||||
|
||||
.Mapper using custom condition check method
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
@Mapper
|
||||
public interface CarMapper {
|
||||
|
||||
CarDto carToCarDto(Car car);
|
||||
|
||||
@Condition
|
||||
default boolean isNotEmpty(String value) {
|
||||
return value != null && !value.isEmpty();
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The generated mapper will look like:
|
||||
|
||||
.try-catch block in generated implementation
|
||||
====
|
||||
[source, java, linenums]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
// GENERATED CODE
|
||||
public class CarMapperImpl implements CarMapper {
|
||||
|
||||
@Override
|
||||
public CarDto carToCarDto(Car car) {
|
||||
if ( car == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CarDto carDto = new CarDto();
|
||||
|
||||
if ( isNotEmpty( car.getOwner() ) ) {
|
||||
carDto.setOwner( car.getOwner() );
|
||||
}
|
||||
|
||||
// Mapping of other properties
|
||||
|
||||
return carDto;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[IMPORTANT]
|
||||
====
|
||||
If there is a custom `@Condition` method applicable for the property it will have a precedence over a presence check method in the bean itself.
|
||||
====
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Methods annotated with `@Condition` in addition to the value of the source property can also have the source parameter as an input.
|
||||
====
|
||||
|
||||
<<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`.
|
||||
|
||||
[[exceptions]]
|
||||
=== Exceptions
|
||||
|
||||
|
@ -12,6 +12,7 @@ import org.mapstruct.AfterMapping;
|
||||
import org.mapstruct.BeanMapping;
|
||||
import org.mapstruct.BeforeMapping;
|
||||
import org.mapstruct.Builder;
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.DecoratedWith;
|
||||
import org.mapstruct.EnumMapping;
|
||||
@ -61,6 +62,7 @@ import org.mapstruct.tools.gem.GemDefinition;
|
||||
@GemDefinition(ValueMappings.class)
|
||||
@GemDefinition(Context.class)
|
||||
@GemDefinition(Builder.class)
|
||||
@GemDefinition(Condition.class)
|
||||
|
||||
@GemDefinition(MappingControl.class)
|
||||
@GemDefinition(MappingControls.class)
|
||||
|
@ -1155,6 +1155,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
|
||||
.dependsOn( mapping.getDependsOn() )
|
||||
.defaultValue( mapping.getDefaultValue() )
|
||||
.defaultJavaExpression( mapping.getDefaultJavaExpression() )
|
||||
.conditionJavaExpression( mapping.getConditionJavaExpression() )
|
||||
.mirror( mapping.getMirror() )
|
||||
.options( mapping )
|
||||
.build();
|
||||
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.ModelElement;
|
||||
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck {
|
||||
|
||||
protected final MethodReference methodReference;
|
||||
|
||||
public MethodReferencePresenceCheck(MethodReference methodReference) {
|
||||
this.methodReference = methodReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Type> getImportTypes() {
|
||||
return methodReference.getImportTypes();
|
||||
}
|
||||
|
||||
public MethodReference getMethodReference() {
|
||||
return methodReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
MethodReferencePresenceCheck that = (MethodReferencePresenceCheck) o;
|
||||
return Objects.equals( methodReference, that.methodReference );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( methodReference );
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.Parameter;
|
||||
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
import org.mapstruct.ap.internal.model.source.Method;
|
||||
import org.mapstruct.ap.internal.model.source.ParameterProvidedMethods;
|
||||
import org.mapstruct.ap.internal.model.source.SelectionParameters;
|
||||
import org.mapstruct.ap.internal.model.source.SourceMethod;
|
||||
import org.mapstruct.ap.internal.model.source.selector.MethodSelectors;
|
||||
import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
|
||||
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
|
||||
import org.mapstruct.ap.internal.util.Message;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public final class PresenceCheckMethodResolver {
|
||||
|
||||
private PresenceCheckMethodResolver() {
|
||||
|
||||
}
|
||||
|
||||
public static PresenceCheck getPresenceCheck(
|
||||
Method method,
|
||||
SelectionParameters selectionParameters,
|
||||
MappingBuilderContext ctx
|
||||
) {
|
||||
SelectedMethod<SourceMethod> matchingMethod = findMatchingPresenceCheckMethod(
|
||||
method,
|
||||
selectionParameters,
|
||||
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.getTypeFactory(),
|
||||
ctx.getMessager()
|
||||
);
|
||||
|
||||
Type booleanType = ctx.getTypeFactory().getType( Boolean.class );
|
||||
List<SelectedMethod<SourceMethod>> matchingMethods = selectors.getMatchingMethods(
|
||||
method,
|
||||
getAllAvailableMethods( method, ctx.getSourceModel() ),
|
||||
Collections.emptyList(),
|
||||
booleanType,
|
||||
booleanType,
|
||||
SelectionCriteria.forPresenceCheckMethods( selectionParameters )
|
||||
);
|
||||
|
||||
if ( matchingMethods.isEmpty() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( matchingMethods.size() > 1 ) {
|
||||
ctx.getMessager().printMessage(
|
||||
method.getExecutable(),
|
||||
Message.GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD,
|
||||
selectionParameters.getSourceRHS().getSourceType().describe(),
|
||||
matchingMethods.stream()
|
||||
.map( SelectedMethod::getMethod )
|
||||
.map( Method::describe )
|
||||
.collect( Collectors.joining( ", " ) )
|
||||
);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return matchingMethods.get( 0 );
|
||||
}
|
||||
|
||||
private static MethodReference getPresenceCheckMethodReference(
|
||||
Method method,
|
||||
SelectedMethod<SourceMethod> matchingMethod,
|
||||
MappingBuilderContext ctx
|
||||
) {
|
||||
|
||||
Parameter providingParameter =
|
||||
method.getContextProvidedMethods().getParameterForProvidedMethod( matchingMethod.getMethod() );
|
||||
if ( providingParameter != null ) {
|
||||
return MethodReference.forParameterProvidedMethod(
|
||||
matchingMethod.getMethod(),
|
||||
providingParameter,
|
||||
matchingMethod.getParameterBindings()
|
||||
);
|
||||
}
|
||||
else {
|
||||
MapperReference ref = MapperReference.findMapperReference(
|
||||
ctx.getMapperReferences(),
|
||||
matchingMethod.getMethod()
|
||||
);
|
||||
|
||||
return MethodReference.forMapperReference(
|
||||
matchingMethod.getMethod(),
|
||||
ref,
|
||||
matchingMethod.getParameterBindings()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<SourceMethod> getAllAvailableMethods(Method method, List<SourceMethod> sourceModelMethods) {
|
||||
ParameterProvidedMethods contextProvidedMethods = method.getContextProvidedMethods();
|
||||
if ( contextProvidedMethods.isEmpty() ) {
|
||||
return sourceModelMethods;
|
||||
}
|
||||
|
||||
List<SourceMethod> methodsProvidedByParams = contextProvidedMethods
|
||||
.getAllProvidedMethodsInParameterOrder( method.getContextParameters() );
|
||||
|
||||
List<SourceMethod> availableMethods =
|
||||
new ArrayList<>( methodsProvidedByParams.size() + sourceModelMethods.size() );
|
||||
|
||||
for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
|
||||
// add only methods from context that do have the @Condition annotation
|
||||
if ( methodProvidedByParams.isPresenceCheck() ) {
|
||||
availableMethods.add( methodProvidedByParams );
|
||||
}
|
||||
}
|
||||
availableMethods.addAll( sourceModelMethods );
|
||||
|
||||
return availableMethods;
|
||||
}
|
||||
}
|
@ -13,6 +13,9 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import javax.lang.model.element.AnnotationMirror;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.BuilderGem;
|
||||
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
|
||||
import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem;
|
||||
import org.mapstruct.ap.internal.model.assignment.AdderWrapper;
|
||||
import org.mapstruct.ap.internal.model.assignment.ArrayCopyWrapper;
|
||||
import org.mapstruct.ap.internal.model.assignment.EnumConstantWrapper;
|
||||
@ -32,6 +35,7 @@ import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.common.SourceRHS;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
import org.mapstruct.ap.internal.model.presence.AllPresenceChecksPresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.presence.NullPresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.source.DelegatingOptions;
|
||||
@ -40,9 +44,6 @@ import org.mapstruct.ap.internal.model.source.MappingOptions;
|
||||
import org.mapstruct.ap.internal.model.source.Method;
|
||||
import org.mapstruct.ap.internal.model.source.SelectionParameters;
|
||||
import org.mapstruct.ap.internal.model.source.selector.SelectionCriteria;
|
||||
import org.mapstruct.ap.internal.gem.BuilderGem;
|
||||
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
|
||||
import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem;
|
||||
import org.mapstruct.ap.internal.util.Message;
|
||||
import org.mapstruct.ap.internal.util.NativeTypes;
|
||||
import org.mapstruct.ap.internal.util.Strings;
|
||||
@ -52,12 +53,12 @@ import org.mapstruct.ap.internal.util.accessor.AccessorType;
|
||||
|
||||
import static org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem.ALWAYS;
|
||||
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE;
|
||||
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT;
|
||||
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL;
|
||||
import static org.mapstruct.ap.internal.model.ForgedMethod.forElementMapping;
|
||||
import static org.mapstruct.ap.internal.model.ForgedMethod.forParameterMapping;
|
||||
import static org.mapstruct.ap.internal.model.ForgedMethod.forPropertyMapping;
|
||||
import static org.mapstruct.ap.internal.model.common.Assignment.AssignmentType.DIRECT;
|
||||
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_DEFAULT;
|
||||
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.SET_TO_NULL;
|
||||
|
||||
/**
|
||||
* Represents the mapping between a source and target property, e.g. from {@code String Source#foo} to
|
||||
@ -142,6 +143,7 @@ public class PropertyMapping extends ModelElement {
|
||||
// initial properties
|
||||
private String defaultValue;
|
||||
private String defaultJavaExpression;
|
||||
private String conditionJavaExpression;
|
||||
private SourceReference sourceReference;
|
||||
private SourceRHS rightHandSide;
|
||||
private FormattingParameters formattingParameters;
|
||||
@ -182,6 +184,11 @@ public class PropertyMapping extends ModelElement {
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyMappingBuilder conditionJavaExpression(String conditionJavaExpression) {
|
||||
this.conditionJavaExpression = conditionJavaExpression;
|
||||
return this;
|
||||
}
|
||||
|
||||
public PropertyMappingBuilder forgeMethodWithMappingReferences(MappingReferences mappingReferences) {
|
||||
this.forgeMethodWithMappingReferences = mappingReferences;
|
||||
return this;
|
||||
@ -557,13 +564,19 @@ public class PropertyMapping extends ModelElement {
|
||||
// simple property
|
||||
else if ( !sourceReference.isNested() ) {
|
||||
String sourceRef = sourceParam.getName() + "." + ValueProvider.of( propertyEntry.getReadAccessor() );
|
||||
return new SourceRHS( sourceParam.getName(),
|
||||
SourceRHS sourceRHS = new SourceRHS(
|
||||
sourceParam.getName(),
|
||||
sourceRef,
|
||||
getSourcePresenceCheckerRef( sourceReference ),
|
||||
null,
|
||||
propertyEntry.getType(),
|
||||
existingVariableNames,
|
||||
sourceReference.toString()
|
||||
);
|
||||
sourceRHS.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef(
|
||||
sourceReference,
|
||||
sourceRHS
|
||||
) );
|
||||
return sourceRHS;
|
||||
}
|
||||
// nested property given as dot path
|
||||
else {
|
||||
@ -598,11 +611,15 @@ public class PropertyMapping extends ModelElement {
|
||||
String sourceRef = forgedName + "( " + sourceParam.getName() + " )";
|
||||
SourceRHS sourceRhs = new SourceRHS( sourceParam.getName(),
|
||||
sourceRef,
|
||||
getSourcePresenceCheckerRef( sourceReference ),
|
||||
null,
|
||||
sourceType,
|
||||
existingVariableNames,
|
||||
sourceReference.toString()
|
||||
);
|
||||
sourceRhs.setSourcePresenceCheckerReference( getSourcePresenceCheckerRef(
|
||||
sourceReference,
|
||||
sourceRhs
|
||||
) );
|
||||
|
||||
// create a local variable to which forged method can be assigned.
|
||||
String desiredName = propertyEntry.getName();
|
||||
@ -613,7 +630,25 @@ public class PropertyMapping extends ModelElement {
|
||||
}
|
||||
}
|
||||
|
||||
private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference ) {
|
||||
private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference,
|
||||
SourceRHS sourceRHS) {
|
||||
|
||||
if ( conditionJavaExpression != null ) {
|
||||
return new JavaExpressionPresenceCheck( conditionJavaExpression );
|
||||
}
|
||||
|
||||
SelectionParameters selectionParameters = this.selectionParameters != null ?
|
||||
this.selectionParameters.withSourceRHS( sourceRHS ) :
|
||||
SelectionParameters.forSourceRHS( sourceRHS );
|
||||
PresenceCheck presenceCheck = PresenceCheckMethodResolver.getPresenceCheck(
|
||||
method,
|
||||
selectionParameters,
|
||||
ctx
|
||||
);
|
||||
if ( presenceCheck != null ) {
|
||||
return presenceCheck;
|
||||
}
|
||||
|
||||
PresenceCheck sourcePresenceChecker = null;
|
||||
if ( !sourceReference.getPropertyEntries().isEmpty() ) {
|
||||
Parameter sourceParam = sourceReference.getParameter();
|
||||
|
@ -7,7 +7,6 @@ package org.mapstruct.ap.internal.model.common;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
@ -31,7 +30,7 @@ public class SourceRHS extends ModelElement implements Assignment {
|
||||
private String sourceLoopVarName;
|
||||
private final Set<String> existingVariableNames;
|
||||
private final String sourceErrorMessagePart;
|
||||
private final PresenceCheck sourcePresenceCheckerReference;
|
||||
private PresenceCheck sourcePresenceCheckerReference;
|
||||
private boolean useElementAsSourceTypeForMatching = false;
|
||||
private final String sourceParameterName;
|
||||
|
||||
@ -65,6 +64,10 @@ public class SourceRHS extends ModelElement implements Assignment {
|
||||
return sourcePresenceCheckerReference;
|
||||
}
|
||||
|
||||
public void setSourcePresenceCheckerReference(PresenceCheck sourcePresenceCheckerReference) {
|
||||
this.sourcePresenceCheckerReference = sourcePresenceCheckerReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getSourceType() {
|
||||
return sourceType;
|
||||
|
@ -6,7 +6,6 @@
|
||||
package org.mapstruct.ap.internal.model.presence;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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.presence;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.ModelElement;
|
||||
import org.mapstruct.ap.internal.model.common.PresenceCheck;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class JavaExpressionPresenceCheck extends ModelElement implements PresenceCheck {
|
||||
|
||||
private final String javaExpression;
|
||||
|
||||
public JavaExpressionPresenceCheck(String javaExpression) {
|
||||
this.javaExpression = javaExpression;
|
||||
}
|
||||
|
||||
public String getJavaExpression() {
|
||||
return javaExpression;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Type> getImportTypes() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
if ( o == null || getClass() != o.getClass() ) {
|
||||
return false;
|
||||
}
|
||||
JavaExpressionPresenceCheck that = (JavaExpressionPresenceCheck) o;
|
||||
return Objects.equals( javaExpression, that.javaExpression );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( javaExpression );
|
||||
}
|
||||
}
|
@ -43,6 +43,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
private final String constant;
|
||||
private final String javaExpression;
|
||||
private final String defaultJavaExpression;
|
||||
private final String conditionJavaExpression;
|
||||
private final String targetName;
|
||||
private final String defaultValue;
|
||||
private final FormattingParameters formattingParameters;
|
||||
@ -114,6 +115,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
String constant = mapping.constant().getValue();
|
||||
String expression = getExpression( mapping, method, messager );
|
||||
String defaultExpression = getDefaultExpression( mapping, method, messager );
|
||||
String conditionExpression = getConditionExpression( mapping, method, messager );
|
||||
String dateFormat = mapping.dateFormat().getValue();
|
||||
String numberFormat = mapping.numberFormat().getValue();
|
||||
String defaultValue = mapping.defaultValue().getValue();
|
||||
@ -132,6 +134,8 @@ public class MappingOptions extends DelegatingOptions {
|
||||
SelectionParameters selectionParams = new SelectionParameters(
|
||||
mapping.qualifiedBy().get(),
|
||||
mapping.qualifiedByName().get(),
|
||||
mapping.conditionQualifiedBy().get(),
|
||||
mapping.conditionQualifiedByName().get(),
|
||||
mapping.resultType().getValue(),
|
||||
typeUtils
|
||||
);
|
||||
@ -145,6 +149,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
constant,
|
||||
expression,
|
||||
defaultExpression,
|
||||
conditionExpression,
|
||||
defaultValue,
|
||||
mapping.ignore().get(),
|
||||
formattingParam,
|
||||
@ -174,6 +179,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
null,
|
||||
null,
|
||||
@ -261,6 +267,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
String constant,
|
||||
String javaExpression,
|
||||
String defaultJavaExpression,
|
||||
String conditionJavaExpression,
|
||||
String defaultValue,
|
||||
boolean isIgnored,
|
||||
FormattingParameters formattingParameters,
|
||||
@ -279,6 +286,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
this.constant = constant;
|
||||
this.javaExpression = javaExpression;
|
||||
this.defaultJavaExpression = defaultJavaExpression;
|
||||
this.conditionJavaExpression = conditionJavaExpression;
|
||||
this.defaultValue = defaultValue;
|
||||
this.isIgnored = isIgnored;
|
||||
this.formattingParameters = formattingParameters;
|
||||
@ -330,6 +338,27 @@ public class MappingOptions extends DelegatingOptions {
|
||||
return javaExpressionMatcher.group( 1 ).trim();
|
||||
}
|
||||
|
||||
private static String getConditionExpression(MappingGem mapping, ExecutableElement element,
|
||||
FormattingMessager messager) {
|
||||
if ( !mapping.conditionExpression().hasValue() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Matcher javaExpressionMatcher = JAVA_EXPRESSION.matcher( mapping.conditionExpression().get() );
|
||||
|
||||
if ( !javaExpressionMatcher.matches() ) {
|
||||
messager.printMessage(
|
||||
element,
|
||||
mapping.mirror(),
|
||||
mapping.conditionExpression().getAnnotationValue(),
|
||||
Message.PROPERTYMAPPING_INVALID_CONDITION_EXPRESSION
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return javaExpressionMatcher.group( 1 ).trim();
|
||||
}
|
||||
|
||||
public String getTargetName() {
|
||||
return targetName;
|
||||
}
|
||||
@ -364,6 +393,10 @@ public class MappingOptions extends DelegatingOptions {
|
||||
return defaultJavaExpression;
|
||||
}
|
||||
|
||||
public String getConditionJavaExpression() {
|
||||
return conditionJavaExpression;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
@ -452,6 +485,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
null, // constant
|
||||
null, // expression
|
||||
null, // defaultExpression
|
||||
null, // conditionExpression
|
||||
null,
|
||||
isIgnored,
|
||||
formattingParameters,
|
||||
@ -481,6 +515,7 @@ public class MappingOptions extends DelegatingOptions {
|
||||
constant,
|
||||
javaExpression,
|
||||
defaultJavaExpression,
|
||||
conditionJavaExpression,
|
||||
defaultValue,
|
||||
isIgnored,
|
||||
formattingParameters,
|
||||
|
@ -90,6 +90,14 @@ public interface Method {
|
||||
*/
|
||||
boolean isObjectFactory();
|
||||
|
||||
/**
|
||||
* Returns whether the method is designated as a presence check method
|
||||
* @return {@code true} if it is a presence check method
|
||||
*/
|
||||
default boolean isPresenceCheck() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType }
|
||||
*
|
||||
|
@ -23,6 +23,8 @@ public class SelectionParameters {
|
||||
|
||||
private final List<TypeMirror> qualifiers;
|
||||
private final List<String> qualifyingNames;
|
||||
private final List<TypeMirror> conditionQualifiers;
|
||||
private final List<String> conditionQualifyingNames;
|
||||
private final TypeMirror resultType;
|
||||
private final TypeUtils typeUtils;
|
||||
private final SourceRHS sourceRHS;
|
||||
@ -39,6 +41,8 @@ public class SelectionParameters {
|
||||
return new SelectionParameters(
|
||||
selectionParameters.qualifiers,
|
||||
selectionParameters.qualifyingNames,
|
||||
selectionParameters.conditionQualifiers,
|
||||
selectionParameters.conditionQualifyingNames,
|
||||
null,
|
||||
selectionParameters.typeUtils
|
||||
);
|
||||
@ -46,13 +50,32 @@ public class SelectionParameters {
|
||||
|
||||
public SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames, TypeMirror resultType,
|
||||
TypeUtils typeUtils) {
|
||||
this( qualifiers, qualifyingNames, resultType, typeUtils, null );
|
||||
this(
|
||||
qualifiers,
|
||||
qualifyingNames,
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
resultType,
|
||||
typeUtils,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
private SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames, TypeMirror resultType,
|
||||
public SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames,
|
||||
List<TypeMirror> conditionQualifiers, List<String> conditionQualifyingNames,
|
||||
TypeMirror resultType,
|
||||
TypeUtils typeUtils) {
|
||||
this( qualifiers, qualifyingNames, conditionQualifiers, conditionQualifyingNames, resultType, typeUtils, null );
|
||||
}
|
||||
|
||||
private SelectionParameters(List<TypeMirror> qualifiers, List<String> qualifyingNames,
|
||||
List<TypeMirror> conditionQualifiers, List<String> conditionQualifyingNames,
|
||||
TypeMirror resultType,
|
||||
TypeUtils typeUtils, SourceRHS sourceRHS) {
|
||||
this.qualifiers = qualifiers;
|
||||
this.qualifyingNames = qualifyingNames;
|
||||
this.conditionQualifiers = conditionQualifiers;
|
||||
this.conditionQualifyingNames = conditionQualifyingNames;
|
||||
this.resultType = resultType;
|
||||
this.typeUtils = typeUtils;
|
||||
this.sourceRHS = sourceRHS;
|
||||
@ -74,6 +97,21 @@ public class SelectionParameters {
|
||||
return qualifyingNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return qualifiers used for further select the appropriate presence check method based on class and name
|
||||
*/
|
||||
public List<TypeMirror> getConditionQualifiers() {
|
||||
return conditionQualifiers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return qualifyingNames, used in combination with with @Named
|
||||
* @see #getConditionQualifiers()
|
||||
*/
|
||||
public List<String> getConditionQualifyingNames() {
|
||||
return conditionQualifyingNames;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return resultType used for further select the appropriate mapping method based on resultType (bean mapping)
|
||||
@ -119,6 +157,14 @@ public class SelectionParameters {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( this.conditionQualifiers, other.conditionQualifiers ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( this.conditionQualifyingNames, other.conditionQualifyingNames ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !Objects.equals( this.sourceRHS, other.sourceRHS ) ) {
|
||||
return false;
|
||||
}
|
||||
@ -151,8 +197,22 @@ public class SelectionParameters {
|
||||
}
|
||||
}
|
||||
|
||||
public SelectionParameters withSourceRHS(SourceRHS sourceRHS) {
|
||||
return new SelectionParameters(
|
||||
this.qualifiers,
|
||||
this.qualifyingNames,
|
||||
this.conditionQualifiers,
|
||||
this.conditionQualifyingNames,
|
||||
null,
|
||||
this.typeUtils,
|
||||
sourceRHS
|
||||
);
|
||||
}
|
||||
|
||||
public static SelectionParameters forSourceRHS(SourceRHS sourceRHS) {
|
||||
return new SelectionParameters(
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
Collections.emptyList(),
|
||||
null,
|
||||
|
@ -14,6 +14,8 @@ import java.util.stream.Collectors;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import javax.lang.model.element.Modifier;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.ConditionGem;
|
||||
import org.mapstruct.ap.internal.util.TypeUtils;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.Accessibility;
|
||||
@ -47,6 +49,7 @@ public class SourceMethod implements Method {
|
||||
private final Parameter mappingTargetParameter;
|
||||
private final Parameter targetTypeParameter;
|
||||
private final boolean isObjectFactory;
|
||||
private final boolean isPresenceCheck;
|
||||
private final Type returnType;
|
||||
private final Accessibility accessibility;
|
||||
private final List<Type> exceptionTypes;
|
||||
@ -230,6 +233,7 @@ public class SourceMethod implements Method {
|
||||
this.targetTypeParameter = Parameter.getTargetTypeParameter( parameters );
|
||||
this.hasObjectFactoryAnnotation = ObjectFactoryGem.instanceOn( executable ) != null;
|
||||
this.isObjectFactory = determineIfIsObjectFactory();
|
||||
this.isPresenceCheck = determineIfIsPresenceCheck();
|
||||
|
||||
this.typeUtils = builder.typeUtils;
|
||||
this.typeFactory = builder.typeFactory;
|
||||
@ -247,6 +251,19 @@ public class SourceMethod implements Method {
|
||||
&& ( hasObjectFactoryAnnotation || hasNoSourceParameters );
|
||||
}
|
||||
|
||||
private boolean determineIfIsPresenceCheck() {
|
||||
if ( returnType.isPrimitive() ) {
|
||||
if ( !returnType.getName().equals( "boolean" ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if ( !returnType.getFullyQualifiedName().equals( Boolean.class.getCanonicalName() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ConditionGem.instanceOn( executable ) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getDeclaringMapper() {
|
||||
return declaringMapper;
|
||||
@ -519,6 +536,11 @@ public class SourceMethod implements Method {
|
||||
return Executables.isLifecycleCallbackMethod( getExecutable() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPresenceCheck() {
|
||||
return isPresenceCheck;
|
||||
}
|
||||
|
||||
public boolean isAfterMappingMethod() {
|
||||
return Executables.isAfterMappingMethod( getExecutable() );
|
||||
}
|
||||
|
@ -36,7 +36,8 @@ public class CreateOrUpdateSelector implements MethodSelector {
|
||||
Type returnType,
|
||||
SelectionCriteria criteria) {
|
||||
|
||||
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired() ) {
|
||||
if ( criteria.isLifecycleCallbackRequired() || criteria.isObjectFactoryRequired()
|
||||
|| criteria.isPresenceCheckRequired() ) {
|
||||
return methods;
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,9 @@ public class MethodFamilySelector implements MethodSelector {
|
||||
List<SelectedMethod<T>> result = new ArrayList<>( methods.size() );
|
||||
for ( SelectedMethod<T> method : methods ) {
|
||||
if ( method.getMethod().isObjectFactory() == criteria.isObjectFactoryRequired()
|
||||
&& method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired() ) {
|
||||
&& method.getMethod().isLifecycleCallbackMethod() == criteria.isLifecycleCallbackRequired()
|
||||
&& method.getMethod().isPresenceCheck() == criteria.isPresenceCheckRequired()
|
||||
) {
|
||||
|
||||
result.add( method );
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ public class MethodSelectors {
|
||||
new XmlElementDeclSelector( typeUtils ),
|
||||
new InheritanceSelector(),
|
||||
new CreateOrUpdateSelector(),
|
||||
new SourceRhsSelector(),
|
||||
new FactoryParameterSelector() );
|
||||
}
|
||||
|
||||
|
@ -26,20 +26,23 @@ public class SelectionCriteria {
|
||||
private final String targetPropertyName;
|
||||
private final TypeMirror qualifyingResultType;
|
||||
private final SourceRHS sourceRHS;
|
||||
private boolean preferUpdateMapping;
|
||||
private final boolean objectFactoryRequired;
|
||||
private final boolean lifecycleCallbackRequired;
|
||||
private Type type;
|
||||
private final boolean allowDirect;
|
||||
private final boolean allowConversion;
|
||||
private final boolean allowMappingMethod;
|
||||
private final boolean allow2Steps;
|
||||
|
||||
public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl,
|
||||
String targetPropertyName, boolean preferUpdateMapping, boolean objectFactoryRequired,
|
||||
boolean lifecycleCallbackRequired) {
|
||||
String targetPropertyName, Type type) {
|
||||
if ( selectionParameters != null ) {
|
||||
if ( type == Type.PRESENCE_CHECK ) {
|
||||
qualifiers.addAll( selectionParameters.getConditionQualifiers() );
|
||||
qualifiedByNames.addAll( selectionParameters.getConditionQualifyingNames() );
|
||||
}
|
||||
else {
|
||||
qualifiers.addAll( selectionParameters.getQualifiers() );
|
||||
qualifiedByNames.addAll( selectionParameters.getQualifyingNames() );
|
||||
}
|
||||
qualifyingResultType = selectionParameters.getResultType();
|
||||
sourceRHS = selectionParameters.getSourceRHS();
|
||||
}
|
||||
@ -60,23 +63,28 @@ public class SelectionCriteria {
|
||||
this.allow2Steps = true;
|
||||
}
|
||||
this.targetPropertyName = targetPropertyName;
|
||||
this.preferUpdateMapping = preferUpdateMapping;
|
||||
this.objectFactoryRequired = objectFactoryRequired;
|
||||
this.lifecycleCallbackRequired = lifecycleCallbackRequired;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if factory methods should be selected, false otherwise.
|
||||
*/
|
||||
public boolean isObjectFactoryRequired() {
|
||||
return objectFactoryRequired;
|
||||
return type == Type.OBJECT_FACTORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if lifecycle callback methods should be selected, false otherwise.
|
||||
*/
|
||||
public boolean isLifecycleCallbackRequired() {
|
||||
return lifecycleCallbackRequired;
|
||||
return type == Type.LIFECYCLE_CALLBACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code true} if presence check methods should be selected, {@code false} otherwise
|
||||
*/
|
||||
public boolean isPresenceCheckRequired() {
|
||||
return type == Type.PRESENCE_CHECK;
|
||||
}
|
||||
|
||||
public List<TypeMirror> getQualifiers() {
|
||||
@ -96,7 +104,7 @@ public class SelectionCriteria {
|
||||
}
|
||||
|
||||
public boolean isPreferUpdateMapping() {
|
||||
return preferUpdateMapping;
|
||||
return type == Type.PREFER_UPDATE_MAPPING;
|
||||
}
|
||||
|
||||
public SourceRHS getSourceRHS() {
|
||||
@ -104,7 +112,7 @@ public class SelectionCriteria {
|
||||
}
|
||||
|
||||
public void setPreferUpdateMapping(boolean preferUpdateMapping) {
|
||||
this.preferUpdateMapping = preferUpdateMapping;
|
||||
this.type = preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null;
|
||||
}
|
||||
|
||||
public boolean hasQualfiers() {
|
||||
@ -135,17 +143,26 @@ public class SelectionCriteria {
|
||||
selectionParameters,
|
||||
mappingControl,
|
||||
targetPropertyName,
|
||||
preferUpdateMapping,
|
||||
false,
|
||||
false
|
||||
preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null
|
||||
);
|
||||
}
|
||||
|
||||
public static SelectionCriteria forFactoryMethods(SelectionParameters selectionParameters) {
|
||||
return new SelectionCriteria( selectionParameters, null, null, false, true, false );
|
||||
return new SelectionCriteria( selectionParameters, null, null, Type.OBJECT_FACTORY );
|
||||
}
|
||||
|
||||
public static SelectionCriteria forLifecycleMethods(SelectionParameters selectionParameters) {
|
||||
return new SelectionCriteria( selectionParameters, null, null, false, false, true );
|
||||
return new SelectionCriteria( selectionParameters, null, null, Type.LIFECYCLE_CALLBACK );
|
||||
}
|
||||
|
||||
public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) {
|
||||
return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK );
|
||||
}
|
||||
|
||||
public enum Type {
|
||||
PREFER_UPDATE_MAPPING,
|
||||
OBJECT_FACTORY,
|
||||
LIFECYCLE_CALLBACK,
|
||||
PRESENCE_CHECK,
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* 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.selector;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.mapstruct.ap.internal.model.common.ParameterBinding;
|
||||
import org.mapstruct.ap.internal.model.common.Type;
|
||||
import org.mapstruct.ap.internal.model.source.Method;
|
||||
|
||||
/**
|
||||
* Selector that tries to resolve an ambiquity between methods that contain source parameters and
|
||||
* {@link org.mapstruct.ap.internal.model.common.SourceRHS SourceRHS} type parameters.
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class SourceRhsSelector implements MethodSelector {
|
||||
|
||||
@Override
|
||||
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(Method mappingMethod,
|
||||
List<SelectedMethod<T>> candidates,
|
||||
List<Type> sourceTypes, Type mappingTargetType,
|
||||
Type returnType, SelectionCriteria criteria) {
|
||||
if ( candidates.size() < 2 || criteria.getSourceRHS() == null ) {
|
||||
return candidates;
|
||||
}
|
||||
|
||||
List<SelectedMethod<T>> sourceRHSFavoringCandidates = new ArrayList<>();
|
||||
|
||||
for ( SelectedMethod<T> candidate : candidates ) {
|
||||
for ( ParameterBinding parameterBinding : candidate.getParameterBindings() ) {
|
||||
if ( parameterBinding.getSourceRHS() != null ) {
|
||||
sourceRHSFavoringCandidates.add( candidate );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( !sourceRHSFavoringCandidates.isEmpty() ) {
|
||||
return sourceRHSFavoringCandidates;
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
}
|
@ -89,7 +89,7 @@ public class TypeSelector implements MethodSelector {
|
||||
List<ParameterBinding> availableParams = new ArrayList<>( method.getParameters().size() + 3 );
|
||||
|
||||
if ( sourceRHS != null ) {
|
||||
availableParams.addAll( ParameterBinding.fromParameters( method.getContextParameters() ) );
|
||||
availableParams.addAll( ParameterBinding.fromParameters( method.getParameters() ) );
|
||||
availableParams.add( ParameterBinding.fromSourceRHS( sourceRHS ) );
|
||||
}
|
||||
else {
|
||||
|
@ -23,6 +23,7 @@ import javax.lang.model.type.ExecutableType;
|
||||
import javax.lang.model.type.TypeKind;
|
||||
|
||||
import org.mapstruct.ap.internal.gem.BeanMappingGem;
|
||||
import org.mapstruct.ap.internal.gem.ConditionGem;
|
||||
import org.mapstruct.ap.internal.gem.IterableMappingGem;
|
||||
import org.mapstruct.ap.internal.gem.MapMappingGem;
|
||||
import org.mapstruct.ap.internal.gem.MappingGem;
|
||||
@ -225,7 +226,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
}
|
||||
// otherwise add reference to existing mapper method
|
||||
else if ( isValidReferencedMethod( parameters ) || isValidFactoryMethod( method, parameters, returnType )
|
||||
|| isValidLifecycleCallbackMethod( method ) ) {
|
||||
|| isValidLifecycleCallbackMethod( method )
|
||||
|| isValidPresenceCheckMethod( method, returnType ) ) {
|
||||
return getReferencedMethod( usedMapper, methodType, method, mapperToImplement, parameters );
|
||||
}
|
||||
else {
|
||||
@ -333,7 +335,8 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
|
||||
List<SourceMethod> contextProvidedMethods = new ArrayList<>( contextParamMethods.size() );
|
||||
for ( SourceMethod sourceMethod : contextParamMethods ) {
|
||||
if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory() ) {
|
||||
if ( sourceMethod.isLifecycleCallbackMethod() || sourceMethod.isObjectFactory()
|
||||
|| sourceMethod.isPresenceCheck() ) {
|
||||
contextProvidedMethods.add( sourceMethod );
|
||||
}
|
||||
}
|
||||
@ -389,10 +392,22 @@ public class MethodRetrievalProcessor implements ModelElementProcessor<Void, Lis
|
||||
return ObjectFactoryGem.instanceOn( method ) != null;
|
||||
}
|
||||
|
||||
private boolean isValidPresenceCheckMethod(ExecutableElement method, Type returnType) {
|
||||
return isBoolean( returnType ) && hasConditionAnnotation( method );
|
||||
}
|
||||
|
||||
private boolean hasConditionAnnotation(ExecutableElement method) {
|
||||
return ConditionGem.instanceOn( method ) != null;
|
||||
}
|
||||
|
||||
private boolean isVoid(Type returnType) {
|
||||
return returnType.getTypeMirror().getKind() == TypeKind.VOID;
|
||||
}
|
||||
|
||||
private boolean isBoolean(Type returnType) {
|
||||
return Boolean.class.getCanonicalName().equals( returnType.getBoxedEquivalent().getFullyQualifiedName() );
|
||||
}
|
||||
|
||||
private boolean isValidReferencedOrFactoryMethod(int sourceParamCount, int targetParamCount,
|
||||
List<Parameter> parameters) {
|
||||
int validSourceParameters = 0;
|
||||
|
@ -66,6 +66,7 @@ public enum Message {
|
||||
PROPERTYMAPPING_EXPRESSION_AND_QUALIFIER_BOTH_DEFINED("Expression and a qualifier both defined in @Mapping, either define an expression or a qualifier."),
|
||||
PROPERTYMAPPING_INVALID_EXPRESSION( "Value for expression must be given in the form \"java(<EXPRESSION>)\"." ),
|
||||
PROPERTYMAPPING_INVALID_DEFAULT_EXPRESSION( "Value for default expression must be given in the form \"java(<EXPRESSION>)\"." ),
|
||||
PROPERTYMAPPING_INVALID_CONDITION_EXPRESSION( "Value for condition expression must be given in the form \"java(<EXPRESSION>)\"." ),
|
||||
PROPERTYMAPPING_INVALID_PARAMETER_NAME( "Method has no source parameter named \"%s\". Method source parameters are: \"%s\"." ),
|
||||
PROPERTYMAPPING_NO_PROPERTY_IN_PARAMETER( "The type of parameter \"%s\" has no property named \"%s\"." ),
|
||||
PROPERTYMAPPING_INVALID_PROPERTY_NAME( "No property named \"%s\" exists in source parameter(s). Did you mean \"%s\"?" ),
|
||||
@ -120,6 +121,7 @@ public enum Message {
|
||||
GENERAL_ABSTRACT_RETURN_TYPE( "The return type %s is an abstract class or interface. Provide a non abstract / non interface result type or a factory method." ),
|
||||
GENERAL_AMBIGUOUS_MAPPING_METHOD( "Ambiguous mapping methods found for mapping %s to %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
|
||||
GENERAL_AMBIGUOUS_FACTORY_METHOD( "Ambiguous factory methods found for creating %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
|
||||
GENERAL_AMBIGUOUS_PRESENCE_CHECK_METHOD( "Ambiguous presence check methods found for checking %s: %s. See " + FAQ_AMBIGUOUS_URL + " for more info." ),
|
||||
GENERAL_AMBIGUOUS_CONSTRUCTORS( "Ambiguous constructors found for creating %s. Either declare parameterless constructor or annotate the default constructor with an annotation named @Default." ),
|
||||
GENERAL_CONSTRUCTOR_PROPERTIES_NOT_MATCHING_PARAMETERS( "Incorrect @ConstructorProperties for %s. The size of the @ConstructorProperties does not match the number of constructor parameters" ),
|
||||
GENERAL_UNSUPPORTED_DATE_FORMAT_CHECK( "No dateFormat check is supported for types %s, %s" ),
|
||||
|
@ -0,0 +1,9 @@
|
||||
<#--
|
||||
|
||||
Copyright MapStruct Authors.
|
||||
|
||||
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
-->
|
||||
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" -->
|
||||
<@includeModel object=methodReference/>
|
@ -0,0 +1,9 @@
|
||||
<#--
|
||||
|
||||
Copyright MapStruct Authors.
|
||||
|
||||
Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
-->
|
||||
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.JavaExpressionPresenceCheck" -->
|
||||
${javaExpression}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class Employee {
|
||||
private String name;
|
||||
private String ssid;
|
||||
private String nin;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getSsid() {
|
||||
return ssid;
|
||||
}
|
||||
|
||||
public void setSsid(String ssid) {
|
||||
this.ssid = ssid;
|
||||
}
|
||||
|
||||
public String getNin() {
|
||||
return nin;
|
||||
}
|
||||
|
||||
public void setNin(String nin) {
|
||||
this.nin = nin;
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class EmployeeDto {
|
||||
private String name;
|
||||
private String country;
|
||||
private String uniqueIdNumber;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getCountry() {
|
||||
return country;
|
||||
}
|
||||
|
||||
public void setCountry(String country) {
|
||||
this.country = country;
|
||||
}
|
||||
|
||||
public String getUniqueIdNumber() {
|
||||
return uniqueIdNumber;
|
||||
}
|
||||
|
||||
public void setUniqueIdNumber(String uniqueIdNumber) {
|
||||
this.uniqueIdNumber = uniqueIdNumber;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class BasicEmployee {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
public class BasicEmployeeDto {
|
||||
|
||||
private final String name;
|
||||
private final String strategy;
|
||||
|
||||
public BasicEmployeeDto(String name) {
|
||||
this( name, "default" );
|
||||
}
|
||||
|
||||
public BasicEmployeeDto(String name, String strategy) {
|
||||
this.name = name;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
/*
|
||||
* 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 java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.testutil.IssueKey;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
import org.mapstruct.ap.testutil.runner.GeneratedSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@IssueKey("2051")
|
||||
@WithClasses({
|
||||
BasicEmployee.class,
|
||||
BasicEmployeeDto.class
|
||||
})
|
||||
@RunWith(AnnotationProcessorTestRunner.class)
|
||||
public class ConditionalMappingTest {
|
||||
|
||||
@Rule
|
||||
public final GeneratedSource generatedSource = new GeneratedSource();
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodInMapper.class
|
||||
})
|
||||
public void conditionalMethodInMapper() {
|
||||
generatedSource.addComparisonToFixtureFor( ConditionalMethodInMapper.class );
|
||||
ConditionalMethodInMapper mapper = ConditionalMethodInMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( " " ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodAndBeanPresenceCheckMapper.class
|
||||
})
|
||||
public void conditionalMethodAndBeanPresenceCheckMapper() {
|
||||
ConditionalMethodAndBeanPresenceCheckMapper mapper = ConditionalMethodAndBeanPresenceCheckMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( "Tester" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
|
||||
employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( "" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new ConditionalMethodAndBeanPresenceCheckMapper.EmployeeDto( " " ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodInUsesMapper.class
|
||||
})
|
||||
public void conditionalMethodInUsesMapper() {
|
||||
ConditionalMethodInUsesMapper mapper = ConditionalMethodInUsesMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( " " ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodInUsesStaticMapper.class
|
||||
})
|
||||
public void conditionalMethodInUsesStaticMapper() {
|
||||
ConditionalMethodInUsesStaticMapper mapper = ConditionalMethodInUsesStaticMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( " " ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodInContextMapper.class
|
||||
})
|
||||
public void conditionalMethodInUsesContextMapper() {
|
||||
ConditionalMethodInContextMapper mapper = ConditionalMethodInContextMapper.INSTANCE;
|
||||
|
||||
ConditionalMethodInContextMapper.PresenceUtils utils = new ConditionalMethodInContextMapper.PresenceUtils();
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ), utils );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "" ), utils );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( " " ), utils );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodWithSourceParameterMapper.class
|
||||
})
|
||||
public void conditionalMethodWithSourceParameter() {
|
||||
ConditionalMethodWithSourceParameterMapper mapper = ConditionalMethodWithSourceParameterMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( "Tester" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "Tester", "map" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodWithSourceParameterAndValueMapper.class
|
||||
})
|
||||
public void conditionalMethodWithSourceParameterAndValue() {
|
||||
generatedSource.addComparisonToFixtureFor( ConditionalMethodWithSourceParameterAndValueMapper.class );
|
||||
ConditionalMethodWithSourceParameterAndValueMapper mapper =
|
||||
ConditionalMethodWithSourceParameterAndValueMapper.INSTANCE;
|
||||
|
||||
BasicEmployee employee = mapper.map( new BasicEmployeeDto( " ", "empty" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( " " );
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( " ", "blank" ) );
|
||||
assertThat( employee.getName() ).isNull();
|
||||
|
||||
employee = mapper.map( new BasicEmployeeDto( "Tester", "blank" ) );
|
||||
assertThat( employee.getName() ).isEqualTo( "Tester" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ErroneousAmbiguousConditionalMethodMapper.class
|
||||
})
|
||||
@ExpectedCompilationOutcome(
|
||||
value = CompilationResult.FAILED,
|
||||
diagnostics = {
|
||||
@Diagnostic(
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
type = ErroneousAmbiguousConditionalMethodMapper.class,
|
||||
line = 17,
|
||||
message = "Ambiguous presence check methods found for checking String: " +
|
||||
"boolean isNotBlank(String value), " +
|
||||
"boolean isNotEmpty(String value). " +
|
||||
"See https://mapstruct.org/faq/#ambiguous for more info."
|
||||
)
|
||||
}
|
||||
)
|
||||
public void ambiguousConditionalMethod() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodForCollectionMapper.class
|
||||
})
|
||||
public void conditionalMethodForCollection() {
|
||||
ConditionalMethodForCollectionMapper mapper = ConditionalMethodForCollectionMapper.INSTANCE;
|
||||
|
||||
ConditionalMethodForCollectionMapper.Author author = new ConditionalMethodForCollectionMapper.Author();
|
||||
ConditionalMethodForCollectionMapper.AuthorDto dto = mapper.map( author );
|
||||
|
||||
assertThat( dto.getBooks() ).isNull();
|
||||
|
||||
author.setBooks( Collections.emptyList() );
|
||||
dto = mapper.map( author );
|
||||
|
||||
assertThat( dto.getBooks() ).isNull();
|
||||
|
||||
author.setBooks( Arrays.asList(
|
||||
new ConditionalMethodForCollectionMapper.Book( "Test" ),
|
||||
new ConditionalMethodForCollectionMapper.Book( "Test Vol. 2" )
|
||||
) );
|
||||
dto = mapper.map( author );
|
||||
|
||||
assertThat( dto.getBooks() )
|
||||
.extracting( ConditionalMethodForCollectionMapper.BookDto::getName )
|
||||
.containsExactly( "Test", "Test Vol. 2" );
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
OptionalLikeConditionalMapper.class
|
||||
})
|
||||
@IssueKey("2084")
|
||||
public void optionalLikeConditional() {
|
||||
OptionalLikeConditionalMapper mapper = OptionalLikeConditionalMapper.INSTANCE;
|
||||
|
||||
OptionalLikeConditionalMapper.Target target = mapper.map( new OptionalLikeConditionalMapper.Source(
|
||||
OptionalLikeConditionalMapper.Nullable.ofNullable( "test" ) ) );
|
||||
|
||||
assertThat( target.getValue() ).isEqualTo( "test" );
|
||||
|
||||
target = mapper.map(
|
||||
new OptionalLikeConditionalMapper.Source( OptionalLikeConditionalMapper.Nullable.undefined() )
|
||||
);
|
||||
|
||||
assertThat( target.getValue() ).isEqualTo( "initial" );
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodAndBeanPresenceCheckMapper {
|
||||
|
||||
ConditionalMethodAndBeanPresenceCheckMapper INSTANCE = Mappers.getMapper(
|
||||
ConditionalMethodAndBeanPresenceCheckMapper.class );
|
||||
|
||||
BasicEmployee map(EmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
class EmployeeDto {
|
||||
|
||||
private final String name;
|
||||
|
||||
public EmployeeDto(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public boolean hasName() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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 java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodForCollectionMapper {
|
||||
|
||||
ConditionalMethodForCollectionMapper INSTANCE = Mappers.getMapper( ConditionalMethodForCollectionMapper.class );
|
||||
|
||||
AuthorDto map(Author author);
|
||||
|
||||
@Condition
|
||||
default <T> boolean isNotEmpty(Collection<T> collection) {
|
||||
return collection != null && !collection.isEmpty();
|
||||
}
|
||||
|
||||
class Author {
|
||||
private List<Book> books;
|
||||
|
||||
public List<Book> getBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public boolean hasBooks() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setBooks(List<Book> books) {
|
||||
this.books = books;
|
||||
}
|
||||
}
|
||||
|
||||
class Book {
|
||||
private final String name;
|
||||
|
||||
public Book(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
class AuthorDto {
|
||||
private List<BookDto> books;
|
||||
|
||||
public List<BookDto> getBooks() {
|
||||
return books;
|
||||
}
|
||||
|
||||
public void setBooks(List<BookDto> books) {
|
||||
this.books = books;
|
||||
}
|
||||
}
|
||||
|
||||
class BookDto {
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodInContextMapper {
|
||||
|
||||
ConditionalMethodInContextMapper INSTANCE = Mappers.getMapper( ConditionalMethodInContextMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee, @Context PresenceUtils utils);
|
||||
|
||||
class PresenceUtils {
|
||||
@Condition
|
||||
public 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.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodInMapper {
|
||||
|
||||
ConditionalMethodInMapper INSTANCE = Mappers.getMapper( ConditionalMethodInMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper(uses = ConditionalMethodInUsesMapper.PresenceUtils.class)
|
||||
public interface ConditionalMethodInUsesMapper {
|
||||
|
||||
ConditionalMethodInUsesMapper INSTANCE = Mappers.getMapper( ConditionalMethodInUsesMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
class PresenceUtils {
|
||||
|
||||
@Condition
|
||||
public boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper(uses = ConditionalMethodInUsesStaticMapper.PresenceUtils.class)
|
||||
public interface ConditionalMethodInUsesStaticMapper {
|
||||
|
||||
ConditionalMethodInUsesStaticMapper INSTANCE = Mappers.getMapper( ConditionalMethodInUsesStaticMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
interface PresenceUtils {
|
||||
|
||||
@Condition
|
||||
static boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodWithSourceParameterAndValueMapper {
|
||||
|
||||
ConditionalMethodWithSourceParameterAndValueMapper INSTANCE = Mappers.getMapper(
|
||||
ConditionalMethodWithSourceParameterAndValueMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isPresent(BasicEmployeeDto source, String value) {
|
||||
if ( value == null ) {
|
||||
return false;
|
||||
}
|
||||
switch ( source.getStrategy() ) {
|
||||
case "blank":
|
||||
return !value.trim().isEmpty();
|
||||
case "empty":
|
||||
return !value.isEmpty();
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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.Condition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ConditionalMethodWithSourceParameterMapper {
|
||||
|
||||
ConditionalMethodWithSourceParameterMapper INSTANCE =
|
||||
Mappers.getMapper( ConditionalMethodWithSourceParameterMapper.class );
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean shouldMap(BasicEmployeeDto source) {
|
||||
return "map".equals( source.getStrategy() );
|
||||
}
|
||||
|
||||
}
|
@ -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.Condition;
|
||||
import org.mapstruct.Mapper;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ErroneousAmbiguousConditionalMethodMapper {
|
||||
|
||||
BasicEmployee map(BasicEmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
@Condition
|
||||
default boolean isNotEmpty(String value) {
|
||||
return value != null && value.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface OptionalLikeConditionalMapper {
|
||||
|
||||
OptionalLikeConditionalMapper INSTANCE = Mappers.getMapper( OptionalLikeConditionalMapper.class );
|
||||
|
||||
Target map(Source source);
|
||||
|
||||
default <T> T map(Nullable<T> nullable) {
|
||||
return nullable.value;
|
||||
}
|
||||
|
||||
@Condition
|
||||
default <T> boolean isPresent(Nullable<T> nullable) {
|
||||
return nullable.isPresent();
|
||||
}
|
||||
|
||||
class Nullable<T> {
|
||||
|
||||
private final T value;
|
||||
private final boolean present;
|
||||
|
||||
private Nullable(T value, boolean present) {
|
||||
this.value = value;
|
||||
this.present = present;
|
||||
}
|
||||
|
||||
public boolean isPresent() {
|
||||
return present;
|
||||
}
|
||||
|
||||
public static <T> Nullable<T> undefined() {
|
||||
return new Nullable<>( null, false );
|
||||
}
|
||||
|
||||
public static <T> Nullable<T> ofNullable(T value) {
|
||||
return new Nullable<>( value, true );
|
||||
}
|
||||
}
|
||||
|
||||
class Source {
|
||||
protected final Nullable<String> value;
|
||||
|
||||
public Source(Nullable<String> value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Nullable<String> getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class Target {
|
||||
protected String value = "initial";
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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.expression;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.test.conditional.Employee;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.ap.test.conditional.basic.BasicEmployee;
|
||||
import org.mapstruct.ap.testutil.IssueKey;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.CompilationResult;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.Diagnostic;
|
||||
import org.mapstruct.ap.testutil.compilation.annotation.ExpectedCompilationOutcome;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@IssueKey("2051")
|
||||
@WithClasses({
|
||||
Employee.class,
|
||||
EmployeeDto.class
|
||||
})
|
||||
@RunWith(AnnotationProcessorTestRunner.class)
|
||||
public class ConditionalExpressionTest {
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodsInUtilClassMapper.class
|
||||
})
|
||||
public void conditionalExpressionInStaticClassMethod() {
|
||||
ConditionalMethodsInUtilClassMapper mapper = ConditionalMethodsInUtilClassMapper.INSTANCE;
|
||||
|
||||
EmployeeDto dto = new EmployeeDto();
|
||||
dto.setName( "Tester" );
|
||||
dto.setUniqueIdNumber( "SSID-001" );
|
||||
dto.setCountry( null );
|
||||
|
||||
Employee employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "UK" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isEqualTo( "SSID-001" );
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "US" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isEqualTo( "SSID-001" );
|
||||
|
||||
dto.setCountry( "CH" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
SimpleConditionalExpressionMapper.class
|
||||
})
|
||||
public void conditionalSimpleExpression() {
|
||||
SimpleConditionalExpressionMapper mapper = SimpleConditionalExpressionMapper.INSTANCE;
|
||||
|
||||
SimpleConditionalExpressionMapper.Target target =
|
||||
mapper.map( new SimpleConditionalExpressionMapper.Source( 50 ) );
|
||||
assertThat( target.getValue() ).isEqualTo( 50 );
|
||||
|
||||
target = mapper.map( new SimpleConditionalExpressionMapper.Source( 101 ) );
|
||||
assertThat( target.getValue() ).isEqualTo( 0 );
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectedCompilationOutcome(
|
||||
value = CompilationResult.FAILED,
|
||||
diagnostics = {
|
||||
@Diagnostic(type = ErroneousConditionExpressionMapper.class,
|
||||
kind = javax.tools.Diagnostic.Kind.ERROR,
|
||||
line = 19,
|
||||
message = "Value for condition expression must be given in the form \"java(<EXPRESSION>)\"."
|
||||
)
|
||||
}
|
||||
)
|
||||
@WithClasses({
|
||||
BasicEmployee.class,
|
||||
ErroneousConditionExpressionMapper.class
|
||||
} )
|
||||
public void invalidJavaConditionExpression() {
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.expression;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ap.test.conditional.Employee;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper(imports = ConditionalMethodsInUtilClassMapper.StaticUtil.class)
|
||||
public interface ConditionalMethodsInUtilClassMapper {
|
||||
|
||||
ConditionalMethodsInUtilClassMapper INSTANCE = Mappers.getMapper( ConditionalMethodsInUtilClassMapper.class );
|
||||
|
||||
@Mapping(target = "ssid", source = "uniqueIdNumber",
|
||||
conditionExpression = "java(StaticUtil.isAmericanCitizen( employee ))")
|
||||
@Mapping(target = "nin", source = "uniqueIdNumber",
|
||||
conditionExpression = "java(StaticUtil.isBritishCitizen( employee ))")
|
||||
Employee map(EmployeeDto employee);
|
||||
|
||||
interface StaticUtil {
|
||||
|
||||
static boolean isAmericanCitizen(EmployeeDto employeeDto) {
|
||||
return "US".equals( employeeDto.getCountry() );
|
||||
}
|
||||
|
||||
static boolean isBritishCitizen(EmployeeDto employeeDto) {
|
||||
return "UK".equals( employeeDto.getCountry() );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.expression;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.ap.test.conditional.basic.BasicEmployee;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface ErroneousConditionExpressionMapper {
|
||||
|
||||
@Mapping(target = "name", conditionExpression = "!employee.getName().isEmpty()")
|
||||
BasicEmployee map(EmployeeDto employee);
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.expression;
|
||||
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper
|
||||
public interface SimpleConditionalExpressionMapper {
|
||||
|
||||
SimpleConditionalExpressionMapper INSTANCE = Mappers.getMapper( SimpleConditionalExpressionMapper.class );
|
||||
|
||||
@Mapping(target = "value", conditionExpression = "java(source.getValue() < 100)")
|
||||
Target map(Source source);
|
||||
|
||||
class Source {
|
||||
private final int value;
|
||||
|
||||
public Source(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class Target {
|
||||
private int value;
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.qualifier;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.Qualifier;
|
||||
import org.mapstruct.ap.test.conditional.Employee;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper(uses = ConditionalMethodWithClassQualifiersMapper.StaticUtil.class)
|
||||
public interface ConditionalMethodWithClassQualifiersMapper {
|
||||
|
||||
ConditionalMethodWithClassQualifiersMapper INSTANCE =
|
||||
Mappers.getMapper( ConditionalMethodWithClassQualifiersMapper.class );
|
||||
|
||||
@Mapping(target = "ssid", source = "uniqueIdNumber",
|
||||
conditionQualifiedBy = UtilConditions.class, conditionQualifiedByName = "american")
|
||||
@Mapping(target = "nin", source = "uniqueIdNumber",
|
||||
conditionQualifiedBy = UtilConditions.class, conditionQualifiedByName = "british")
|
||||
Employee map(EmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
@UtilConditions
|
||||
interface StaticUtil {
|
||||
|
||||
@Condition
|
||||
@Named("american")
|
||||
static boolean isAmericanCitizen(EmployeeDto employerDto) {
|
||||
return "US".equals( employerDto.getCountry() );
|
||||
}
|
||||
|
||||
@Condition
|
||||
@Named("british")
|
||||
static boolean isBritishCitizen(EmployeeDto employeeDto) {
|
||||
return "UK".equals( employeeDto.getCountry() );
|
||||
}
|
||||
}
|
||||
|
||||
@Qualifier
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.CLASS)
|
||||
@interface UtilConditions {
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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.qualifier;
|
||||
|
||||
import org.mapstruct.Condition;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.Named;
|
||||
import org.mapstruct.ap.test.conditional.Employee;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@Mapper(uses = ConditionalMethodWithSourceParameterMapper.StaticUtil.class)
|
||||
public interface ConditionalMethodWithSourceParameterMapper {
|
||||
|
||||
ConditionalMethodWithSourceParameterMapper INSTANCE =
|
||||
Mappers.getMapper( ConditionalMethodWithSourceParameterMapper.class );
|
||||
|
||||
@Mapping(target = "ssid", source = "uniqueIdNumber", conditionQualifiedByName = "isAmericanCitizen")
|
||||
@Mapping(target = "nin", source = "uniqueIdNumber", conditionQualifiedByName = "isBritishCitizen")
|
||||
Employee map(EmployeeDto employee);
|
||||
|
||||
@Condition
|
||||
default boolean isNotBlank(String value) {
|
||||
return value != null && !value.trim().isEmpty();
|
||||
}
|
||||
|
||||
@Condition
|
||||
@Named("isAmericanCitizen")
|
||||
default boolean isAmericanCitizen(EmployeeDto employerDto) {
|
||||
return "US".equals( employerDto.getCountry() );
|
||||
}
|
||||
|
||||
interface StaticUtil {
|
||||
|
||||
@Condition
|
||||
@Named("isBritishCitizen")
|
||||
static boolean isBritishCitizen(EmployeeDto employeeDto) {
|
||||
return "UK".equals( employeeDto.getCountry() );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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.qualifier;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mapstruct.ap.test.conditional.Employee;
|
||||
import org.mapstruct.ap.test.conditional.EmployeeDto;
|
||||
import org.mapstruct.ap.testutil.IssueKey;
|
||||
import org.mapstruct.ap.testutil.WithClasses;
|
||||
import org.mapstruct.ap.testutil.runner.AnnotationProcessorTestRunner;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Filip Hrisafov
|
||||
*/
|
||||
@IssueKey("2051")
|
||||
@WithClasses({
|
||||
Employee.class,
|
||||
EmployeeDto.class
|
||||
})
|
||||
@RunWith(AnnotationProcessorTestRunner.class)
|
||||
public class ConditionalQualifierTest {
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodWithSourceParameterMapper.class
|
||||
})
|
||||
public void conditionalMethodWithSourceParameter() {
|
||||
ConditionalMethodWithSourceParameterMapper mapper = ConditionalMethodWithSourceParameterMapper.INSTANCE;
|
||||
|
||||
EmployeeDto dto = new EmployeeDto();
|
||||
dto.setName( "Tester" );
|
||||
dto.setUniqueIdNumber( "SSID-001" );
|
||||
dto.setCountry( null );
|
||||
|
||||
Employee employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "UK" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isEqualTo( "SSID-001" );
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "US" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isEqualTo( "SSID-001" );
|
||||
|
||||
dto.setCountry( "CH" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithClasses({
|
||||
ConditionalMethodWithClassQualifiersMapper.class
|
||||
})
|
||||
public void conditionalClassQualifiers() {
|
||||
ConditionalMethodWithClassQualifiersMapper mapper = ConditionalMethodWithClassQualifiersMapper.INSTANCE;
|
||||
|
||||
EmployeeDto dto = new EmployeeDto();
|
||||
dto.setName( "Tester" );
|
||||
dto.setUniqueIdNumber( "SSID-001" );
|
||||
dto.setCountry( null );
|
||||
|
||||
Employee employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "UK" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isEqualTo( "SSID-001" );
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
|
||||
dto.setCountry( "US" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isEqualTo( "SSID-001" );
|
||||
|
||||
dto.setCountry( "CH" );
|
||||
employee = mapper.map( dto );
|
||||
assertThat( employee.getNin() ).isNull();
|
||||
assertThat( employee.getSsid() ).isNull();
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 javax.annotation.processing.Generated;
|
||||
|
||||
@Generated(
|
||||
value = "org.mapstruct.ap.MappingProcessor",
|
||||
date = "2021-04-19T21:10:40+0200",
|
||||
comments = "version: , compiler: javac, environment: Java 11.0.9.1 (AdoptOpenJDK)"
|
||||
)
|
||||
public class ConditionalMethodInMapperImpl implements ConditionalMethodInMapper {
|
||||
|
||||
@Override
|
||||
public BasicEmployee map(BasicEmployeeDto employee) {
|
||||
if ( employee == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BasicEmployee basicEmployee = new BasicEmployee();
|
||||
|
||||
if ( isNotBlank( employee.getName() ) ) {
|
||||
basicEmployee.setName( employee.getName() );
|
||||
}
|
||||
|
||||
return basicEmployee;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 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 javax.annotation.processing.Generated;
|
||||
|
||||
@Generated(
|
||||
value = "org.mapstruct.ap.MappingProcessor",
|
||||
date = "2021-04-19T21:10:38+0200",
|
||||
comments = "version: , compiler: javac, environment: Java 11.0.9.1 (AdoptOpenJDK)"
|
||||
)
|
||||
public class ConditionalMethodWithSourceParameterAndValueMapperImpl implements ConditionalMethodWithSourceParameterAndValueMapper {
|
||||
|
||||
@Override
|
||||
public BasicEmployee map(BasicEmployeeDto employee) {
|
||||
if ( employee == null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
BasicEmployee basicEmployee = new BasicEmployee();
|
||||
|
||||
if ( isPresent( employee, employee.getName() ) ) {
|
||||
basicEmployee.setName( employee.getName() );
|
||||
}
|
||||
|
||||
return basicEmployee;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user