Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Use ConditionStrategy
  • Loading branch information
filiphr committed Apr 28, 2024
commit 3bb37355bca05c44d1208f6ce1ad65b88d75c1ca
31 changes: 24 additions & 7 deletions core/src/main/java/org/mapstruct/Condition.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
import java.lang.annotation.Target;

/**
* This annotation marks a method as a <em>presence check method</em> to check for presence in beans.
* This annotation marks a method as a <em>presence check method</em> to check for presence in beans
* or it can be used to define additional check methods for something like source parameters.
* <p>
* By default, bean properties are checked against {@code null} or using a presence check method in the source bean.
* If a presence check method is available then it will be used instead.
Expand All @@ -20,11 +21,19 @@
* The following parameters are accepted for the presence check methods:
* <ul>
* <li>The parameter with the value of the source property.
* e.g. the value given by calling {@code getName()} for the name property of the source bean</li>
* e.g. the value given by calling {@code getName()} for the name property of the source bean
* - only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>The mapping source parameter</li>
* <li>{@code @}{@link Context} parameter</li>
* <li>{@code @}{@link TargetPropertyName} parameter</li>
* <li>{@code @}{@link SourcePropertyName} parameter</li>
* <li>
* {@code @}{@link TargetPropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* <li>
* {@code @}{@link SourcePropertyName} parameter -
* only possible when using the {@link ConditionStrategy#PROPERTIES}
* </li>
* </ul>
*
* <strong>Note:</strong> The usage of this annotation is <em>mandatory</em>
Expand All @@ -45,7 +54,7 @@
* MovieDto map(Movie movie);
* }
* </code></pre>
*
* <p>
* The following implementation of {@code MovieMapper} will be generated:
*
* <pre><code>
Expand All @@ -67,13 +76,21 @@
* }
* }
* </code></pre>
* <p>
* This annotation can also be used as a meta-annotation to define the condition strategy.
*
* @author Filip Hrisafov
* @since 1.5
* @see SourceCondition
* @since 1.5
*/
@Target({ ElementType.METHOD })
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.CLASS)
public @interface Condition {

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

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

/**
* Strategy for defining what to what a condition (check) method is applied to
*
* @author Filip Hrisafov
* @since 1.6
*/
public enum ConditionStrategy {
/**
* The condition method should be applied whether a property should be mapped.
*/
PROPERTIES,
/**
* The condition method should be applied to check if a source parameters should be mapped.
*/
SOURCE_PARAMETERS,
}
1 change: 1 addition & 0 deletions core/src/main/java/org/mapstruct/SourceCondition.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.CLASS)
@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)
public @interface SourceCondition {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.gem;

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

PROPERTIES,
SOURCE_PARAMETERS
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.util.List;
import java.util.stream.Collectors;

import org.mapstruct.ap.internal.gem.ConditionStrategyGem;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.source.Method;
Expand Down Expand Up @@ -172,14 +173,16 @@ private static List<SourceMethod> getAllAvailableMethods(Method method, List<Sou

for ( SourceMethod methodProvidedByParams : methodsProvidedByParams ) {
if ( selectionCriteria.isPresenceCheckRequired() ) {
// add only methods from context that do have the @Condition annotation
if ( methodProvidedByParams.isPresenceCheck() ) {
// add only methods from context that do have the @Condition for properties annotation
if ( methodProvidedByParams.getConditionOptions()
.isStrategyApplicable( ConditionStrategyGem.PROPERTIES ) ) {
availableMethods.add( methodProvidedByParams );
}
}
else if ( selectionCriteria.isSourceParameterCheckRequired() ) {
// add only methods from context that do have the @SourceCondition annotation
if ( methodProvidedByParams.isSourceParameterCheck() ) {
// add only methods from context that do have the @Condition for source parameters annotation
if ( methodProvidedByParams.getConditionOptions()
.isStrategyApplicable( ConditionStrategyGem.SOURCE_PARAMETERS ) ) {
availableMethods.add( methodProvidedByParams );
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,6 @@ public interface Method {
*/
boolean isObjectFactory();

/**
* Returns whether the method is designated as a presence check method
* @return {@code true} if it is a presence check method
*/
default boolean isPresenceCheck() {
return false;
}

/**
* Returns whether the method is designated as a source parameter check method
* @return {@code true} if it is a source parameter check method
*/
default boolean isSourceParameterCheck() {
return false;
}

/**
* Returns the parameter designated as target type (if present) {@link org.mapstruct.TargetType }
*
Expand Down Expand Up @@ -195,6 +179,10 @@ default boolean isSourceParameterCheck() {
*/
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
Expand Down
Loading