optimizations: do not calculate containing file too often, do not traverse list unnecessarily, to alleviate some of IDEA-275798 IDE causes high CPU load (after some time)

GitOrigin-RevId: cd891346ca7fb3de8e6af0817f9c5ff74a6091ad
This commit is contained in:
Alexey Kudravtsev
2021-09-29 15:18:44 +02:00
committed by intellij-monorepo-bot
parent 93da6311fc
commit b03472d8ca

View File

@@ -4,11 +4,8 @@ package com.intellij.psi.impl.search;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.NotNullLazyValue;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.CommonClassNames;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCompiledElement;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.*;
import com.intellij.psi.impl.ResolveScopeManager;
import com.intellij.psi.impl.source.resolve.JavaResolveUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.PsiSearchScopeUtil;
@@ -18,12 +15,9 @@ 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 org.jetbrains.annotations.NotNull;
import java.lang.ref.Reference;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
@@ -34,7 +28,7 @@ import java.util.concurrent.ConcurrentHashMap;
public final class RelaxedDirectInheritorChecker {
private final String myBaseClassName;
private final PsiClass myBaseClass;
private final NotNullLazyValue<Pair<PsiClass[], Boolean>> myClasses;
private final NotNullLazyValue<ClassesAndAmbiguities> myClasses;
private final ProjectFileIndex myFileIndex;
public RelaxedDirectInheritorChecker(@NotNull PsiClass baseClass) {
@@ -44,58 +38,82 @@ public final class RelaxedDirectInheritorChecker {
myFileIndex = ProjectFileIndex.getInstance(myBaseClass.getProject());
}
private static @NotNull Pair<PsiClass[], Boolean> getClassesAndTheirAmbiguities(@NotNull Project project, @NotNull String classShortName) {
Map<String, Reference<Pair<PsiClass[],Boolean>>> cache = CachedValuesManager.getManager(project).getCachedValue(project, () ->
private static class ClassesAndAmbiguities {
@NotNull final PsiClass @NotNull[] classes;
@NotNull final PsiFile @NotNull[] containingFiles;
final boolean isAmbiguous;
private ClassesAndAmbiguities(@NotNull PsiClass @NotNull[] classes, @NotNull PsiFile @NotNull[] containingFiles, boolean isAmbiguous) {
this.classes = classes;
this.containingFiles = containingFiles;
this.isAmbiguous = isAmbiguous;
}
}
@NotNull
private static ClassesAndAmbiguities getClassesAndTheirAmbiguities(@NotNull Project project, @NotNull String classShortName) {
Map<String, Reference<ClassesAndAmbiguities>> cache = CachedValuesManager.getManager(project).getCachedValue(project, () ->
CachedValueProvider.Result.create(new ConcurrentHashMap<>(), PsiModificationTracker.MODIFICATION_COUNT));
Pair<PsiClass[], Boolean> result = SoftReference.dereference(cache.get(classShortName));
ClassesAndAmbiguities result = SoftReference.dereference(cache.get(classShortName));
if (result == null) {
PsiClass[] classes = PsiShortNamesCache.getInstance(project).getClassesByName(classShortName, GlobalSearchScope.allScope(project));
boolean ambiguities = hasAmbiguities(Arrays.asList(classes));
result = Pair.create(classes, ambiguities);
String ambiguity = null;
PsiFile[] files = new PsiFile[classes.length];
for (int i = 0; i < classes.length; i++) {
PsiClass psiClass = classes[i];
ambiguity = hasAmbiguitiesSoFar(psiClass, ambiguity);
files[i] = psiClass.getContainingFile();
}
boolean ambiguities = ambiguity == AMBIGUITY_FOUND;
result = new ClassesAndAmbiguities(classes, files, ambiguities);
cache.put(classShortName, new SoftReference<>(result));
}
return result;
}
// false if all classes in the list have the same FQN
private static boolean hasAmbiguities(@NotNull List<? extends PsiClass> classes) {
int locals = 0;
String theFQN = null;
for (PsiClass psiClass : classes) {
String qName = psiClass.getQualifiedName();
if (qName == null) {
locals++;
if (locals > 1) return true;
}
else if (theFQN == null) {
theFQN = qName;
}
else if (!theFQN.equals(qName)) {
return true;
}
}
return locals == 1 && theFQN != null;
// because of lack of multiple-value return, we have this monstrosity
// in the end (after the whole list is traversed with 'a=hasAmbiguitiesSoFar(c,a)' called on each element), one of these cases is returned:
// - AMBIGUITY_FOUND: if there exist two classes with different FQNs (or at least one local class and at least one with not-null FQN)
// - LOCAL_CLASS_FOUND: one local class found, all others have same FQN
// - other: if all classes in the list have the same FQN
private static String hasAmbiguitiesSoFar(@NotNull PsiClass psiClass, String oldFqn) {
if (oldFqn == AMBIGUITY_FOUND) return AMBIGUITY_FOUND;
String qName = Objects.requireNonNullElse(psiClass.getQualifiedName(), LOCAL_CLASS_FOUND);
return oldFqn == null || oldFqn.equals(qName) && !oldFqn.equals(LOCAL_CLASS_FOUND) ? qName : AMBIGUITY_FOUND;
}
private static final String LOCAL_CLASS_FOUND = "?LOCAL_CLASS_FOUND";
private static final String AMBIGUITY_FOUND = "?AMBIGUITY_FOUND";
/**
* This assumes that {@code inheritorCandidate} is in the use scope of {@link #myBaseClass}
*/
public boolean checkInheritance(@NotNull PsiClass inheritorCandidate) {
if (!inheritorCandidate.isValid() || !myBaseClass.isValid()) return false;
if (myFileIndex.isInSourceContent(inheritorCandidate.getContainingFile().getVirtualFile())) {
Pair<PsiClass[], Boolean> value = myClasses.getValue();
boolean hasGlobalAmbiguities = value.getSecond();
PsiFile inheritorCandidateContainingFile = inheritorCandidate.getContainingFile();
if (myFileIndex.isInSourceContent(inheritorCandidateContainingFile.getVirtualFile())) {
ClassesAndAmbiguities value = myClasses.getValue();
boolean hasGlobalAmbiguities = value.isAmbiguous;
if (!hasGlobalAmbiguities) {
return true;
}
PsiClass[] classes = value.getFirst();
GlobalSearchScope scope = inheritorCandidate.getResolveScope();
List<PsiClass> accessible = ContainerUtil.findAll(classes, base ->
PsiSearchScopeUtil.isInScope(scope, base) && isAccessibleLight(inheritorCandidate, base));
if (!hasAmbiguities(accessible)) {
return accessible.contains(myBaseClass);
PsiClass[] classes = value.classes;
PsiFile[] files = value.containingFiles;
GlobalSearchScope scope = ResolveScopeManager.getInstance(inheritorCandidateContainingFile.getProject()).getResolveScope(inheritorCandidateContainingFile);
String ambiguity = null;
boolean hasBaseClass = false;
for (int i = 0; i < classes.length; i++) {
PsiClass base = classes[i];
PsiFile file = files[i];
if (PsiSearchScopeUtil.isInScope(scope, file) && isAccessibleLight(inheritorCandidate, inheritorCandidateContainingFile, base)) {
hasBaseClass |= base.equals(myBaseClass);
ambiguity = hasAmbiguitiesSoFar(base, ambiguity);
}
}
if (ambiguity != AMBIGUITY_FOUND) {
return hasBaseClass;
}
}
@@ -111,12 +129,14 @@ public final class RelaxedDirectInheritorChecker {
inheritorCandidate.isAnnotationType() && CommonClassNames.JAVA_LANG_ANNOTATION_ANNOTATION.equals(myBaseClass.getQualifiedName());
}
private static boolean isAccessibleLight(@NotNull PsiClass inheritorCandidate, @NotNull PsiClass base) {
private static boolean isAccessibleLight(@NotNull PsiClass inheritorCandidate,
@NotNull PsiFile inheritorCandidateContainingFile,
@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);
return JavaResolveUtil.isAccessible(base, base.getContainingClass(), modifierList, inheritorCandidate, null, null, inheritorCandidateContainingFile);
}
}