Skip to content

Commit f602c3a

Browse files
authored
#167 Support Map as MappingSource
1 parent cc1be91 commit f602c3a

18 files changed

+939
-29
lines changed
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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 com.intellij.codeInspection.LocalQuickFixOnPsiElement;
9+
import com.intellij.codeInspection.ProblemsHolder;
10+
import com.intellij.codeInspection.util.IntentionFamilyName;
11+
import com.intellij.codeInspection.util.IntentionName;
12+
import com.intellij.openapi.project.Project;
13+
import com.intellij.psi.JavaElementVisitor;
14+
import com.intellij.psi.PsiElement;
15+
import com.intellij.psi.PsiElementVisitor;
16+
import com.intellij.psi.PsiFile;
17+
import com.intellij.psi.PsiMethod;
18+
import com.intellij.psi.PsiParameter;
19+
import com.intellij.psi.PsiType;
20+
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.Nullable;
22+
import org.mapstruct.intellij.MapStructBundle;
23+
import org.mapstruct.intellij.util.MapStructVersion;
24+
import org.mapstruct.intellij.util.MapstructUtil;
25+
26+
import java.util.Set;
27+
28+
import static com.intellij.psi.PsiElementFactory.getInstance;
29+
import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters;
30+
import static org.mapstruct.intellij.util.SourceUtils.findAllDefinedMappingSources;
31+
import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes;
32+
import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties;
33+
import static org.mapstruct.intellij.util.TargetUtils.getTargetType;
34+
35+
/**
36+
* @author hduelme
37+
*/
38+
public class FromMapMappingMapTypeInspection extends InspectionBase {
39+
40+
@NotNull
41+
@Override
42+
PsiElementVisitor buildVisitorInternal(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
43+
return new MyJavaElementVisitor( holder, MapstructUtil.resolveMapStructProjectVersion( holder.getFile() ) );
44+
}
45+
46+
private static class MyJavaElementVisitor extends JavaElementVisitor {
47+
private final ProblemsHolder holder;
48+
private final MapStructVersion mapStructVersion;
49+
50+
private MyJavaElementVisitor(ProblemsHolder holder, MapStructVersion mapStructVersion) {
51+
this.holder = holder;
52+
this.mapStructVersion = mapStructVersion;
53+
}
54+
55+
@Override
56+
public void visitMethod(@NotNull PsiMethod method) {
57+
super.visitMethod( method );
58+
59+
if (!MapstructUtil.isMapper( method.getContainingClass() ) ) {
60+
return;
61+
}
62+
63+
PsiType targetType = getTargetType( method );
64+
if (targetType == null) {
65+
return;
66+
}
67+
68+
PsiParameter fromMapMappingParameter = getFromMapMappingParameter( method );
69+
if (fromMapMappingParameter == null) {
70+
return;
71+
}
72+
PsiType[] parameters = getGenericTypes( fromMapMappingParameter );
73+
if (parameters == null) {
74+
return;
75+
}
76+
Set<String> allTargetProperties = findAllTargetProperties( targetType, mapStructVersion, method );
77+
if ( allTargetProperties.contains( fromMapMappingParameter.getName() ) ) {
78+
return;
79+
}
80+
if ( findAllDefinedMappingSources( method, mapStructVersion )
81+
.anyMatch( source -> fromMapMappingParameter.getName().equals( source ) ) ) {
82+
return;
83+
}
84+
if (parameters.length == 0) {
85+
// handle raw type
86+
holder.registerProblem( fromMapMappingParameter,
87+
MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw" ),
88+
new ReplaceByStringStringMapTypeFix( fromMapMappingParameter ) );
89+
}
90+
else if (parameters.length == 2) {
91+
// only if both parameters of the map are set
92+
PsiType keyParameter = parameters[0];
93+
if ( !keyParameter.equalsToText( "java.lang.String" ) ) {
94+
// handle wrong map key type
95+
holder.registerProblem( fromMapMappingParameter,
96+
MapStructBundle.message( "inspection.wrong.map.mapping.map.key" ),
97+
new ReplaceMapKeyByStringTypeFix( fromMapMappingParameter ) );
98+
}
99+
}
100+
}
101+
102+
@Nullable
103+
private static PsiParameter getFromMapMappingParameter(@NotNull PsiMethod method) {
104+
PsiParameter[] sourceParameters = getSourceParameters( method );
105+
if (sourceParameters.length == 1) {
106+
PsiParameter parameter = sourceParameters[0];
107+
if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(),
108+
method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) {
109+
return parameter;
110+
}
111+
}
112+
return null;
113+
}
114+
115+
private static class ReplaceByStringStringMapTypeFix extends LocalQuickFixOnPsiElement {
116+
117+
private final String text;
118+
119+
private ReplaceByStringStringMapTypeFix(@NotNull PsiParameter element) {
120+
super( element );
121+
this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.type.raw.set.default",
122+
element.getType().getPresentableText() );
123+
}
124+
125+
@Override
126+
public @IntentionName @NotNull String getText() {
127+
return text;
128+
}
129+
130+
@Override
131+
public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file,
132+
@NotNull PsiElement startElement, @NotNull PsiElement endElement) {
133+
if (!super.isAvailable( project, file, startElement, endElement ) ) {
134+
return false;
135+
}
136+
if (startElement instanceof PsiParameter) {
137+
PsiParameter parameter = (PsiParameter) startElement;
138+
PsiType[] parameters = getGenericTypes( parameter );
139+
return parameters != null && parameters.length == 0;
140+
}
141+
return false;
142+
}
143+
144+
@Override
145+
public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement,
146+
@NotNull PsiElement psiElement1) {
147+
if (psiElement instanceof PsiParameter) {
148+
String mapText = psiElement.getText();
149+
String prefix = mapText.substring( 0, mapText.indexOf( ' ' ) );
150+
String end = mapText.substring( mapText.lastIndexOf( ' ' ) );
151+
String result = prefix + "<String, String>" + end;
152+
psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) );
153+
}
154+
}
155+
156+
@Override
157+
public @IntentionFamilyName @NotNull String getFamilyName() {
158+
return MapStructBundle.message( "intention.wrong.map.mapping.map.type.raw" );
159+
}
160+
}
161+
162+
private static class ReplaceMapKeyByStringTypeFix extends LocalQuickFixOnPsiElement {
163+
164+
private final String text;
165+
166+
private ReplaceMapKeyByStringTypeFix(@NotNull PsiParameter element) {
167+
super( element );
168+
this.text = MapStructBundle.message( "inspection.wrong.map.mapping.map.key.change.to.string" );
169+
}
170+
171+
@Override
172+
public @IntentionName @NotNull String getText() {
173+
return text;
174+
}
175+
176+
@Override
177+
public boolean isAvailable(@NotNull Project project, @NotNull PsiFile file,
178+
@NotNull PsiElement startElement, @NotNull PsiElement endElement) {
179+
if (!super.isAvailable( project, file, startElement, endElement ) ) {
180+
return false;
181+
}
182+
if (startElement instanceof PsiParameter) {
183+
PsiParameter parameter = (PsiParameter) startElement;
184+
PsiType[] parameters = getGenericTypes( parameter );
185+
if (parameters == null || parameters.length != 2) {
186+
return false;
187+
}
188+
return !parameters[0].equalsToText( "java.lang.String" );
189+
}
190+
return false;
191+
}
192+
193+
@Override
194+
public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiElement psiElement,
195+
@NotNull PsiElement psiElement1) {
196+
if (psiElement instanceof PsiParameter) {
197+
String mapText = psiElement.getText();
198+
String prefix = mapText.substring( 0, mapText.indexOf( '<' ) + 1 );
199+
String end = mapText.substring( mapText.indexOf( ',' ) );
200+
String result = prefix + "String" + end;
201+
psiElement.replace( getInstance( project ).createParameterFromText( result, psiElement ) );
202+
}
203+
}
204+
205+
@Override
206+
public @IntentionFamilyName @NotNull String getFamilyName() {
207+
return MapStructBundle.message( "intention.wrong.map.mapping.map.key" );
208+
}
209+
}
210+
}
211+
212+
}

