#3601 Always use SourceParameterCondition when checking source parameter

This is a breaking change, with this change whenever a source parameter is used as a source for a target property the condition has to apply to source parameters and not properties
This commit is contained in:
Filip Hrisafov 2024-07-20 13:53:39 +02:00 committed by GitHub
parent 52877d36c2
commit 66f4288842
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 252 additions and 48 deletions

View File

@ -8,7 +8,75 @@ Source properties are ignored anyway, the `BeanMapping#unmappedSourcePolicy` sho
### Bugs ### Bugs
* Breaking change: Presence check method used only once when multiple source parameters are provided (#3601)
### Documentation ### Documentation
### Build ### Build
## Breaking changes
### Presence checks for source parameters
In 1.6, support for presence checks on source parameters has been added.
This means that even if you want to map a source parameter directly to some target property the new `@SourceParameterCondition` or `@Condition(appliesTo = ConditionStrategy.SOURCE_PARAMETERS)` should be used.
e.g.
If we had the following in 1.5:
```java
@Mapper
public interface OrderMapper {
@Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder")
Order map(OrderDTO dto);
@Condition
@Named("mapCustomerFromOrder")
default boolean mapCustomerFromOrder(OrderDTO dto) {
return dto != null && dto.getCustomerName() != null;
}
}
```
Them MapStruct would generate
```java
public class OrderMapperImpl implements OrderMapper {
@Override
public Order map(OrderDTO dto) {
if ( dto == null ) {
return null;
}
Order order = new Order();
if ( mapCustomerFromOrder( dto ) ) {
order.setCustomer( orderDtoToCustomer( orderDTO ) );
}
return order;
}
}
```
In order for the same to be generated in 1.6, the mapper needs to look like this:
```java
@Mapper
public interface OrderMapper {
@Mapping(source = "dto", target = "customer", conditionQualifiedByName = "mapCustomerFromOrder")
Order map(OrderDTO dto);
@SourceParameterCondition
@Named("mapCustomerFromOrder")
default boolean mapCustomerFromOrder(OrderDTO dto) {
return dto != null && dto.getCustomerName() != null;
}
}
```

View File

@ -5,7 +5,6 @@
*/ */
package org.mapstruct.ap.internal.model.source.selector; package org.mapstruct.ap.internal.model.source.selector;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -22,50 +21,31 @@ import org.mapstruct.ap.internal.model.source.SelectionParameters;
*/ */
public class SelectionCriteria { public class SelectionCriteria {
private final List<TypeMirror> qualifiers = new ArrayList<>(); private final QualifyingInfo qualifyingInfo;
private final List<String> qualifiedByNames = new ArrayList<>();
private final String targetPropertyName; private final String targetPropertyName;
private final TypeMirror qualifyingResultType;
private final SourceRHS sourceRHS; private final SourceRHS sourceRHS;
private boolean ignoreQualifiers = false; private boolean ignoreQualifiers = false;
private Type type; private Type type;
private final boolean allowDirect; private final MappingControl mappingControl;
private final boolean allowConversion;
private final boolean allowMappingMethod;
private final boolean allow2Steps;
public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl, public SelectionCriteria(SelectionParameters selectionParameters, MappingControl mappingControl,
String targetPropertyName, Type type) { String targetPropertyName, Type type) {
if ( selectionParameters != null ) { this(
if ( type == Type.PRESENCE_CHECK ) { QualifyingInfo.fromSelectionParameters( selectionParameters ),
qualifiers.addAll( selectionParameters.getConditionQualifiers() ); selectionParameters != null ? selectionParameters.getSourceRHS() : null,
qualifiedByNames.addAll( selectionParameters.getConditionQualifyingNames() ); mappingControl,
} targetPropertyName,
else { type
qualifiers.addAll( selectionParameters.getQualifiers() ); );
qualifiedByNames.addAll( selectionParameters.getQualifyingNames() ); }
}
qualifyingResultType = selectionParameters.getResultType(); private SelectionCriteria(QualifyingInfo qualifyingInfo, SourceRHS sourceRHS, MappingControl mappingControl,
sourceRHS = selectionParameters.getSourceRHS(); String targetPropertyName, Type type) {
} this.qualifyingInfo = qualifyingInfo;
else {
this.qualifyingResultType = null;
sourceRHS = null;
}
if ( mappingControl != null ) {
this.allowDirect = mappingControl.allowDirect();
this.allowConversion = mappingControl.allowTypeConversion();
this.allowMappingMethod = mappingControl.allowMappingMethod();
this.allow2Steps = mappingControl.allowBy2Steps();
}
else {
this.allowDirect = true;
this.allowConversion = true;
this.allowMappingMethod = true;
this.allow2Steps = true;
}
this.targetPropertyName = targetPropertyName; this.targetPropertyName = targetPropertyName;
this.sourceRHS = sourceRHS;
this.type = type; this.type = type;
this.mappingControl = mappingControl;
} }
/** /**
@ -109,11 +89,11 @@ public class SelectionCriteria {
} }
public List<TypeMirror> getQualifiers() { public List<TypeMirror> getQualifiers() {
return ignoreQualifiers ? Collections.emptyList() : qualifiers; return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiers();
} }
public List<String> getQualifiedByNames() { public List<String> getQualifiedByNames() {
return ignoreQualifiers ? Collections.emptyList() : qualifiedByNames; return ignoreQualifiers ? Collections.emptyList() : qualifyingInfo.qualifiedByNames();
} }
public String getTargetPropertyName() { public String getTargetPropertyName() {
@ -121,7 +101,7 @@ public class SelectionCriteria {
} }
public TypeMirror getQualifyingResultType() { public TypeMirror getQualifyingResultType() {
return qualifyingResultType; return qualifyingInfo.qualifyingResultType();
} }
public boolean isPreferUpdateMapping() { public boolean isPreferUpdateMapping() {
@ -137,23 +117,23 @@ public class SelectionCriteria {
} }
public boolean hasQualfiers() { public boolean hasQualfiers() {
return !qualifiedByNames.isEmpty() || !qualifiers.isEmpty(); return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty();
} }
public boolean isAllowDirect() { public boolean isAllowDirect() {
return allowDirect; return mappingControl == null || mappingControl.allowDirect();
} }
public boolean isAllowConversion() { public boolean isAllowConversion() {
return allowConversion; return mappingControl == null || mappingControl.allowTypeConversion();
} }
public boolean isAllowMappingMethod() { public boolean isAllowMappingMethod() {
return allowMappingMethod; return mappingControl == null || mappingControl.allowMappingMethod();
} }
public boolean isAllow2Steps() { public boolean isAllow2Steps() {
return allow2Steps; return mappingControl == null || mappingControl.allowBy2Steps();
} }
public boolean isSelfAllowed() { public boolean isSelfAllowed() {
@ -181,7 +161,22 @@ public class SelectionCriteria {
} }
public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) { public static SelectionCriteria forPresenceCheckMethods(SelectionParameters selectionParameters) {
return new SelectionCriteria( selectionParameters, null, null, Type.PRESENCE_CHECK ); SourceRHS sourceRHS = selectionParameters.getSourceRHS();
Type type;
QualifyingInfo qualifyingInfo = new QualifyingInfo(
selectionParameters.getConditionQualifiers(),
selectionParameters.getConditionQualifyingNames(),
selectionParameters.getResultType()
);
if ( sourceRHS != null && sourceRHS.isSourceReferenceParameter() ) {
// If the source reference is for a source parameter,
// then the presence check should be for the source parameter
type = Type.SOURCE_PARAMETER_CHECK;
}
else {
type = Type.PRESENCE_CHECK;
}
return new SelectionCriteria( qualifyingInfo, sourceRHS, null, null, type );
} }
public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) { public static SelectionCriteria forSourceParameterCheckMethods(SelectionParameters selectionParameters) {
@ -193,6 +188,50 @@ public class SelectionCriteria {
return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED ); return new SelectionCriteria( selectionParameters, mappingControl, null, Type.SELF_NOT_ALLOWED );
} }
private static class QualifyingInfo {
private static final QualifyingInfo EMPTY = new QualifyingInfo(
Collections.emptyList(),
Collections.emptyList(),
null
);
private final List<TypeMirror> qualifiers;
private final List<String> qualifiedByNames;
private final TypeMirror qualifyingResultType;
private QualifyingInfo(List<TypeMirror> qualifiers, List<String> qualifiedByNames,
TypeMirror qualifyingResultType) {
this.qualifiers = qualifiers;
this.qualifiedByNames = qualifiedByNames;
this.qualifyingResultType = qualifyingResultType;
}
public List<TypeMirror> qualifiers() {
return qualifiers;
}
public List<String> qualifiedByNames() {
return qualifiedByNames;
}
public TypeMirror qualifyingResultType() {
return qualifyingResultType;
}
private static QualifyingInfo fromSelectionParameters(SelectionParameters selectionParameters) {
if ( selectionParameters == null ) {
return EMPTY;
}
return new QualifyingInfo(
selectionParameters.getQualifiers(),
selectionParameters.getQualifyingNames(),
selectionParameters.getResultType()
);
}
}
public enum Type { public enum Type {
PREFER_UPDATE_MAPPING, PREFER_UPDATE_MAPPING,
OBJECT_FACTORY, OBJECT_FACTORY,

View File

@ -0,0 +1,50 @@
/*
* 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.bugs._3601;
import java.util.List;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.SourceParameterCondition;
import org.mapstruct.factory.Mappers;
/**
* @author Filip Hrisafov
*/
@Mapper
public interface Issue3601Mapper {
Issue3601Mapper INSTANCE = Mappers.getMapper( Issue3601Mapper.class );
@Mapping(target = "currentId", source = "source.uuid")
@Mapping(target = "targetIds", source = "sourceIds")
Target map(Source source, List<String> sourceIds);
@SourceParameterCondition
default boolean isNotEmpty(List<String> elements) {
return elements != null && !elements.isEmpty();
}
class Source {
private final String uuid;
public Source(String uuid) {
this.uuid = uuid;
}
public String getUuid() {
return uuid;
}
}
class Target {
//CHECKSTYLE:OFF
public String currentId;
public List<String> targetIds;
//CHECKSTYLE:ON
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.bugs._3601;
import java.util.Collections;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static org.assertj.core.api.Assertions.assertThat;
/**
* @author Filip Hrisafov
*/
@IssueKey("3601")
class Issue3601Test {
@RegisterExtension
final GeneratedSource generatedSource = new GeneratedSource();
@ProcessorTest
@WithClasses( Issue3601Mapper.class )
void shouldUseSourceParameterPresenceCheckCorrectly() {
Issue3601Mapper.Target target = Issue3601Mapper.INSTANCE.map(
new Issue3601Mapper.Source( "test1" ),
Collections.emptyList()
);
assertThat( target ).isNotNull();
assertThat( target.currentId ).isEqualTo( "test1" );
assertThat( target.targetIds ).isNull();
target = Issue3601Mapper.INSTANCE.map(
null,
Collections.emptyList()
);
assertThat( target ).isNull();
}
}

View File

@ -5,10 +5,10 @@
*/ */
package org.mapstruct.ap.test.conditional.qualifier; package org.mapstruct.ap.test.conditional.qualifier;
import org.mapstruct.Condition;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.Named; import org.mapstruct.Named;
import org.mapstruct.SourceParameterCondition;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
/** /**
@ -29,13 +29,13 @@ public interface ConditionalMethodWithSourceToTargetMapper {
Address convertToAddress(OrderDTO orderDTO); Address convertToAddress(OrderDTO orderDTO);
@Condition @SourceParameterCondition
@Named("mapCustomerFromOrder") @Named("mapCustomerFromOrder")
default boolean mapCustomerFromOrder(OrderDTO orderDTO) { default boolean mapCustomerFromOrder(OrderDTO orderDTO) {
return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) ); return orderDTO != null && ( orderDTO.getCustomerName() != null || mapAddressFromOrder( orderDTO ) );
} }
@Condition @SourceParameterCondition
@Named("mapAddressFromOrder") @Named("mapAddressFromOrder")
default boolean mapAddressFromOrder(OrderDTO orderDTO) { default boolean mapAddressFromOrder(OrderDTO orderDTO) {
return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null ); return orderDTO != null && ( orderDTO.getLine1() != null || orderDTO.getLine2() != null );