/* * Copyright 2000-2014 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.psi.impl; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.InheritanceUtil; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.containers.ConcurrentWeakHashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; import java.util.Set; public class InheritanceImplUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.InheritanceImplUtil"); public static boolean isInheritor(@NotNull final PsiClass candidateClass, @NotNull PsiClass baseClass, final boolean checkDeep) { if (baseClass instanceof PsiAnonymousClass) return false; if (!checkDeep) { return isInheritor(candidateClass.getManager(), candidateClass, baseClass, false, null); } if (hasObjectQualifiedName(candidateClass)) return false; if (hasObjectQualifiedName(baseClass)) return true; Map map = CachedValuesManager. getCachedValue(candidateClass, new CachedValueProvider>() { @Nullable @Override public Result> compute() { final Map map = new ConcurrentWeakHashMap(); return Result.create(map, candidateClass, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT); } }); Boolean computed = map.get(baseClass); if (computed == null) { computed = isInheritor(candidateClass.getManager(), candidateClass, baseClass, true, null); map.put(baseClass, computed); } return computed; } public static boolean hasObjectQualifiedName(@NotNull PsiClass candidateClass) { if (!CommonClassNames.JAVA_LANG_OBJECT_SHORT.equals(candidateClass.getName())) { return false; } PsiElement parent = candidateClass.getParent(); return parent instanceof PsiJavaFile && CommonClassNames.DEFAULT_PACKAGE.equals(((PsiJavaFile)parent).getPackageName()); } private static boolean isInheritor(@NotNull PsiManager manager, @NotNull PsiClass candidateClass, @NotNull PsiClass baseClass, boolean checkDeep, @Nullable Set checkedClasses) { if (candidateClass instanceof PsiAnonymousClass) { final PsiClass baseCandidateClass = ((PsiAnonymousClass)candidateClass).getBaseClassType().resolve(); return baseCandidateClass != null && InheritanceUtil.isInheritorOrSelf(baseCandidateClass, baseClass, checkDeep); } /* //TODO fix classhashprovider so it doesn't use class qnames only final ClassHashProvider provider = getHashProvider((PsiManagerImpl) manager); if (checkDeep && provider != null) { try { return provider.isInheritor(baseClass, candidateClass); } catch (ClassHashProvider.OutOfRangeException e) { } } */ if(checkDeep && LOG.isDebugEnabled()){ LOG.debug("Using uncached version for " + candidateClass.getQualifiedName() + " and " + baseClass); } if (hasObjectQualifiedName(baseClass)) { PsiClass objectClass = JavaPsiFacade.getInstance(manager.getProject()).findClass(CommonClassNames.JAVA_LANG_OBJECT, candidateClass.getResolveScope()); if (manager.areElementsEquivalent(baseClass, objectClass)) { if (manager.areElementsEquivalent(candidateClass, objectClass)) return false; if (checkDeep || candidateClass.isInterface()) return true; return manager.areElementsEquivalent(candidateClass.getSuperClass(), objectClass); } } if (!checkDeep) { final boolean cInt = candidateClass.isInterface(); final boolean bInt = baseClass.isInterface(); if (candidateClass instanceof PsiCompiledElement) { String baseQName = baseClass.getQualifiedName(); if (baseQName == null) return false; GlobalSearchScope scope = candidateClass.getResolveScope(); if (cInt == bInt && checkReferenceListWithQualifiedNames(baseQName, candidateClass.getExtendsList(), manager, scope)) return true; return bInt && !cInt && checkReferenceListWithQualifiedNames(baseQName, candidateClass.getImplementsList(), manager, scope); } String baseName = baseClass.getName(); if (cInt == bInt) { for (PsiClassType type : candidateClass.getExtendsListTypes()) { if (Comparing.equal(type.getClassName(), baseName)) { if (manager.areElementsEquivalent(baseClass, type.resolve())) { return true; } } } } else if (!cInt) { for (PsiClassType type : candidateClass.getImplementsListTypes()) { if (Comparing.equal(type.getClassName(), baseName)) { if (manager.areElementsEquivalent(baseClass, type.resolve())) { return true; } } } } return false; } return isInheritorWithoutCaching(manager, candidateClass, baseClass, checkedClasses); } private static boolean checkReferenceListWithQualifiedNames(final String baseQName, final PsiReferenceList extList, final PsiManager manager, final GlobalSearchScope scope) { if (extList != null) { final PsiJavaCodeReferenceElement[] refs = extList.getReferenceElements(); for (PsiJavaCodeReferenceElement ref : refs) { if (Comparing.equal(PsiNameHelper.getQualifiedClassName(ref.getQualifiedName(), false), baseQName) && JavaPsiFacade .getInstance(manager.getProject()).findClass(baseQName, scope) != null) return true; } } return false; } private static boolean isInheritorWithoutCaching(@NotNull PsiManager manager, @NotNull PsiClass aClass, @NotNull PsiClass baseClass, @Nullable Set checkedClasses) { if (manager.areElementsEquivalent(aClass, baseClass)) return false; if (aClass.isInterface() && !baseClass.isInterface()) { return false; } if (checkedClasses == null) { checkedClasses = new THashSet(); } checkedClasses.add(aClass); return checkInheritor(manager, aClass.getExtendsListTypes(), baseClass, checkedClasses) || checkInheritor(manager, aClass.getImplementsListTypes(), baseClass, checkedClasses); } private static boolean checkInheritor(@NotNull PsiManager manager, @NotNull PsiClassType[] supers, @NotNull PsiClass baseClass, @NotNull Set checkedClasses) { for (PsiClassType aSuper : supers) { PsiClass aClass = aSuper.resolve(); if (aClass != null && checkInheritor(manager, aClass, baseClass, checkedClasses)) { return true; } } return false; } private static boolean checkInheritor(@NotNull PsiManager manager, @NotNull PsiClass aClass, @NotNull PsiClass baseClass, @NotNull Set checkedClasses) { ProgressIndicatorProvider.checkCanceled(); if (manager.areElementsEquivalent(baseClass, aClass)) { return true; } if (checkedClasses.contains(aClass)) { // to prevent infinite recursion return false; } return isInheritor(manager, aClass, baseClass, true, checkedClasses); } public static boolean isInheritorDeep(@NotNull PsiClass candidateClass, @NotNull PsiClass baseClass, @Nullable final PsiClass classToByPass) { if (baseClass instanceof PsiAnonymousClass) { return false; } Set checkedClasses = null; if (classToByPass != null) { checkedClasses = new THashSet(); checkedClasses.add(classToByPass); } return isInheritor(candidateClass.getManager(), candidateClass, baseClass, true, checkedClasses); } }