direct inheritors optimisations: checking inheritors in parallel, simplify data structures

This commit is contained in:
Alexey Kudravtsev
2016-05-27 15:48:07 +03:00
parent 6e91d83881
commit 1f2fb7f51d
2 changed files with 113 additions and 111 deletions
@@ -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
@@ -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) {