Cleanup: NotNull/Nullable
[idea/community.git] / java / java-impl / src / com / intellij / codeInspection / reflectiveAccess / Java9ReflectionClassVisibilityInspection.java
1 // Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.codeInspection.reflectiveAccess;
3
4 import com.intellij.codeInsight.daemon.impl.analysis.JavaModuleGraphUtil;
5 import com.intellij.codeInsight.daemon.impl.quickfix.AddRequiresDirectiveFix;
6 import com.intellij.codeInspection.AbstractBaseJavaLocalInspectionTool;
7 import com.intellij.codeInspection.InspectionsBundle;
8 import com.intellij.codeInspection.ProblemsHolder;
9 import com.intellij.openapi.project.Project;
10 import com.intellij.psi.*;
11 import com.intellij.psi.util.PsiTreeUtil;
12 import com.intellij.psi.util.PsiUtil;
13 import org.jetbrains.annotations.NotNull;
14
15 import java.util.List;
16
17 import static com.intellij.psi.CommonClassNames.JAVA_LANG_CLASS;
18 import static com.intellij.psi.impl.source.resolve.reference.impl.JavaReflectionReferenceUtil.*;
19
20 /**
21  * @author Pavel.Dolgov
22  */
23 public class Java9ReflectionClassVisibilityInspection extends AbstractBaseJavaLocalInspectionTool {
24
25   @NotNull
26   @Override
27   public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
28     final PsiFile file = holder.getFile();
29     if (PsiUtil.isLanguageLevel9OrHigher(file)) {
30       final PsiJavaModule javaModule = JavaModuleGraphUtil.findDescriptorByElement(file);
31       if (javaModule != null) {
32         return new JavaElementVisitor() {
33           @Override
34           public void visitMethodCallExpression(PsiMethodCallExpression expression) {
35             super.visitMethodCallExpression(expression);
36
37             if (isCallToMethod(expression, JAVA_LANG_CLASS, FOR_NAME) || isCallToMethod(expression, JAVA_LANG_CLASS_LOADER, LOAD_CLASS)) {
38               checkClassVisibility(expression, holder, javaModule);
39             }
40           }
41         };
42       }
43     }
44
45     return PsiElementVisitor.EMPTY_VISITOR;
46   }
47
48   private static void checkClassVisibility(@NotNull PsiMethodCallExpression callExpression,
49                                            @NotNull ProblemsHolder holder,
50                                            @NotNull PsiJavaModule javaModule) {
51
52     final PsiExpression[] arguments = callExpression.getArgumentList().getExpressions();
53     if (arguments.length != 0) {
54       final PsiExpression classNameArgument = arguments[0];
55       final String className = computeConstantExpression(classNameArgument, String.class);
56       if (className != null) {
57         final Project project = holder.getProject();
58         final JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
59         final PsiClass psiClass = facade.findClass(className, callExpression.getResolveScope());
60         if (psiClass != null) {
61           final PsiJavaModule otherModule = JavaModuleGraphUtil.findDescriptorByElement(psiClass);
62           if (otherModule != null && otherModule != javaModule) {
63             if (!JavaModuleGraphUtil.reads(javaModule, otherModule)) {
64               String message = InspectionsBundle.message(
65                 "module.not.in.requirements", javaModule.getName(), otherModule.getName());
66               holder.registerProblem(classNameArgument, message, new AddRequiresDirectiveFix(javaModule, otherModule.getName()));
67               return;
68             }
69
70             if (otherModule.hasModifierProperty(PsiModifier.OPEN)) {
71               return;
72             }
73             final PsiJavaFile file = PsiTreeUtil.getParentOfType(psiClass, PsiJavaFile.class);
74             if (file != null) {
75               final String packageName = file.getPackageName();
76               if (isPackageAccessible(otherModule.getOpens(), packageName, javaModule)) {
77                 return;
78               }
79               final boolean publicApi = isPublicApi(psiClass);
80               if (publicApi && isPackageAccessible(otherModule.getExports(), packageName, javaModule)) {
81                 return;
82               }
83               final String message = InspectionsBundle.message(
84                 publicApi ? "module.package.not.exported" : "module.package.not.open",
85                 otherModule.getName(), packageName, javaModule.getName());
86               holder.registerProblem(classNameArgument, message);
87             }
88           }
89         }
90       }
91     }
92   }
93
94   private static boolean isPackageAccessible(@NotNull Iterable<PsiPackageAccessibilityStatement> statements,
95                                              @NotNull String packageName,
96                                              @NotNull PsiJavaModule javaModule) {
97     for (PsiPackageAccessibilityStatement statement : statements) {
98       if (packageName.equals(statement.getPackageName())) {
99         final List<String> moduleNames = statement.getModuleNames();
100         if (moduleNames.isEmpty() || moduleNames.contains(javaModule.getName())) {
101           return true;
102         }
103       }
104     }
105     return false;
106   }
107
108   private static boolean isPublicApi(@NotNull PsiClass psiClass) {
109     if (psiClass.hasModifierProperty(PsiModifier.PUBLIC) || psiClass.hasModifierProperty(PsiModifier.PROTECTED)) {
110       final PsiElement parent = psiClass.getParent();
111       return parent instanceof PsiJavaFile || parent instanceof PsiClass && isPublicApi((PsiClass)parent);
112     }
113     return false;
114   }
115 }