mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-26 19:06:24 +07:00
direct inheritors optimisations: checking inheritors in parallel, simplify data structures
This commit is contained in:
@@ -17,7 +17,6 @@ package com.intellij.psi.impl.search;
|
||||
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.psi.PsiMethod;
|
||||
import com.intellij.psi.impl.AnyPsiChangeListener;
|
||||
@@ -28,7 +27,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
|
||||
class HighlightingCaches {
|
||||
public static HighlightingCaches getInstance(Project project) {
|
||||
@@ -53,8 +51,8 @@ class HighlightingCaches {
|
||||
});
|
||||
}
|
||||
|
||||
// baseClass -> (list of direct subclasses, isInheritor flag array)
|
||||
final Map<PsiClass, Pair<List<PsiClass>, AtomicIntegerArray>> DIRECT_SUB_CLASSES = createWeakCache();
|
||||
// baseClass -> list of direct subclasses
|
||||
final ConcurrentMap<PsiClass, PsiClass[]> DIRECT_SUB_CLASSES = createWeakCache();
|
||||
// baseClass -> all sub classes transitively, including anonymous
|
||||
final ConcurrentMap<PsiClass, Iterable<PsiClass>> ALL_SUB_CLASSES = createWeakCache();
|
||||
// baseMethod -> all overriding methods
|
||||
|
||||
+111
-107
@@ -15,12 +15,13 @@
|
||||
*/
|
||||
package com.intellij.psi.impl.search;
|
||||
|
||||
import com.intellij.concurrency.JobLauncher;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
@@ -33,14 +34,18 @@ import com.intellij.psi.search.searches.AllClassesSearch;
|
||||
import com.intellij.psi.search.searches.DirectClassInheritorsSearch;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.PsiUtilCore;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.QueryExecutor;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicIntegerArray;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/**
|
||||
* @author max
|
||||
@@ -65,147 +70,162 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor<PsiClass, Dir
|
||||
}
|
||||
|
||||
SearchScope scope = parameters.getScope();
|
||||
Pair<List<PsiClass>, AtomicIntegerArray> pair = getOrCalculateDirectSubClasses(project, baseClass);
|
||||
List<PsiClass> result = pair.getFirst();
|
||||
AtomicIntegerArray isInheritorFlag = pair.getSecond();
|
||||
PsiClass[] cache = getOrCalculateDirectSubClasses(project, baseClass);
|
||||
|
||||
if (result.isEmpty()) {
|
||||
if (cache.length == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final VirtualFile jarFile = getJarFile(baseClass);
|
||||
VirtualFile baseClassJarFile = null;
|
||||
// iterate by same-FQN groups. For each group process only same-jar subclasses, or all of them if they are all outside the jarFile.
|
||||
int groupStart = 0;
|
||||
boolean sameJarClassFound = false;
|
||||
for (int i = 0; i < result.size(); i++) {
|
||||
String currentFQN = null;
|
||||
for (int i = 0; i < cache.length +1; i++) {
|
||||
ProgressManager.checkCanceled();
|
||||
PsiClass subClass = result.get(i);
|
||||
|
||||
PsiClass subClass = i == cache.length ? null : cache[i];
|
||||
if (subClass instanceof PsiAnonymousClass) {
|
||||
// we reached anonymous classes tail, process them all and exit
|
||||
if (!parameters.includeAnonymous()) {
|
||||
return true;
|
||||
}
|
||||
for (; i < result.size(); i++) {
|
||||
ProgressManager.checkCanceled();
|
||||
subClass = result.get(i);
|
||||
|
||||
CheckResult checkResult = checkAndProcessCandidate(scope, subClass, parameters, baseClass, project, isInheritorFlag, i, consumer);
|
||||
if (checkResult == CheckResult.PROCESSED_ABORT) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subClass == PsiUtil.NULL_PSI_CLASS) {
|
||||
// the end of the same-FQN group. Process only same-jar classes in the group or the whole group if there were none.
|
||||
String fqn = i == cache.length ? null : ApplicationManager.getApplication().runReadAction((Computable<String>)subClass::getQualifiedName);
|
||||
if (i != cache.length && !isInScope(scope, subClass)) continue;
|
||||
|
||||
if (currentFQN != null && Comparing.equal(fqn, currentFQN)) {
|
||||
VirtualFile currentJarFile = getJarFile(subClass);
|
||||
if (baseClassJarFile == null) {
|
||||
baseClassJarFile = getJarFile(baseClass);
|
||||
}
|
||||
boolean fromSameJar = Comparing.equal(currentJarFile, baseClassJarFile);
|
||||
if (fromSameJar) {
|
||||
if (!consumer.process(subClass)) return false;
|
||||
sameJarClassFound = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
currentFQN = fqn;
|
||||
// the end of the same-FQN group. Process only same-jar classes in subClasses[groupStart..i-1] group or the whole group if there were none.
|
||||
if (!sameJarClassFound) {
|
||||
for (int g=groupStart; g<i; g++) {
|
||||
ProgressManager.checkCanceled();
|
||||
subClass = result.get(g);
|
||||
CheckResult checkResult = checkAndProcessCandidate(scope, subClass, parameters, baseClass, project, isInheritorFlag, g, consumer);
|
||||
if (checkResult == CheckResult.PROCESSED_ABORT) return false;
|
||||
PsiClass anonSubClass = cache[g];
|
||||
if (!consumer.process(anonSubClass)) return false;
|
||||
}
|
||||
}
|
||||
groupStart = i+1;
|
||||
groupStart = i;
|
||||
sameJarClassFound = false;
|
||||
}
|
||||
else {
|
||||
VirtualFile currentJarFile = getJarFile(subClass);
|
||||
boolean fromSameJar = Comparing.equal(currentJarFile, jarFile);
|
||||
if (!fromSameJar) continue;
|
||||
CheckResult checkResult = checkAndProcessCandidate(scope, subClass, parameters, baseClass, project, isInheritorFlag, i, consumer);
|
||||
if (checkResult == CheckResult.PROCESSED_ABORT) return false;
|
||||
if (checkResult == CheckResult.CANDIDATE_REJECTED) continue;
|
||||
sameJarClassFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private enum CheckResult {
|
||||
CANDIDATE_REJECTED, PROCESSED_OK, PROCESSED_ABORT
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static CheckResult checkAndProcessCandidate(@NotNull SearchScope scope,
|
||||
@NotNull PsiClass candidate,
|
||||
@NotNull DirectClassInheritorsSearch.SearchParameters parameters,
|
||||
@NotNull PsiClass baseClass,
|
||||
@NotNull Project project,
|
||||
@NotNull AtomicIntegerArray isInheritorFlag, int i,
|
||||
@NotNull Processor<PsiClass> consumer) {
|
||||
if (!isInScope(scope, candidate)) return CheckResult.CANDIDATE_REJECTED;
|
||||
if (!checkInheritance(parameters.isCheckInheritance(), baseClass, candidate, project, isInheritorFlag, i)) return CheckResult.CANDIDATE_REJECTED;
|
||||
return consumer.process(candidate) ? CheckResult.PROCESSED_OK : CheckResult.PROCESSED_ABORT;
|
||||
}
|
||||
|
||||
private static boolean isInScope(@NotNull SearchScope scope, @NotNull PsiClass subClass) {
|
||||
return ApplicationManager.getApplication().runReadAction((Computable<Boolean>)() -> PsiSearchScopeUtil.isInScope(scope, subClass));
|
||||
}
|
||||
|
||||
private static final int INHERITANCE_UNKNOWN = 0;
|
||||
private static final int INHERITANCE_YES = 1;
|
||||
private static final int INHERITANCE_NO = 2;
|
||||
|
||||
// Returns pair ( list of direct subclasses, array of corresponding isInheritor flags )
|
||||
// The array initially contains INHERITANCE_UNKNOWN values, then the isInheritor() method result is cached in the array as INHERITANCE_YES or INHERITANCE_NO.
|
||||
// The list starts with non-anonymous classes, ends with anonymous sub classes
|
||||
// Regular classes grouped by their FQN. (Because among the same-named subclasses we should return only the same-jar ones, or all of them if there were none)
|
||||
// The groups are separated with NULL_PSI_CLASS
|
||||
// Classes grouped by their FQN. (Because among the same-named subclasses we should return only the same-jar ones, or all of them if there were none)
|
||||
@NotNull
|
||||
private static Pair<List<PsiClass>, AtomicIntegerArray> getOrCalculateDirectSubClasses(@NotNull Project project, @NotNull PsiClass baseClass) {
|
||||
Pair<List<PsiClass>, AtomicIntegerArray> cached = HighlightingCaches.getInstance(project).DIRECT_SUB_CLASSES.get(baseClass);
|
||||
if (cached != null) {
|
||||
return cached;
|
||||
private static PsiClass[] getOrCalculateDirectSubClasses(@NotNull Project project, @NotNull PsiClass baseClass) {
|
||||
ConcurrentMap<PsiClass, PsiClass[]> map = HighlightingCaches.getInstance(project).DIRECT_SUB_CLASSES;
|
||||
PsiClass[] cache = map.get(baseClass);
|
||||
if (cache != null) {
|
||||
return cache;
|
||||
}
|
||||
|
||||
final String baseClassName = ApplicationManager.getApplication().runReadAction((Computable<String>)baseClass::getName);
|
||||
if (StringUtil.isEmpty(baseClassName)) {
|
||||
return Pair.create(Collections.emptyList(), new AtomicIntegerArray(0));
|
||||
return PsiClass.EMPTY_ARRAY;
|
||||
}
|
||||
Pair<List<PsiClass>, AtomicIntegerArray> pair = calculateDirectSubClasses(project, baseClass, baseClassName);
|
||||
cache = calculateDirectSubClasses(project, baseClass, baseClassName);
|
||||
// for non-physical elements ignore the cache completely because non-physical elements created so often/unpredictably so I can't figure out when to clear caches in this case
|
||||
if (ApplicationManager.getApplication().runReadAction((Computable<Boolean>)baseClass::isPhysical)) {
|
||||
HighlightingCaches.getInstance(project).DIRECT_SUB_CLASSES.put(baseClass, pair);
|
||||
cache = ConcurrencyUtil.cacheOrGet(map, baseClass, cache);
|
||||
}
|
||||
return pair;
|
||||
return cache;
|
||||
}
|
||||
|
||||
private static <T> boolean processConcurrentlyIfTooMany(@NotNull Collection<T> collection, @NotNull Processor<? super T> processor) {
|
||||
int size = collection.size();
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
if (size > 100) {
|
||||
return JobLauncher.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<>(collection), ProgressIndicatorProvider.getGlobalProgressIndicator(), true, processor);
|
||||
}
|
||||
return ContainerUtil.process(collection, processor);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Pair<List<PsiClass>, AtomicIntegerArray> calculateDirectSubClasses(@NotNull Project project,
|
||||
@NotNull PsiClass baseClass,
|
||||
@NotNull String baseClassName) {
|
||||
private static PsiClass[] calculateDirectSubClasses(@NotNull Project project, @NotNull PsiClass baseClass, @NotNull String baseClassName) {
|
||||
GlobalSearchScope allScope = GlobalSearchScope.allScope(project);
|
||||
Collection<PsiReferenceList> candidates =
|
||||
MethodUsagesSearcher.resolveInReadAction(project, () -> JavaSuperClassNameOccurenceIndex.getInstance().get(baseClassName, project, allScope));
|
||||
|
||||
Map<String, List<PsiClass>> classes = new HashMap<>();
|
||||
int count = 0;
|
||||
// memory/speed optimisation: it really is a map(string -> PsiClass or List<PsiClass>)
|
||||
final Map<String, Object> classes = new HashMap<>();
|
||||
|
||||
for (final PsiReferenceList referenceList : candidates) {
|
||||
ProgressManager.checkCanceled();
|
||||
final PsiClass candidate = (PsiClass)ApplicationManager.getApplication().runReadAction((Computable<PsiElement>)referenceList::getParent);
|
||||
processConcurrentlyIfTooMany(candidates,
|
||||
referenceList -> {
|
||||
ProgressManager.checkCanceled();
|
||||
ApplicationManager.getApplication().runReadAction(() -> {
|
||||
final PsiClass candidate = (PsiClass)referenceList.getParent();
|
||||
boolean isInheritor = candidate.isInheritor(baseClass, false);
|
||||
if (isInheritor) {
|
||||
String fqn = candidate.getQualifiedName();
|
||||
synchronized (classes) {
|
||||
Object value = classes.get(fqn);
|
||||
if (value == null) {
|
||||
classes.put(fqn, candidate);
|
||||
}
|
||||
else if (value instanceof PsiClass) {
|
||||
List<PsiClass> list = new ArrayList<>();
|
||||
list.add((PsiClass)value);
|
||||
list.add(candidate);
|
||||
classes.put(fqn, list);
|
||||
}
|
||||
else {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<PsiClass> list = (List<PsiClass>)value;
|
||||
list.add(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String fqn = ApplicationManager.getApplication().runReadAction((Computable<String>)candidate::getQualifiedName);
|
||||
List<PsiClass> list = classes.get(fqn);
|
||||
if (list == null) {
|
||||
list = new SmartList<>();
|
||||
classes.put(fqn, list);
|
||||
return true;
|
||||
});
|
||||
|
||||
final List<PsiClass> result = new ArrayList<>();
|
||||
for (Object value : classes.values()) {
|
||||
if (value instanceof PsiClass) {
|
||||
result.add((PsiClass)value);
|
||||
}
|
||||
else {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<PsiClass> list = (List<PsiClass>)value;
|
||||
result.addAll(list);
|
||||
}
|
||||
list.add(candidate);
|
||||
count++;
|
||||
}
|
||||
|
||||
Collection<PsiAnonymousClass> anonymousCandidates =
|
||||
MethodUsagesSearcher.resolveInReadAction(project, () -> JavaAnonymousClassBaseRefOccurenceIndex.getInstance().get(baseClassName, project, allScope));
|
||||
|
||||
List<PsiClass> result = new ArrayList<>(count + classes.size() + anonymousCandidates.size() + 1);
|
||||
for (Map.Entry<String, List<PsiClass>> entry : classes.entrySet()) {
|
||||
result.addAll(entry.getValue());
|
||||
result.add(PsiUtil.NULL_PSI_CLASS);
|
||||
}
|
||||
|
||||
result.addAll(anonymousCandidates);
|
||||
processConcurrentlyIfTooMany(anonymousCandidates,
|
||||
candidate-> {
|
||||
boolean isInheritor = MethodUsagesSearcher.resolveInReadAction(project, () -> candidate.isInheritor(baseClass, false));
|
||||
if (isInheritor) {
|
||||
synchronized (result) {
|
||||
result.add(candidate);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
boolean isEnum = ApplicationManager.getApplication().runReadAction((Computable<Boolean>)baseClass::isEnum);
|
||||
if (isEnum) {
|
||||
@@ -217,29 +237,13 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor<PsiClass, Dir
|
||||
PsiEnumConstantInitializer initializingClass =
|
||||
ApplicationManager.getApplication().runReadAction((Computable<PsiEnumConstantInitializer>)((PsiEnumConstant)field)::getInitializingClass);
|
||||
if (initializingClass != null) {
|
||||
result.add(initializingClass);
|
||||
result.add(initializingClass); // it surely is an inheritor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Pair.create(result, new AtomicIntegerArray(result.size()));
|
||||
}
|
||||
|
||||
private static boolean checkInheritance(boolean checkInheritance,
|
||||
@NotNull PsiClass baseClass,
|
||||
@NotNull PsiClass candidate,
|
||||
@NotNull Project project,
|
||||
@NotNull AtomicIntegerArray isInheritorFlags,
|
||||
int i) {
|
||||
if (!checkInheritance) return true;
|
||||
int cachedFlag = isInheritorFlags.get(i);
|
||||
if (cachedFlag == INHERITANCE_YES) return true;
|
||||
if (cachedFlag == INHERITANCE_NO) return false;
|
||||
assert cachedFlag == INHERITANCE_UNKNOWN;
|
||||
boolean isReallyInherited = MethodUsagesSearcher.resolveInReadAction(project, () -> candidate.isInheritor(baseClass, false));
|
||||
isInheritorFlags.set(i, isReallyInherited ? INHERITANCE_YES : INHERITANCE_NO);
|
||||
return isReallyInherited;
|
||||
return result.isEmpty() ? PsiClass.EMPTY_ARRAY : result.toArray(new PsiClass[result.size()]);
|
||||
}
|
||||
|
||||
private static VirtualFile getJarFile(@NotNull PsiClass aClass) {
|
||||
|
||||
Reference in New Issue
Block a user