src/main/java/org/mapstruct/intellij/inspection/UnmappedTargetPropertiesInspection.java

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,16 @@
2121
import com.intellij.psi.JavaElementVisitor;
2222
import com.intellij.psi.JavaPsiFacade;
2323
import com.intellij.psi.PsiAnnotation;
24-
import com.intellij.psi.PsiClass;
2524
import com.intellij.psi.PsiElement;
2625
import com.intellij.psi.PsiElementVisitor;
2726
import com.intellij.psi.PsiFile;
2827
import com.intellij.psi.PsiMethod;
29-
import com.intellij.psi.PsiModifier;
3028
import com.intellij.psi.PsiModifierListOwner;
29+
import com.intellij.psi.PsiParameter;
3130
import com.intellij.psi.PsiType;
3231
import com.intellij.psi.util.PsiUtil;
3332
import org.jetbrains.annotations.Nls;
3433
import org.jetbrains.annotations.NotNull;
35-
import org.jetbrains.annotations.Nullable;
3634
import org.mapstruct.ReportingPolicy;
3735
import org.mapstruct.intellij.MapStructBundle;
3836
import org.mapstruct.intellij.settings.ProjectSettings;
@@ -44,14 +42,13 @@
4442
import static org.mapstruct.intellij.inspection.inheritance.InheritConfigurationUtils.findInheritedTargetProperties;
4543
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.addMappingAnnotation;
4644
import static org.mapstruct.intellij.util.MapstructAnnotationUtils.getUnmappedTargetPolicy;
47-
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
48-
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
49-
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
45+
import static org.mapstruct.intellij.util.MapstructUtil.getSourceParameters;
5046
import static org.mapstruct.intellij.util.SourceUtils.findAllSourceProperties;
47+
import static org.mapstruct.intellij.util.SourceUtils.getGenericTypes;
5148
import static org.mapstruct.intellij.util.TargetUtils.findAllDefinedMappingTargets;
5249
import static org.mapstruct.intellij.util.TargetUtils.findAllSourcePropertiesForCurrentTarget;
5350
import static org.mapstruct.intellij.util.TargetUtils.findAllTargetProperties;
54-
import static org.mapstruct.intellij.util.TargetUtils.getRelevantType;
51+
import static org.mapstruct.intellij.util.TargetUtils.getTargetType;
5552

