blob: 9427551aacf8ff1c2f58d10761f538b9fab9e786 [file] [log] [blame]
/*
* 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<PsiClass, Boolean> map = CachedValuesManager.
getCachedValue(candidateClass, new CachedValueProvider<Map<PsiClass, Boolean>>() {
@Nullable
@Override
public Result<Map<PsiClass, Boolean>> compute() {
final Map<PsiClass, Boolean> map = new ConcurrentWeakHashMap<PsiClass, Boolean>();
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<PsiClass> 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<PsiClass> checkedClasses) {
if (manager.areElementsEquivalent(aClass, baseClass)) return false;
if (aClass.isInterface() && !baseClass.isInterface()) {
return false;
}
if (checkedClasses == null) {
checkedClasses = new THashSet<PsiClass>();
}
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<PsiClass> 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<PsiClass> 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<PsiClass> checkedClasses = null;
if (classToByPass != null) {
checkedClasses = new THashSet<PsiClass>();
checkedClasses.add(classToByPass);
}
return isInheritor(candidateClass.getManager(), candidateClass, baseClass, true, checkedClasses);
}
}