generate java 9 api usage
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / java15api / Java15APIUsageInspectionBase.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.codeInspection.java15api;
17
18 import com.intellij.ToolExtensionPoints;
19 import com.intellij.codeHighlighting.HighlightDisplayLevel;
20 import com.intellij.codeInsight.AnnotationUtil;
21 import com.intellij.codeInsight.daemon.GroupNames;
22 import com.intellij.codeInsight.intention.QuickFixFactory;
23 import com.intellij.codeInspection.*;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.extensions.ExtensionPoint;
26 import com.intellij.openapi.extensions.ExtensionPointName;
27 import com.intellij.openapi.extensions.Extensions;
28 import com.intellij.openapi.module.EffectiveLanguageLevelUtil;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.module.ModuleUtilCore;
31 import com.intellij.openapi.projectRoots.JavaSdkVersion;
32 import com.intellij.openapi.projectRoots.JavaVersionService;
33 import com.intellij.openapi.util.InvalidDataException;
34 import com.intellij.openapi.util.WriteExternalException;
35 import com.intellij.openapi.util.io.FileUtil;
36 import com.intellij.openapi.vfs.CharsetToolkit;
37 import com.intellij.pom.java.LanguageLevel;
38 import com.intellij.psi.*;
39 import com.intellij.psi.javadoc.PsiDocComment;
40 import com.intellij.psi.util.InheritanceUtil;
41 import com.intellij.psi.util.PsiTreeUtil;
42 import com.intellij.psi.util.PsiUtil;
43 import com.intellij.reference.SoftReference;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.containers.hash.HashSet;
46 import gnu.trove.THashSet;
47 import org.jdom.Element;
48 import org.jetbrains.annotations.NotNull;
49 import org.jetbrains.annotations.Nullable;
50
51 import java.io.BufferedReader;
52 import java.io.IOException;
53 import java.io.InputStreamReader;
54 import java.io.UnsupportedEncodingException;
55 import java.lang.ref.Reference;
56 import java.net.URL;
57 import java.util.ArrayList;
58 import java.util.List;
59 import java.util.Map;
60 import java.util.Set;
61
62 /**
63  * @author max
64  */
65 public class Java15APIUsageInspectionBase extends BaseJavaBatchLocalInspectionTool {
66   public static final String SHORT_NAME = "Since15";
67   public static final ExtensionPointName<FileCheckingInspection> EP_NAME =
68     ExtensionPointName.create(ToolExtensionPoints.JAVA15_INSPECTION_TOOL);
69
70   private static final String EFFECTIVE_LL = "effectiveLL";
71
72   private static final Map<LanguageLevel, Reference<Set<String>>> ourForbiddenAPI = ContainerUtil.newEnumMap(LanguageLevel.class);
73   private static final Set<String> ourIgnored16ClassesAPI = new THashSet<String>(10);
74   private static final Map<LanguageLevel, String> ourPresentableShortMessage = ContainerUtil.newEnumMap(LanguageLevel.class);
75   static {
76     ourPresentableShortMessage.put(LanguageLevel.JDK_1_3, "1.4");
77     ourPresentableShortMessage.put(LanguageLevel.JDK_1_4, "1.5");
78     ourPresentableShortMessage.put(LanguageLevel.JDK_1_5, "1.6");
79     ourPresentableShortMessage.put(LanguageLevel.JDK_1_6, "1.7");
80     ourPresentableShortMessage.put(LanguageLevel.JDK_1_7, "1.8");
81     ourPresentableShortMessage.put(LanguageLevel.JDK_1_9, "1.9");
82
83     loadForbiddenApi("ignore16List.txt", ourIgnored16ClassesAPI);
84   }
85
86   private static final Set<String> ourGenerifiedClasses = new HashSet<String>();
87   static {
88     ourGenerifiedClasses.add("javax.swing.JComboBox");
89     ourGenerifiedClasses.add("javax.swing.ListModel");
90     ourGenerifiedClasses.add("javax.swing.JList");
91   }
92   
93   private static final Set<String> ourDefaultMethods = new HashSet<String>();
94   static {
95     ourDefaultMethods.add("java.util.Iterator#remove()");
96   }
97
98   protected LanguageLevel myEffectiveLanguageLevel = null;
99
100   @Nullable
101   private static Set<String> getForbiddenApi(@NotNull LanguageLevel languageLevel) {
102     if (!ourPresentableShortMessage.containsKey(languageLevel)) return null;
103     Reference<Set<String>> ref = ourForbiddenAPI.get(languageLevel);
104     Set<String> result = SoftReference.dereference(ref);
105     if (result == null) {
106       result = new THashSet<String>(1000);
107       loadForbiddenApi("api" + getShortName(languageLevel) + ".txt", result);
108       ourForbiddenAPI.put(languageLevel, new SoftReference<Set<String>>(result));
109     }
110     return result;
111   }
112
113   private static void loadForbiddenApi(String fileName, Set<String> set) {
114     URL resource = Java15APIUsageInspectionBase.class.getResource(fileName);
115     if (resource == null) {
116       Logger.getInstance(Java15APIUsageInspectionBase.class).warn("not found: " + fileName);
117       return;
118     }
119
120     try {
121       BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), CharsetToolkit.UTF8_CHARSET));
122       try {
123         set.addAll(FileUtil.loadLines(reader));
124       }
125       finally {
126         reader.close();
127       }
128     }
129     catch (UnsupportedEncodingException ignored) { }
130     catch (IOException ignored) { }
131   }
132
133   @Override
134   @NotNull
135   public String getGroupDisplayName() {
136     return GroupNames.LANGUAGE_LEVEL_SPECIFIC_GROUP_NAME;
137   }
138
139   @Override
140   @NotNull
141   public String getDisplayName() {
142     return InspectionsBundle.message("inspection.1.5.display.name");
143   }
144
145   @Override
146   @NotNull
147   public String getShortName() {
148     return SHORT_NAME;
149   }
150
151
152   @NotNull
153   @Override
154   public HighlightDisplayLevel getDefaultLevel() {
155     return HighlightDisplayLevel.ERROR;
156   }
157
158   @Override
159   public void readSettings(@NotNull Element node) throws InvalidDataException {
160     final Element element = node.getChild(EFFECTIVE_LL);
161     if (element != null) {
162       myEffectiveLanguageLevel = LanguageLevel.valueOf(element.getAttributeValue("value"));
163     }
164   }
165
166   @Override
167   public void writeSettings(@NotNull Element node) throws WriteExternalException {
168     if (myEffectiveLanguageLevel != null) {
169       final Element llElement = new Element(EFFECTIVE_LL);
170       llElement.setAttribute("value", myEffectiveLanguageLevel.toString());
171       node.addContent(llElement);
172     }
173   }
174
175   @Override
176   @NotNull
177   public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
178     return new MyVisitor(holder, isOnTheFly);
179   }
180
181   private static boolean isInProject(final PsiElement elt) {
182     return elt.getManager().isInProject(elt);
183   }
184
185   public static String getShortName(LanguageLevel languageLevel) {
186     return ourPresentableShortMessage.get(languageLevel);
187   }
188
189   private class MyVisitor extends JavaElementVisitor {
190     private final ProblemsHolder myHolder;
191     private final boolean myOnTheFly;
192     private final ExtensionPoint<FileCheckingInspection> point = Extensions.getRootArea().getExtensionPoint(EP_NAME);
193
194     public MyVisitor(final ProblemsHolder holder, boolean onTheFly) {
195       myHolder = holder;
196       myOnTheFly = onTheFly;
197     }
198
199     @Override public void visitDocComment(PsiDocComment comment) {
200       // No references inside doc comment are of interest.
201     }
202
203     @Override public void visitClass(PsiClass aClass) {
204       // Don't go into classes (anonymous, locals).
205       if (!aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !(aClass instanceof PsiTypeParameter)) {
206         final Module module = ModuleUtilCore.findModuleForPsiElement(aClass);
207         final LanguageLevel effectiveLanguageLevel = module != null ? getEffectiveLanguageLevel(module) : null;
208         if (effectiveLanguageLevel != null && !effectiveLanguageLevel.isAtLeast(LanguageLevel.JDK_1_8)) {
209           final JavaSdkVersion version = JavaVersionService.getInstance().getJavaSdkVersion(aClass);
210           if (version != null && version.isAtLeast(JavaSdkVersion.JDK_1_8)) {
211             final List<PsiMethod> methods = new ArrayList<PsiMethod>();
212             for (HierarchicalMethodSignature methodSignature : aClass.getVisibleSignatures()) {
213               final PsiMethod method = methodSignature.getMethod();
214               if (ourDefaultMethods.contains(getSignature(method))) {
215                 methods.add(method);
216               }
217             }
218   
219             if (!methods.isEmpty()) {
220               PsiElement element2Highlight = aClass.getNameIdentifier();
221               if (element2Highlight == null) {
222                 element2Highlight = aClass;
223               }
224               myHolder.registerProblem(element2Highlight,
225                                        methods.size() == 1 ? InspectionsBundle.message("inspection.1.8.problem.single.descriptor", methods.get(0).getName(), getJdkName(effectiveLanguageLevel)) 
226                                                            : InspectionsBundle.message("inspection.1.8.problem.descriptor", methods.size(), getJdkName(effectiveLanguageLevel)),
227                                        QuickFixFactory.getInstance().createImplementMethodsFix(aClass));
228             }
229           }
230         }
231       }
232     }
233
234     @Override public void visitReferenceExpression(PsiReferenceExpression expression) {
235       visitReferenceElement(expression);
236     }
237
238     @Override public void visitReferenceElement(PsiJavaCodeReferenceElement reference) {
239       super.visitReferenceElement(reference);
240       final PsiElement resolved = reference.resolve();
241
242       if (resolved instanceof PsiCompiledElement && resolved instanceof PsiMember) {
243         final Module module = ModuleUtilCore.findModuleForPsiElement(reference.getElement());
244         if (module != null) {
245           final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
246           if (isForbiddenApiUsage((PsiMember)resolved, languageLevel)) {
247             PsiClass psiClass = null;
248             final PsiElement qualifier = reference.getQualifier();
249             if (qualifier != null) {
250               if (qualifier instanceof PsiExpression) {
251                 psiClass = PsiUtil.resolveClassInType(((PsiExpression)qualifier).getType());
252               }
253             }
254             else {
255               psiClass = PsiTreeUtil.getParentOfType(reference, PsiClass.class);
256             }
257             if (psiClass != null) {
258               if (isIgnored(psiClass)) return;
259               for (PsiClass superClass : psiClass.getSupers()) {
260                 if (isIgnored(superClass)) return;
261               }
262             }
263             registerError(reference, languageLevel);
264           } else if (resolved instanceof PsiClass && isInProject(reference)&& !languageLevel.isAtLeast(LanguageLevel.JDK_1_7)) {
265             final PsiReferenceParameterList parameterList = reference.getParameterList();
266             if (parameterList != null && parameterList.getTypeParameterElements().length > 0) {
267               for (String generifiedClass : ourGenerifiedClasses) {
268                 if (InheritanceUtil.isInheritor((PsiClass)resolved, generifiedClass) && 
269                     !isRawInheritance(generifiedClass, (PsiClass)resolved, new HashSet<PsiClass>())) {
270                   String message = InspectionsBundle.message("inspection.1.7.problem.descriptor", getJdkName(languageLevel));
271                   myHolder.registerProblem(reference, message);
272                   break;
273                 }
274               }
275             }
276           }
277         }
278       }
279     }
280
281     private boolean isRawInheritance(String generifiedClassQName, PsiClass currentClass, Set<PsiClass> visited) {
282       for (PsiClassType classType : currentClass.getSuperTypes()) {
283         if (classType.isRaw()) {
284           return true;
285         }
286         final PsiClassType.ClassResolveResult resolveResult = classType.resolveGenerics();
287         final PsiClass superClass = resolveResult.getElement();
288         if (visited.add(superClass) && InheritanceUtil.isInheritor(superClass, generifiedClassQName)) {
289           if (isRawInheritance(generifiedClassQName, superClass, visited)) {
290             return true;
291           }
292         }
293       }
294       return false;
295     }
296
297     private boolean isIgnored(PsiClass psiClass) {
298       final String qualifiedName = psiClass.getQualifiedName();
299       return qualifiedName != null && ourIgnored16ClassesAPI.contains(qualifiedName);
300     }
301
302     @Override public void visitNewExpression(final PsiNewExpression expression) {
303       super.visitNewExpression(expression);
304       final PsiMethod constructor = expression.resolveConstructor();
305       final Module module = ModuleUtilCore.findModuleForPsiElement(expression);
306       if (module != null) {
307         final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
308         if (constructor instanceof PsiCompiledElement) {
309           if (isForbiddenApiUsage(constructor, languageLevel)) {
310             registerError(expression.getClassReference(), languageLevel);
311           }
312         }
313       }
314     }
315
316     @Override
317     public void visitMethod(PsiMethod method) {
318       super.visitMethod(method);
319       PsiAnnotation annotation = AnnotationUtil.findAnnotation(method, CommonClassNames.JAVA_LANG_OVERRIDE);
320       if (annotation != null) {
321         final Module module = ModuleUtilCore.findModuleForPsiElement(annotation);
322         if (module != null) {
323           final LanguageLevel languageLevel = getEffectiveLanguageLevel(module);
324           final PsiMethod[] methods = method.findSuperMethods();
325           for (PsiMethod superMethod : methods) {
326             if (superMethod instanceof PsiCompiledElement) {
327               if (!isForbiddenApiUsage(superMethod, languageLevel)) {
328                 return;
329               }
330             }
331             else {
332               return;
333             }
334           }
335           if (methods.length > 0) {
336             registerError(annotation.getNameReferenceElement(), languageLevel);
337           }
338         }
339       }
340     }
341
342     private LanguageLevel getEffectiveLanguageLevel(Module module) {
343       if (myEffectiveLanguageLevel != null) return myEffectiveLanguageLevel;
344       return EffectiveLanguageLevelUtil.getEffectiveLanguageLevel(module);
345     }
346
347     private void registerError(PsiJavaCodeReferenceElement reference, LanguageLevel api) {
348       if (reference != null && isInProject(reference)) {
349         //noinspection DialogTitleCapitalization
350         myHolder.registerProblem(reference, InspectionsBundle.message("inspection.1.5.problem.descriptor", getShortName(api)));
351       }
352     }
353
354     @Override
355     public void visitFile(PsiFile file) {
356       for (FileCheckingInspection inspection : point.getExtensions()) {
357         ProblemDescriptor[] descriptors = inspection.checkFile(file, InspectionManager.getInstance(file.getProject()), myOnTheFly);
358         if (descriptors != null) {
359           for (ProblemDescriptor descriptor : descriptors) {
360             myHolder.registerProblem(descriptor);
361           }
362         }
363       }
364     }
365   }
366
367   private static String getJdkName(LanguageLevel languageLevel) {
368     final String presentableText = languageLevel.getPresentableText();
369     return presentableText.substring(0, presentableText.indexOf(" "));
370   }
371
372   public static boolean isForbiddenApiUsage(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) {
373     if (member instanceof PsiAnonymousClass) return false;
374     PsiClass containingClass = member.getContainingClass();
375     if (containingClass instanceof PsiAnonymousClass) return false;
376     if (member instanceof PsiClass && !(member.getParent() instanceof PsiClass || member.getParent() instanceof PsiFile)) return false;
377
378     return isForbiddenSignature(member, languageLevel) ||
379            containingClass != null && isForbiddenApiUsage(containingClass, languageLevel);
380
381   }
382
383   private static boolean isForbiddenSignature(@NotNull PsiMember member, @NotNull LanguageLevel languageLevel) {
384     Set<String> forbiddenApi = getForbiddenApi(languageLevel);
385     String signature = getSignature(member);
386     return forbiddenApi != null && signature != null && isForbiddenSignature(signature, languageLevel, forbiddenApi);
387   }
388
389   private static boolean isForbiddenSignature(@NotNull String signature, @NotNull LanguageLevel languageLevel, @NotNull Set<String> forbiddenApi) {
390     if (forbiddenApi.contains(signature)) {
391       return true;
392     }
393     if (languageLevel.compareTo(LanguageLevel.HIGHEST) == 0) {
394       return false;
395     }
396     LanguageLevel nextLanguageLevel = LanguageLevel.values()[languageLevel.ordinal() + 1];
397     Set<String> nextForbiddenApi = getForbiddenApi(nextLanguageLevel);
398     return nextForbiddenApi != null && isForbiddenSignature(signature, nextLanguageLevel, nextForbiddenApi);
399   }
400
401   /**
402    * please leave public for JavaAPIUsagesInspectionTest#testCollectSinceApiUsages
403    */
404   @Nullable
405   public static String getSignature(@Nullable PsiMember member) {
406     if (member instanceof PsiClass) {
407       return ((PsiClass)member).getQualifiedName();
408     }
409     if (member instanceof PsiField) {
410       String containingClass = getSignature(member.getContainingClass());
411       return containingClass == null ? null : containingClass + "#" + member.getName();
412     }
413     if (member instanceof PsiMethod) {
414       final PsiMethod method = (PsiMethod)member;
415       String containingClass = getSignature(member.getContainingClass());
416       if (containingClass == null) return null;
417
418       StringBuilder buf = new StringBuilder();
419       buf.append(containingClass);
420       buf.append('#');
421       buf.append(method.getName());
422       buf.append('(');
423       for (PsiType type : method.getSignature(PsiSubstitutor.EMPTY).getParameterTypes()) {
424         buf.append(type.getCanonicalText());
425         buf.append(";");
426       }
427       buf.append(')');
428       return buf.toString();
429     }
430     return null;
431   }
432 }