5653
/**
5754
* Inspection that checks if there are unmapped target properties.
@@ -91,6 +88,10 @@ public void visitMethod(PsiMethod method) {
9188
return;
9289
}
9390

91+
if ( isFromMapMapping( method ) ) {
92+
return;
93+
}
94+
9495
ReportingPolicy reportingPolicy = getUnmappedTargetPolicy( method );
9596
if (reportingPolicy == ReportingPolicy.IGNORE) {
9697
return;
@@ -185,28 +186,18 @@ private static boolean isBeanMappingIgnoreByDefault(PsiMethod method) {
185186
return false;
186187
}
187188

188-
/**
189-
* @param method the method to be used
190-
*
191-
* @return the target class for the inspection, or {@code null} if no inspection needs to be performed
192-
*/
193-
@Nullable
194-
private static PsiType getTargetType(PsiMethod method) {
195-
if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) {
196-
return null;
197-
}
198-
199-
if ( isInheritInverseConfiguration( method ) ) {
200-
return null;
201-
}
202-
PsiClass containingClass = method.getContainingClass();
203-
204-
if ( containingClass == null
205-
|| method.getNameIdentifier() == null
206-
|| !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) {
207-
return null;
189+
private static boolean isFromMapMapping(@NotNull PsiMethod method) {
190+
PsiParameter[] sourceParameters = getSourceParameters( method );
191+
for (PsiParameter parameter : sourceParameters) {
192+
if (parameter != null && PsiType.getTypeByName( "java.util.Map", method.getProject(),
193+
method.getResolveScope() ).isAssignableFrom( parameter.getType() ) ) {
194+
PsiType[] generics = getGenericTypes( parameter );
195+
if (generics != null && generics.length > 0) {
196+
return generics[0].equalsToText( "java.lang.String" );
197+
}
198+
}
208199
}
209-
return getRelevantType( method );
200+
return false;
210201
}
211202

212203
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.intellij.psi.PsiRecordComponent;
2626
import com.intellij.psi.PsiSubstitutor;
2727
import com.intellij.psi.PsiType;
28+
import com.intellij.psi.impl.source.PsiClassReferenceType;
2829
import com.intellij.psi.util.PsiUtil;
2930
import org.jetbrains.annotations.NotNull;
3031
import org.jetbrains.annotations.Nullable;
@@ -211,4 +212,13 @@ else if ( methodName.startsWith( "is" ) && (
211212
}
212213

213214
}
215+
216+
@Nullable
217+
public static PsiType[] getGenericTypes(@Nullable PsiParameter fromMapMappingParameter) {
218+
if (fromMapMappingParameter == null ||
219+
!(fromMapMappingParameter.getType() instanceof PsiClassReferenceType)) {
220+
return null;
221+
}
222+
return ((PsiClassReferenceType) fromMapMappingParameter.getType()).getParameters();
223+
}
214224
}

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

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.intellij.psi.PsiJavaCodeReferenceElement;
3030
import com.intellij.psi.PsiMember;
3131
import com.intellij.psi.PsiMethod;
32+
import com.intellij.psi.PsiModifier;
3233
import com.intellij.psi.PsiModifierListOwner;
3334
import com.intellij.psi.PsiParameter;
3435
import com.intellij.psi.PsiSubstitutor;
@@ -45,6 +46,9 @@
4546
import static org.mapstruct.intellij.util.MapstructUtil.MAPPER_ANNOTATION_FQN;
4647
import static org.mapstruct.intellij.util.MapstructUtil.canDescendIntoType;
4748
import static org.mapstruct.intellij.util.MapstructUtil.isFluentSetter;
49+
import static org.mapstruct.intellij.util.MapstructUtil.isInheritInverseConfiguration;
50+
import static org.mapstruct.intellij.util.MapstructUtil.isMapper;
51+
import static org.mapstruct.intellij.util.MapstructUtil.isMapperConfig;
4852
import static org.mapstruct.intellij.util.MapstructUtil.publicFields;
4953

