#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.VERBOSE,
MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY, MappingProcessor.NULL_VALUE_ITERABLE_MAPPING_STRATEGY,
MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY, MappingProcessor.NULL_VALUE_MAP_MAPPING_STRATEGY,
MappingProcessor.DISABLE_LIFECYCLE_OVERLOAD_DEDUPLICATE_SELECTOR,
}) })
public class MappingProcessor extends AbstractProcessor { 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 VERBOSE = "mapstruct.verbose";
protected static final String NULL_VALUE_ITERABLE_MAPPING_STRATEGY = "mapstruct.nullValueIterableMappingStrategy"; 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 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 Set<String> additionalSupportedOptions;
private final String additionalSupportedOptionsError; private final String additionalSupportedOptionsError;
@ -174,6 +177,8 @@ public class MappingProcessor extends AbstractProcessor {
String nullValueIterableMappingStrategy = processingEnv.getOptions() String nullValueIterableMappingStrategy = processingEnv.getOptions()
.get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY ); .get( NULL_VALUE_ITERABLE_MAPPING_STRATEGY );
String nullValueMapMappingStrategy = processingEnv.getOptions().get( NULL_VALUE_MAP_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( return new Options(
Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ), Boolean.parseBoolean( processingEnv.getOptions().get( SUPPRESS_GENERATOR_TIMESTAMP ) ),
@ -189,7 +194,8 @@ public class MappingProcessor extends AbstractProcessor {
NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) : NullValueMappingStrategyGem.valueOf( nullValueIterableMappingStrategy.toUpperCase( Locale.ROOT ) ) :
null, null,
nullValueMapMappingStrategy != 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) { MappingBuilderContext ctx, Set<String> existingVariableNames) {
MethodSelectors selectors = 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( List<SelectedMethod<SourceMethod>> matchingMethods = selectors.getMatchingMethods(
callbackMethods, callbackMethods,

View File

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

View File

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

View File

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

View File

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