add clarifying comments around JavaResolveUtil.getContextClass
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / source / resolve / JavaResolveUtil.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
17 /*
18  * @author max
19  */
20 package com.intellij.psi.impl.source.resolve;
21
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.projectRoots.JavaSdkVersion;
24 import com.intellij.openapi.projectRoots.JavaVersionService;
25 import com.intellij.psi.*;
26 import com.intellij.psi.impl.PsiImplUtil;
27 import com.intellij.psi.infos.CandidateInfo;
28 import com.intellij.psi.javadoc.PsiDocComment;
29 import com.intellij.psi.scope.PsiScopeProcessor;
30 import com.intellij.psi.util.InheritanceUtil;
31 import com.intellij.psi.util.PsiTreeUtil;
32 import com.intellij.psi.util.PsiUtil;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35
36 public class JavaResolveUtil {
37   public static PsiClass getContextClass(@NotNull PsiElement element) {
38     PsiElement prev = element;
39     PsiElement scope = element.getContext();
40     while (scope != null) {
41       // skip the class if coming from its extends/implements list: those references only rely on the outer context for resolve
42       if (scope instanceof PsiClass && (prev instanceof PsiMember || prev instanceof PsiDocComment)) {
43         return (PsiClass)scope;
44       }
45       prev = scope;
46       scope = scope.getContext();
47     }
48     return null;
49   }
50
51   public static PsiElement findParentContextOfClass(PsiElement element, Class aClass, boolean strict){
52     PsiElement scope = strict ? element.getContext() : element;
53     while(scope != null && !aClass.isInstance(scope)){
54       scope = scope.getContext();
55     }
56     return scope;
57   }
58
59   public static boolean isAccessible(@NotNull PsiMember member,
60                                      @Nullable PsiClass memberClass,
61                                      @Nullable PsiModifierList modifierList,
62                                      @NotNull PsiElement place,
63                                      @Nullable PsiClass accessObjectClass,
64                                      @Nullable PsiElement fileResolveScope) {
65     return isAccessible(member, memberClass, modifierList, place, accessObjectClass, fileResolveScope, place.getContainingFile());
66   }
67
68   public static boolean isAccessible(@NotNull PsiMember member,
69                                      @Nullable PsiClass memberClass,
70                                      @Nullable PsiModifierList modifierList,
71                                      @NotNull PsiElement place,
72                                      @Nullable PsiClass accessObjectClass,
73                                      @Nullable PsiElement fileResolveScope,
74                                      @Nullable PsiFile placeFile) {
75     if (modifierList == null || isInJavaDoc(place)) {
76       return true;
77     }
78
79     if (placeFile instanceof JavaCodeFragment) {
80       JavaCodeFragment fragment = (JavaCodeFragment)placeFile;
81       JavaCodeFragment.VisibilityChecker visibilityChecker = fragment.getVisibilityChecker();
82       if (visibilityChecker != null) {
83         JavaCodeFragment.VisibilityChecker.Visibility visibility = visibilityChecker.isDeclarationVisible(member, place);
84         if (visibility == JavaCodeFragment.VisibilityChecker.Visibility.VISIBLE) return true;
85         if (visibility == JavaCodeFragment.VisibilityChecker.Visibility.NOT_VISIBLE) return false;
86       }
87     }
88     else if (ignoreReferencedElementAccessibility(placeFile)) {
89       return true;
90     }
91
92     if (accessObjectClass != null) {
93       PsiClass containingClass = accessObjectClass.getContainingClass();
94       if (!isAccessible(accessObjectClass, containingClass, accessObjectClass.getModifierList(), place, null, null, placeFile)) {
95         return false;
96       }
97     }
98
99     PsiFile file = placeFile == null ? null : FileContextUtil.getContextFile(placeFile); //TODO: implementation method!!!!
100     if (PsiImplUtil.isInServerPage(file) && PsiImplUtil.isInServerPage(member.getContainingFile())) {
101       return true;
102     }
103
104     int effectiveAccessLevel = PsiUtil.getAccessLevel(modifierList);
105     if (ignoreReferencedElementAccessibility(file) || effectiveAccessLevel == PsiUtil.ACCESS_LEVEL_PUBLIC) {
106       return true;
107     }
108
109     PsiManager manager = member.getManager();
110     JavaPsiFacade facade = JavaPsiFacade.getInstance(manager.getProject());
111
112     if (effectiveAccessLevel == PsiUtil.ACCESS_LEVEL_PROTECTED) {
113       if (facade.arePackagesTheSame(member, place)) {
114         return true;
115       }
116       if (memberClass == null) {
117         return false;
118       }
119       // if resolving supertype reference, skip its containing class with getContextClass
120       PsiClass contextClass = member instanceof PsiClass ? getContextClass(place)
121                                                          : PsiTreeUtil.getContextOfType(place, PsiClass.class, false);
122       while (contextClass != null) {
123         if (InheritanceUtil.isInheritorOrSelf(contextClass, memberClass, true)) {
124           if (member instanceof PsiClass ||
125               modifierList.hasModifierProperty(PsiModifier.STATIC) ||
126               accessObjectClass == null ||
127               InheritanceUtil.isInheritorOrSelf(accessObjectClass, contextClass, true)) {
128             return true;
129           }
130         }
131
132         contextClass = getContextClass(contextClass);
133       }
134       return false;
135     }
136
137     if (effectiveAccessLevel == PsiUtil.ACCESS_LEVEL_PRIVATE) {
138       if (memberClass == null) return true;
139       if (accessObjectClass != null) {
140         PsiClass topMemberClass = getTopLevelClass(memberClass, accessObjectClass);
141         PsiClass topAccessClass = getTopLevelClass(accessObjectClass, memberClass);
142         if (!manager.areElementsEquivalent(topMemberClass, topAccessClass)) return false;
143         if (accessObjectClass instanceof PsiAnonymousClass && accessObjectClass.isInheritor(memberClass, true)) {
144           if (place instanceof PsiMethodCallExpression) {
145             return false;
146           }
147         }
148       }
149
150       if (fileResolveScope == null) {
151         PsiClass placeTopLevelClass = getTopLevelClass(place, null);
152         PsiClass memberTopLevelClass = getTopLevelClass(memberClass, null);
153         return manager.areElementsEquivalent(placeTopLevelClass, memberTopLevelClass);
154       }
155       else {
156         return fileResolveScope instanceof PsiClass &&
157                !((PsiClass)fileResolveScope).isInheritor(memberClass, true);
158       }
159     }
160
161     if (!facade.arePackagesTheSame(member, place)) return false;
162     //if (modifierList.hasModifierProperty(PsiModifier.STATIC)) return true;
163     // maybe inheritance lead through package-private class in other package ?
164     final PsiClass placeClass = getContextClass(place);
165     if (memberClass == null || placeClass == null) return true;
166     // check only classes since interface members are public,  and if placeClass is interface,
167     // then its members are static, and cannot refer to non-static members of memberClass
168     if (memberClass.isInterface() || placeClass.isInterface()) return true;
169     PsiClass clazz = accessObjectClass != null ?
170                      accessObjectClass :
171                      placeClass.getSuperClass(); //may start from super class
172     if (clazz != null && clazz.isInheritor(memberClass, true)) {
173       PsiClass superClass = clazz;
174       while (!manager.areElementsEquivalent(superClass, memberClass)) {
175         if (superClass == null || !facade.arePackagesTheSame(superClass, memberClass)) return false;
176         superClass = superClass.getSuperClass();
177       }
178     }
179
180     return true;
181   }
182
183   private static boolean ignoreReferencedElementAccessibility(PsiFile placeFile) {
184     return placeFile instanceof FileResolveScopeProvider &&
185            ((FileResolveScopeProvider) placeFile).ignoreReferencedElementAccessibility() &&
186            !PsiImplUtil.isInServerPage(placeFile);
187   }
188
189   public static boolean isInJavaDoc(final PsiElement place) {
190     PsiElement scope = place;
191     while(scope != null){
192       if (scope instanceof PsiDocComment) return true;
193       if (scope instanceof PsiMember || scope instanceof PsiMethodCallExpression || scope instanceof PsiFile) return false;
194       scope = scope.getContext();
195     }
196     return false;
197   }
198
199   private static PsiClass getTopLevelClass(@NotNull PsiElement place, PsiClass memberClass) {
200     PsiClass lastClass = null;
201     Boolean isAtLeast17 = null;
202     for (PsiElement placeParent = place; placeParent != null; placeParent = placeParent.getContext()) {
203       if (placeParent instanceof PsiClass && !(placeParent instanceof PsiAnonymousClass)) {
204         final boolean isTypeParameter = placeParent instanceof PsiTypeParameter;
205         if (isTypeParameter && isAtLeast17 == null) {
206           isAtLeast17 = JavaVersionService.getInstance().isAtLeast(placeParent, JavaSdkVersion.JDK_1_7);
207         }
208         if (!isTypeParameter || isAtLeast17) {
209           PsiClass aClass = (PsiClass)placeParent;
210
211           if (memberClass != null && aClass.isInheritor(memberClass, true)) return aClass;
212
213           lastClass = aClass;
214         }
215       }
216     }
217
218     return lastClass;
219   }
220
221   public static boolean processImplicitlyImportedPackages(final PsiScopeProcessor processor,
222                                                           final ResolveState state,
223                                                           final PsiElement place,
224                                                           PsiManager manager) {
225     PsiPackage defaultPackage = JavaPsiFacade.getInstance(manager.getProject()).findPackage("");
226     if (defaultPackage != null) {
227       if (!defaultPackage.processDeclarations(processor, state, null, place)) return false;
228     }
229
230     PsiPackage langPackage = JavaPsiFacade.getInstance(manager.getProject()).findPackage(CommonClassNames.DEFAULT_PACKAGE);
231     if (langPackage != null) {
232       if (!langPackage.processDeclarations(processor, state, null, place)) return false;
233     }
234
235     return true;
236   }
237
238   public static void substituteResults(@NotNull final PsiJavaCodeReferenceElement ref, @NotNull JavaResolveResult[] result) {
239     if (result.length > 0 && result[0].getElement() instanceof PsiClass) {
240       for (int i = 0; i < result.length; i++) {
241         final CandidateInfo resolveResult = (CandidateInfo)result[i];
242         final PsiElement resultElement = resolveResult.getElement();
243         if (resultElement instanceof PsiClass && ((PsiClass)resultElement).hasTypeParameters()) {
244           PsiSubstitutor substitutor = resolveResult.getSubstitutor();
245           result[i] = new CandidateInfo(resolveResult, substitutor) {
246             @NotNull
247             @Override
248             public PsiSubstitutor getSubstitutor() {
249               final PsiType[] parameters = ref.getTypeParameters();
250               return super.getSubstitutor().putAll((PsiClass)resultElement, parameters);
251             }
252           };
253         }
254       }
255     }
256   }
257
258   @NotNull
259   public static <T extends PsiPolyVariantReference> JavaResolveResult[] resolveWithContainingFile(@NotNull T ref,
260                                                                                                   @NotNull ResolveCache.PolyVariantContextResolver<T> resolver,
261                                                                                                   boolean needToPreventRecursion,
262                                                                                                   boolean incompleteCode,
263                                                                                                   @NotNull PsiFile containingFile) {
264     boolean valid = containingFile.isValid();
265     if (!valid) {
266       return JavaResolveResult.EMPTY_ARRAY;
267     }
268     Project project = containingFile.getProject();
269     ResolveResult[] results = ResolveCache.getInstance(project).resolveWithCaching(ref, resolver, needToPreventRecursion, incompleteCode,
270                                                                                    containingFile);
271     return results.length == 0 ? JavaResolveResult.EMPTY_ARRAY : (JavaResolveResult[])results;
272   }
273 }