#3678 improve removal of duplicate lifecycle callback methods

This commit is contained in:
thunderhook 2024-08-25 20:44:23 +02:00
parent af7b02cb1b
commit 3b5b028ffa
7 changed files with 106 additions and 53 deletions

View File

@ -407,10 +407,8 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
) ); ) );
// remove methods that are already being invoked // remove methods that are already being invoked
removeMappingReferencesWithoutSourceParameters( beforeMappingReferencesWithFinalizedReturnType ); beforeMappingReferencesWithFinalizedReturnType.removeAll( beforeMappingMethods );
removeMappingReferencesWithoutSourceParameters( afterMappingReferencesWithFinalizedReturnType ); afterMappingReferencesWithFinalizedReturnType.removeAll( afterMappingMethods );
removeMappingReferencesWithSingleSourceParameter( beforeMappingReferencesWithFinalizedReturnType );
removeMappingReferencesWithSingleSourceParameter( afterMappingReferencesWithFinalizedReturnType );
} }
Map<String, PresenceCheck> presenceChecksByParameter = new LinkedHashMap<>(); Map<String, PresenceCheck> presenceChecksByParameter = new LinkedHashMap<>();
@ -453,21 +451,6 @@ public class BeanMappingMethod extends NormalTypeMappingMethod {
); );
} }
private void removeMappingReferencesWithoutSourceParameters(List<LifecycleCallbackMethodReference> references) {
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) { private boolean doesNotAllowAbstractReturnTypeAndCanBeConstructed(Type returnTypeImpl) {
return !isAbstractReturnTypeAllowed() return !isAbstractReturnTypeAllowed()
&& canReturnTypeBeConstructed( returnTypeImpl ); && canReturnTypeBeConstructed( returnTypeImpl );

View File

@ -5,6 +5,7 @@
*/ */
package org.mapstruct.ap.internal.model; package org.mapstruct.ap.internal.model;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.mapstruct.ap.internal.model.common.Parameter; import org.mapstruct.ap.internal.model.common.Parameter;
@ -118,4 +119,36 @@ public class LifecycleCallbackMethodReference extends MethodReference {
containingMethod, containingMethod,
existingVariableNames ); existingVariableNames );
} }
@Override
public boolean equals(Object obj) {
if ( this == obj ) {
return true;
}
if ( !super.equals( obj ) ) {
return false;
}
if ( getClass() != obj.getClass() ) {
return false;
}
LifecycleCallbackMethodReference other = (LifecycleCallbackMethodReference) obj;
if ( !Objects.equals( declaringType, other.declaringType )) {
return false;
}
if ( !Objects.equals( methodReturnType, other.methodReturnType )) {
return false;
}
if ( !Objects.equals( methodResultType, other.methodResultType )) {
return false;
}
if ( !Objects.equals( targetVariableName, other.targetVariableName )) {
return false;
}
return true;
}
@Override
public int hashCode() {
return Objects.hash( super.hashCode(), declaringType, methodReturnType, methodResultType, targetVariableName );
}
} }

View File

@ -387,9 +387,6 @@ public class MethodReference extends ModelElement implements Assignment {
if ( this == obj ) { if ( this == obj ) {
return true; return true;
} }
if ( !super.equals( obj ) ) {
return false;
}
if ( getClass() != obj.getClass() ) { if ( getClass() != obj.getClass() ) {
return false; return false;
} }
@ -400,6 +397,9 @@ public class MethodReference extends ModelElement implements Assignment {
if ( !Objects.equals( providingParameter, other.providingParameter ) ) { if ( !Objects.equals( providingParameter, other.providingParameter ) ) {
return false; return false;
} }
if ( !Objects.equals( name, other.name ) ) {
return false;
}
return true; return true;
} }

View File

@ -7,7 +7,6 @@ package org.mapstruct.ap.internal.model.source.selector;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeMirror;
import org.mapstruct.ap.internal.model.common.SourceRHS; import org.mapstruct.ap.internal.model.common.SourceRHS;
@ -116,7 +115,7 @@ public class SelectionCriteria {
this.type = preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null; this.type = preferUpdateMapping ? Type.PREFER_UPDATE_MAPPING : null;
} }
public boolean hasQualfiers() { public boolean hasQualifiers() {
return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty(); return !qualifyingInfo.qualifiedByNames().isEmpty() || !qualifyingInfo.qualifiers().isEmpty();
} }

View File

