Skip to content

Commit f9e342b

Browse files
committed
add generics support
1 parent 37cbb5a commit f9e342b

14 files changed

Lines changed: 593 additions & 30 deletions

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ licenseFormat.dependsOn licenseFormatForKotlin
7676
licenseTest.dependsOn licenseTestData
7777

7878
checkstyle {
79-
toolVersion = '8.36.1'
79+
toolVersion = '10.12.0'
8080
config = resources.text.fromUri("https://raw.githubusercontent.com/mapstruct/mapstruct/master/build-config/src/main/resources/build-config/checkstyle.xml")
8181
configProperties = [
8282
'checkstyle.cache.file': layout.buildDirectory.get().asFile.toPath( ).resolve( 'checkstyle-cachefile').toString(),

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructBaseReference.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ public String getValue() {
6060
return super.getValue();
6161
}
6262

63+
@Nullable
64+
public MapstructBaseReference getPrevious() {
65+
return this.previous;
66+
}
67+
6368
@Nullable
6469
@Override
6570
public final PsiElement resolve() {

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructSourceReference.java

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package org.mapstruct.intellij.codeinsight.references;
77

88
import java.util.Objects;
9+
import java.util.Optional;
910
import java.util.stream.Stream;
1011

1112
import com.intellij.codeInsight.lookup.LookupElement;
@@ -23,6 +24,7 @@
2324
import org.jetbrains.annotations.NotNull;
2425
import org.jetbrains.annotations.Nullable;
2526
import org.mapstruct.intellij.util.MapstructUtil;
27+
import org.mapstruct.intellij.util.SourceUtils;
2628

2729
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
2830
import static org.mapstruct.intellij.util.MapstructUtil.findRecordComponent;
@@ -133,21 +135,31 @@ Object[] getVariantsInternal(@NotNull PsiMethod mappingMethod) {
133135
@Override
134136
PsiType resolvedType() {
135137
PsiElement element = resolve();
136-
137-
if ( element instanceof PsiMethod psiMethod ) {
138-
return psiMethod.getReturnType();
139-
}
140-
else if ( element instanceof PsiParameter psiParameter ) {
141-
return psiParameter.getType();
142-
}
143-
else if ( element instanceof PsiRecordComponent psiRecordComponent ) {
144-
return psiRecordComponent.getType();
145-
}
146-
else if ( element instanceof PsiField psiField ) {
147-
return psiField.getType();
138+
PsiType elementType = switch ( element ) {
139+
case PsiMethod psiMethod -> psiMethod.getReturnType();
140+
case PsiParameter psiParameter -> psiParameter.getType();
141+
case PsiRecordComponent psiRecordComponent -> psiRecordComponent.getType();
142+
case PsiField psiField -> psiField.getType();
143+
case null, default -> null;
144+
};
145+
146+
if ( elementType == null ) {
147+
return null;
148148
}
149149

150-
return null;
150+
PsiType contextType = Optional.ofNullable( getPrevious() )
151+
.map( MapstructBaseReference::resolvedType )
152+
.or( () -> Optional.ofNullable( this.getMappingMethod() )
153+
.map( MapstructUtil::getSourceParameters )
154+
.filter( params -> params.length == 1 )
155+
.map( psiParameters -> psiParameters[0] )
156+
.map( SourceUtils::getParameterType )
157+
)
158+
.orElse( null );
159+
160+
return PsiUtil.resolveGenericsClassInType( contextType )
161+
.getSubstitutor()
162+
.substitute( elementType );
151163
}
152164

153165
/**

src/main/java/org/mapstruct/intellij/codeinsight/references/MapstructTargetReference.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import java.util.Map;
99
import java.util.Objects;
10+
import java.util.Optional;
1011
import java.util.stream.Stream;
1112

1213
import com.intellij.codeInsight.AnnotationUtil;
@@ -27,7 +28,6 @@
2728
import com.intellij.psi.util.PsiUtil;
2829
import org.jetbrains.annotations.NotNull;
2930
import org.jetbrains.annotations.Nullable;
30-
import org.mapstruct.Mapping;
3131
import org.mapstruct.intellij.util.MapStructVersion;
3232
import org.mapstruct.intellij.util.MapstructUtil;
3333
import org.mapstruct.intellij.util.TargetType;
@@ -203,21 +203,28 @@ Object[] getVariantsInternal(@NotNull PsiMethod mappingMethod) {
203203
@Override
204204
PsiType resolvedType() {
205205
PsiElement element = resolve();
206-
207-
if ( element instanceof PsiMethod psiMethod ) {
208-
return firstParameterPsiType( psiMethod );
209-
}
210-
else if ( element instanceof PsiParameter psiParameter ) {
211-
return psiParameter.getType();
212-
}
213-
else if ( element instanceof PsiRecordComponent psiRecordComponent ) {
214-
return psiRecordComponent.getType();
215-
}
216-
else if ( element instanceof PsiField psiField ) {
217-
return psiField.getType();
206+
PsiType elementType = switch ( element ) {
207+
case PsiMethod psiMethod -> firstParameterPsiType( psiMethod );
208+
case PsiParameter psiParameter -> psiParameter.getType();
209+
case PsiRecordComponent psiRecordComponent -> psiRecordComponent.getType();
210+
case PsiField psiField -> psiField.getType();
211+
case null, default -> null;
212+
};
213+
214+
if ( elementType == null ) {
215+
return null;
218216
}
219217

220-
return null;
218+
PsiType contextType = Optional.ofNullable( getPrevious() )
219+
.map( MapstructBaseReference::resolvedType )
220+
.or( () -> Optional.ofNullable( getMappingMethod() )
221+
.map( TargetUtils::getRelevantType )
222+
)
223+
.orElse( null );
224+
225+
return PsiUtil.resolveGenericsClassInType( contextType )
226+
.getSubstitutor()
227+
.substitute( elementType );
221228
}
222229

223230
/**

src/test/java/org/mapstruct/intellij/MapstructBaseCompletionTestCase.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase;
1111
import com.intellij.openapi.util.text.StringUtil;
12-
import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess;
1312
import com.intellij.testFramework.LightProjectDescriptor;
1413
import com.intellij.testFramework.PsiTestUtil;
1514
import com.intellij.util.PathUtil;
@@ -29,7 +28,13 @@ protected void setUp() throws Exception {
2928
super.setUp();
3029
final String mapstructLibPath = PathUtil.toSystemIndependentName( new File( BUILD_LIBS_DIRECTORY )
3130
.getAbsolutePath() );
32-
VfsRootAccess.allowRootAccess( getTestRootDisposable(), mapstructLibPath );
31+
32+
allowAccessToDirsIfExists(
33+
BUILD_LIBS_DIRECTORY,
34+
"testData",
35+
"build/test-libs"
36+
);
37+
3338
PsiTestUtil.addLibrary(
3439
myFixture.getProjectDisposable(),
3540
myFixture.getModule(),

src/test/java/org/mapstruct/intellij/MapstructCompletionTestCase.java

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,115 @@ public void testFluentGenericTargetMapper() {
488488
);
489489
}
490490

491+
public void testGenericCarWrapperSourceAutoCompleteAfterCar() {
492+
configureByTestName();
493+
494+
assertThat( myItems )
495+
.extracting( LookupElement::getLookupString )
496+
.containsExactlyInAnyOrder(
497+
"winCode"
498+
);
499+
500+
assertThat( myItems )
501+
.extracting( LookupElementPresentation::renderElement )
502+
.usingRecursiveFieldByFieldElementComparator()
503+
.containsExactlyInAnyOrder(
504+
createVariable( "winCode", "String" )
505+
);
506+
}
507+
508+
public void testGenericCarWrapperTargetAutoCompleteAfterCar() {
509+
configureByTestName();
510+
511+
assertThat( myItems )
512+
.extracting( LookupElement::getLookupString )
513+
.containsExactlyInAnyOrder( "winCode" );
514+
515+
assertThat( myItems )
516+
.extracting( LookupElementPresentation::renderElement )
517+
.usingRecursiveFieldByFieldElementComparator()
518+
.containsExactlyInAnyOrder( createVariable( "winCode", "String" ) );
519+
}
520+
521+
public void testGenericCarWrapperSourceAutoCompleteAfterCarWithoutParameterPrefix() {
522+
configureByTestName();
523+
524+
assertThat( myItems )
525+
.extracting( LookupElement::getLookupString )
526+
.containsExactlyInAnyOrder( "winCode" );
527+
528+
assertThat( myItems )
529+
.extracting( LookupElementPresentation::renderElement )
530+
.usingRecursiveFieldByFieldElementComparator()
531+
.containsExactlyInAnyOrder( createVariable( "winCode", "String" ) );
532+
}
533+
534+
public void testGenericConstructorCarMapper() {
535+
configureByTestName();
536+
537+
assertThat( myItems )
538+
.extracting( LookupElement::getLookupString )
539+
.containsExactlyInAnyOrder( "winCode" );
540+
541+
assertThat( myItems )
542+
.extracting( LookupElementPresentation::renderElement )
543+
.usingRecursiveFieldByFieldElementComparator()
544+
.containsExactlyInAnyOrder( createVariable( "winCode", "String" ) );
545+
}
546+
547+
public void testGenericRecordConstructorTargetMapper() {
548+
configureByTestName();
549+
550+
assertThat( myItems )
551+
.extracting( LookupElement::getLookupString )
552+
.containsExactlyInAnyOrder( "winCode" );
553+
554+
assertThat( myItems )
555+
.extracting( LookupElementPresentation::renderElement )
556+
.usingRecursiveFieldByFieldElementComparator()
557+
.usingElementComparatorIgnoringFields( "myIcon", "myTail" )
558+
.containsExactlyInAnyOrder( createParameter( "winCode", "String" ) );
559+
}
560+
561+
public void testGenericConstructorMappingTargetUpdateMapper() {
562+
configureByTestName();
563+
564+
assertThat( myItems )
565+
.extracting( LookupElement::getLookupString )
566+
.containsExactlyInAnyOrder( "winCode" );
567+
568+
assertThat( myItems )
569+
.extracting( LookupElementPresentation::renderElement )
570+
.usingRecursiveFieldByFieldElementComparator()
571+
.containsExactlyInAnyOrder( createVariable( "winCode", "String" ) );
572+
}
573+
574+
public void testGenericTwoTypeParamsConstructorTargetMapper() {
575+
configureByTestName();
576+
577+
assertThat( myItems )
578+
.extracting( LookupElement::getLookupString )
579+
.containsExactlyInAnyOrder( "serial" );
580+
581+
assertThat( myItems )
582+
.extracting( LookupElementPresentation::renderElement )
583+
.usingRecursiveFieldByFieldElementComparator()
584+
.containsExactlyInAnyOrder( createVariable( "serial", "String" ) );
585+
}
586+
587+
public void testGenericNestedGenericTargetMapper() {
588+
configureByTestName();
589+
590+
assertThat( myItems )
591+
.extracting( LookupElement::getLookupString )
592+
.containsExactlyInAnyOrder( "winCode" );
593+
594+
assertThat( myItems )
595+
.extracting( LookupElementPresentation::renderElement )
596+
.usingRecursiveFieldByFieldElementComparator()
597+
.containsExactlyInAnyOrder( createVariable( "winCode", "String" ) );
598+
}
599+
491600
public void testVariantsCarMapperNoSourceClass() {
492601
myFixture.configureByFile( "CarMapperNoSourceClass.java" );
493602
complete();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.ap.test.complex;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.Mapping;
10+
11+
@Mapper
12+
public interface GenericCarWrapperMapper {
13+
14+
@Mapping(target = "id", source = "wrapper.car.<caret>winCode")
15+
CarEntity toCarDto(CarWrapper<Car> wrapper);
16+
}
17+
18+
class CarEntity {
19+
20+
private String id;
21+
22+
public String getId() {
23+
return id;
24+
}
25+
26+
public void setId(String id) {
27+
this.id = id;
28+
}
29+
30+
}
31+
32+
class Car {
33+
34+
private String winCode;
35+
36+
public String getWinCode() {
37+
return winCode;
38+
}
39+
40+
public void setWinCode(String winCode) {
41+
this.winCode = winCode;
42+
}
43+
}
44+
45+
class CarWrapper<T> {
46+
47+
private T car;
48+
49+
public T getCar() {
50+
return car;
51+
}
52+
53+
public void setCar(T car) {
54+
this.car = car;
55+
}
56+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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.ap.test.complex;
7+
8+
import org.mapstruct.Mapper;
9+
import org.mapstruct.Mapping;
10+
11+
@Mapper
12+
public interface GenericCarWrapperSourceAutoCompleteAfterCarWithoutParameterPrefix {
13+
@Mapping(target = "id", source = "car.<caret>winCode")
14+
CarEntity toCarDto(CarWrapper<Car> wrapper);
15+
}
16+
17+
class CarEntity {
18+
private String id;
19+
public String getId() {
20+
return id;
21+
}
22+
public void setId(String id) {
23+
this.id = id;
24+
}
25+
}
26+
27+
class Car {
28+
private String winCode;
29+
public String getWinCode() {
30+
return winCode;
31+
}
32+
public void setWinCode(String winCode) {
33+
this.winCode = winCode;
34+
}
35+
}
36+
37+
class CarWrapper<T> {
38+
private T car;
39+
public T getCar() {
40+
return car;
41+
}
42+
public void setCar(T car) {
43+
this.car = car;
44+
}
45+
}

0 commit comments

Comments
 (0)