#3884 Ensure NullValuePropertyMappingStrategy.SET_TO_DEFAULT initializes empty collection/map when target is null

Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
Yang Tang 2025-06-15 14:29:45 +08:00 committed by GitHub
parent c90c93630e
commit e4bc1cdf1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 248 additions and 0 deletions

View File

@ -30,6 +30,10 @@
<@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment> <@lib.handleLocalVarNullCheck needs_explicit_local_var=directAssignment>
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/></#if></@lib.handleWrite>; ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><#if directAssignment><@wrapLocalVarInCollectionInitializer/><#else><@lib.handleWithAssignmentOrNullCheckVar/></#if></@lib.handleWrite>;
</@lib.handleLocalVarNullCheck> </@lib.handleLocalVarNullCheck>
<#if !ext.defaultValueAssignment?? && !sourcePresenceCheckerReference?? && mapNullToDefault>else {
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWrite><@lib.initTargetObject/></@lib.handleWrite>;
}
</#if>
</#macro> </#macro>
<#-- <#--
wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf) wraps the local variable in a collection initializer (new collection, or EnumSet.copyOf)

View File

@ -0,0 +1,22 @@
/*
* 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._3884;
import java.util.List;
import java.util.Map;
/**
* Destination type interface for testing null value property mapping strategy with Map properties.
*/
public interface DestinationType {
Map<String, String> getAttributes();
void setAttributes(Map<String, String> attributes);
List<String> getItems();
void setItems(List<String> items);
}

View File

@ -0,0 +1,21 @@
/*
* 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._3884;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
/**
* Mapper for testing null value property mapping strategy with Map properties.
*/
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT)
public interface Issue3884Mapper {
Issue3884Mapper INSTANCE = Mappers.getMapper( Issue3884Mapper.class );
void update(@MappingTarget DestinationType destination, SourceType source);
}

View File

@ -0,0 +1,116 @@
/*
* 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._3884;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Test for issue 3884: NullValuePropertyMappingStrategy.SET_TO_DEFAULT should set target Map/Collection to default
* when source and target are all null.
*/
@IssueKey("3884")
@WithClasses({
DestinationType.class,
SourceType.class,
Issue3884Mapper.class
})
public class Issue3884Test {
@ProcessorTest
public void shouldSetTargetToDefaultWhenBothSourceAndTargetAreNull() {
DestinationType target = new SourceType();
SourceType source = new SourceType();
assertThat( source.getAttributes() ).isNull();
assertThat( target.getAttributes() ).isNull();
assertThat( source.getItems() ).isNull();
assertThat( target.getItems() ).isNull();
Issue3884Mapper.INSTANCE.update( target, source );
assertThat( target.getAttributes() ).isEmpty();
assertThat( target.getItems() ).isEmpty();
}
@ProcessorTest
public void shouldClearTargetWhenSourceIsNullAndTargetIsInitialized() {
DestinationType target = new SourceType();
SourceType source = new SourceType();
Map<String, String> targetAttributes = new HashMap<>();
targetAttributes.put( "targetKey", "targetValue" );
target.setAttributes( targetAttributes );
List<String> targetItems = new ArrayList<>();
targetItems.add( "targetItem" );
target.setItems( targetItems );
assertThat( source.getAttributes() ).isNull();
assertThat( target.getAttributes() ).isNotEmpty();
assertThat( source.getItems() ).isNull();
assertThat( target.getItems() ).isNotEmpty();
Issue3884Mapper.INSTANCE.update( target, source );
assertThat( target.getAttributes() ).isEmpty();
assertThat( target.getItems() ).isEmpty();
}
@ProcessorTest
public void shouldCopySourceToTargetWhenSourceIsInitializedAndTargetIsNull() {
DestinationType target = new SourceType();
SourceType source = new SourceType();
source.setAttributes( Map.of( "sourceKey", "sourceValue" ) );
source.setItems( List.of( "sourceItem" ) );
assertThat( source.getAttributes() ).isNotEmpty();
assertThat( target.getAttributes() ).isNull();
assertThat( source.getItems() ).isNotEmpty();
assertThat( target.getItems() ).isNull();
Issue3884Mapper.INSTANCE.update( target, source );
assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) );
assertThat( target.getItems() ).containsExactly( "sourceItem" );
}
@ProcessorTest
public void shouldCopySourceToTargetWhenBothSourceAndTargetAreInitialized() {
DestinationType target = new SourceType();
SourceType source = new SourceType();
source.setAttributes( Map.of( "sourceKey", "sourceValue" ) );
source.setItems( List.of( "sourceItem" ) );
Map<String, String> targetAttributes = new HashMap<>();
targetAttributes.put( "targetKey", "targetValue" );
target.setAttributes( targetAttributes );
List<String> targetItems = new ArrayList<>();
targetItems.add( "targetItem" );
target.setItems( targetItems );
assertThat( source.getAttributes() ).isNotEmpty();
assertThat( target.getAttributes() ).isNotEmpty();
assertThat( source.getItems() ).isNotEmpty();
assertThat( target.getItems() ).isNotEmpty();
Issue3884Mapper.INSTANCE.update( target, source );
assertThat( target.getAttributes() ).containsOnly( entry( "sourceKey", "sourceValue" ) );
assertThat( target.getItems() ).containsExactly( "sourceItem" );
}
}