@ -5,10 +5,6 @@
*/ */
package org.mapstruct.ap.internal.processor.creation; package org.mapstruct.ap.internal.processor.creation;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.firstKey;
import static org.mapstruct.ap.internal.util.Collections.firstValue;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -20,7 +16,6 @@ import java.util.Set;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element; import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.ExecutableElement;
@ -67,6 +62,10 @@ import org.mapstruct.ap.internal.util.NativeTypes;
import org.mapstruct.ap.internal.util.Strings; import org.mapstruct.ap.internal.util.Strings;
import org.mapstruct.ap.internal.util.TypeUtils; import org.mapstruct.ap.internal.util.TypeUtils;
import static org.mapstruct.ap.internal.util.Collections.first;
import static org.mapstruct.ap.internal.util.Collections.firstKey;
import static org.mapstruct.ap.internal.util.Collections.firstValue;
/** /**
* The one and only implementation of {@link MappingResolver}. The class has been split into an interface an * The one and only implementation of {@link MappingResolver}. The class has been split into an interface an
* implementation for the sake of avoiding package dependencies. Specifically, this implementation refers to classes * implementation for the sake of avoiding package dependencies. Specifically, this implementation refers to classes
@ -335,7 +334,7 @@ public class MappingResolverImpl implements MappingResolver {
} }
private boolean hasQualfiers() { private boolean hasQualfiers() {
return selectionCriteria != null && selectionCriteria.hasQualfiers(); return selectionCriteria != null && selectionCriteria.hasQualifiers();
} }
private void printQualifierMessage(SelectionCriteria selectionCriteria ) { private void printQualifierMessage(SelectionCriteria selectionCriteria ) {

View File

@ -11,54 +11,76 @@ import java.util.List;
import org.mapstruct.AfterMapping; import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping; import org.mapstruct.BeforeMapping;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ReportingPolicy; import org.mapstruct.Mapping;
@Mapper(unmappedSourcePolicy = ReportingPolicy.ERROR) @Mapper
public abstract class Issue3678Mapper { public abstract class Issue3678Mapper {
abstract SimpleDestination sourceToDestination(SimpleSource source); @Mapping( target = "name", source = "sourceA.name")
@Mapping( target = "description", source = "sourceB.description")
abstract Target mapTwoSources(SourceA sourceA, SourceB sourceB);
@Mapping( target = "description", constant = "some description")
abstract Target mapSingleSource(SourceA sourceA);
List<String> invocations = new ArrayList<>(); List<String> invocations = new ArrayList<>();
@BeforeMapping @BeforeMapping
void beforeMapping(SimpleSource simpleSource) { void beforeMappingSourceA(SourceA sourceA) {
invocations.add( "beforeMapping" ); invocations.add( "beforeMappingSourceA" );
} }
@AfterMapping @AfterMapping
void afterMapping(SimpleSource simpleSource) { void afterMappingSourceB(SourceA sourceA) {
invocations.add( "afterMapping" ); invocations.add( "afterMappingSourceA" );
}
@BeforeMapping
void beforeMappingSourceB(SourceB sourceB) {
invocations.add( "beforeMappingSourceB" );
}
@AfterMapping
void afterMappingSourceB(SourceB sourceB) {
invocations.add( "afterMappingSourceB" );
} }
public List<String> getInvocations() { public List<String> getInvocations() {
return invocations; return invocations;
} }
public static class SimpleSource { public static class SourceA {
private final String name; private final String name;
private final String description;
public SimpleSource(String name, String description) { public SourceA(String name) {
this.name = name; this.name = name;
this.description = description;
} }
public String getName() { public String getName() {
return name; return name;
} }
}
public static class SourceB {
private final String description;
public SourceB(String description) {
this.description = description;
}
public String getDescription() { public String getDescription() {
return description; return description;
} }
} }
public static final class SimpleDestination { public static final class Target {
private final String name; private final String name;
private final String description; private final String description;
private SimpleDestination(String name, String description) { private Target(String name, String description) {
this.name = name; this.name = name;
this.description = description; this.description = description;
} }
@ -93,8 +115,8 @@ public abstract class Issue3678Mapper {
return this; return this;
} }
public SimpleDestination build() { public Target build() {
return new SimpleDestination( this.name, this.description ); return new Target( this.name, this.description );
} }
} }
} }

View File

@ -14,21 +14,38 @@ import org.mapstruct.factory.Mappers;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@IssueKey("3678") @IssueKey("3678")
@WithClasses(Issue3678Mapper.class)
public class Issue3678Test { public class Issue3678Test {
@WithClasses({
Issue3678Mapper.class,
})
@ProcessorTest @ProcessorTest
void ignoreMappingsWithoutSourceShouldBeInvertible() { void beforeAndAfterMappingOnlyCalledOnceForTwoSources() {
Issue3678Mapper mapper = Mappers.getMapper( Issue3678Mapper.class ); Issue3678Mapper mapper = Mappers.getMapper( Issue3678Mapper.class );
Issue3678Mapper.SimpleSource source = new Issue3678Mapper.SimpleSource( "name", "description" ); Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" );
Issue3678Mapper.SimpleDestination simpleDestination = mapper.sourceToDestination( source ); Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" );
Issue3678Mapper.Target target = mapper.mapTwoSources( sourceA, sourceB );
assertThat( mapper.getInvocations() ) assertThat( mapper.getInvocations() )
.containsExactly( "beforeMapping", "afterMapping" ) .containsExactly(
.doesNotHaveDuplicates(); "beforeMappingSourceA",
"beforeMappingSourceB",
"afterMappingSourceA",
"afterMappingSourceB"
);
}
@ProcessorTest
void beforeAndAfterMappingOnlyCalledOnceForSingleSource() {
Issue3678Mapper mapper = Mappers.getMapper( Issue3678Mapper.class );
Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" );
Issue3678Mapper.Target target = mapper.mapSingleSource( sourceA );
assertThat( mapper.getInvocations() )
.containsExactly(
"beforeMappingSourceA",
"afterMappingSourceA"
);
} }
} }