Skip to content

Commit 32da403

Browse files
unsharefiliphr
andauthored
Improve Java expression injector both for better code completion and for more useable inspections (#89)
* Add proper distinction between interfaces and abstract classes Co-authored-by: Filip Hrisafov <[email protected]>
1 parent deae6ee commit 32da403

File tree

8 files changed

+612
-91
lines changed

8 files changed

+612
-91
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ dependencies {
109109
compile group: 'org.mapstruct', name: 'mapstruct', version: '1.4.0.Beta1'
110110
testCompile group: 'junit', name: 'junit', version: '4.12'
111111
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.11.1'
112+
testCompile group: 'org.apache.commons', name: 'commons-text', version: '1.9'
112113
}
113114

114115
task libs(type: Sync) {

change-notes.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
<html>
1+
<html lang="en">
2+
<h2>1.3.1</h2>
3+
<ul>
4+
<li>Improve language injections, especially for generics</li>
5+
</ul>
26
<h2>1.3.0</h2>
37
<ul>
48
<li>Support code completion in <code>BeanMapping#ignoreUnmappedSourceProperties</code></li>
@@ -32,8 +36,8 @@ <h2>1.2.1</h2>
3236
</ul>
3337
<h2>1.2.0</h2>
3438
<ul>
35-
<li>Support for public fields (auto completion and unmapped target / source inspection warnings)</li>
36-
<li>Support for constructor auto completion (find usages and renaming of constructor fields doesn't work yet)</li>
39+
<li>Support for public fields (auto-completion and unmapped target / source inspection warnings)</li>
40+
<li>Support for constructor auto-completion (find usages and renaming of constructor fields doesn't work yet)</li>
3741
<li>Quick Fix: Add ignore all unmapped target properties</li>
3842
<li>Bug fix: Correctly resolve fluent Boolean accessor</li>
3943
<li>Bug fix: Only treat public non-static getters and setters as accessors</li>

description.html

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
<html>
1+
<html lang="en">
22
<strong>MapStruct support for IntelliJ IDEA</strong>
33
<br/>
44
<br/>
5-
<a href="http://mapstruct.org/">Website</a> |
5+
<a href="https://mapstruct.org/">Website</a> |
66
<a href="https://github.com/mapstruct/mapstruct-idea">GitHub</a> |
77
<a href="https://github.com/mapstruct/mapstruct-idea/issues">Issue Tracker</a> |
88
<br/>
99
<br/>
10-
This plugin gives some assistance in projects that use <a href="http://mapstruct.org/">MapStruct</a> to generate bean mapping code.
10+
This plugin gives some assistance in projects that use <a href="https://mapstruct.org/">MapStruct</a> to generate bean mapping code.
1111
<br/>
1212
<br/>
1313
MapStruct is a Java annotation processor for the generation of type-safe and performant mappers for Java bean classes.
@@ -19,20 +19,24 @@
1919
<b>Features:</b>
2020
<ul>
2121
<li>Code Completion:</li>
22+
<li>
2223
<ul>
2324
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@Mapping</code> annotation (nested properties also work)</li>
2425
<li>Completion of <code>target</code> and <code>source</code> properties in <code>@ValueMapping</code> annotation</li>
2526
<li>Completion of <code>componentModel</code> in <code>@Mapper</code> and <code>@MapperConfig</code> annotations</li>
2627
</ul>
28+
</li>
2729
<li>Go To Declaration for properties in <code>target</code> and <code>source</code> to setters / getters</li>
2830
<li>Find usages of properties in <code>target</code> and <code>source</code> and find usages of setters / getters in <code>@Mapping</code> annotations</li>
2931
<li>Highlighting properties in <code>target</code> and <code>source</code></li>
3032
<li>Refactoring support for properties and methods renaming</li>
31-
<li>Errors and Quick fixesL</li>
33+
<li>Errors and Quick Fixes:</li>
34+
<li>
3235
<ul>
3336
<li><code>@Mapper</code> or <code>@MapperConfig</code> annotation missing</li>
3437
<li>Unmapped target properties with quick fixes: Add unmapped target property and Ignore unmapped target property.
3538
NB: <code>unmappedTargetPolicy</code> is not yet considered</li>
3639
</ul>
40+
</li>
3741
</ul>
3842
</html>

src/main/java/org/mapstruct/intellij/expression/JavaExpressionInjector.java

Lines changed: 138 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
import java.util.Collections;
99
import java.util.List;
10+
import java.util.Set;
11+
import java.util.SortedSet;
12+
import java.util.TreeSet;
1013
import java.util.regex.Pattern;
14+
import java.util.stream.Collectors;
1115

1216
import com.intellij.codeInsight.AnnotationUtil;
1317
import com.intellij.lang.injection.MultiHostInjector;
@@ -20,6 +24,7 @@
2024
import com.intellij.psi.PsiAnnotationMemberValue;
2125
import com.intellij.psi.PsiAnnotationParameterList;
2226
import com.intellij.psi.PsiClass;
27+
import com.intellij.psi.PsiClassObjectAccessExpression;
2328
import com.intellij.psi.PsiClassType;
2429
import com.intellij.psi.PsiElement;
2530
import com.intellij.psi.PsiJavaCodeReferenceElement;
@@ -30,6 +35,7 @@
3035
import com.intellij.psi.PsiParameter;
3136
import com.intellij.psi.PsiReference;
3237
import com.intellij.psi.PsiType;
38+
import com.intellij.psi.PsiTypeParameter;
3339
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
3440
import com.intellij.psi.util.PsiTreeUtil;
3541
import com.intellij.psi.util.PsiUtil;
@@ -50,14 +56,102 @@ public class JavaExpressionInjector implements MultiHostInjector {
5056
MapstructElementUtils.mappingElementPattern( "defaultExpression" )
5157
);
5258

59+
private void importIfNecessary(PsiClass cls, @NotNull Set<String> imports) {
60+
if ( cls != null
61+
&& cls.getQualifiedName() != null
62+
&& !cls.getQualifiedName().startsWith( "java.lang." )
63+
) {
64+
imports.add( cls.getQualifiedName() );
65+
}
66+
}
67+
68+
private void appendType(@NotNull StringBuilder sb, @NotNull Set<String> imports, @NotNull PsiType type) {
69+
importIfNecessary( PsiUtil.resolveClassInType( type ), imports );
70+
if ( !( type instanceof PsiClassType ) ) {
71+
sb.append( type.getPresentableText() );
72+
return;
73+
}
74+
PsiClassType ct = (PsiClassType) type;
75+
sb.append( ct.getName() );
76+
PsiType[] typeParameters = ct.getParameters();
77+
if ( typeParameters.length == 0 ) {
78+
return;
79+
}
80+
sb.append( '<' );
81+
for ( int i = 0; i < typeParameters.length; ++i ) {
82+
if ( i != 0 ) {
83+
sb.append( ", " );
84+
}
85+
appendType( sb, imports, typeParameters[i] );
86+
}
87+
sb.append( '>' );
88+
}
89+
90+
private void appendClassSimple(@NotNull StringBuilder sb, @NotNull Set<String> imports, @NotNull PsiClass cls) {
91+
importIfNecessary( cls, imports );
92+
sb.append( cls.getName() );
93+
PsiTypeParameter[] typeParameters = cls.getTypeParameters();
94+
if ( typeParameters.length == 0 ) {
95+
return;
96+
}
97+
sb.append( '<' );
98+
for ( int i = 0; i < typeParameters.length; ++i ) {
99+
if ( i != 0 ) {
100+
sb.append( ", " );
101+
}
102+
appendClassSimple( sb, imports, typeParameters[i] );
103+
}
104+
sb.append( '>' );
105+
}
106+
107+
private void appendClassImpl(@NotNull StringBuilder sb, @NotNull Set<String> imports, @NotNull PsiClass cls) {
108+
importIfNecessary( cls, imports );
109+
sb.append( cls.getName() ).append( "Impl" );
110+
appendTypeParametersHard( sb, imports, cls.getTypeParameters() );
111+
}
112+
113+
private boolean appendTypeParametersHard(
114+
@NotNull StringBuilder sb, @NotNull Set<String> imports, PsiTypeParameter[] typeParameters
115+
) {
116+
if ( typeParameters.length == 0 ) {
117+
return false;
118+
}
119+
sb.append( "<" );
120+
for ( int i = 0; i < typeParameters.length; ++i ) {
121+
if ( i != 0 ) {
122+
sb.append( ", " );
123+
}
124+
sb.append( typeParameters[i].getName() );
125+
PsiClassType[] ext = typeParameters[i].getExtendsListTypes();
126+
if ( ext.length == 0 ) {
127+
continue;
128+
}
129+
sb.append( " extends " );
130+
for ( int j = 0; j < ext.length; ++j ) {
131+
if ( j != 0 ) {
132+
sb.append( ", " );
133+
}
134+
appendType( sb, imports, ext[j] );
135+
}
136+
}
137+
sb.append( ">" );
138+
return true;
139+
}
140+
141+
private void appendNesting(StringBuilder sb, int level) {
142+
for ( int i = 0; i < level; i++ ) {
143+
sb.append( " " );
144+
}
145+
}
146+
53147
@Override
54148
public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) {
55149

56150
if ( PATTERN.accepts( context ) && context instanceof PsiLiteralExpression &&
57151
JAVA_EXPRESSION.matcher( context.getText() ).matches() ) {
58152

59153
// Context is the PsiLiteralExpression
60-
// In order to reach the method have the following steps to do:
154+
// In order to reach the method have the following steps to take:
61155
// PsiLiteralExpression - "java(something)"
62156
// PsiNameValuePair - expression = "java(something)"
63157
// PsiAnnotationParameterList - target = "", expression = "java(something)"
@@ -74,7 +168,7 @@ public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull
74168
}
75169
PsiType targetType = null;
76170
for ( PsiNameValuePair attribute : annotationParameterList.getAttributes() ) {
77-
if ( "target" .equals( attribute.getAttributeName() ) ) {
171+
if ( "target".equals( attribute.getAttributeName() ) ) {
78172
PsiAnnotationMemberValue attributeValue = attribute.getValue();
79173
if ( attributeValue != null ) {
80174
PsiReference[] references = ReferenceProvidersRegistry.getReferencesFromProviders(
@@ -106,62 +200,65 @@ else if ( resolved instanceof PsiParameter ) {
106200
if ( mapperClass == null ) {
107201
return;
108202
}
109-
StringBuilder importsBuilder = new StringBuilder();
203+
204+
SortedSet<String> imports = new TreeSet<>();
110205
StringBuilder prefixBuilder = new StringBuilder();
111206

112-
prefixBuilder.append( "public class " )
113-
.append( mapperClass.getName() ).append( "Impl" )
114-
.append( " implements " ).append( mapperClass.getQualifiedName() ).append( "{ " )
115-
.append( "public " ).append( method.getReturnType().getCanonicalText() ).append( " " )
116-
.append( method.getName() ).append( "(" );
207+
prefixBuilder.append( "\n@SuppressWarnings(\"unused\")" );
208+
prefixBuilder.append( "\nabstract class " );
209+
appendClassImpl( prefixBuilder, imports, mapperClass );
210+
prefixBuilder.append( "\n" );
211+
appendNesting( prefixBuilder, 1 );
212+
prefixBuilder.append( mapperClass.isInterface() ? "implements " : "extends " );
213+
appendClassSimple( prefixBuilder, imports, mapperClass );
214+
prefixBuilder.append( " {\n\n" );
215+
appendNesting( prefixBuilder, 1 );
216+
if ( appendTypeParametersHard( prefixBuilder, imports, method.getTypeParameters() ) ) {
217+
prefixBuilder.append( " " );
218+
}
219+
prefixBuilder.append( "void __test__(\n" );
117220

118221
PsiParameter[] parameters = method.getParameterList().getParameters();
119222
for ( int i = 0; i < parameters.length; i++ ) {
120223
if ( i != 0 ) {
121-
prefixBuilder.append( "," );
224+
prefixBuilder.append( ",\n" );
122225
}
123226

124227
PsiParameter parameter = parameters[i];
125228
PsiType parameterType = parameter.getType();
126-
PsiClass parameterClass = PsiUtil.resolveClassInType( parameterType );
127-
128-
if ( parameterClass == null ) {
129-
return;
229+
for ( PsiAnnotation a : parameter.getAnnotations() ) {
230+
appendNesting( prefixBuilder, 2 );
231+
prefixBuilder.append( a.getText() ).append( "\n" );
130232
}
131-
132-
importsBuilder.append( "import " ).append( parameterClass.getQualifiedName() ).append( ";\n" );
133-
134-
prefixBuilder.append( parameterType.getCanonicalText() ).append( " " ).append( parameter.getName() );
233+
appendNesting( prefixBuilder, 2 );
234+
appendType( prefixBuilder, imports, parameterType );
235+
prefixBuilder.append( " " ).append( parameter.getName() );
135236
}
136237

137-
prefixBuilder.append( ") {" )
138-
.append( targetType.getCanonicalText() ).append( " __target__ =" );
139-
140-
141-
PsiClass targetClass = PsiUtil.resolveClassInType( targetType );
142-
if ( targetClass != null ) {
143-
importsBuilder.append( "import " ).append( targetClass.getQualifiedName() ).append( ";\n" );
144-
}
145-
if ( targetType instanceof PsiClassType ) {
146-
for ( PsiType typeParameter : ( (PsiClassType) targetType ).getParameters() ) {
147-
PsiClass typeClass = PsiUtil.resolveClassInType( typeParameter );
148-
if ( typeClass != null ) {
149-
importsBuilder.append( "import " ).append( typeClass.getQualifiedName() ).append( ";\n" );
150-
}
151-
}
152-
}
238+
prefixBuilder.append( "\n" );
239+
appendNesting( prefixBuilder, 1 );
240+
prefixBuilder.append( ") {\n" );
241+
appendNesting( prefixBuilder, 2 );
242+
appendType( prefixBuilder, imports, targetType );
243+
prefixBuilder.append( " __target__ = " );
153244

154245
PsiAnnotation mapper = mapperClass.getAnnotation( MapstructUtil.MAPPER_ANNOTATION_FQN );
155246
if ( mapper != null ) {
156247
for ( PsiNameValuePair attribute : mapper.getParameterList().getAttributes() ) {
157-
if ( "imports" .equals( attribute.getName() ) ) {
248+
if ( "imports".equals( attribute.getName() ) ) {
158249
for ( PsiAnnotationMemberValue importValue : AnnotationUtil.arrayAttributeValues(
159250
attribute.getValue() ) ) {
160251

161252
if ( importValue instanceof PsiJavaCodeReferenceElement ) {
162-
importsBuilder.append( "import " )
163-
.append( ( (PsiJavaCodeReferenceElement) importValue ).getQualifiedName() )
164-
.append( ";" );
253+
imports.add( ( (PsiJavaCodeReferenceElement) importValue ).getQualifiedName() );
254+
}
255+
else if ( importValue instanceof PsiClassObjectAccessExpression ) {
256+
PsiJavaCodeReferenceElement referenceElement =
257+
( (PsiClassObjectAccessExpression) importValue ).getOperand()
258+
.getInnermostComponentReferenceElement();
259+
if ( referenceElement != null ) {
260+
imports.add( referenceElement.getQualifiedName() );
261+
}
165262
}
166263
}
167264
}
@@ -170,10 +267,11 @@ else if ( resolved instanceof PsiParameter ) {
170267

171268
registrar.startInjecting( JavaLanguage.INSTANCE )
172269
.addPlace(
173-
importsBuilder.toString() + prefixBuilder.toString(),
174-
";} }",
270+
imports.stream().map( imp -> "import " + imp + ";" ).collect( Collectors.joining( "\n", "", "\n" ) )
271+
+ prefixBuilder,
272+
";\n }\n}",
175273
(PsiLanguageInjectionHost) context,
176-
new TextRange( 6, context.getTextRange().getLength() - 2 )
274+
new TextRange( "\"java(".length(), context.getTextRange().getLength() - ")\"".length() )
177275
)
178276
.doneInjecting();
179277
}

0 commit comments

Comments
 (0)