#3678 Prevent duplicate @BeforeMapping and @AfterMapping calls on classes that use the Builder pattern.

This commit is contained in:
thunderhook 2024-08-21 23:47:59 +02:00
parent b452d7f2c8
commit b74bde5c22
4 changed files with 122 additions and 1 deletions

View File

@ -406,9 +406,11 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
existingVariableNames
) );
// remove methods without parameters as they are already being invoked
// remove methods that are already being invoked
removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType );
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType );
removeMappingReferencesWithSingleSourceParameter( beforeMappingReferencesWithFinalizedReturnType );
removeMappingReferencesWithSingleSourceParameter( afterMappingReferencesWithFinalizedReturnType );
}
Map<String, PresenceCheck> presenceChecksByParameter = new LinkedHashMap<>();
@ -455,6 +457,17 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
references.removeIf( r -> r.getSourceParameters().isEmpty() && r.getReturnType().isVoid() );
}
private void removeMappingReferencesWithSingleSourceParameter(
List<LifecycleCallbackMethodReference> references) {
references.removeIf( Builder::isSingleSourceParameter );
}
private static boolean isSingleSourceParameter(LifecycleCallbackMethodReference reference) {
return reference.getParameterBindings().size() == 1
&& reference.getParameterBindings().stream().allMatch( ParameterBinding::isSourceParameter )
&& reference.getReturnType().isVoid();
}
private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
return !isAbstractReturnTypeAllowed()
&& canReturnTypeBeConstructed( returnTypeImpl );

View File

@ -7,9 +7,11 @@ package org.mapstruct.ap.test.builder.lifecycle;
import java.util.Arrays;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.runner.GeneratedSource;
import static org.assertj.core.api.Assertions.assertThat;
@ -27,6 +29,9 @@ import static org.assertj.core.api.Assertions.assertThat;
} )
public class BuilderLifecycleCallbacksTest {
@RegisterExtension
final GeneratedSource source = new GeneratedSource().addComparisonToFixtureFor( OrderMapper.class );
@ProcessorTest
public void lifecycleMethodsShouldBeInvoked() {
OrderDto source = new OrderDto();
@ -43,6 +48,7 @@ public class BuilderLifecycleCallbacksTest {
assertThat( context.getInvokedMethods() )
.contains(
"beforeWithoutParameters",
"beforeWithSource",
"beforeWithTargetType",
"beforeWithBuilderTargetType",
"beforeWithBuilderTarget",
@ -50,6 +56,7 @@ public class BuilderLifecycleCallbacksTest {
"afterWithBuilderTargetType",
"afterWithBuilderTarget",
"afterWithBuilderTargetReturningTarget",
"afterWithSource",
"afterWithTargetType",
"afterWithTarget",
"afterWithTargetReturningTarget"

View File

@ -25,6 +25,11 @@ public class MappingContext {
invokedMethods.add( "beforeWithoutParameters" );
}
@BeforeMapping
public void beforeWithSource(OrderDto source) {
invokedMethods.add( "beforeWithSource" );
}
@BeforeMapping
public void beforeWithTargetType(OrderDto source, @TargetType Class<Order> orderClass) {
invokedMethods.add( "beforeWithTargetType" );
@ -50,6 +55,11 @@ public class MappingContext {
invokedMethods.add( "afterWithoutParameters" );
}
@AfterMapping
public void afterWithSource(OrderDto source) {
invokedMethods.add( "afterWithSource" );
}
@AfterMapping
public void afterWithTargetType(OrderDto source, @TargetType Class<Order> orderClass) {
invokedMethods.add( "afterWithTargetType" );

View File

@ -0,0 +1,91 @@
/*
* 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.builder.lifecycle;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.processing.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-08-21T23:18:51+0200",
comments = "version: , compiler: javac, environment: Java 21.0.1 (Oracle Corporation)"
)
public class OrderMapperImpl implements OrderMapper {
@Override
public Order map(OrderDto source, MappingContext context) {
context.beforeWithoutParameters();
context.beforeWithSource( source );
context.beforeWithBuilderTargetType( source, Order.Builder.class );
context.beforeWithTargetType( source, Order.class );
if ( source == null ) {
return null;
}
Order.Builder order = Order.builder();
context.beforeWithBuilderTarget( source, order );
order.items( itemDtoListToItemList( source.getItems(), context ) );
context.afterWithoutParameters();
context.afterWithSource( source );
context.afterWithBuilderTargetType( source, Order.Builder.class );
context.afterWithBuilderTarget( source, order );
Order target = context.afterWithBuilderTargetReturningTarget( order );
if ( target != null ) {
return target;
}
Order orderResult = order.create();
context.afterWithTargetType( source, Order.class );
context.afterWithTarget( source, orderResult );
Order target1 = context.afterWithTargetReturningTarget( orderResult );
if ( target1 != null ) {
return target1;
}
return orderResult;
}
@Override
public Item map(ItemDto source, MappingContext context) {
context.beforeWithoutParameters();
if ( source == null ) {
return null;
}
Item.Builder item = Item.builder();
item.name( source.getName() );
context.afterWithoutParameters();
return item.create();
}
protected List<Item> itemDtoListToItemList(List<ItemDto> list, MappingContext context) {
context.beforeWithoutParameters();
if ( list == null ) {
return null;
}
List<Item> list1 = new ArrayList<Item>( list.size() );
for ( ItemDto itemDto : list ) {
list1.add( map( itemDto, context ) );
}
context.afterWithoutParameters();
return list1;
}
}