#3678 before / after mapping for type using builder should only be kept if they are using the actual type in @TargetType or @MappingTarget

This commit is contained in:
Filip Hrisafov 2024-09-15 10:28:20 +02:00
parent 4c1df35ba6
commit 3011dd77d7
3 changed files with 206 additions and 5 deletions

View File

@ -408,9 +408,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
existingVariableNames
) );
// remove methods without parameters as they are already being invoked
removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType );
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
keepMappingReferencesUsingTarget( beforeMappingReferencesWithFinalizedReturnType, actualReturnType );
keepMappingReferencesUsingTarget( afterMappingReferencesWithFinalizedReturnType, actualReturnType );
}
Map<String, PresenceCheck> presenceChecksByParameter = new LinkedHashMap<>();
@ -453,8 +452,32 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
);
}
private void removeMappingReferencesWithoutSourceParameters(List<LifecycleCallbackMethodReference> references) {
references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() );
private void keepMappingReferencesUsingTarget(List<LifecycleCallbackMethodReference> references, Type type) {
references.removeIf( reference -> {
List<ParameterBinding> bindings = reference.getParameterBindings();
if ( bindings.isEmpty() ) {
return true;
}
for ( ParameterBinding binding : bindings ) {
if ( binding.isMappingTarget() ) {
if ( type.isAssignableTo( binding.getType() ) ) {
// If the mapping target matches the type then we need to keep this
return false;
}
}
else if ( binding.isTargetType() ) {
Type targetType = binding.getType();
List<Type> targetTypeTypeParameters = targetType.getTypeParameters();
if ( targetTypeTypeParameters.size() == 1 ) {
if ( type.isAssignableTo( targetTypeTypeParameters.get( 0 ) ) ) {
return false;
}
}
}
}
return true;
} );
}
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {

View File

@ -0,0 +1,128 @@
/*
* 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._3678;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper
public interface Issue3678Mapper {
Issue3678Mapper INSTANCE = Mappers.getMapper( Issue3678Mapper.class );
@Mapping(target = "name", source = "sourceA.name")
@Mapping(target = "description", source = "sourceB.description")
Target map(SourceA sourceA, SourceB sourceB, @Context MappingContext context);
@Mapping(target = "description", constant = "some description")
Target map(SourceA sourceA, @Context MappingContext context);
class MappingContext {
private final List<String> invokedMethods = new ArrayList<>();
@BeforeMapping
public void beforeMappingSourceA(SourceA sourceA) {
invokedMethods.add( "beforeMappingSourceA" );
}
@AfterMapping
public void afterMappingSourceB(SourceA sourceA) {
invokedMethods.add( "afterMappingSourceA" );
}
@BeforeMapping
public void beforeMappingSourceB(SourceB sourceB) {
invokedMethods.add( "beforeMappingSourceB" );
}
@AfterMapping
public void afterMappingSourceB(SourceB sourceB) {
invokedMethods.add( "afterMappingSourceB" );
}
public List<String> getInvokedMethods() {
return invokedMethods;
}
}
class SourceA {
private final String name;
public SourceA(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class SourceB {
private final String description;
public SourceB(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
final class Target {
private final String name;
private final String description;
private Target(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String name;
private String description;
public Builder name(String name) {
this.name = name;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Target build() {
return new Target( this.name, this.description );
}
}
}
}

View File

@ -0,0 +1,50 @@
/*
* 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._3678;
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;
@IssueKey("3678")
@WithClasses(Issue3678Mapper.class)
public class Issue3678Test {
@ProcessorTest
void beforeAndAfterMappingOnlyCalledOnceForTwoSources() {
Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext();
Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" );
Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" );
Issue3678Mapper.INSTANCE.map( sourceA, sourceB, mappingContext );
assertThat( mappingContext.getInvokedMethods() )
.containsExactly(
"beforeMappingSourceA",
"beforeMappingSourceB",
"afterMappingSourceA",
"afterMappingSourceB"
);
}
@ProcessorTest
void beforeAndAfterMappingOnlyCalledOnceForSingleSource() {
Issue3678Mapper.MappingContext mappingContext = new Issue3678Mapper.MappingContext();
Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" );
Issue3678Mapper.INSTANCE.map( sourceA, mappingContext );
assertThat( mappingContext.getInvokedMethods() )
.containsExactly(
"beforeMappingSourceA",
"afterMappingSourceA"
);
}
}