View File

@ -0,0 +1,37 @@
/*
* 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._3884;
import java.util.List;
import java.util.Map;
/**
* Source type class implementing DestinationType for testing null value property mapping strategy with Map properties.
*/
public class SourceType implements DestinationType {
private Map<String, String> attributes;
private List<String> items;
@Override
public Map<String, String> getAttributes() {
return attributes;
}
@Override
public void setAttributes(Map<String, String> attributes) {
this.attributes = attributes;
}
@Override
public List<String> getItems() {
return items;
}
@Override
public void setItems(List<String> items) {
this.items = items;
}
}

View File

@ -67,6 +67,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list != null ) { if ( list != null ) {
target.setStrings( new LinkedHashSet<String>( list ) ); target.setStrings( new LinkedHashSet<String>( list ) );
} }
else {
target.setStrings( new LinkedHashSet<String>() );
}
} }
if ( target.getLongs() != null ) { if ( target.getLongs() != null ) {
Set<Long> set = stringListToLongSet( source.getStrings() ); Set<Long> set = stringListToLongSet( source.getStrings() );
@ -83,6 +86,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set != null ) { if ( set != null ) {
target.setLongs( set ); target.setLongs( set );
} }
else {
target.setLongs( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsInitialized() != null ) { if ( target.getStringsInitialized() != null ) {
List<String> list1 = source.getStringsInitialized(); List<String> list1 = source.getStringsInitialized();
@ -99,6 +105,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list1 != null ) { if ( list1 != null ) {
target.setStringsInitialized( new LinkedHashSet<String>( list1 ) ); target.setStringsInitialized( new LinkedHashSet<String>( list1 ) );
} }
else {
target.setStringsInitialized( new LinkedHashSet<String>() );
}
} }
if ( target.getLongsInitialized() != null ) { if ( target.getLongsInitialized() != null ) {
Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() ); Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() );
@ -115,6 +124,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set1 != null ) { if ( set1 != null ) {
target.setLongsInitialized( set1 ); target.setLongsInitialized( set1 );
} }
else {
target.setLongsInitialized( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsWithDefault() != null ) { if ( target.getStringsWithDefault() != null ) {
List<String> list2 = source.getStringsWithDefault(); List<String> list2 = source.getStringsWithDefault();
@ -157,6 +169,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list != null ) { if ( list != null ) {
target.setStrings( new LinkedHashSet<String>( list ) ); target.setStrings( new LinkedHashSet<String>( list ) );
} }
else {
target.setStrings( new LinkedHashSet<String>() );
}
} }
if ( target.getLongs() != null ) { if ( target.getLongs() != null ) {
Set<Long> set = stringListToLongSet( source.getStrings() ); Set<Long> set = stringListToLongSet( source.getStrings() );
@ -173,6 +188,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set != null ) { if ( set != null ) {
target.setLongs( set ); target.setLongs( set );
} }
else {
target.setLongs( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsInitialized() != null ) { if ( target.getStringsInitialized() != null ) {
List<String> list1 = source.getStringsInitialized(); List<String> list1 = source.getStringsInitialized();
@ -189,6 +207,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list1 != null ) { if ( list1 != null ) {
target.setStringsInitialized( new LinkedHashSet<String>( list1 ) ); target.setStringsInitialized( new LinkedHashSet<String>( list1 ) );
} }
else {
target.setStringsInitialized( new LinkedHashSet<String>() );
}
} }
if ( target.getLongsInitialized() != null ) { if ( target.getLongsInitialized() != null ) {
Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() ); Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() );
@ -205,6 +226,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set1 != null ) { if ( set1 != null ) {
target.setLongsInitialized( set1 ); target.setLongsInitialized( set1 );
} }
else {
target.setLongsInitialized( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsWithDefault() != null ) { if ( target.getStringsWithDefault() != null ) {
List<String> list2 = source.getStringsWithDefault(); List<String> list2 = source.getStringsWithDefault();

View File

@ -67,6 +67,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list != null ) { if ( list != null ) {
target.setStrings( new LinkedHashSet<String>( list ) ); target.setStrings( new LinkedHashSet<String>( list ) );
} }
else {
target.setStrings( new LinkedHashSet<String>() );
}
} }
if ( target.getLongs() != null ) { if ( target.getLongs() != null ) {
Set<Long> set = stringListToLongSet( source.getStrings() ); Set<Long> set = stringListToLongSet( source.getStrings() );
@ -83,6 +86,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set != null ) { if ( set != null ) {
target.setLongs( set ); target.setLongs( set );
} }
else {
target.setLongs( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsInitialized() != null ) { if ( target.getStringsInitialized() != null ) {
List<String> list1 = source.getStringsInitialized(); List<String> list1 = source.getStringsInitialized();
@ -99,6 +105,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list1 != null ) { if ( list1 != null ) {
target.setStringsInitialized( new LinkedHashSet<String>( list1 ) ); target.setStringsInitialized( new LinkedHashSet<String>( list1 ) );
} }
else {
target.setStringsInitialized( new LinkedHashSet<String>() );
}
} }
if ( target.getLongsInitialized() != null ) { if ( target.getLongsInitialized() != null ) {
Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() ); Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() );
@ -115,6 +124,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set1 != null ) { if ( set1 != null ) {
target.setLongsInitialized( set1 ); target.setLongsInitialized( set1 );
} }
else {
target.setLongsInitialized( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsWithDefault() != null ) { if ( target.getStringsWithDefault() != null ) {
List<String> list2 = source.getStringsWithDefault(); List<String> list2 = source.getStringsWithDefault();
@ -157,6 +169,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list != null ) { if ( list != null ) {
target.setStrings( new LinkedHashSet<String>( list ) ); target.setStrings( new LinkedHashSet<String>( list ) );
} }
else {
target.setStrings( new LinkedHashSet<String>() );
}
} }
if ( target.getLongs() != null ) { if ( target.getLongs() != null ) {
Set<Long> set = stringListToLongSet( source.getStrings() ); Set<Long> set = stringListToLongSet( source.getStrings() );
@ -173,6 +188,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set != null ) { if ( set != null ) {
target.setLongs( set ); target.setLongs( set );
} }
else {
target.setLongs( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsInitialized() != null ) { if ( target.getStringsInitialized() != null ) {
List<String> list1 = source.getStringsInitialized(); List<String> list1 = source.getStringsInitialized();
@ -189,6 +207,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( list1 != null ) { if ( list1 != null ) {
target.setStringsInitialized( new LinkedHashSet<String>( list1 ) ); target.setStringsInitialized( new LinkedHashSet<String>( list1 ) );
} }
else {
target.setStringsInitialized( new LinkedHashSet<String>() );
}
} }
if ( target.getLongsInitialized() != null ) { if ( target.getLongsInitialized() != null ) {
Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() ); Set<Long> set1 = stringListToLongSet( source.getStringsInitialized() );
@ -205,6 +226,9 @@ public class DomainDtoWithNvmsDefaultMapperImpl implements DomainDtoWithNvmsDefa
if ( set1 != null ) { if ( set1 != null ) {
target.setLongsInitialized( set1 ); target.setLongsInitialized( set1 );
} }
else {
target.setLongsInitialized( new LinkedHashSet<Long>() );
}
} }
if ( target.getStringsWithDefault() != null ) { if ( target.getStringsWithDefault() != null ) {
List<String> list2 = source.getStringsWithDefault(); List<String> list2 = source.getStringsWithDefault();