5054
/**
@@ -433,4 +437,28 @@ public static Set<String> findAllTargetProperties(@NotNull PsiType targetType, M
433437
return publicWriteAccessors( targetType, mapStructVersion, mappingMethod ).keySet();
434438
}
435439

440+
/**
441+
* @param method the method to be used
442+
*
443+
* @return the target class for the inspection, or {@code null} if no inspection needs to be performed
444+
*/
445+
@Nullable
446+
public static PsiType getTargetType( @NotNull PsiMethod method) {
447+
if ( !method.getModifierList().hasModifierProperty( PsiModifier.ABSTRACT ) ) {
448+
return null;
449+
}
450+
451+
if ( isInheritInverseConfiguration( method ) ) {
452+
return null;
453+
}
454+
PsiClass containingClass = method.getContainingClass();
455+
456+
if ( containingClass == null
457+
|| method.getNameIdentifier() == null
458+
|| !( isMapper( containingClass ) || isMapperConfig( containingClass ) ) ) {
459+
return null;
460+
}
461+
return getRelevantType( method );
462+
}
463+
436464
}

src/main/resources/META-INF/plugin.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@
112112
key="inspection.java.expression.unnecessary.whitespace.title"
113113
shortName="JavaExpressionUnnecessaryWhitespaces"
114114
implementationClass="org.mapstruct.intellij.inspection.JavaExpressionUnnecessaryWhitespacesInspector"/>
115+
<localInspection
116+
language="JAVA"
117+
enabledByDefault="true"
118+
level="WARNING"
119+
bundle="org.mapstruct.intellij.messages.MapStructBundle"
120+
key="inspection.wrong.map.mapping.map.type"
121+
shortName="FromMapMappingInspection"
122+
implementationClass="org.mapstruct.intellij.inspection.FromMapMappingMapTypeInspection"/>
115123
</extensions>
116124

117125
<actions>

0 commit comments

Comments
 (0)