Use presence checks for checking source parameter presence

Instead of explicitly doing a null check use a PresenceCheck mechanism for
* BeanMappingMethod
* ContainerMappingMethod
* MapMappingMethod
This commit is contained in:
Filip Hrisafov 2023-08-01 09:53:58 +02:00
parent b2dc64136d
commit 812faeef51
18 changed files with 216 additions and 43 deletions

View File

@ -46,11 +46,13 @@ import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
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.common.TypeFactory;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer;
import org.mapstruct.ap.internal.model.dependency.GraphAnalyzer.GraphAnalyzerBuilder;
import org.mapstruct.ap.internal.model.presence.NullPresenceCheck;
import org.mapstruct.ap.internal.model.source.BeanMappingOptions;
import org.mapstruct.ap.internal.model.source.MappingOptions;
import org.mapstruct.ap.internal.model.source.Method;
@ -88,6 +90,7 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
private final List<PropertyMapping> propertyMappings;
private final Map<String, List<PropertyMapping>> mappingsByParameter;
private final Map<String, List<PropertyMapping>> constructorMappingsByParameter;
private final Map<String, PresenceCheck> presenceChecksByParameter;
private final List<PropertyMapping> constantMappings;
private final List<PropertyMapping> constructorConstantMappings;
private final List<SubclassMapping> subclassMappings;
@ -1869,11 +1872,19 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
// parameter mapping.
this.mappingsByParameter = new HashMap<>();
this.constantMappings = new ArrayList<>( propertyMappings.size() );
this.presenceChecksByParameter = new LinkedHashMap<>();
this.constructorMappingsByParameter = new LinkedHashMap<>();
this.constructorConstantMappings = new ArrayList<>();
Set<String> sourceParameterNames = getSourceParameters().stream()
.map( Parameter::getName )
.collect( Collectors.toSet() );
Set<String> sourceParameterNames = new HashSet<>();
for ( Parameter sourceParameter : getSourceParameters() ) {
sourceParameterNames.add( sourceParameter.getName() );
if ( !sourceParameter.getType().isPrimitive() ) {
presenceChecksByParameter.put(
sourceParameter.getName(),
new NullPresenceCheck( sourceParameter.getName() )
);
}
}
for ( PropertyMapping mapping : propertyMappings ) {
if ( mapping.isConstructorMapping() ) {
if ( sourceParameterNames.contains( mapping.getSourceBeanName() ) ) {
@ -1984,44 +1995,50 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
return types;
}
public List<Parameter> getSourceParametersExcludingPrimitives() {
public Collection<PresenceCheck> getSourcePresenceChecks() {
return presenceChecksByParameter.values();
}
public Map<String, PresenceCheck> getPresenceChecksByParameter() {
return presenceChecksByParameter;
}
public PresenceCheck getPresenceCheckByParameter(Parameter parameter) {
return presenceChecksByParameter.get( parameter.getName() );
}
public List<Parameter> getSourceParametersNeedingPresenceCheck() {
return getSourceParameters().stream()
.filter( parameter -> !parameter.getType().isPrimitive() )
.filter( this::needsPresenceCheck )
.collect( Collectors.toList() );
}
public List<Parameter> getSourceParametersNeedingNullCheck() {
public List<Parameter> getSourceParametersNotNeedingPresenceCheck() {
return getSourceParameters().stream()
.filter( this::needsNullCheck )
.filter( parameter -> !needsPresenceCheck( parameter ) )
.collect( Collectors.toList() );
}
public List<Parameter> getSourceParametersNotNeedingNullCheck() {
return getSourceParameters().stream()
.filter( parameter -> !needsNullCheck( parameter ) )
.collect( Collectors.toList() );
}
private boolean needsNullCheck(Parameter parameter) {
if ( parameter.getType().isPrimitive() ) {
private boolean needsPresenceCheck(Parameter parameter) {
if ( !presenceChecksByParameter.containsKey( parameter.getName() ) ) {
return false;
}
List<PropertyMapping> mappings = propertyMappingsByParameter( parameter );
if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) {
if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) {
return false;
}
mappings = constructorPropertyMappingsByParameter( parameter );
if ( mappings.size() == 1 && doesNotNeedNullCheckForSourceParameter( mappings.get( 0 ) ) ) {
if ( mappings.size() == 1 && doesNotNeedPresenceCheckForSourceParameter( mappings.get( 0 ) ) ) {
return false;
}
return true;
}
private boolean doesNotNeedNullCheckForSourceParameter(PropertyMapping mapping) {
private boolean doesNotNeedPresenceCheckForSourceParameter(PropertyMapping mapping) {
if ( mapping.getAssignment().isCallingUpdateMethod() ) {
// If the mapping assignment is calling an update method then we should do a null check
// in the bean mapping

View File

@ -12,7 +12,9 @@ import java.util.Set;
import org.mapstruct.ap.internal.model.common.Assignment;
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.presence.NullPresenceCheck;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.SelectionParameters;
import org.mapstruct.ap.internal.util.Strings;
@ -30,6 +32,8 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod {
private final SelectionParameters selectionParameters;
private final String index1Name;
private final String index2Name;
private final Parameter sourceParameter;
private final PresenceCheck sourceParameterPresenceCheck;
private IterableCreation iterableCreation;
ContainerMappingMethod(Method method, List<Annotation> annotations,
@ -45,16 +49,30 @@ public abstract class ContainerMappingMethod extends NormalTypeMappingMethod {
this.selectionParameters = selectionParameters;
this.index1Name = Strings.getSafeVariableName( "i", existingVariables );
this.index2Name = Strings.getSafeVariableName( "j", existingVariables );
}
public Parameter getSourceParameter() {
Parameter sourceParameter = null;
for ( Parameter parameter : getParameters() ) {
if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) {
return parameter;
sourceParameter = parameter;
break;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
if ( sourceParameter == null ) {
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
this.sourceParameter = sourceParameter;
this.sourceParameterPresenceCheck = new NullPresenceCheck( this.sourceParameter.getName() );
}
public Parameter getSourceParameter() {
return sourceParameter;
}
public PresenceCheck getSourceParameterPresenceCheck() {
return sourceParameterPresenceCheck;
}
public IterableCreation getIterableCreation() {

View File

@ -15,8 +15,10 @@ import org.mapstruct.ap.internal.model.assignment.LocalVarWrapper;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.common.Parameter;
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.NullPresenceCheck;
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;
@ -35,6 +37,8 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
private final Assignment keyAssignment;
private final Assignment valueAssignment;
private final Parameter sourceParameter;
private final PresenceCheck sourceParameterPresenceCheck;
private IterableCreation iterableCreation;
public static class Builder extends AbstractMappingMethodBuilder<Builder, MapMappingMethod> {
@ -235,16 +239,28 @@ public class MapMappingMethod extends NormalTypeMappingMethod {
this.keyAssignment = keyAssignment;
this.valueAssignment = valueAssignment;
}
public Parameter getSourceParameter() {
Parameter sourceParameter = null;
for ( Parameter parameter : getParameters() ) {
if ( !parameter.isMappingTarget() && !parameter.isMappingContext() ) {
return parameter;
sourceParameter = parameter;
break;
}
}
throw new IllegalStateException( "Method " + this + " has no source parameter." );
if ( sourceParameter == null ) {
throw new IllegalStateException( "Method " + this + " has no source parameter." );
}
this.sourceParameter = sourceParameter;
this.sourceParameterPresenceCheck = new NullPresenceCheck( this.sourceParameter.getName() );
}
public Parameter getSourceParameter() {
return sourceParameter;
}
public PresenceCheck getSourceParameterPresenceCheck() {
return sourceParameterPresenceCheck;
}
public List<Type> getSourceElementTypes() {

View File

@ -18,9 +18,15 @@ import org.mapstruct.ap.internal.model.common.Type;
public class MethodReferencePresenceCheck extends ModelElement implements PresenceCheck {
protected final MethodReference methodReference;
protected final boolean negate;
public MethodReferencePresenceCheck(MethodReference methodReference) {
this( methodReference, false );
}
public MethodReferencePresenceCheck(MethodReference methodReference, boolean negate) {
this.methodReference = methodReference;
this.negate = negate;
}
@Override
@ -32,6 +38,15 @@ public class MethodReferencePresenceCheck extends ModelElement implements Presen
return methodReference;
}
public boolean isNegate() {
return negate;
}
@Override
public PresenceCheck negate() {
return new MethodReferencePresenceCheck( methodReference, true );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {

View File

@ -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.common;
import java.util.Objects;
import java.util.Set;
/**
* @author Filip Hrisafov
*/
public class NegatePresenceCheck extends ModelElement implements PresenceCheck {
private final PresenceCheck presenceCheck;
public NegatePresenceCheck(PresenceCheck presenceCheck) {
this.presenceCheck = presenceCheck;
}
public PresenceCheck getPresenceCheck() {
return presenceCheck;
}
@Override
public Set<Type> getImportTypes() {
return presenceCheck.getImportTypes();
}
@Override
public PresenceCheck negate() {
return presenceCheck;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
NegatePresenceCheck that = (NegatePresenceCheck) o;
return Objects.equals( presenceCheck, that.presenceCheck );
}
@Override
public int hashCode() {
return Objects.hash( presenceCheck );
}
}

View File

@ -21,4 +21,6 @@ public interface PresenceCheck {
*/
Set<Type> getImportTypes();
PresenceCheck negate();
}

View File

@ -11,6 +11,7 @@ import java.util.Objects;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.NegatePresenceCheck;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.common.Type;
@ -29,6 +30,11 @@ public class AllPresenceChecksPresenceCheck extends ModelElement implements Pres
return presenceChecks;
}
@Override
public PresenceCheck negate() {
return new NegatePresenceCheck( this );
}
@Override
public Set<Type> getImportTypes() {
Set<Type> importTypes = new HashSet<>();

View File

@ -10,6 +10,7 @@ import java.util.Objects;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.NegatePresenceCheck;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.common.Type;
@ -28,6 +29,11 @@ public class JavaExpressionPresenceCheck extends ModelElement implements Presenc
return javaExpression;
}
@Override
public PresenceCheck negate() {
return new NegatePresenceCheck( this );
}
@Override
public Set<Type> getImportTypes() {
return Collections.emptySet();

View File

@ -19,20 +19,36 @@ import org.mapstruct.ap.internal.model.common.Type;
public class NullPresenceCheck extends ModelElement implements PresenceCheck {
private final String sourceReference;
private final boolean negate;
public NullPresenceCheck(String sourceReference) {
this.sourceReference = sourceReference;
this.negate = false;
}
public NullPresenceCheck(String sourceReference, boolean negate) {
this.sourceReference = sourceReference;
this.negate = negate;
}
public String getSourceReference() {
return sourceReference;
}
public boolean isNegate() {
return negate;
}
@Override
public Set<Type> getImportTypes() {
return Collections.emptySet();
}
@Override
public PresenceCheck negate() {
return new NullPresenceCheck( sourceReference, !negate );
}
@Override
public boolean equals(Object o) {
if ( this == o ) {

View File

@ -10,6 +10,7 @@ import java.util.Objects;
import java.util.Set;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.NegatePresenceCheck;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.common.Type;
@ -20,10 +21,16 @@ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck {
private final String sourceReference;
private final String suffix;
private final boolean negate;
public SuffixPresenceCheck(String sourceReference, String suffix) {
this( sourceReference, suffix, false );
}
public SuffixPresenceCheck(String sourceReference, String suffix, boolean negate) {
this.sourceReference = sourceReference;
this.suffix = suffix;
this.negate = negate;
}
public String getSourceReference() {
@ -34,6 +41,15 @@ public class SuffixPresenceCheck extends ModelElement implements PresenceCheck {
return suffix;
}
public boolean isNegate() {
return negate;
}
@Override
public PresenceCheck negate() {
return new NegatePresenceCheck( this );
}
@Override
public Set<Type> getImportTypes() {
return Collections.emptySet();

View File

@ -27,8 +27,8 @@
</#if>
</#list>
<#if !mapNullToDefault && !sourceParametersExcludingPrimitives.empty>
if ( <#list sourceParametersExcludingPrimitives as sourceParam>${sourceParam.name} == null<#if sourceParam_has_next> && </#if></#list> ) {
<#if !mapNullToDefault && !sourcePresenceChecks.empty>
if ( <#list sourcePresenceChecks as sourcePresenceCheck><@includeModel object=sourcePresenceCheck.negate() /><#if sourcePresenceCheck_has_next> && </#if></#list> ) {
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#if finalizerMethod??>.<@includeModel object=finalizerMethod /></#if><#else>null</#if></#if>;
}
</#if>
@ -47,19 +47,19 @@
<#if !existingInstanceMapping>
<#if hasConstructorMappings()>
<#if (sourceParameters?size > 1)>
<#list sourceParametersNeedingNullCheck as sourceParam>
<#list sourceParametersNeedingPresenceCheck as sourceParam>
<#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)>
<#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
<@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null};
</#list>
if ( ${sourceParam.name} != null ) {
if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) {
<#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
<@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
</#list>
}
</#if>
</#list>
<#list sourceParametersNotNeedingNullCheck as sourceParam>
<#list sourceParametersNotNeedingPresenceCheck as sourceParam>
<#if (constructorPropertyMappingsByParameter(sourceParam)?size > 0)>
<#list constructorPropertyMappingsByParameter(sourceParam) as propertyMapping>
<@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null};
@ -71,7 +71,7 @@
<#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
<@includeModel object=propertyMapping.targetType /> ${propertyMapping.targetWriteAccessorName} = ${propertyMapping.targetType.null};
</#list>
<#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) {</#if>
<#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) {</#if>
<#list constructorPropertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
<@includeModel object=propertyMapping existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
</#list>
@ -100,16 +100,16 @@
</#if>
</#list>
<#if (sourceParameters?size > 1)>
<#list sourceParametersNeedingNullCheck as sourceParam>
<#list sourceParametersNeedingPresenceCheck as sourceParam>
<#if (propertyMappingsByParameter(sourceParam)?size > 0)>
if ( ${sourceParam.name} != null ) {
if ( <@includeModel object=getPresenceCheckByParameter(sourceParam) /> ) {
<#list propertyMappingsByParameter(sourceParam) as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
</#list>
}
</#if>
</#list>
<#list sourceParametersNotNeedingNullCheck as sourceParam>
<#list sourceParametersNotNeedingPresenceCheck as sourceParam>
<#if (propertyMappingsByParameter(sourceParam)?size > 0)>
<#list propertyMappingsByParameter(sourceParam) as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
@ -117,7 +117,7 @@
</#if>
</#list>
<#else>
<#if mapNullToDefault>if ( ${sourceParameters[0].name} != null ) {</#if>
<#if mapNullToDefault>if ( <@includeModel object=getPresenceCheckByParameter(sourceParameters[0]) /> ) {</#if>
<#list propertyMappingsByParameter(sourceParameters[0]) as propertyMapping>
<@includeModel object=propertyMapping targetBeanName=resultName existingInstanceMapping=existingInstanceMapping defaultValueAssignment=propertyMapping.defaultValueAssignment/>
</#list>

View File

@ -17,7 +17,7 @@
</#if>
</#list>
if ( ${sourceParameter.name} == null ) {
if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) {
<#if !mapNullToDefault>
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null</#if></#if>;
<#else>

View File

@ -17,7 +17,7 @@
</#if>
</#list>
if ( ${sourceParameter.name} == null ) {
if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) {
<#if !mapNullToDefault>
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null</#if></#if>;
<#else>

View File

@ -6,7 +6,7 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.MethodReferencePresenceCheck" -->
<@includeModel object=methodReference
<#if isNegate()>!</#if><@includeModel object=methodReference
presenceCheck=true
targetPropertyName=ext.targetPropertyName
targetType=ext.targetType/>

View File

@ -18,7 +18,7 @@
</#if>
</#list>
if ( ${sourceParameter.name} == null ) {
if ( <@includeModel object=sourceParameterPresenceCheck.negate() /> ) {
<#if !mapNullToDefault>
return<#if returnType.name != "void"> <#if existingInstanceMapping>${resultName}<#else>null</#if></#if>;
<#else>

View File

@ -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.common.NegatePresenceCheck" -->
!( <@includeModel object=presenceCheck /> )

View File

@ -6,4 +6,4 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.NullPresenceCheck" -->
${sourceReference} != null
${sourceReference} <#if isNegate()>==<#else>!=</#if> null

View File

@ -6,4 +6,4 @@
-->
<#-- @ftlvariable name="" type="org.mapstruct.ap.internal.model.presence.SuffixPresenceCheck" -->
${sourceReference}${suffix}
<#if isNegate()>!</#if>${sourceReference}${suffix}