#3849: Resolve duplicate invocation of overloaded lifecycle methods with inheritance

Add compiler option `mapstruct.disableLifecycleOverloadDeduplicateSelector` to disable the deduplication if needed.

Signed-off-by: TangYang <tangyang9464@163.com>
This commit is contained in:
Yang Tang 2025-05-25 22:35:38 +08:00 committed by GitHub
parent 3a5c70224d
commit 0badba7003
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 642 additions and 13 deletions

View File

@ -95,6 +95,7 @@ import static javax.lang.model.element.ElementKind.CLASS;
MappingProcessor.VERBOSE,
MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY,
MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY,
MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR,
})
public class MappingProcessor extends AbstractProcessor {
@ -115,6 +116,8 @@ public class MappingProcessor extends AbstractProcessor {
protected static final String VERBOSE = "mapstruct.verbose";
protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy";
protected static final String NULL_VALUE_MAP_MAPPING_STRATEGY = "mapstruct.nullValueMapMappingStrategy";
protected static final String DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR =
"mapstruct.disableLifecycleOverloadDeduplicateSelector";
private final Set<String> additionalSupportedOptions;
private final String additionalSupportedOptionsError;
@ -174,6 +177,8 @@ public class MappingProcessor extends AbstractProcessor {
String nullValueIterableMappingStrategy = processingEnv.getOptions()
.get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY );
String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_MAPPING_STRATEGY );
String disableLifecycleOverloadDeduplicateSelector = processingEnv.getOptions()
.get( DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR );
return new Options(
Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
@ -189,7 +194,8 @@ public class MappingProcessor extends AbstractProcessor {
NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) :
null,
nullValueMapMappingStrategy != null ?
NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null
NullValueMappingStrategyGem.valueOf( nullValueMapMappingStrategy.toUpperCase( Locale.ROOT ) ) : null,
Boolean.parseBoolean( disableLifecycleOverloadDeduplicateSelector )
);
}

View File

