Skip to content

Commit c916d22

Browse files
committed
Add auto completion support for constructor parameters
Closes #45
1 parent b9d0c60 commit c916d22

21 files changed

+817
-20
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ jacocoTestReport {
8282
}
8383

8484
dependencies {
85-
compile group: 'org.mapstruct', name: 'mapstruct', version: '1.3.0.Final'
85+
compile group: 'org.mapstruct', name: 'mapstruct', version: '1.4.0.Beta1'
8686
testCompile group: 'junit', name: 'junit', version: '4.12'
8787
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.11.1'
8888
}
@@ -93,7 +93,7 @@ task libs(type: Sync) {
9393
preserve {
9494
include 'mapstruct-intellij-*.jar'
9595
}
96-
rename 'mapstruct-1.3.0.Final.jar', 'mapstruct.jar'
96+
rename 'mapstruct-1.4.0.Beta1.jar', 'mapstruct.jar'
9797
}
9898

9999
def mockJdkLocation = "https://github.com/JetBrains/intellij-community/raw/master/java/mock"

change-notes.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h2>1.2.0</h2>
33
<ul>
44
<li>Support for public fields (auto completion and unmapped target / source inspection warnings)</li>
5+
<li>Support for constructor auto completion (find usages and renaming of constructor fields doesn't work yet)</li>
56
<li>Quick Fix: Add ignore all unmapped target properties</li>
67
<li>Bug fix: Correctly resolve fluent Boolean accessor</li>
78
<li>Bug fix: Only treat public non-static getters and setters as accessors</li>

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import com.intellij.psi.PsiElement;
1515
import com.intellij.psi.PsiField;
1616
import com.intellij.psi.PsiLiteral;
17-
import com.intellij.psi.PsiMember;
1817
import com.intellij.psi.PsiMethod;
1918
import com.intellij.psi.PsiParameter;
2019
import com.intellij.psi.PsiReference;
@@ -134,7 +133,7 @@ static PsiReference[] create(PsiLiteral psiLiteral) {
134133
return MapstructBaseReference.create( psiLiteral, MapstructSourceReference::new );
135134
}
136135

137-
private static PsiType memberPsiType(PsiMember psiMember) {
136+
private static PsiType memberPsiType(PsiElement psiMember) {
138137
if ( psiMember instanceof PsiMethod ) {
139138
return ( (PsiMethod) psiMember ).getReturnType();
140139
}

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

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import com.intellij.psi.PsiElement;
1616
import com.intellij.psi.PsiField;
1717
import com.intellij.psi.PsiLiteral;
18-
import com.intellij.psi.PsiMember;
1918
import com.intellij.psi.PsiMethod;
2019
import com.intellij.psi.PsiParameter;
2120
import com.intellij.psi.PsiReference;
@@ -26,6 +25,7 @@
2625
import org.jetbrains.annotations.Nullable;
2726
import org.mapstruct.intellij.util.MapStructVersion;
2827
import org.mapstruct.intellij.util.MapstructUtil;
28+
import org.mapstruct.intellij.util.TargetUtils;
2929

3030
import static org.mapstruct.intellij.util.MapstructUtil.asLookup;
3131
import static org.mapstruct.intellij.util.MapstructUtil.isPublicModifiable;
@@ -67,6 +67,17 @@ PsiElement resolveInternal(@NotNull String value, @NotNull PsiType psiType) {
6767
PsiClass psiClass = pair.getFirst();
6868
PsiType typeToUse = pair.getSecond();
6969

70+
if ( mapStructVersion.isConstructorSupported() ) {
71+
PsiMethod constructor = TargetUtils.resolveMappingConstructor( psiClass );
72+
if ( constructor != null && constructor.hasParameters() ) {
73+
for ( PsiParameter parameter : constructor.getParameterList().getParameters() ) {
74+
if ( value.equals( parameter.getName() ) ) {
75+
return parameter;
76+
}
77+
}
78+
}
79+
}
80+
7081
PsiMethod[] methods = psiClass.findMethodsByName( "set" + MapstructUtil.capitalize( value ), true );
7182
if ( methods.length != 0 ) {
7283
return methods[0];
@@ -150,7 +161,7 @@ static PsiReference[] create(PsiLiteral psiLiteral) {
150161
return MapstructBaseReference.create( psiLiteral, MapstructTargetReference::new );
151162
}
152163

153-
private static PsiType memberPsiType(PsiMember psiMember) {
164+
private static PsiType memberPsiType(PsiElement psiMember) {
154165
if ( psiMember instanceof PsiMethod ) {
155166
return firstParameterPsiType( (PsiMethod) psiMember );
156167
}

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
import com.intellij.psi.PsiArrayType;
2626
import com.intellij.psi.PsiClass;
2727
import com.intellij.psi.PsiClassType;
28+
import com.intellij.psi.PsiElement;
2829
import com.intellij.psi.PsiField;
2930
import com.intellij.psi.PsiFile;
30-
import com.intellij.psi.PsiMember;
3131
import com.intellij.psi.PsiMethod;
3232
import com.intellij.psi.PsiModifier;
3333
import com.intellij.psi.PsiModifierList;
@@ -94,15 +94,15 @@ public final class MapstructUtil {
9494
private MapstructUtil() {
9595
}
9696

97-
public static LookupElement[] asLookup(Map<String, Pair<? extends PsiMember, PsiSubstitutor>> accessors,
98-
Function<PsiMember, PsiType> typeMapper) {
97+
public static LookupElement[] asLookup(Map<String, Pair<? extends PsiElement, PsiSubstitutor>> accessors,
98+
Function<PsiElement, PsiType> typeMapper) {
9999
if ( !accessors.isEmpty() ) {
100100
LookupElement[] lookupElements = new LookupElement[accessors.size()];
101101
int index = 0;
102-
for ( Map.Entry<String, Pair<? extends PsiMember, PsiSubstitutor>> entry :
102+
for ( Map.Entry<String, Pair<? extends PsiElement, PsiSubstitutor>> entry :
103103
accessors.entrySet() ) {
104104
String propertyName = entry.getKey();
105-
Pair<? extends PsiMember, PsiSubstitutor> pair = entry.getValue();
105+
Pair<? extends PsiElement, PsiSubstitutor> pair = entry.getValue();
106106
lookupElements[index++] = asLookup(
107107
propertyName,
108108
pair,
@@ -117,9 +117,9 @@ public static LookupElement[] asLookup(Map<String, Pair<? extends PsiMember, Psi
117117

118118
}
119119

120-
public static LookupElement asLookup(String propertyName, @NotNull Pair<? extends PsiMember, PsiSubstitutor> pair,
121-
Function<PsiMember, PsiType> typeMapper) {
122-
PsiMember member = pair.getFirst();
120+
public static LookupElement asLookup(String propertyName, @NotNull Pair<? extends PsiElement, PsiSubstitutor> pair,
121+
Function<PsiElement, PsiType> typeMapper) {
122+
PsiElement member = pair.getFirst();
123123
PsiSubstitutor substitutor = pair.getSecond();
124124

125125
LookupElementBuilder builder = LookupElementBuilder.create( member, propertyName )
@@ -134,7 +134,7 @@ public static LookupElement asLookup(String propertyName, @NotNull Pair<? extend
134134
return builder;
135135
}
136136

137-
private static String formatTailText(PsiMember member, PsiSubstitutor substitutor) {
137+
private static String formatTailText(PsiElement member, PsiSubstitutor substitutor) {
138138
if ( member instanceof PsiMethod ) {
139139
return PsiFormatUtil.formatMethod(
140140
(PsiMethod) member,

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import com.intellij.openapi.util.Pair;
1919
import com.intellij.psi.CommonClassNames;
2020
import com.intellij.psi.PsiClass;
21-
import com.intellij.psi.PsiMember;
21+
import com.intellij.psi.PsiElement;
2222
import com.intellij.psi.PsiMethod;
2323
import com.intellij.psi.PsiParameter;
2424
import com.intellij.psi.PsiSubstitutor;
@@ -94,14 +94,14 @@ public static PsiType getParameterType(@NotNull PsiParameter parameter) {
9494
*
9595
* @return a stream that holds all public read accessors for the given {@code psiType}
9696
*/
97-
public static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicReadAccessors(
97+
public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicReadAccessors(
9898
@Nullable PsiType psiType) {
9999
PsiClass psiClass = PsiUtil.resolveClassInType( psiType );
100100
if ( psiClass == null ) {
101101
return Collections.emptyMap();
102102
}
103103

104-
Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicReadAccessors = new HashMap<>();
104+
Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicReadAccessors = new HashMap<>();
105105

106106
publicReadAccessors.putAll( publicGetters( psiClass ) );
107107
publicReadAccessors.putAll( publicFields( psiClass ) );

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

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,27 @@
66
package org.mapstruct.intellij.util;
77

88
import java.beans.Introspector;
9+
import java.util.ArrayList;
910
import java.util.Arrays;
1011
import java.util.Collections;
12+
import java.util.HashMap;
1113
import java.util.HashSet;
1214
import java.util.LinkedHashMap;
15+
import java.util.List;
1316
import java.util.Map;
1417
import java.util.Objects;
1518
import java.util.Set;
1619
import java.util.stream.Stream;
1720

21+
import com.intellij.lang.jvm.JvmModifier;
1822
import com.intellij.openapi.util.Pair;
1923
import com.intellij.psi.ElementManipulators;
2024
import com.intellij.psi.PsiAnnotation;
2125
import com.intellij.psi.PsiAnnotationMemberValue;
2226
import com.intellij.psi.PsiArrayInitializerMemberValue;
2327
import com.intellij.psi.PsiClass;
28+
import com.intellij.psi.PsiElement;
29+
import com.intellij.psi.PsiJavaCodeReferenceElement;
2430
import com.intellij.psi.PsiMember;
2531
import com.intellij.psi.PsiMethod;
2632
import com.intellij.psi.PsiNameValuePair;
@@ -84,25 +90,98 @@ public static PsiType getRelevantType(@NotNull PsiMethod mappingMethod) {
8490
*
8591
* @return a stream that holds all public write accessors for the given {@code psiType}
8692
*/
87-
public static Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType,
93+
public static Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors(@NotNull PsiType psiType,
8894
MapStructVersion mapStructVersion) {
8995
boolean builderSupportPresent = mapStructVersion.isBuilderSupported();
9096
Pair<PsiClass, PsiType> classAndType = resolveBuilderOrSelfClass( psiType, builderSupportPresent );
9197
if ( classAndType == null ) {
9298
return Collections.emptyMap();
9399
}
94100

95-
Map<String, Pair<? extends PsiMember, PsiSubstitutor>> publicWriteAccessors = new LinkedHashMap<>();
101+
Map<String, Pair<? extends PsiElement, PsiSubstitutor>> publicWriteAccessors = new LinkedHashMap<>();
96102

97103
PsiClass psiClass = classAndType.getFirst();
98104
PsiType typeToUse = classAndType.getSecond();
99105

100106
publicWriteAccessors.putAll( publicSetters( psiClass, typeToUse, builderSupportPresent ) );
101107
publicWriteAccessors.putAll( publicFields( psiClass ) );
102108

109+
if ( mapStructVersion.isConstructorSupported() ) {
110+
publicWriteAccessors.putAll( constructorParameters( psiClass ) );
111+
}
112+
103113
return publicWriteAccessors;
104114
}
105115

116+
private static Map<String, Pair<PsiParameter, PsiSubstitutor>> constructorParameters(@NotNull PsiClass psiClass) {
117+
PsiMethod constructor = resolveMappingConstructor( psiClass );
118+
if ( constructor == null || !constructor.hasParameters() ) {
119+
return Collections.emptyMap();
120+
}
121+
122+
Map<String, Pair<PsiParameter, PsiSubstitutor>> constructorParameters = new HashMap<>();
123+
124+
for ( PsiParameter parameter : constructor.getParameterList().getParameters() ) {
125+
constructorParameters.put( parameter.getName(), Pair.create( parameter, PsiSubstitutor.EMPTY ) );
126+
}
127+
128+
return constructorParameters;
129+
}
130+
131+
/**
132+
* Find the constructor that the code generation will use when mapping the psiClass.
133+
*
134+
* @param psiClass the class for which the constructor should be found
135+
* @return the constructor or {@code null} is there is no constructor that the mapping will use
136+
*/
137+
public static PsiMethod resolveMappingConstructor(@NotNull PsiClass psiClass) {
138+
139+
PsiMethod[] constructors = psiClass.getConstructors();
140+
if ( constructors.length == 0 ) {
141+
return null;
142+
}
143+
144+
if ( constructors.length == 1 ) {
145+
PsiMethod constructor = constructors[0];
146+
return !constructor.hasModifier( JvmModifier.PRIVATE ) ? constructor : null;
147+
}
148+
149+
List<PsiMethod> accessibleConstructors = new ArrayList<>(constructors.length);
150+
151+
for ( PsiMethod constructor : constructors ) {
152+
if ( constructor.hasModifier( JvmModifier.PRIVATE ) ) {
153+
// private constructors are ignored
154+
continue;
155+
}
156+
if ( !constructor.hasParameters() ) {
157+
// If there is an empty constructor then that constructor should be used
158+
return constructor;
159+
}
160+
161+
accessibleConstructors.add( constructor );
162+
}
163+
164+
if ( accessibleConstructors.size() == 1 ) {
165+
// If there is only one accessible constructor then use that one
166+
return accessibleConstructors.get( 0 );
167+
}
168+
169+
// If there are more accessible constructor then look for one annotated with @Default.
170+
// Otherwise return null
171+
for ( PsiMethod constructor : accessibleConstructors ) {
172+
for ( PsiAnnotation annotation : constructor.getAnnotations() ) {
173+
PsiJavaCodeReferenceElement nameReferenceElement = annotation.getNameReferenceElement();
174+
if ( nameReferenceElement != null && "Default".equals( nameReferenceElement.getReferenceName() ) ) {
175+
// If there is a constructor annotated with an annotation named @Default
176+
// then we should use that one
177+
return constructor;
178+
}
179+
}
180+
}
181+
182+
return null;
183+
}
184+
106185
/**
107186
* Extract all public setters with their psi substitutors from the given {@code psiClass}
108187
*

0 commit comments

Comments
 (0)