inheritance transitivity with several versions of same library in classpath (IDEA...
[idea/community.git] / java / java-psi-impl / src / com / intellij / psi / impl / InheritanceImplUtil.java
1 /*
2  * Copyright 2000-2009 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.psi.impl;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.progress.ProgressIndicatorProvider;
20 import com.intellij.openapi.util.Comparing;
21 import com.intellij.psi.*;
22 import com.intellij.psi.util.InheritanceUtil;
23 import com.intellij.util.containers.HashSet;
24 import gnu.trove.THashSet;
25 import org.jetbrains.annotations.NonNls;
26 import org.jetbrains.annotations.NotNull;
27 import org.jetbrains.annotations.Nullable;
28
29 import java.util.Set;
30
31 public class InheritanceImplUtil {
32   private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.InheritanceImplUtil");
33
34   public static boolean isInheritor(@NotNull PsiClass candidateClass, @NotNull PsiClass baseClass, final boolean checkDeep) {
35     return !(baseClass instanceof PsiAnonymousClass) && isInheritor(candidateClass, baseClass, checkDeep, null);
36   }
37
38   private static boolean isInheritor(@NotNull PsiClass candidateClass, @NotNull PsiClass baseClass, boolean checkDeep, Set<PsiClass> checkedClasses) {
39     if (candidateClass instanceof PsiAnonymousClass) {
40       final PsiClass baseCandidateClass = ((PsiAnonymousClass)candidateClass).getBaseClassType().resolve();
41       return baseCandidateClass != null && InheritanceUtil.isInheritorOrSelf(baseCandidateClass, baseClass, checkDeep);
42     }
43     PsiManager manager = candidateClass.getManager();
44     /* //TODO fix classhashprovider so it doesn't use class qnames only
45     final ClassHashProvider provider = getHashProvider((PsiManagerImpl) manager);
46     if (checkDeep && provider != null) {
47       try {
48         return provider.isInheritor(baseClass, candidateClass);
49       }
50       catch (ClassHashProvider.OutOfRangeException e) {
51       }
52     }
53     */
54     if(checkDeep && LOG.isDebugEnabled()){
55       LOG.debug("Using uncached version for " + candidateClass.getQualifiedName() + " and " + baseClass);
56     }
57
58     @NonNls final String baseName = baseClass.getName();
59     if ("Object".equals(baseName)) {
60       PsiClass objectClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, candidateClass.getResolveScope());
61       if (manager.areElementsEquivalent(baseClass, objectClass)) {
62         if (manager.areElementsEquivalent(candidateClass, objectClass)) return false;
63         if (checkDeep || candidateClass.isInterface()) return true;
64         return manager.areElementsEquivalent(candidateClass.getSuperClass(), objectClass);
65       }
66     }
67
68     if (!checkDeep) {
69       final boolean cInt = candidateClass.isInterface();
70       final boolean bInt = baseClass.isInterface();
71
72       if (candidateClass instanceof PsiCompiledElement) {
73         if (cInt == bInt && checkReferenceListWithQualifiedNames(candidateClass.getExtendsList(), baseClass)) return true;
74         return bInt && !cInt && checkReferenceListWithQualifiedNames(candidateClass.getImplementsList(), baseClass);
75       }
76       if (cInt == bInt) {
77         for (PsiClassType type : candidateClass.getExtendsListTypes()) {
78           if (Comparing.equal(type.getClassName(), baseName)) {
79             if (manager.areElementsEquivalent(baseClass, type.resolve())) {
80               return true;
81             }
82           }
83         }
84       }
85       else if (!cInt) {
86         for (PsiClassType type : candidateClass.getImplementsListTypes()) {
87           if (Comparing.equal(type.getClassName(), baseName)) {
88             if (manager.areElementsEquivalent(baseClass, type.resolve())) {
89               return true;
90             }
91           }
92         }
93       }
94
95       return false;
96     }
97
98     return isInheritorWithoutCaching(candidateClass, baseClass, checkDeep, checkedClasses);
99   }
100
101   private static boolean checkReferenceListWithQualifiedNames(final PsiReferenceList extList, PsiClass baseClass) {
102     if (extList != null) {
103       String qname = baseClass.getQualifiedName();
104       if (qname != null) {
105         for (PsiJavaCodeReferenceElement ref : extList.getReferenceElements()) {
106           if (Comparing.equal(PsiNameHelper.getQualifiedClassName(ref.getQualifiedName(), false), qname) &&
107               baseClass.isEquivalentTo(ref.resolve())) {
108             return true;
109           }
110         }
111       }
112     }
113     return false;
114   }
115
116   private static boolean isInheritorWithoutCaching(PsiClass aClass, PsiClass baseClass, boolean checkDeep, Set<PsiClass> checkedClasses) {
117     PsiManager manager = aClass.getManager();
118     if (manager.areElementsEquivalent(aClass, baseClass)) return false;
119
120     if (aClass.isInterface() && !baseClass.isInterface()) {
121       return false;
122     }
123
124     //if (PsiUtil.hasModifierProperty(baseClass, PsiModifier.FINAL)) {
125     //  return false;
126     //}
127
128     if (checkDeep) {
129       if (checkedClasses == null) {
130         checkedClasses = new THashSet<PsiClass>();
131       }
132       checkedClasses.add(aClass);
133     }
134
135     if (!aClass.isInterface() && baseClass.isInterface()) {
136       if (checkDeep && checkInheritor(aClass.getSuperClass(), baseClass, checkDeep, checkedClasses)) {
137         return true;
138       }
139       return checkInheritor(aClass.getInterfaces(), baseClass, checkDeep, checkedClasses);
140
141     }
142     else {
143       return checkInheritor(aClass.getSupers(), baseClass, checkDeep, checkedClasses);
144     }
145   }
146
147   private static boolean checkInheritor(PsiClass[] supers, PsiClass baseClass, boolean checkDeep, Set<PsiClass> checkedClasses) {
148     for (PsiClass aSuper : supers) {
149       if (checkInheritor(aSuper, baseClass, checkDeep, checkedClasses)) {
150         return true;
151       }
152     }
153     return false;
154   }
155
156   private static boolean checkInheritor(PsiClass aClass, PsiClass baseClass, boolean checkDeep, Set<PsiClass> checkedClasses) {
157     ProgressIndicatorProvider.checkCanceled();
158     if (aClass != null) {
159       PsiManager manager = baseClass.getManager();
160       if (manager.areElementsEquivalent(baseClass, aClass)) {
161         return true;
162       }
163       if (checkedClasses != null && checkedClasses.contains(aClass)) { // to prevent infinite recursion
164         return false;
165       }
166       if (checkDeep) {
167         if (isInheritor(aClass, baseClass, checkDeep, checkedClasses)) {
168           return true;
169         }
170       }
171     }
172     return false;
173   }
174
175   public static boolean isInheritorDeep(@NotNull PsiClass candidateClass, @NotNull PsiClass baseClass, @Nullable final PsiClass classToByPass) {
176     if (baseClass instanceof PsiAnonymousClass) {
177       return false;
178     }
179
180     Set<PsiClass> checkedClasses = null;
181     if (classToByPass != null) {
182       checkedClasses = new HashSet<PsiClass>();
183       checkedClasses.add(classToByPass);
184     }
185     return isInheritor(candidateClass, baseClass, true, checkedClasses);
186   }
187 }