Skip to content

Commit dab3eaf

Browse files
authored
#3949: Support SET_TO_NULL for overloaded target methods, requiring a cast (#3988)
1 parent e0fa288 commit dab3eaf

File tree

19 files changed

+399
-9
lines changed

19 files changed

+399
-9
lines changed

processor/src/main/java/org/mapstruct/ap/internal/model/CollectionAssignmentBuilder.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ public Assignment build() {
163163
targetType,
164164
true,
165165
nvpms == SET_TO_NULL && !targetType.isPrimitive(),
166-
nvpms == SET_TO_DEFAULT
166+
nvpms == SET_TO_DEFAULT,
167+
false
167168
);
168169
}
169170
else if ( method.isUpdateMethod() && !targetImmutable ) {

processor/src/main/java/org/mapstruct/ap/internal/model/PropertyMapping.java

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import java.util.Set;
1414
import java.util.function.Supplier;
1515
import javax.lang.model.element.AnnotationMirror;
16+
import javax.lang.model.element.Element;
17+
import javax.lang.model.element.ElementKind;
1618

1719
import org.mapstruct.ap.internal.gem.BuilderGem;
1820
import org.mapstruct.ap.internal.gem.NullValueCheckStrategyGem;
@@ -454,7 +456,8 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) {
454456
targetType,
455457
!rhs.isSourceReferenceParameter(),
456458
nvpms == SET_TO_NULL && !targetType.isPrimitive(),
457-
nvpms == SET_TO_DEFAULT
459+
nvpms == SET_TO_DEFAULT,
460+
hasTwoOrMoreSettersWithName()
458461
);
459462
}
460463
else {
@@ -471,7 +474,10 @@ private Assignment assignToPlainViaSetter(Type targetType, Assignment rhs) {
471474
isFieldAssignment(),
472475
includeSourceNullCheck,
473476
includeSourceNullCheck && nvpms == SET_TO_NULL && !targetType.isPrimitive(),
474-
nvpms == SET_TO_DEFAULT );
477+
nvpms == SET_TO_DEFAULT,
478+
hasTwoOrMoreSettersWithName(),
479+
targetType
480+
);
475481
}
476482
}
477483

@@ -547,12 +553,33 @@ else if ( result.getSourceType().isStreamType() ) {
547553
isFieldAssignment(),
548554
true,
549555
nvpms == SET_TO_NULL && !targetType.isPrimitive(),
550-
nvpms == SET_TO_DEFAULT
556+
nvpms == SET_TO_DEFAULT,
557+
hasTwoOrMoreSettersWithName(),
558+
targetType
551559
);
552560
}
553561
return result;
554562
}
555563

