Refactor presence checks to object in order to simplify the conditional mapping

This commit is contained in:
Filip Hrisafov 2021-03-27 18:38:41 +01:00
parent 1c8fff1475
commit a2e1404b93
16 changed files with 309 additions and 29 deletions

View File

@ -19,6 +19,7 @@ import org.mapstruct.ap.internal.model.common.ConversionContext;
import org.mapstruct.ap.internal.model.common.ModelElement;
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.Type;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.model.source.builtin.BuiltInMethod;
@ -197,7 +198,7 @@ public class MethodReference extends ModelElement implements Assignment {
}
@Override
public String getSourcePresenceCheckerReference() {
public PresenceCheck getSourcePresenceCheckerReference() {
return assignment.getSourcePresenceCheckerReference();
}

View File

@ -11,8 +11,10 @@ import java.util.Objects;
import java.util.Set;
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.beanmapping.PropertyEntry;
import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck;
import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.ValueProvider;
@ -52,17 +54,27 @@ public class NestedPropertyMappingMethod extends MappingMethod {
public NestedPropertyMappingMethod build() {
List<String> existingVariableNames = new ArrayList<>();
Parameter sourceParameter = null;
for ( Parameter parameter : method.getSourceParameters() ) {
existingVariableNames.add( parameter.getName() );
if ( sourceParameter == null && !parameter.isMappingTarget() && !parameter.isMappingContext() ) {
sourceParameter = parameter;
}
}
final List<Type> thrownTypes = new ArrayList<>();
List<SafePropertyEntry> safePropertyEntries = new ArrayList<>();
if ( sourceParameter == null ) {
throw new IllegalStateException( "Method " + method + " has no source parameter." );
}
String previousPropertyName = sourceParameter.getName();
for ( PropertyEntry propertyEntry : propertyEntries ) {
String safeName = Strings.getSafeVariableName( propertyEntry.getName(), existingVariableNames );
safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName ) );
safePropertyEntries.add( new SafePropertyEntry( propertyEntry, safeName, previousPropertyName ) );
existingVariableNames.add( safeName );
thrownTypes.addAll( ctx.getTypeFactory().getThrownTypes(
propertyEntry.getReadAccessor() ) );
previousPropertyName = safeName;
}
method.addThrownTypes( thrownTypes );
return new NestedPropertyMappingMethod( method, safePropertyEntries );
@ -92,6 +104,9 @@ public class NestedPropertyMappingMethod extends MappingMethod {
Set<Type> types = super.getImportTypes();
for ( SafePropertyEntry propertyEntry : safePropertyEntries) {
types.add( propertyEntry.getType() );
if ( propertyEntry.getPresenceChecker() != null ) {
types.addAll( propertyEntry.getPresenceChecker().getImportTypes() );
}
}
return types;
}
@ -143,18 +158,23 @@ public class NestedPropertyMappingMethod extends MappingMethod {
private final String safeName;
private final String readAccessorName;
private final String presenceCheckerName;
private final PresenceCheck presenceChecker;
private final String previousPropertyName;
private final Type type;
public SafePropertyEntry(PropertyEntry entry, String safeName) {
public SafePropertyEntry(PropertyEntry entry, String safeName, String previousPropertyName) {
this.safeName = safeName;
this.readAccessorName = ValueProvider.of( entry.getReadAccessor() ).getValue();
if ( entry.getPresenceChecker() != null ) {
this.presenceCheckerName = entry.getPresenceChecker().getSimpleName();
this.presenceChecker = new SourceReferenceMethodPresenceCheck(
previousPropertyName,
entry.getPresenceChecker().getSimpleName()
);
}
else {
this.presenceCheckerName = null;
this.presenceChecker = null;
}
this.previousPropertyName = previousPropertyName;
this.type = entry.getType();
}
@ -166,8 +186,12 @@ public class NestedPropertyMappingMethod extends MappingMethod {
return readAccessorName;
}
public String getPresenceCheckerName() {
return presenceCheckerName;
public PresenceCheck getPresenceChecker() {
return presenceChecker;
}
public String getPreviousPropertyName() {
return previousPropertyName;
}
public Type getType() {
@ -189,7 +213,11 @@ public class NestedPropertyMappingMethod extends MappingMethod {
return false;
}
if ( !Objects.equals( presenceCheckerName, that.presenceCheckerName ) ) {
if ( !Objects.equals( presenceChecker, that.presenceChecker ) ) {
return false;
}
if ( !Objects.equals( previousPropertyName, that.previousPropertyName ) ) {
return false;
}
@ -203,7 +231,7 @@ public class NestedPropertyMappingMethod extends MappingMethod {
@Override
public int hashCode() {
int result = readAccessorName != null ? readAccessorName.hashCode() : 0;
result = 31 * result + ( presenceCheckerName != null ? presenceCheckerName.hashCode() : 0 );
result = 31 * result + ( presenceChecker != null ? presenceChecker.hashCode() : 0 );
result = 31 * result + ( type != null ? type.hashCode() : 0 );
return result;
}

View File

@ -28,8 +28,12 @@ import org.mapstruct.ap.internal.model.common.BuilderType;
import org.mapstruct.ap.internal.model.common.FormattingParameters;
import org.mapstruct.ap.internal.model.common.ModelElement;
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.AllPresenceChecksPresenceCheck;
import org.mapstruct.ap.internal.model.presence.NullPresenceCheck;
import org.mapstruct.ap.internal.model.presence.SourceReferenceMethodPresenceCheck;
import org.mapstruct.ap.internal.model.source.DelegatingOptions;
import org.mapstruct.ap.internal.model.source.MappingControl;
import org.mapstruct.ap.internal.model.source.MappingOptions;
@ -609,30 +613,43 @@ public class PropertyMapping extends ModelElement {
}
}
private String getSourcePresenceCheckerRef( SourceReference sourceReference ) {
String sourcePresenceChecker = null;
private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference ) {
PresenceCheck sourcePresenceChecker = null;
if ( !sourceReference.getPropertyEntries().isEmpty() ) {
Parameter sourceParam = sourceReference.getParameter();
// TODO is first correct here?? shouldn't it be last since the remainer is checked
// in the forged method?
PropertyEntry propertyEntry = sourceReference.getShallowestProperty();
if ( propertyEntry.getPresenceChecker() != null ) {
sourcePresenceChecker = sourceParam.getName()
+ "." + propertyEntry.getPresenceChecker().getSimpleName() + "()";
List<PresenceCheck> presenceChecks = new ArrayList<>();
presenceChecks.add( new SourceReferenceMethodPresenceCheck(
sourceParam.getName(),
propertyEntry.getPresenceChecker().getSimpleName()
) );
String variableName = sourceParam.getName() + "."
+ propertyEntry.getReadAccessor().getSimpleName() + "()";
for (int i = 1; i < sourceReference.getPropertyEntries().size(); i++) {
PropertyEntry entry = sourceReference.getPropertyEntries().get( i );
if (entry.getPresenceChecker() != null && entry.getReadAccessor() != null) {
sourcePresenceChecker += " && " + variableName + " != null && "
+ variableName + "." + entry.getPresenceChecker().getSimpleName() + "()";
presenceChecks.add( new NullPresenceCheck( variableName ) );
presenceChecks.add( new SourceReferenceMethodPresenceCheck(
variableName,
entry.getPresenceChecker().getSimpleName()
) );
variableName = variableName + "." + entry.getReadAccessor().getSimpleName() + "()";
}
else {
break;
}
}
if ( presenceChecks.size() == 1 ) {
sourcePresenceChecker = presenceChecks.get( 0 );
}
else {
sourcePresenceChecker = new AllPresenceChecksPresenceCheck( presenceChecks );
}
}
}
return sourcePresenceChecker;

View File

@ -11,6 +11,7 @@ import java.util.Set;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.common.Type;
/**
@ -79,7 +80,7 @@ public class TypeConversion extends ModelElement implements Assignment {
}
@Override
public String getSourcePresenceCheckerReference() {
public PresenceCheck getSourcePresenceCheckerReference() {
return assignment.getSourcePresenceCheckerReference();
}

View File

@ -10,6 +10,7 @@ import java.util.Set;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.ModelElement;
import org.mapstruct.ap.internal.model.common.PresenceCheck;
import org.mapstruct.ap.internal.model.common.Type;
/**
@ -57,7 +58,7 @@ public abstract class AssignmentWrapper extends ModelElement implements Assignme
}
@Override
public String getSourcePresenceCheckerReference() {
public PresenceCheck getSourcePresenceCheckerReference() {
return decoratedAssignment.getSourcePresenceCheckerReference();
}

View File

@ -90,7 +90,7 @@ public interface Assignment {
*
* @return source reference
*/
String getSourcePresenceCheckerReference();
PresenceCheck getSourcePresenceCheckerReference();
/**
* the source type used in the matching process

View File

@ -0,0 +1,24 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.internal.model.common;
import java.util.Set;
/**
* Marker interface for presence checks.
*
* @author Filip Hrisafov
*/
public interface PresenceCheck {
/**
* returns all types required as import by the presence check.
*
* @return imported types
*/
Set<Type> getImportTypes();
}

View File

@ -7,6 +7,7 @@ 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;
@ -30,7 +31,7 @@ public class SourceRHS extends ModelElement implements Assignment {
private String sourceLoopVarName;
private final Set<String> existingVariableNames;
private final String sourceErrorMessagePart;
private final String sourcePresenceCheckerReference;
private final PresenceCheck sourcePresenceCheckerReference;
private boolean useElementAsSourceTypeForMatching = false;
private final String sourceParameterName;
@ -39,7 +40,7 @@ public class SourceRHS extends ModelElement implements Assignment {
this( sourceReference, sourceReference, null, sourceType, existingVariableNames, sourceErrorMessagePart );
}
public SourceRHS(String sourceParameterName, String sourceReference, String sourcePresenceCheckerReference,
public SourceRHS(String sourceParameterName, String sourceReference, PresenceCheck sourcePresenceCheckerReference,
Type sourceType, Set<String> existingVariableNames, String sourceErrorMessagePart ) {
this.sourceReference = sourceReference;
this.sourceType = sourceType;
@ -60,7 +61,7 @@ public class SourceRHS extends ModelElement implements Assignment {
}
@Override
public String getSourcePresenceCheckerReference() {
public PresenceCheck getSourcePresenceCheckerReference() {
return sourcePresenceCheckerReference;
}
@ -96,6 +97,10 @@ public class SourceRHS extends ModelElement implements Assignment {
@Override
public Set<Type> getImportTypes() {
if ( sourcePresenceCheckerReference != null ) {
return sourcePresenceCheckerReference.getImportTypes();
}
return Collections.emptySet();
}

View File

@ -0,0 +1,59 @@
/*
* 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.Collection;
import java.util.Collections;
import java.util.HashSet;
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 AllPresenceChecksPresenceCheck extends ModelElement implements PresenceCheck {
private final Collection<PresenceCheck> presenceChecks;
public AllPresenceChecksPresenceCheck(Collection<PresenceCheck> presenceChecks) {
this.presenceChecks = presenceChecks;
}
public Collection<PresenceCheck> getPresenceChecks() {
return presenceChecks;
}
@Override
public Set<Type> getImportTypes() {
Set<Type> importTypes = new HashSet<>();
for ( PresenceCheck presenceCheck : presenceChecks ) {
importTypes.addAll( presenceCheck.getImportTypes() );
}
return importTypes;
}
@Override
public boolean equals(Object o) {
if ( this == o ) {
return true;
}
if ( o == null || getClass() != o.getClass() ) {
return false;
}
AllPresenceChecksPresenceCheck that = (AllPresenceChecksPresenceCheck) o;
return Objects.equals( presenceChecks, that.presenceChecks );
}
@Override
public int hashCode() {
return Objects.hash( presenceChecks );
}
}

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.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 NullPresenceCheck extends ModelElement implements PresenceCheck {
private final String sourceReference;
public NullPresenceCheck(String sourceReference) {
this.sourceReference = sourceReference;
}
public String getSourceReference() {
return sourceReference;
}
@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;
}
NullPresenceCheck that = (NullPresenceCheck) o;
return Objects.equals( sourceReference, that.sourceReference );
}
@Override
public int hashCode() {
return Objects.hash( sourceReference );
}
}

View File

@ -0,0 +1,59 @@
/*
* 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 SourceReferenceMethodPresenceCheck extends ModelElement implements PresenceCheck {
private final String sourceReference;
private final String methodName;
public SourceReferenceMethodPresenceCheck(String sourceReference, String methodName) {
this.sourceReference = sourceReference;
this.methodName = methodName;
}
public String getSourceReference() {
return sourceReference;
}
public String getMethodName() {
return methodName;
}
@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;
}
SourceReferenceMethodPresenceCheck that = (SourceReferenceMethodPresenceCheck) o;
return Objects.equals( sourceReference, that.sourceReference ) &&
Objects.equals( methodName, that.methodName );
}
@Override
public int hashCode() {
return Objects.hash( sourceReference, methodName );
}
}

View File

@ -11,13 +11,13 @@
return ${returnType.null};
}
<#list propertyEntries as entry>
<#if entry.presenceCheckerName?? >
if ( <#if entry_index != 0><@localVarName index=entry_index/> == null || </#if>!<@localVarName index=entry_index/>.${entry.presenceCheckerName}() ) {
<#if entry.presenceChecker?? >
if ( <#if entry_index != 0>${entry.previousPropertyName} == null || </#if>!<@includeModel object=entry.presenceChecker /> ) {
return ${returnType.null};
}
</#if>
<@includeModel object=entry.type.typeBound/> ${entry.name} = <@localVarName index=entry_index/>.${entry.accessorName};
<#if !entry.presenceCheckerName?? >
<@includeModel object=entry.type.typeBound/> ${entry.name} = ${entry.previousPropertyName}.${entry.accessorName};
<#if !entry.presenceChecker?? >
<#if !entry.type.primitive>
if ( ${entry.name} == null ) {
return ${returnType.null};
@ -29,7 +29,6 @@
</#if>
</#list>
}
<#macro localVarName index><#if index == 0>${sourceParameter.name}<#else>${propertyEntries[index-1].name}</#if></#macro>
<#macro throws>
<#if (thrownTypes?size > 0)><#lt> throws </#if><@compress single_line=true>
<#list thrownTypes as exceptionType>

View File

@ -15,7 +15,7 @@
-->
<#macro handleSourceReferenceNullCheck>
<#if sourcePresenceCheckerReference??>
if ( ${sourcePresenceCheckerReference} ) {
if ( <@includeModel object=sourcePresenceCheckerReference /> ) {
<#nested>
}
<@elseDefaultAssignment/>
@ -57,7 +57,7 @@
-->
<#macro handleLocalVarNullCheck needs_explicit_local_var>
<#if sourcePresenceCheckerReference??>
if ( ${sourcePresenceCheckerReference} ) {
if ( <@includeModel object=sourcePresenceCheckerReference /> ) {
<#if needs_explicit_local_var>
<@includeModel object=nullCheckLocalVarType/> ${nullCheckLocalVarName} = <@lib.handleAssignment/>;
<#nested>

View File

@ -0,0 +1,16 @@
<#--
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.AllPresenceChecksPresenceCheck" -->
<@compress single_line=true>
<#list presenceChecks as presenceCheck>
<#if presenceCheck_index != 0>
&&
</#if>
<@includeModel object=presenceCheck />
</#list>
</@compress>

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.presence.NullPresenceCheck" -->
${sourceReference} != null

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.presence.SourceReferenceMethodPresenceCheck" -->
${sourceReference}.${methodName}()