Java: Moved text message to the resource bundle in the inspection "Non-accessible...
[idea/community.git] / java / java-analysis-impl / src / com / intellij / codeInspection / java19modules / Java9NonAccessibleTypeExposedInspection.java
1 /*
2  * Copyright 2000-2016 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.java19modules;
17
18 import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
19 import com.intellij.codeInspection.BaseJavaLocalInspectionTool;
20 import com.intellij.codeInspection.InspectionsBundle;
21 import com.intellij.codeInspection.ProblemsHolder;
22 import com.intellij.openapi.module.Module;
23 import com.intellij.openapi.roots.ModuleFileIndex;
24 import com.intellij.openapi.roots.ModuleRootManager;
25 import com.intellij.openapi.roots.ProjectFileIndex;
26 import com.intellij.openapi.vfs.VirtualFile;
27 import com.intellij.pom.java.LanguageLevel;
28 import com.intellij.psi.*;
29 import com.intellij.psi.util.PsiTreeUtil;
30 import com.intellij.psi.util.PsiUtil;
31 import com.intellij.util.containers.ContainerUtil;
32 import gnu.trove.THashSet;
33 import org.jetbrains.annotations.Contract;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.Set;
38
39 /**
40  * @author Pavel.Dolgov
41  */
42 public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspectionTool {
43
44   @NotNull
45   @Override
46   public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
47     PsiFile file = holder.getFile();
48     if (file instanceof PsiJavaFile) {
49       PsiJavaFile javaFile = (PsiJavaFile)file;
50       if (javaFile.getLanguageLevel().isAtLeast(LanguageLevel.JDK_1_9)) {
51         PsiJavaModule psiModule = JavaModuleGraphUtil.findDescriptorByElement(file);
52         if (psiModule != null) {
53           VirtualFile vFile = file.getVirtualFile();
54           if (vFile != null) {
55             Module module = ProjectFileIndex.SERVICE.getInstance(holder.getProject()).getModuleForFile(vFile);
56             if (module != null) {
57               Set<String> exportedPackageNames = new THashSet<>(ContainerUtil.mapNotNull(psiModule.getExports(), p -> p.getPackageName()));
58               if (exportedPackageNames.contains(javaFile.getPackageName())) {
59                 return new NonAccessibleTypeExposedVisitor(holder, module, exportedPackageNames);
60               }
61             }
62           }
63         }
64       }
65     }
66     return PsiElementVisitor.EMPTY_VISITOR;
67   }
68
69   private static class NonAccessibleTypeExposedVisitor extends JavaElementVisitor {
70     private final ProblemsHolder myHolder;
71     private final ModuleFileIndex myModuleFileIndex;
72     private final Set<String> myExportedPackageNames;
73
74     public NonAccessibleTypeExposedVisitor(@NotNull ProblemsHolder holder,
75                                            @NotNull Module module,
76                                            @NotNull Set<String> exportedPackageNames) {
77       myHolder = holder;
78       myModuleFileIndex = ModuleRootManager.getInstance(module).getFileIndex();
79       myExportedPackageNames = exportedPackageNames;
80     }
81
82     @Override
83     public void visitMethod(PsiMethod method) {
84       super.visitMethod(method);
85       if (isModulePublicApi(method)) {
86         if (!method.isConstructor()) {
87           checkType(method.getReturnType(), method.getReturnTypeElement());
88         }
89         checkTypeParameters(method.getTypeParameterList());
90         for (PsiParameter parameter : method.getParameterList().getParameters()) {
91           checkType(parameter.getType(), parameter.getTypeElement());
92         }
93         for (PsiJavaCodeReferenceElement referenceElement : method.getThrowsList().getReferenceElements()) {
94           PsiElement resolved = referenceElement.resolve();
95           if (resolved instanceof PsiClass) {
96             checkType((PsiClass)resolved, referenceElement);
97           }
98         }
99       }
100     }
101
102     @Override
103     public void visitField(PsiField field) {
104       super.visitField(field);
105       if (isModulePublicApi(field)) {
106         checkType(field.getType(), field.getTypeElement());
107       }
108     }
109
110     @Override
111     public void visitAnnotation(PsiAnnotation annotation) {
112       super.visitAnnotation(annotation);
113       PsiJavaCodeReferenceElement referenceElement = annotation.getNameReferenceElement();
114       if (referenceElement != null) {
115         PsiElement resolved = referenceElement.resolve();
116         if (resolved instanceof PsiClass) {
117           PsiClass annotationClass = (PsiClass)resolved;
118           if (isInModuleSource(annotationClass) && !isModulePublicApi(annotationClass)) {
119             PsiAnnotationOwner owner = annotation.getOwner();
120             if (isModulePublicApi(owner)) {
121               registerProblem(referenceElement);
122             }
123             if (owner instanceof PsiParameter) {
124               PsiElement parent = ((PsiParameter)owner).getParent();
125               if (parent instanceof PsiMember && isModulePublicApi((PsiMember)parent)) {
126                 registerProblem(referenceElement);
127               }
128             }
129           }
130         }
131       }
132     }
133
134     @Override
135     public void visitClass(PsiClass aClass) {
136       super.visitClass(aClass);
137       if (isModulePublicApi(aClass)) {
138         checkTypeParameters(aClass.getTypeParameterList());
139       }
140     }
141
142     private void checkType(@Nullable PsiType type, @Nullable PsiTypeElement typeElement) {
143       if (typeElement != null) {
144         if (type instanceof PsiWildcardType) {
145           type = ((PsiWildcardType)type).getBound();
146           PsiElement lastChild = typeElement.getLastChild();
147           if (lastChild instanceof PsiTypeElement) {
148             typeElement = (PsiTypeElement)lastChild;
149           }
150         }
151         PsiClass psiClass = PsiUtil.resolveClassInType(type);
152         checkType(psiClass, typeElement);
153         if (type instanceof PsiClassType && !(psiClass instanceof PsiTypeParameter)) {
154           PsiJavaCodeReferenceElement referenceElement = typeElement.getInnermostComponentReferenceElement();
155           if (referenceElement != null) {
156             checkTypeParameters(referenceElement.getParameterList());
157           }
158         }
159       }
160     }
161
162     private void checkType(@Nullable PsiClass psiClass, @NotNull PsiElement typeElement) {
163       if (psiClass != null && !(psiClass instanceof PsiTypeParameter) && isInModuleSource(psiClass) && !isModulePublicApi(psiClass)) {
164         registerProblem(typeElement);
165       }
166     }
167
168     private void checkTypeParameters(@Nullable PsiReferenceParameterList parameterList) {
169       if (parameterList != null) {
170         PsiTypeElement[] typeParameterElements = parameterList.getTypeParameterElements();
171         for (PsiTypeElement typeParameterElement : typeParameterElements) {
172           checkType(typeParameterElement.getType(), typeParameterElement);
173         }
174       }
175     }
176
177     private void checkTypeParameters(@Nullable PsiTypeParameterList parameterList) {
178       if (parameterList != null) {
179         for (PsiTypeParameter typeParameter : parameterList.getTypeParameters()) {
180           for (PsiJavaCodeReferenceElement referenceElement : typeParameter.getExtendsList().getReferenceElements()) {
181             PsiElement resolved = referenceElement.resolve();
182             if (resolved instanceof PsiClass) {
183               checkType((PsiClass)resolved, referenceElement);
184             }
185             checkTypeParameters(referenceElement.getParameterList());
186           }
187         }
188       }
189     }
190
191     @Contract("null -> false")
192     private boolean isModulePublicApi(@Nullable PsiMember member) {
193       if (member != null &&
194           !(member instanceof PsiTypeParameter) &&
195           (member.hasModifierProperty(PsiModifier.PUBLIC) || member.hasModifierProperty(PsiModifier.PROTECTED))) {
196         PsiElement parent = member.getParent();
197         if (parent instanceof PsiClass) {
198           return isModulePublicApi((PsiClass)parent);
199         }
200         if (parent instanceof PsiJavaFile) {
201           String packageName = ((PsiJavaFile)parent).getPackageName();
202           return myExportedPackageNames.contains(packageName);
203         }
204       }
205       return false;
206     }
207
208     @Contract("null -> false")
209     private boolean isModulePublicApi(@Nullable PsiAnnotationOwner owner) {
210       if (owner instanceof PsiModifierList) {
211         PsiElement parent = ((PsiModifierList)owner).getParent();
212         if (parent instanceof PsiMember) { // class or field or method
213           return isModulePublicApi((PsiMember)parent);
214         }
215         if (parent instanceof PsiParameter) { // method parameter
216           PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope();
217           if (declarationScope instanceof PsiMethod) {
218             return isModulePublicApi((PsiMethod)declarationScope);
219           }
220         }
221       }
222       else if (owner instanceof PsiTypeElement) { // type argument (aka type_use)
223         PsiElement grandParent = PsiTreeUtil.skipParentsOfType(((PsiTypeElement)owner),
224                                                                PsiParameter.class, PsiParameterList.class, PsiTypeElement.class,
225                                                                PsiReferenceList.class, PsiReferenceParameterList.class,
226                                                                PsiJavaCodeReferenceElement.class);
227         if (grandParent instanceof PsiMember) {
228           return isModulePublicApi((PsiMember)grandParent);
229         }
230       }
231       else if (owner instanceof PsiTypeParameter) { // type parameter declaration
232         PsiElement parent = ((PsiTypeParameter)owner).getParent();
233         if (parent instanceof PsiTypeParameterList) {
234           PsiElement grandParent = parent.getParent();
235           if (grandParent instanceof PsiMember) {
236             return isModulePublicApi((PsiMember)grandParent);
237           }
238         }
239       }
240       return false;
241     }
242
243     private boolean isInModuleSource(@NotNull PsiClass psiClass) {
244       PsiFile psiFile = psiClass.getContainingFile();
245       if (psiFile != null) {
246         VirtualFile vFile = psiFile.getVirtualFile();
247         if (vFile != null) {
248           return myModuleFileIndex.isInSourceContent(vFile);
249         }
250       }
251       return false;
252     }
253
254     private void registerProblem(PsiElement element) {
255       myHolder.registerProblem(element, InspectionsBundle.message("inspection.non.accessible.type.exposed.name"));
256     }
257   }
258 }