564+
private boolean hasTwoOrMoreSettersWithName() {
565+
Element enclosingClass = this.targetWriteAccessor.getElement().getEnclosingElement();
566+
if ( enclosingClass == null || !( ElementKind.CLASS.equals( enclosingClass.getKind() )
567+
|| ElementKind.INTERFACE.equals( enclosingClass.getKind() ) ) ) {
568+
return false;
569+
}
570+
String simpleWriteAccessorName = this.targetWriteAccessor.getSimpleName();
571+
boolean firstMatchFound = false;
572+
for ( Accessor setter : ctx.getTypeFactory().getType( enclosingClass.asType() ).getSetters() ) {
573+
if ( setter.getSimpleName().equals( simpleWriteAccessorName ) ) {
574+
if ( firstMatchFound ) {
575+
return true;
576+
}
577+
firstMatchFound = true;
578+
}
579+
}
580+
return false;
581+
}
582+
556583
private Assignment assignToCollection(Type targetType, AccessorType targetAccessorType,
557584
Assignment rhs) {
558585
return new CollectionAssignmentBuilder()
@@ -991,6 +1018,7 @@ public PropertyMapping build() {
9911018
targetType,
9921019
false,
9931020
false,
1021+
false,
9941022
false );
9951023
}
9961024
else {

processor/src/main/java/org/mapstruct/ap/internal/model/assignment/SetterWrapper.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
package org.mapstruct.ap.internal.model.assignment;
77

88
import java.util.ArrayList;
9+
import java.util.HashSet;
910
import java.util.List;
11+
import java.util.Set;
1012

1113
import org.mapstruct.ap.internal.model.common.Assignment;
1214
import org.mapstruct.ap.internal.model.common.Type;
@@ -22,19 +24,25 @@ public class SetterWrapper extends AssignmentWrapper {
2224
private final boolean includeSourceNullCheck;
2325
private final boolean setExplicitlyToNull;
2426
private final boolean setExplicitlyToDefault;
27+
private final boolean mustCastForNull;
28+
private final Type nullCastType;
2529

2630
public SetterWrapper(Assignment rhs,
2731
List<Type> thrownTypesToExclude,
2832
boolean fieldAssignment,
2933
boolean includeSourceNullCheck,
3034
boolean setExplicitlyToNull,
31-
boolean setExplicitlyToDefault) {
35+
boolean setExplicitlyToDefault,
36+
boolean mustCastForNull,
37+
Type nullCastType) {
3238

3339
super( rhs, fieldAssignment );
3440
this.thrownTypesToExclude = thrownTypesToExclude;
3541
this.includeSourceNullCheck = includeSourceNullCheck;
3642
this.setExplicitlyToDefault = setExplicitlyToDefault;
3743
this.setExplicitlyToNull = setExplicitlyToNull;
44+
this.mustCastForNull = mustCastForNull;
45+
this.nullCastType = nullCastType;
3846
}
3947

4048
public SetterWrapper(Assignment rhs, List<Type> thrownTypesToExclude, boolean fieldAssignment ) {
@@ -43,6 +51,8 @@ public SetterWrapper(Assignment rhs, List<Type> thrownTypesToExclude, boolean fi
4351
this.includeSourceNullCheck = false;
4452
this.setExplicitlyToNull = false;
4553
this.setExplicitlyToDefault = false;
54+
this.mustCastForNull = false;
55+
this.nullCastType = null;
4656
}
4757

4858
@Override
@@ -59,6 +69,15 @@ public List<Type> getThrownTypes() {
5969
return result;
6070
}
6171

72+
@Override
73+
public Set<Type> getImportTypes() {
74+
Set<Type> imported = new HashSet<>( super.getImportTypes() );
75+
if ( isSetExplicitlyToNull() && isMustCastForNull() ) {
76+
imported.add( nullCastType );
77+
}
78+
return imported;
79+
}
80+
6281
public boolean isSetExplicitlyToNull() {
6382
return setExplicitlyToNull;
6483
}
@@ -71,4 +90,7 @@ public boolean isIncludeSourceNullCheck() {
7190
return includeSourceNullCheck;
7291
}
7392

93+
public boolean isMustCastForNull() {
94+
return mustCastForNull;
95+
}
7496
}

processor/src/main/java/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public class UpdateWrapper extends AssignmentWrapper {
2626
private final boolean includeSourceNullCheck;
2727
private final boolean setExplicitlyToNull;
2828
private final boolean setExplicitlyToDefault;
29+
private final boolean mustCastForNull;
2930

3031
public UpdateWrapper( Assignment decoratedAssignment,
3132
List<Type> thrownTypesToExclude,
@@ -34,14 +35,16 @@ public UpdateWrapper( Assignment decoratedAssignment,
3435
Type targetType,
3536
boolean includeSourceNullCheck,
3637
boolean setExplicitlyToNull,
37-
boolean setExplicitlyToDefault ) {
38+
boolean setExplicitlyToDefault,
39+
boolean mustCastForNull) {
3840
super( decoratedAssignment, fieldAssignment );
3941
this.thrownTypesToExclude = thrownTypesToExclude;
4042
this.factoryMethod = factoryMethod;
4143
this.targetImplementationType = determineImplType( factoryMethod, targetType );
4244
this.includeSourceNullCheck = includeSourceNullCheck;
4345
this.setExplicitlyToDefault = setExplicitlyToDefault;
4446
this.setExplicitlyToNull = setExplicitlyToNull;
47+
this.mustCastForNull = mustCastForNull;
4548
}
4649

4750
private static Type determineImplType(Assignment factoryMethod, Type targetType) {
@@ -100,4 +103,8 @@ public boolean isSetExplicitlyToNull() {
100103
public boolean isSetExplicitlyToDefault() {
101104
return setExplicitlyToDefault;
102105
}
106+
107+
public boolean isMustCastForNull() {
108+
return mustCastForNull;
109+
}
103110
}

processor/src/main/java/org/mapstruct/ap/internal/model/common/Type.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1061,7 +1061,7 @@ private TypeMirror boxed(TypeMirror possiblePrimitive) {
10611061
*
10621062
* @return an unmodifiable list of all setters
10631063
*/
1064-
private List<Accessor> getSetters() {
1064+
public List<Accessor> getSetters() {
10651065
if ( setters == null ) {
10661066
setters = Collections.unmodifiableList( filters.setterMethodsIn( getAllMethods() ) );
10671067
}

processor/src/main/resources/org/mapstruct/ap/internal/model/assignment/UpdateWrapper.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
}
1919
<#if setExplicitlyToDefault || setExplicitlyToNull>
2020
else {
21-
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}</#if></@lib.handleWrite>;
21+
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull>(<@includeModel object=ext.targetType/>) </#if>${ext.targetType.null}</#if></@lib.handleWrite>;
2222
}
2323
</#if>
2424
<#else>

processor/src/main/resources/org/mapstruct/ap/internal/model/macro/CommonMacros.ftl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
}
4646
<#elseif setExplicitlyToDefault || setExplicitlyToNull>
4747
else {
48-
<#if ext.targetBeanName?has_content>${ext.targetBeanName}.</#if>${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else>${ext.targetType.null}</#if></@lib.handleWrite>;
48+
<#if ext.targetBeanName?has_content>${ext.targetBeanName}.</#if>${ext.targetWriteAccessorName}<@lib.handleWrite><#if setExplicitlyToDefault><@lib.initTargetObject/><#else><#if mustCastForNull!false>(<@includeModel object=ext.targetType/>) </#if>${ext.targetType.null}</#if></@lib.handleWrite>;
4949
}
5050
</#if>
5151
</#macro>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.bugs._3949;
7+
8+
import java.time.LocalDate;
9+
10+
public class DateSource {
11+
public LocalDate getDate() {
12+
return null;
13+
}
14+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.bugs._3949;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.MappingTarget;
10+
import org.mapstruct.NullValueCheckStrategy;
11+
import org.mapstruct.NullValuePropertyMappingStrategy;
12+
import org.mapstruct.factory.Mappers;
13+
14+
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
15+
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
16+
public interface Issue3949ClassMapper {
17+
18+
Issue3949ClassMapper INSTANCE = Mappers.getMapper( Issue3949ClassMapper.class );
19+
20+
void overwriteDate(@MappingTarget TargetDate target, DateSource dateSource);
21+
22+
void overwriteString(@MappingTarget TargetString target, StringSource stringSource);
23+
24+
void overwriteDateWithConversion(@MappingTarget TargetDate target, StringSource dateSource);
25+
26+
void overwriteStringWithConversion(@MappingTarget TargetString target, DateSource stringSource);
27+
28+
void updateParent(@MappingTarget ParentTarget target, ParentSource source);
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.ap.test.bugs._3949;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.MappingTarget;
10+
import org.mapstruct.NullValueCheckStrategy;
11+
import org.mapstruct.NullValuePropertyMappingStrategy;
12+
import org.mapstruct.factory.Mappers;
13+
14+
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
15+
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_NULL)
16+
public interface Issue3949InterfaceMapper {
17+
18+
Issue3949InterfaceMapper INSTANCE = Mappers.getMapper( Issue3949InterfaceMapper.class );
19+
20+
void overwriteDate(@MappingTarget TargetDateInterface target, DateSource dateSource);
21+
22+
void overwriteString(@MappingTarget TargetStringInterface target, StringSource stringSource);
23+
24+
void overwriteDateWithConversion(@MappingTarget TargetDateInterface target, StringSource dateSource);
25+
26+
void overwriteStringWithConversion(@MappingTarget TargetStringInterface target, DateSource stringSource);
27+
28+
void updateParent(@MappingTarget ParentTargetInterface target, ParentSource source);
29+
}

0 commit comments

Comments
 (0)