-
-
Notifications
You must be signed in to change notification settings - Fork 1k
#3678 Prevent duplicate @BeforeMapping and @AfterMapping calls on classes that use the Builder pattern. #3680
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b74bde5
b5b94bd
af7b02c
3b5b028
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -387,9 +387,6 @@ public boolean equals(Object obj) { | |
| if ( this == obj ) { | ||
| return true; | ||
| } | ||
| if ( !super.equals( obj ) ) { | ||
| return false; | ||
| } | ||
| if ( getClass() != obj.getClass() ) { | ||
| return false; | ||
| } | ||
|
|
@@ -400,6 +397,9 @@ public boolean equals(Object obj) { | |
| if ( !Objects.equals( providingParameter, other.providingParameter ) ) { | ||
| return false; | ||
| } | ||
| if ( !Objects.equals( name, other.name ) ) { | ||
| return false; | ||
| } | ||
|
Comment on lines
+400
to
+402
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not 100% sure if the name (and already been there: declaringMapper + providingParameter) is enough to differ between multiple method references. All unit tests are green. |
||
|
|
||
| return true; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| /* | ||
| * 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.Mapper; | ||
| import org.mapstruct.Mapping; | ||
|
|
||
| @Mapper | ||
| public abstract class Issue3678Mapper { | ||
|
|
||
| @Mapping( target = "name", source = "sourceA.name") | ||
| @Mapping( target = "description", source = "sourceB.description") | ||
| abstract Target mapTwoSources(SourceA sourceA, SourceB sourceB); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a specific test with two sources to be sure. |
||
|
|
||
| @Mapping( target = "description", constant = "some description") | ||
| abstract Target mapSingleSource(SourceA sourceA); | ||
|
|
||
| List<String> invocations = new ArrayList<>(); | ||
|
|
||
| @BeforeMapping | ||
| void beforeMappingSourceA(SourceA sourceA) { | ||
| invocations.add( "beforeMappingSourceA" ); | ||
| } | ||
|
|
||
| @AfterMapping | ||
| void afterMappingSourceB(SourceA sourceA) { | ||
| invocations.add( "afterMappingSourceA" ); | ||
| } | ||
|
|
||
| @BeforeMapping | ||
| void beforeMappingSourceB(SourceB sourceB) { | ||
| invocations.add( "beforeMappingSourceB" ); | ||
| } | ||
|
|
||
| @AfterMapping | ||
| void afterMappingSourceB(SourceB sourceB) { | ||
| invocations.add( "afterMappingSourceB" ); | ||
| } | ||
|
|
||
| public List<String> getInvocations() { | ||
| return invocations; | ||
| } | ||
|
|
||
| public static class SourceA { | ||
|
|
||
| private final String name; | ||
|
|
||
| public SourceA(String name) { | ||
| this.name = name; | ||
| } | ||
|
|
||
| public String getName() { | ||
| return name; | ||
| } | ||
| } | ||
|
|
||
| public static class SourceB { | ||
|
|
||
| private final String description; | ||
|
|
||
| public SourceB(String description) { | ||
| this.description = description; | ||
| } | ||
|
|
||
| public String getDescription() { | ||
| return description; | ||
| } | ||
| } | ||
|
|
||
| public static 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() { | ||
| } | ||
|
|
||
| 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 ); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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._3678; | ||
|
|
||
| import org.mapstruct.ap.testutil.IssueKey; | ||
| import org.mapstruct.ap.testutil.ProcessorTest; | ||
| import org.mapstruct.ap.testutil.WithClasses; | ||
| import org.mapstruct.factory.Mappers; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
|
|
||
| @IssueKey("3678") | ||
| @WithClasses(Issue3678Mapper.class) | ||
| public class Issue3678Test { | ||
|
|
||
| @ProcessorTest | ||
| void beforeAndAfterMappingOnlyCalledOnceForTwoSources() { | ||
|
|
||
| Issue3678Mapper mapper = Mappers.getMapper( Issue3678Mapper.class ); | ||
| Issue3678Mapper.SourceA sourceA = new Issue3678Mapper.SourceA( "name" ); | ||
| Issue3678Mapper.SourceB sourceB = new Issue3678Mapper.SourceB( "description" ); | ||
| Issue3678Mapper.Target target = mapper.mapTwoSources( sourceA, sourceB ); | ||
|
|
||
| assertThat( mapper.getInvocations() ) | ||
| .containsExactly( | ||
| "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" | ||
| ); | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,13 +43,15 @@ public void lifecycleMethodsShouldBeInvoked() { | |
| assertThat( context.getInvokedMethods() ) | ||
| .contains( | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @filiphr So I added an additional fixture test that covers this. Do you think this should be in a separate
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed the fixture, added the before/afterMapping only with source for the sake of completeness here. But i provided a separate test that explicitly checks if before and after mapping methods are not called more than once.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we have a dedicated test do we even need changes in this particular test here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not necessariliy. It was however easier to debug the internals of the method resolution due to multiple lifecycle callback methods. I will remove it after implementing the fix. |
||
| "beforeWithoutParameters", | ||
| "beforeWithSource", | ||
| "beforeWithTargetType", | ||
| "beforeWithBuilderTargetType", | ||
| "beforeWithBuilderTarget", | ||
| "afterWithoutParameters", | ||
| "afterWithBuilderTargetType", | ||
| "afterWithBuilderTarget", | ||
| "afterWithBuilderTargetReturningTarget", | ||
| "afterWithSource", | ||
| "afterWithTargetType", | ||
| "afterWithTarget", | ||
| "afterWithTargetReturningTarget" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This evaluated against
Object#equalsand therefore was always false. This might be a relic from legacy inheritance structures or never worked as intended.