2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.codeInspection.java19modules;
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;
40 * @author Pavel.Dolgov
42 public class Java9NonAccessibleTypeExposedInspection extends BaseJavaLocalInspectionTool {
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();
55 Module module = ProjectFileIndex.SERVICE.getInstance(holder.getProject()).getModuleForFile(vFile);
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);
66 return PsiElementVisitor.EMPTY_VISITOR;
69 private static class NonAccessibleTypeExposedVisitor extends JavaElementVisitor {
70 private final ProblemsHolder myHolder;
71 private final ModuleFileIndex myModuleFileIndex;
72 private final Set<String> myExportedPackageNames;
74 public NonAccessibleTypeExposedVisitor(@NotNull ProblemsHolder holder,
75 @NotNull Module module,
76 @NotNull Set<String> exportedPackageNames) {
78 myModuleFileIndex = ModuleRootManager.getInstance(module).getFileIndex();
79 myExportedPackageNames = exportedPackageNames;
83 public void visitMethod(PsiMethod method) {
84 super.visitMethod(method);
85 if (isModulePublicApi(method)) {
86 if (!method.isConstructor()) {
87 checkType(method.getReturnType(), method.getReturnTypeElement());
89 checkTypeParameters(method.getTypeParameterList());
90 for (PsiParameter parameter : method.getParameterList().getParameters()) {
91 checkType(parameter.getType(), parameter.getTypeElement());
93 for (PsiJavaCodeReferenceElement referenceElement : method.getThrowsList().getReferenceElements()) {
94 PsiElement resolved = referenceElement.resolve();
95 if (resolved instanceof PsiClass) {
96 checkType((PsiClass)resolved, referenceElement);
103 public void visitField(PsiField field) {
104 super.visitField(field);
105 if (isModulePublicApi(field)) {
106 checkType(field.getType(), field.getTypeElement());
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);
123 if (owner instanceof PsiParameter) {
124 PsiElement parent = ((PsiParameter)owner).getParent();
125 if (parent instanceof PsiMember && isModulePublicApi((PsiMember)parent)) {
126 registerProblem(referenceElement);
135 public void visitClass(PsiClass aClass) {
136 super.visitClass(aClass);
137 if (isModulePublicApi(aClass)) {
138 checkTypeParameters(aClass.getTypeParameterList());
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;
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());
162 private void checkType(@Nullable PsiClass psiClass, @NotNull PsiElement typeElement) {
163 if (psiClass != null && !(psiClass instanceof PsiTypeParameter) && isInModuleSource(psiClass) && !isModulePublicApi(psiClass)) {
164 registerProblem(typeElement);
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);
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);
185 checkTypeParameters(referenceElement.getParameterList());
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);
200 if (parent instanceof PsiJavaFile) {
201 String packageName = ((PsiJavaFile)parent).getPackageName();
202 return myExportedPackageNames.contains(packageName);
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);
215 if (parent instanceof PsiParameter) { // method parameter
216 PsiElement declarationScope = ((PsiParameter)parent).getDeclarationScope();
217 if (declarationScope instanceof PsiMethod) {
218 return isModulePublicApi((PsiMethod)declarationScope);
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);
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);
243 private boolean isInModuleSource(@NotNull PsiClass psiClass) {
244 PsiFile psiFile = psiClass.getContainingFile();
245 if (psiFile != null) {
246 VirtualFile vFile = psiFile.getVirtualFile();
248 return myModuleFileIndex.isInSourceContent(vFile);
254 private void registerProblem(PsiElement element) {
255 myHolder.registerProblem(element, InspectionsBundle.message("inspection.non.accessible.type.exposed.name"));