Skip to content

Commit 27c6cfc

Browse files
authored
#159 Support fluent setters with super types (e.g. lomboks @SuperBuilder)
1 parent 8e25048 commit 27c6cfc

File tree

5 files changed

+359
-7
lines changed

5 files changed

+359
-7
lines changed

src/main/java/org/mapstruct/intellij/util/MapstructUtil.java

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ private MapstructUtil() {
108108
}
109109

110110
public static LookupElement[] asLookup(Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors,
111-
Function<PsiElement, PsiType> typeMapper) {
111+
Function<PsiElement, PsiType> typeMapper) {
112112
if ( !accessors.isEmpty() ) {
113113
LookupElement[] lookupElements = new LookupElement[accessors.size()];
114114
int index = 0;
@@ -163,7 +163,7 @@ public static <T extends PsiElement> LookupElement asLookup(String propertyName,
163163
}
164164

165165
public static LookupElement asLookup(String propertyName, @NotNull Pair<? extends PsiElement, PsiSubstitutor> pair,
166-
Function<PsiElement, PsiType> typeMapper, Icon icon) {
166+
Function<PsiElement, PsiType> typeMapper, Icon icon) {
167167
PsiElement member = pair.getFirst();
168168
PsiSubstitutor substitutor = pair.getSecond();
169169

@@ -200,17 +200,35 @@ private static boolean isPublic(@NotNull PsiField field) {
200200

201201
public static boolean isPublicModifiable(@NotNull PsiField field) {
202202
return isPublicNonStatic( field ) &&
203-
!field.hasModifierProperty( PsiModifier.FINAL );
203+
!field.hasModifierProperty( PsiModifier.FINAL );
204204
}
205205

206206
public static boolean isFluentSetter(@NotNull PsiMethod method, PsiType psiType) {
207207
return !psiType.getCanonicalText().startsWith( "java.lang" ) &&
208208
method.getReturnType() != null &&
209209
!isAdderWithUpperCase4thCharacter( method ) &&
210-
TypeConversionUtil.isAssignable(
211-
psiType,
212-
PsiUtil.resolveGenericsClassInType( psiType ).getSubstitutor().substitute( method.getReturnType() )
213-
);
210+
isAssignableFromReturnTypeOrSuperTypes( psiType, method.getReturnType() );
211+
}
212+
213+
private static boolean isAssignableFromReturnTypeOrSuperTypes(PsiType psiType, PsiType returnType) {
214+
215+
if ( isAssignableFrom( psiType, returnType ) ) {
216+
return true;
217+
}
218+
219+
for ( PsiType superType : returnType.getSuperTypes() ) {
220+
if ( isAssignableFrom( psiType, superType ) ) {
221+
return true;
222+
}
223+
}
224+
return false;
225+
}
226+
227+
private static boolean isAssignableFrom(PsiType psiType, @Nullable PsiType returnType) {
228+
return TypeConversionUtil.isAssignable(
229+
psiType,
230+
PsiUtil.resolveGenericsClassInType( psiType ).getSubstitutor().substitute( returnType )
231+
);
214232
}
215233

216234
private static boolean isAdderWithUpperCase4thCharacter(@NotNull PsiMethod method) {
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.mapstruct.intellij.inspection;
7+
8+
import java.util.List;
9+
10+
import com.intellij.codeInsight.intention.IntentionAction;
11+
import org.jetbrains.annotations.NotNull;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
/**
16+
* @author Oliver Erhart
17+
*/
18+
public class UnmappedSuperBuilderTargetPropertiesInspectionTest extends BaseInspectionTest {
19+
20+
@NotNull
21+
@Override
22+
protected Class<UnmappedTargetPropertiesInspection> getInspection() {
23+
return UnmappedTargetPropertiesInspection.class;
24+
}
25+
26+
@Override
27+
protected void setUp() throws Exception {
28+
super.setUp();
29+
myFixture.copyFileToProject(
30+
"UnmappedSuperBuilderTargetPropertiesData.java",
31+
"org/example/data/UnmappedSuperBuilderTargetPropertiesData.java"
32+
);
33+
}
34+
35+
public void testUnmappedSuperBuilderTargetProperties() {
36+
doTest();
37+
String testName = getTestName( false );
38+
List<IntentionAction> allQuickFixes = myFixture.getAllQuickFixes();
39+
40+
assertThat( allQuickFixes )
41+
.extracting( IntentionAction::getText )
42+
.as( "Intent Text" )
43+
.containsExactly(
44+
"Ignore unmapped target property: 'testName'",
45+
"Add unmapped target property: 'testName'",
46+
"Ignore unmapped target property: 'moreTarget'",
47+
"Add unmapped target property: 'moreTarget'",
48+
"Ignore unmapped target property: 'moreTarget'",
49+
"Add unmapped target property: 'moreTarget'",
50+
"Ignore unmapped target property: 'testName'",
51+
"Add unmapped target property: 'testName'",
52+
"Ignore all unmapped target properties",
53+
"Ignore unmapped target property: 'testName'",
54+
"Add unmapped target property: 'testName'",
55+
"Ignore unmapped target property: 'moreTarget'",
56+
"Add unmapped target property: 'moreTarget'"
57+
);
58+
59+
allQuickFixes.forEach( myFixture::launchAction );
60+
myFixture.checkResultByFile( testName + "_after.java" );
61+
}
62+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
import org.mapstruct.Context;
8+
import org.mapstruct.InheritInverseConfiguration;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.Mapping;
11+
import org.mapstruct.MappingTarget;
12+
import org.mapstruct.Mappings;
13+
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Target;
14+
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Source;
15+
16+
interface NotMapStructMapper {
17+
18+
Target map(Source source);
19+
}
20+
21+
@Mapper
22+
interface SingleMappingsMapper {
23+
24+
@Mappings({
25+
@Mapping(target = "moreTarget", source = "moreSource")
26+
})
27+
Target <warning descr="Unmapped target property: testName">map</warning>(Source source);
28+
}
29+
30+
@Mapper
31+
interface SingleMappingMapper {
32+
33+
@Mapping(target = "testName", source = "name")
34+
Target <warning descr="Unmapped target property: moreTarget">map</warning>(Source source);
35+
}
36+
37+
@Mapper
38+
interface NoMappingMapper {
39+
40+
Target <warning descr="Unmapped target properties: moreTarget, testName">map</warning>(Source source);
41+
42+
@InheritInverseConfiguration
43+
Source reverse(Target target);
44+
}
45+
46+
@Mapper
47+
interface AllMappingMapper {
48+
49+
@Mapping(target = "testName", source = "name")
50+
@Mapping(target = "moreTarget", source = "moreSource")
51+
Target mapWithAllMapping(Source source);
52+
}
53+
54+
@Mapper
55+
interface UpdateMapper {
56+
57+
@Mapping(target = "moreTarget", source = "moreSource")
58+
void <warning descr="Unmapped target property: testName">update</warning>(@MappingTarget Target target, Source source);
59+
}
60+
61+
@Mapper
62+
interface MultiSourceUpdateMapper {
63+
64+
void <warning descr="Unmapped target property: moreTarget">update</warning>(@MappingTarget Target moreTarget, Source source, String testName, @Context String matching);
65+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
package org.example.data;
7+
8+
public class UnmappedSuperBuilderTargetPropertiesData {
9+
public static class Source {
10+
11+
private String name;
12+
private String matching;
13+
private String moreSource;
14+
private String onlyInSource;
15+
16+
public String getName() {
17+
return name;
18+
}
19+
20+
public void setName(String name) {
21+
this.name = name;
22+
}
23+
24+
public String getMatching() {
25+
return matching;
26+
}
27+
28+
public void setMatching(String matching) {
29+
this.matching = matching;
30+
}
31+
32+
public String getMoreSource() {
33+
return moreSource;
34+
}
35+
36+
public void setMoreSource(String moreSource) {
37+
this.moreSource = moreSource;
38+
}
39+
40+
public String getOnlyInSource() {
41+
return onlyInSource;
42+
}
43+
44+
public void setOnlyInSource(String onlyInSource) {
45+
this.onlyInSource = onlyInSource;
46+
}
47+
}
48+
49+
public static class Target {
50+
51+
private String testName;
52+
private String matching;
53+
private String moreTarget;
54+
55+
protected Target(TargetBuilder<?, ?> b) {
56+
this.testName = b.testName;
57+
this.matching = b.matching;
58+
this.moreTarget = b.moreTarget;
59+
}
60+
61+
public static TargetBuilder<?, ?> builder() {
62+
return new TargetBuilderImpl();
63+
}
64+
65+
public String getTestName() {
66+
return this.testName;
67+
}
68+
69+
public String getMatching() {
70+
return this.matching;
71+
}
72+
73+
public String getMoreTarget() {
74+
return this.moreTarget;
75+
}
76+
77+
public void setTestName(String testName) {
78+
this.testName = testName;
79+
}
80+
81+
public void setMatching(String matching) {
82+
this.matching = matching;
83+
}
84+
85+
public void setMoreTarget(String moreTarget) {
86+
this.moreTarget = moreTarget;
87+
}
88+
89+
public static abstract class TargetBuilder<C extends Target, B extends TargetBuilder<C, B>> {
90+
private String testName;
91+
private String matching;
92+
private String moreTarget;
93+
94+
public B testName(String testName) {
95+
this.testName = testName;
96+
return self();
97+
}
98+
99+
public B matching(String matching) {
100+
this.matching = matching;
101+
return self();
102+
}
103+
104+
public B moreTarget(String moreTarget) {
105+
this.moreTarget = moreTarget;
106+
return self();
107+
}
108+
109+
protected abstract B self();
110+
111+
public abstract C build();
112+
}
113+
114+
private static final class TargetBuilderImpl extends TargetBuilder<Target, TargetBuilderImpl> {
115+
private TargetBuilderImpl() {
116+
}
117+
118+
protected TargetBuilderImpl self() {
119+
return this;
120+
}
121+
122+
public Target build() {
123+
return new Target( this );
124+
}
125+
}
126+
}
127+
128+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright MapStruct Authors.
3+
*
4+
* Licensed under the Apache License version 2.0, available at https://www.apache.org/licenses/LICENSE-2.0
5+
*/
6+
7+
import org.mapstruct.Context;
8+
import org.mapstruct.InheritInverseConfiguration;
9+
import org.mapstruct.Mapper;
10+
import org.mapstruct.Mapping;
11+
import org.mapstruct.MappingTarget;
12+
import org.mapstruct.Mappings;
13+
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Target;
14+
import org.example.data.UnmappedSuperBuilderTargetPropertiesData.Source;
15+
16+
interface NotMapStructMapper {
17+
18+
Target map(Source source);
19+
}
20+
21+
@Mapper
22+
interface SingleMappingsMapper {
23+
24+
@Mappings({
25+
@Mapping(target = "moreTarget", source = "moreSource"),
26+
@Mapping(target = "testName", ignore = true),
27+
@Mapping(target = "testName", source = "")
28+
})
29+
Target map(Source source);
30+
}
31+
32+
@Mapper
33+
interface SingleMappingMapper {
34+
35+
@Mapping(target = "moreTarget", source = "")
36+
@Mapping(target = "moreTarget", ignore = true)
37+
@Mapping(target = "testName", source = "name")
38+
Target map(Source source);
39+
}
40+
41+
@Mapper
42+
interface NoMappingMapper {
43+
44+
@Mapping(target = "testName", ignore = true)
45+
@Mapping(target = "moreTarget", ignore = true)
46+
@Mapping(target = "testName", source = "")
47+
@Mapping(target = "testName", ignore = true)
48+
@Mapping(target = "moreTarget", source = "")
49+
@Mapping(target = "moreTarget", ignore = true)
50+
Target map(Source source);
51+
52+
@InheritInverseConfiguration
53+
Source reverse(Target target);
54+
}
55+
56+
@Mapper
57+
interface AllMappingMapper {
58+
59+
@Mapping(target = "testName", source = "name")
60+
@Mapping(target = "moreTarget", source = "moreSource")
61+
Target mapWithAllMapping(Source source);
62+
}
63+
64+
@Mapper
65+
interface UpdateMapper {
66+
67+
@Mapping(target = "testName", source = "")
68+
@Mapping(target = "testName", ignore = true)
69+
@Mapping(target = "moreTarget", source = "moreSource")
70+
void update(@MappingTarget Target target, Source source);
71+
}
72+
73+
@Mapper
74+
interface MultiSourceUpdateMapper {
75+
76+
@Mapping(target = "moreTarget", source = "")
77+
@Mapping(target = "moreTarget", ignore = true)
78+
void update(@MappingTarget Target moreTarget, Source source, String testName, @Context String matching);
79+
}

0 commit comments

Comments
 (0)