#3806: Properly apply NullValuePropertyMappingStrategy.IGNORE for collections / maps without setters

Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
Yang Tang 2025-05-31 17:13:50 +08:00 committed by GitHub
parent 5464c3cff8
commit 8fc97f5f62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 171 additions and 1 deletions

View File

@ -240,6 +240,7 @@ public class CollectionAssignmentBuilder {
result,
method.getThrownTypes(),
targetType,
nvpms,
targetAccessorType.isFieldAssignment()
);
}

View File

@ -9,9 +9,12 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem;
import org.mapstruct.ap.internal.model.common.Assignment;
import org.mapstruct.ap.internal.model.common.Type;
import static org.mapstruct.ap.internal.gem.NullValuePropertyMappingStrategyGem.IGNORE;
/**
* This wrapper handles the situation were an assignment must be done via a target getter method because there
* is no setter available.
@ -26,6 +29,14 @@ import org.mapstruct.ap.internal.model.common.Type;
* @author Sjaak Derksen
*/
public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAndMaps {
private final boolean ignoreMapNull;
public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment,
List<Type> thrownTypesToExclude,
Type targetType,
boolean fieldAssignment) {
this( decoratedAssignment, thrownTypesToExclude, targetType, null, fieldAssignment );
}
/**
* @param decoratedAssignment source RHS
@ -36,6 +47,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd
public GetterWrapperForCollectionsAndMaps(Assignment decoratedAssignment,
List<Type> thrownTypesToExclude,
Type targetType,
NullValuePropertyMappingStrategyGem nvpms,
boolean fieldAssignment) {
super(
@ -44,6 +56,7 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd
targetType,
fieldAssignment
);
this.ignoreMapNull = nvpms == IGNORE;
}
@Override
@ -54,4 +67,8 @@ public class GetterWrapperForCollectionsAndMaps extends WrapperForCollectionsAnd
}
return imported;
}
public boolean isIgnoreMapNull() {
return ignoreMapNull;
}
}

View File

@ -10,10 +10,13 @@
<@lib.sourceLocalVarAssignment/>
if ( ${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing /> != null ) {
<@lib.handleExceptions>
<#if ext.existingInstanceMapping>
<#if ext.existingInstanceMapping && !ignoreMapNull>
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear();
</#if>
<@lib.handleLocalVarNullCheck needs_explicit_local_var=false>
<#if ext.existingInstanceMapping && ignoreMapNull>
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.clear();
</#if>
${ext.targetBeanName}.${ext.targetWriteAccessorName}<@lib.handleWriteAccesing />.<#if ext.targetType.collectionType>addAll<#else>putAll</#if>( <@lib.handleWithAssignmentOrNullCheckVar/> );
</@lib.handleLocalVarNullCheck>
</@lib.handleExceptions>

View File

@ -0,0 +1,63 @@
/*
* 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._3806;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.NullValuePropertyMappingStrategy;
import org.mapstruct.factory.Mappers;
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface Issue3806Mapper {
Issue3806Mapper INSTANCE = Mappers.getMapper( Issue3806Mapper.class );
void update(@MappingTarget Target target, Target source);
class Target {
private final Collection<String> authors;
private final Map<String, String> booksByAuthor;
protected Collection<String> books;
protected Map<String, String> booksByPublisher;
public Target(Collection<String> authors, Map<String, String> booksByAuthor) {
this.authors = authors != null ? new ArrayList<>( authors ) : null;
this.booksByAuthor = booksByAuthor != null ? new HashMap<>( booksByAuthor ) : null;
}
public Collection<String> getAuthors() {
return authors;
}
public Map<String, String> getBooksByAuthor() {
return booksByAuthor;
}
public Collection<String> getBooks() {
return books;
}
public void setBooks(Collection<String> books) {
this.books = books;
}
public Map<String, String> getBooksByPublisher() {
return booksByPublisher;
}
public void setBooksByPublisher(Map<String, String> booksByPublisher) {
this.booksByPublisher = booksByPublisher;
}
}
}

View File

@ -0,0 +1,86 @@
/*
* 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._3806;
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;
@WithClasses(Issue3806Mapper.class)
@IssueKey("3806")
class Issue3806Test {
@ProcessorTest
void shouldNotClearGetterOnlyCollectionsInUpdateMapping() {
Map<String, String> booksByAuthor = new HashMap<>();
booksByAuthor.put( "author1", "book1" );
booksByAuthor.put( "author2", "book2" );
List<String> authors = new ArrayList<>();
authors.add( "author1" );
authors.add( "author2" );
List<String> books = new ArrayList<>();
books.add( "book1" );
books.add( "book2" );
Map<String, String> booksByPublisher = new HashMap<>();
booksByPublisher.put( "publisher1", "book1" );
booksByPublisher.put( "publisher2", "book2" );
Issue3806Mapper.Target target = new Issue3806Mapper.Target( authors, booksByAuthor );
target.setBooks( books );
target.setBooksByPublisher( booksByPublisher );
Issue3806Mapper.Target source = new Issue3806Mapper.Target( null, null );
Issue3806Mapper.INSTANCE.update( target, source );
assertThat( target.getAuthors() ).containsExactly( "author1", "author2" );
assertThat( target.getBooksByAuthor() )
.containsOnly(
entry( "author1", "book1" ),
entry( "author2", "book2" )
);
assertThat( target.getBooks() ).containsExactly( "book1", "book2" );
assertThat( target.getBooksByPublisher() )
.containsOnly(
entry( "publisher1", "book1" ),
entry( "publisher2", "book2" )
);
booksByAuthor = new HashMap<>();
booksByAuthor.put( "author3", "book3" );
authors = new ArrayList<>();
authors.add( "author3" );
books = new ArrayList<>();
books.add( "book3" );
booksByPublisher = new HashMap<>();
booksByPublisher.put( "publisher3", "book3" );
source = new Issue3806Mapper.Target( authors, booksByAuthor );
source.setBooks( books );
source.setBooksByPublisher( booksByPublisher );
Issue3806Mapper.INSTANCE.update( target, source );
assertThat( target.getAuthors() ).containsExactly( "author3" );
assertThat( target.getBooksByAuthor() )
.containsOnly(
entry( "author3", "book3" )
);
assertThat( target.getBooks() ).containsExactly( "book3" );
assertThat( target.getBooksByPublisher() )
.containsOnly(
entry( "publisher3", "book3" )
);
}
}