@ -133,7 +133,7 @@ public final class LifecycleMethodResolver {
MappingBuilderContext ctx, Set<String> existingVariableNames) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() );
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), ctx.getOptions() );
List<SelectedMethod<SourceMethod>> matchingMethods = selectors.getMatchingMethods(
callbackMethods,

View File

@ -5,12 +5,9 @@
*/
package org.mapstruct.ap.internal.model;
import static org.mapstruct.ap.internal.util.Collections.first;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
@ -26,6 +23,8 @@ import org.mapstruct.ap.internal.model.source.selector.SelectedMethod;
import org.mapstruct.ap.internal.model.source.selector.SelectionContext;
import org.mapstruct.ap.internal.util.Message;
import static org.mapstruct.ap.internal.util.Collections.first;
/**
*
* @author Sjaak Derksen
@ -126,7 +125,7 @@ public class ObjectFactoryMethodResolver {
MappingBuilderContext ctx) {
MethodSelectors selectors =
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager() );
new MethodSelectors( ctx.getTypeUtils(), ctx.getElementUtils(), ctx.getMessager(), null );
return selectors.getMatchingMethods(
getAllAvailableMethods( method, ctx.getSourceModel() ),

View File

@ -119,7 +119,8 @@ public final class PresenceCheckMethodResolver {
MethodSelectors selectors = new MethodSelectors(
ctx.getTypeUtils(),
ctx.getElementUtils(),
ctx.getMessager()
ctx.getMessager(),
null
);
return selectors.getMatchingMethods(

View File

@ -0,0 +1,129 @@
/*
* 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.internal.model.source.selector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.stream.Collectors;
import org.mapstruct.ap.internal.model.common.Parameter;
import org.mapstruct.ap.internal.model.common.ParameterBinding;
import org.mapstruct.ap.internal.model.source.Method;
/**
* Selector for deduplicating overloaded lifecycle callback methods
* whose parameter signatures differ only by type hierarchy.
* <p>
* In the context of lifecycle callback method selection
* (such as @BeforeMapping or @AfterMapping), it is possible to have multiple overloaded methods
* whose parameter lists are structurally identical except for the specific types,
* where those types are related by inheritance (e.g., one parameter is a superclass or subclass of another).
* <p>
* This selector groups such methods by their effective parameter signature
* (ignoring differences only in type hierarchy), and, within each group,
* retains only the method whose parameter types have the closest inheritance distance
* to the actual invocation types.
* This ensures that, for each group of nearly identical overloads,
* only the most specific and appropriate method is selected.
* <p>
* <b>Example (see Issue3849Test):</b>
*
* <pre>{@code
* @AfterMapping
* default void afterMapping(Parent source, @MappingTarget ParentDto target) { ... }
* @AfterMapping
* default void afterMapping(Parent source, @MappingTarget ChildDto target) { ... }
* }</pre>
* When mapping a Child to a ChildDto,
* only the method with ChildDto is selected, even though both methods match by signature
* except for the target type's inheritance relationship.
*/
public class LifecycleOverloadDeduplicateSelector implements MethodSelector {
@Override
public <T extends Method> List<SelectedMethod<T>> getMatchingMethods(List<SelectedMethod<T>> methods,
SelectionContext context) {
if ( !context.getSelectionCriteria().isLifecycleCallbackRequired() || methods.size() <= 1 ) {
return methods;
}
Collection<List<SelectedMethod<T>>> methodSignatureGroups =
methods.stream()
.collect( Collectors.groupingBy(
LifecycleOverloadDeduplicateSelector::buildSignatureKey,
LinkedHashMap::new,
Collectors.toList()
) )
.values();
List<SelectedMethod<T>> deduplicatedMethods = new ArrayList<>( methods.size() );
for ( List<SelectedMethod<T>> signatureGroup : methodSignatureGroups ) {
if ( signatureGroup.size() == 1 ) {
deduplicatedMethods.add( signatureGroup.get( 0 ) );
continue;
}
SelectedMethod<T> bestInheritanceMethod = signatureGroup.get( 0 );
for ( int i = 1; i < signatureGroup.size(); i++ ) {
SelectedMethod<T> candidateMethod = signatureGroup.get( i );
if ( isInheritanceBetter( candidateMethod, bestInheritanceMethod ) ) {
bestInheritanceMethod = candidateMethod;
}
}
deduplicatedMethods.add( bestInheritanceMethod );
}
return deduplicatedMethods;
}
/**
* Builds a grouping key for a method based on its defining type,
* method name, and a detailed breakdown of each parameter binding.
* <p>
* The key consists of:
* <ul>
* <li>The type that defines the method</li>
* <li>The method name</li>
* <li>parameter bindings</li>
* </ul>
* This ensures that methods are grouped together only if all these aspects match,
* except for differences in type hierarchy, which are handled separately.
*/
private static <T extends Method> List<Object> buildSignatureKey(SelectedMethod<T> method) {
List<Object> key = new ArrayList<>();
key.add( method.getMethod().getDefiningType() );
key.add( method.getMethod().getName() );
for ( ParameterBinding binding : method.getParameterBindings() ) {
key.add( binding.getType() );
key.add( binding.getVariableName() );
}
return key;
}
/**
* Compare the inheritance distance of parameters between two methods to determine if candidateMethod is better.
* Compares parameters in order, returns as soon as a better one is found.
*/
private <T extends Method> boolean isInheritanceBetter(SelectedMethod<T> candidateMethod,
SelectedMethod<T> currentBestMethod) {
List<ParameterBinding> candidateBindings = candidateMethod.getParameterBindings();
List<ParameterBinding> bestBindings = currentBestMethod.getParameterBindings();
List<Parameter> candidateParams = candidateMethod.getMethod().getParameters();
List<Parameter> bestParams = currentBestMethod.getMethod().getParameters();
int paramCount = candidateBindings.size();
for ( int i = 0; i < paramCount; i++ ) {
int candidateDistance = candidateBindings.get( i )
.getType()
.distanceTo( candidateParams.get( i ).getType() );
int bestDistance = bestBindings.get( i ).getType().distanceTo( bestParams.get( i ).getType() );
if ( candidateDistance < bestDistance ) {
return true;
}
else if ( candidateDistance > bestDistance ) {
return false;
}
}
return false;
}
}

View File

@ -10,6 +10,7 @@ import java.util.Arrays;
import java.util.List;
import org.mapstruct.ap.internal.model.source.Method;
import org.mapstruct.ap.internal.option.Options;
import org.mapstruct.ap.internal.util.ElementUtils;
import org.mapstruct.ap.internal.util.FormattingMessager;
import org.mapstruct.ap.internal.util.TypeUtils;
@ -24,20 +25,27 @@ public class MethodSelectors {
private final List<MethodSelector> selectors;
public MethodSelectors(TypeUtils typeUtils, ElementUtils elementUtils,
FormattingMessager messager) {
selectors = Arrays.asList(
FormattingMessager messager, Options options) {
List<MethodSelector> selectorList = new ArrayList<>( Arrays.asList(
new MethodFamilySelector(),
new TypeSelector( messager ),
new QualifierSelector( typeUtils, elementUtils ),
new TargetTypeSelector( typeUtils ),
new JavaxXmlElementDeclSelector( typeUtils ),
new JakartaXmlElementDeclSelector( typeUtils ),
new InheritanceSelector(),
new InheritanceSelector()
) );
if ( options != null && !options.isDisableLifecycleOverloadDeduplicateSelector() ) {
selectorList.add( new LifecycleOverloadDeduplicateSelector() );
}
selectorList.addAll( Arrays.asList(
new CreateOrUpdateSelector(),
new SourceRhsSelector(),
new FactoryParameterSelector(),
new MostSpecificResultTypeSelector()
);
) );
this.selectors = selectorList;
}
/**

View File

@ -26,6 +26,7 @@ public class Options {
private final boolean verbose;
private final NullValueMappingStrategyGem nullValueIterableMappingStrategy;
private final NullValueMappingStrategyGem nullValueMapMappingStrategy;
private final boolean disableLifecycleOverloadDeduplicateSelector;
//CHECKSTYLE:OFF
public Options(boolean suppressGeneratorTimestamp, boolean suppressGeneratorVersionComment,
@ -36,7 +37,8 @@ public class Options {
boolean disableBuilders,
boolean verbose,
NullValueMappingStrategyGem nullValueIterableMappingStrategy,
NullValueMappingStrategyGem nullValueMapMappingStrategy
NullValueMappingStrategyGem nullValueMapMappingStrategy,
boolean disableLifecycleOverloadDeduplicateSelector
) {
//CHECKSTYLE:ON
this.suppressGeneratorTimestamp = suppressGeneratorTimestamp;
@ -50,6 +52,7 @@ public class Options {
this.verbose = verbose;
this.nullValueIterableMappingStrategy = nullValueIterableMappingStrategy;
this.nullValueMapMappingStrategy = nullValueMapMappingStrategy;
this.disableLifecycleOverloadDeduplicateSelector = disableLifecycleOverloadDeduplicateSelector;
}
public boolean isSuppressGeneratorTimestamp() {
@ -95,4 +98,8 @@ public class Options {
public NullValueMappingStrategyGem getNullValueMapMappingStrategy() {
return nullValueMapMappingStrategy;
}
public boolean isDisableLifecycleOverloadDeduplicateSelector() {
return disableLifecycleOverloadDeduplicateSelector;
}
}

View File

@ -116,7 +116,7 @@ public class MappingResolverImpl implements MappingResolver {
this.conversions = new Conversions( typeFactory );
this.builtInMethods = new BuiltInMappingMethods( typeFactory );
this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager );
this.methodSelectors = new MethodSelectors( typeUtils, elementUtils, messager, null );
this.verboseLogging = verboseLogging;
}

View File

@ -0,0 +1,12 @@
/*
* 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._3849;
public class Child extends Parent {
public Child() {
super();
}
}

View File

@ -0,0 +1,16 @@
/*
* 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._3849;
public class ChildDto extends ParentDto {
public ChildDto(String value) {
super( value );
}
public void setValue(String value) {
super.setValue( value );
}
}

View File

@ -0,0 +1,69 @@
/*
* 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._3849;
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.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DeduplicateBySourceMapper {
DeduplicateBySourceMapper INSTANCE = Mappers.getMapper( DeduplicateBySourceMapper.class );
List<String> INVOKED_METHODS = new ArrayList<>();
ParentDto mapParent(Parent source, @Context MappingContext context);
ParentDto mapChild(Child source, @Context MappingContext context);
class MappingContext {
@BeforeMapping
void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentSourceInOtherClass" );
}
@BeforeMapping
void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingChildSourceInOtherClass" );
}
@AfterMapping
void deduplicateBySource(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentSourceInOtherClass" );
}
@AfterMapping
void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingChildSourceInOtherClass" );
}
}
@BeforeMapping
default void deduplicateBySourceForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentSource" );
}
@BeforeMapping
default void deduplicateBySourceForBefore(Child sourceChild, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingChildSource" );
}
@AfterMapping
default void deduplicateBySource(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentSource" );
}
@AfterMapping
default void deduplicateBySource(Child sourceChild, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingChildSource" );
}
}

View File

@ -0,0 +1,69 @@
/*
* 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._3849;
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.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DeduplicateByTargetMapper {
DeduplicateByTargetMapper INSTANCE = Mappers.getMapper( DeduplicateByTargetMapper.class );
List<String> INVOKED_METHODS = new ArrayList<>();
ParentDto mapParent(Parent source, @Context MappingContext context);
ChildDto mapChild(Parent source, @Context MappingContext context);
class MappingContext {
@BeforeMapping
void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" );
}
@BeforeMapping
void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" );
}
@AfterMapping
void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" );
}
@AfterMapping
void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" );
}
}
@BeforeMapping
default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentTarget" );
}
@BeforeMapping
default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "beforeMappingChildTarget" );
}
@AfterMapping
default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentTarget" );
}
@AfterMapping
default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "afterMappingChildTarget" );
}
}

View File

@ -0,0 +1,69 @@
/*
* 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._3849;
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.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DeduplicateForCompileArgsMapper {
DeduplicateForCompileArgsMapper INSTANCE = Mappers.getMapper( DeduplicateForCompileArgsMapper.class );
List<String> INVOKED_METHODS = new ArrayList<>();
ParentDto mapParent(Parent source, @Context MappingContext context);
ChildDto mapChild(Parent source, @Context MappingContext context);
class MappingContext {
@BeforeMapping
void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentTargetInOtherClass" );
}
@BeforeMapping
void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "beforeMappingChildTargetInOtherClass" );
}
@AfterMapping
void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentTargetInOtherClass" );
}
@AfterMapping
void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "afterMappingChildTargetInOtherClass" );
}
}
@BeforeMapping
default void deduplicateByTargetForBefore(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "beforeMappingParentTarget" );
}
@BeforeMapping
default void deduplicateByTargetForBefore(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "beforeMappingChildTarget" );
}
@AfterMapping
default void deduplicateByTarget(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParentTarget" );
}
@AfterMapping
default void deduplicateByTarget(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "afterMappingChildTarget" );
}
}

View File

@ -0,0 +1,51 @@
/*
* 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._3849;
import java.util.ArrayList;
import java.util.List;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
@Mapper
public interface DeduplicateGenericMapper {
DeduplicateGenericMapper INSTANCE = Mappers.getMapper( DeduplicateGenericMapper.class );
List<String> INVOKED_METHODS = new ArrayList<>();
ParentDto mapParent(Parent source);
ChildDto mapChild(Parent source);
@BeforeMapping
default <T extends ParentDto> void deduplicateBefore(Parent source, @MappingTarget T target) {
INVOKED_METHODS.add( "beforeMappingParentGeneric" );
}
@BeforeMapping
default void deduplicateBefore(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "beforeMappingChild" );
}
@AfterMapping
default <T> void deduplicate(Parent source, @MappingTarget T target) {
INVOKED_METHODS.add( "afterMappingGeneric" );
}
@AfterMapping
default void deduplicate(Parent source, @MappingTarget ParentDto target) {
INVOKED_METHODS.add( "afterMappingParent" );
}
@AfterMapping
default void deduplicate(Parent source, @MappingTarget ChildDto target) {
INVOKED_METHODS.add( "afterMappingChild" );
}
}

View File

@ -0,0 +1,150 @@
/*
* 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._3849;
import org.mapstruct.ap.testutil.IssueKey;
import org.mapstruct.ap.testutil.ProcessorTest;
import org.mapstruct.ap.testutil.WithClasses;
import org.mapstruct.ap.testutil.compilation.annotation.ProcessorOption;
import static org.assertj.core.api.Assertions.assertThat;
@IssueKey("3849")
@WithClasses({
Parent.class,
ParentDto.class,
Child.class,
ChildDto.class
})
public class Issue3849Test {
@ProcessorOption(name = "mapstruct.disableLifecycleOverloadDeduplicateSelector", value = "true")
@ProcessorTest()
@WithClasses(DeduplicateForCompileArgsMapper.class)
void lifecycleMappingOverloadSelectorDisableCompileArgs() {
Child child = new Child();
Parent parent = new Parent();
DeduplicateForCompileArgsMapper.MappingContext mappingContext =
new DeduplicateForCompileArgsMapper.MappingContext();
ParentDto parentDto = DeduplicateForCompileArgsMapper.INSTANCE.mapParent( parent, mappingContext );
assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingParentTargetInOtherClass",
"beforeMappingParentTarget",
"afterMappingParentTargetInOtherClass",
"afterMappingParentTarget"
);
DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear();
ParentDto childDto = DeduplicateForCompileArgsMapper.INSTANCE.mapChild( child, mappingContext );
assertThat( DeduplicateForCompileArgsMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingChildTargetInOtherClass",
"beforeMappingChildTargetInOtherClass",
"beforeMappingChildTarget",
"beforeMappingChildTarget",
"afterMappingChildTargetInOtherClass",
"afterMappingChildTargetInOtherClass",
"afterMappingChildTarget",
"afterMappingChildTarget"
);
DeduplicateForCompileArgsMapper.INVOKED_METHODS.clear();
}
@ProcessorTest()
@WithClasses( DeduplicateByTargetMapper.class )
void lifecycleMappingOverloadByTarget() {
Child child = new Child();
Parent parent = new Parent();
DeduplicateByTargetMapper.MappingContext mappingContext = new DeduplicateByTargetMapper.MappingContext();
ParentDto parentDto = DeduplicateByTargetMapper.INSTANCE.mapParent( parent, mappingContext );
assertThat( DeduplicateByTargetMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingParentTargetInOtherClass",
"beforeMappingParentTarget",
"afterMappingParentTargetInOtherClass",
"afterMappingParentTarget"
);
DeduplicateByTargetMapper.INVOKED_METHODS.clear();
ParentDto childDto = DeduplicateByTargetMapper.INSTANCE.mapChild( child, mappingContext );
assertThat( DeduplicateByTargetMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingChildTargetInOtherClass",
"beforeMappingChildTarget",
"afterMappingChildTargetInOtherClass",
"afterMappingChildTarget"
);
DeduplicateByTargetMapper.INVOKED_METHODS.clear();
}
@ProcessorTest
@WithClasses( DeduplicateBySourceMapper.class )
void lifecycleMappingOverloadBySource() {
Child child = new Child();
Parent parent = new Parent();
DeduplicateBySourceMapper.MappingContext mappingContext = new DeduplicateBySourceMapper.MappingContext();
ParentDto parentDto = DeduplicateBySourceMapper.INSTANCE.mapParent( parent, mappingContext );
assertThat( DeduplicateBySourceMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingParentSourceInOtherClass",
"beforeMappingParentSource",
"afterMappingParentSourceInOtherClass",
"afterMappingParentSource"
);
DeduplicateBySourceMapper.INVOKED_METHODS.clear();
ParentDto childDto = DeduplicateBySourceMapper.INSTANCE.mapChild( child, mappingContext );
assertThat( DeduplicateBySourceMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingChildSourceInOtherClass",
"beforeMappingChildSource",
"afterMappingChildSourceInOtherClass",
"afterMappingChildSource"
);
DeduplicateBySourceMapper.INVOKED_METHODS.clear();
}
@ProcessorTest
@WithClasses(DeduplicateGenericMapper.class)
void lifecycleMappingOverloadForGeneric() {
Child child = new Child();
Parent parent = new Parent();
ParentDto parentDto = DeduplicateGenericMapper.INSTANCE.mapParent( parent );
assertThat( DeduplicateGenericMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingParentGeneric",
"afterMappingParent"
);
DeduplicateGenericMapper.INVOKED_METHODS.clear();
ParentDto childDto = DeduplicateGenericMapper.INSTANCE.mapChild( child );
assertThat( DeduplicateGenericMapper.INVOKED_METHODS )
.containsExactly(
"beforeMappingChild",
"afterMappingChild"
);
DeduplicateGenericMapper.INVOKED_METHODS.clear();
}
}

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._3849;
public class Parent {
private String value;
public Parent() {
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

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._3849;
public class ParentDto {
private String value;
public ParentDto(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}