java class inheritor search: avoid expensive super type resolve

if there's only one suitable class with the required name in the project anyway
This commit is contained in:
peter
2018-04-16 13:51:28 +02:00
parent ab20d9e09d
commit aea5af6584
3 changed files with 125 additions and 30 deletions
@@ -5,7 +5,6 @@ import com.intellij.compiler.CompilerDirectHierarchyInfo;
import com.intellij.compiler.CompilerReferenceService;
import com.intellij.concurrency.JobLauncher;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
@@ -178,20 +177,20 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor<PsiClass, Dir
@NotNull SearchScope useScope) {
DumbService dumbService = DumbService.getInstance(project);
GlobalSearchScope globalUseScope = dumbService.runReadActionInSmartMode(
() -> StubHierarchyInheritorSearcher.restrictScope(GlobalSearchScopeUtil.toGlobalSearchScope(useScope, project)));
() -> StubHierarchyInheritorSearcher.restrictScope(new JavaSourceFilterScope(GlobalSearchScopeUtil.toGlobalSearchScope(useScope, project))));
Collection<PsiReferenceList> candidates =
dumbService.runReadActionInSmartMode(() -> JavaSuperClassNameOccurenceIndex.getInstance().get(baseClassName, project, globalUseScope));
RelaxedDirectInheritorChecker checker = dumbService.runReadActionInSmartMode(() -> new RelaxedDirectInheritorChecker(baseClass));
// memory/speed optimisation: it really is a map(string -> PsiClass or List<PsiClass>)
final Map<String, Object> classesWithFqn = new HashMap<>();
processConcurrentlyIfTooMany(candidates,
referenceList -> {
ProgressManager.checkCanceled();
ApplicationManager.getApplication().runReadAction(() -> {
dumbService.runReadActionInSmartMode(() -> {
final PsiClass candidate = (PsiClass)referenceList.getParent();
boolean isInheritor = candidate.isInheritor(baseClass, false);
if (isInheritor) {
if (checker.checkInheritance(candidate)) {
String fqn = candidate.getQualifiedName();
synchronized (classesWithFqn) {
Object value = classesWithFqn.get(fqn);
@@ -231,16 +230,14 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor<PsiClass, Dir
Collection<PsiAnonymousClass> anonymousCandidates =
dumbService.runReadActionInSmartMode(() -> JavaAnonymousClassBaseRefOccurenceIndex.getInstance().get(baseClassName, project, globalUseScope));
processConcurrentlyIfTooMany(anonymousCandidates,
candidate-> {
boolean isInheritor = dumbService.runReadActionInSmartMode(() -> candidate.isInheritor(baseClass, false));
if (isInheritor) {
synchronized (result) {
result.add(candidate);
}
}
return true;
});
processConcurrentlyIfTooMany(anonymousCandidates, candidate-> {
if (dumbService.runReadActionInSmartMode(() -> checker.checkInheritance(candidate))) {
synchronized (result) {
result.add(candidate);
}
}
return true;
});
boolean isEnum = ReadAction.compute(baseClass::isEnum);
if (isEnum) {
@@ -0,0 +1,100 @@
// Copyright 2000-2018 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.
package com.intellij.psi.impl.search;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.VolatileNotNullLazyValue;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchScopeUtil;
import com.intellij.psi.search.PsiShortNamesCache;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.psi.util.PsiUtil;
import com.intellij.reference.SoftReference;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.JBIterable;
import org.jetbrains.annotations.NotNull;
import java.lang.ref.Reference;
import java.util.*;
/**
* To avoid expensive super type resolve, if there's only one suitable class with the required name in the project anyway
*/
public class RelaxedDirectInheritorChecker {
private final String myBaseClassName;
private final PsiClass myBaseClass;
private final VolatileNotNullLazyValue<PsiClass[]> myClasses;
private final VolatileNotNullLazyValue<Boolean> myHasGlobalAmbiguities;
private final ProjectFileIndex myFileIndex;
public RelaxedDirectInheritorChecker(@NotNull PsiClass baseClass) {
myBaseClass = baseClass;
myBaseClassName = Objects.requireNonNull(baseClass.getName());
myClasses = VolatileNotNullLazyValue.createValue(() -> getClassesByName(myBaseClass.getProject(), myBaseClassName));
myHasGlobalAmbiguities = VolatileNotNullLazyValue.createValue(() -> hasAmbiguities(JBIterable.of(myClasses.getValue())));
myFileIndex = ProjectFileIndex.getInstance(myBaseClass.getProject());
}
@NotNull
private static PsiClass[] getClassesByName(Project project, String name) {
Map<String, Reference<PsiClass[]>> cache = CachedValuesManager.getManager(project).getCachedValue(project, () -> {
Map<String, Reference<PsiClass[]>> map = ContainerUtil.newConcurrentMap();
return CachedValueProvider.Result.create(map, PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT);
});
PsiClass[] result = SoftReference.dereference(cache.get(name));
if (result == null) {
result = PsiShortNamesCache.getInstance(project).getClassesByName(name, GlobalSearchScope.allScope(project));
cache.put(name, new SoftReference<>(result));
}
return result;
}
private static boolean hasAmbiguities(Iterable<PsiClass> classes) {
int locals = 0;
Set<String> qNames = new HashSet<>();
for (PsiClass psiClass : classes) {
String qName = psiClass.getQualifiedName();
if (qName == null) {
locals++;
if (locals > 1) return true;
} else {
qNames.add(qName);
if (qNames.size() > 1) return true;
}
}
return false;
}
public boolean checkInheritance(@NotNull PsiClass inheritorCandidate) {
if (!inheritorCandidate.isValid() || !myBaseClass.isValid()) return false;
if (myFileIndex.isInSourceContent(inheritorCandidate.getContainingFile().getVirtualFile())) {
if (!myHasGlobalAmbiguities.getValue()) {
return true;
}
GlobalSearchScope scope = inheritorCandidate.getResolveScope();
List<PsiClass> accessible = ContainerUtil.findAll(myClasses.getValue(), base ->
PsiSearchScopeUtil.isInScope(scope, base) && isAccessibleLight(inheritorCandidate, base));
if (!hasAmbiguities(accessible)) {
return accessible.contains(myBaseClass);
}
}
return inheritorCandidate.isInheritor(myBaseClass, false);
}
private static boolean isAccessibleLight(@NotNull PsiClass inheritorCandidate, @NotNull PsiClass base) {
PsiModifierList modifierList = base.getModifierList();
if (modifierList != null && PsiUtil.getAccessLevel(modifierList) == PsiUtil.ACCESS_LEVEL_PROTECTED) {
return true; // requires hierarchy checks => resolve
}
return JavaResolveUtil.isAccessible(base, base.getContainingClass(), modifierList, inheritorCandidate, null, null);
}
}