diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java index c4fc4eb1a051..bd124ec8fa6e 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/JavaDirectInheritorsSearcher.java @@ -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 StubHierarchyInheritorSearcher.restrictScope(GlobalSearchScopeUtil.toGlobalSearchScope(useScope, project))); + () -> StubHierarchyInheritorSearcher.restrictScope(new JavaSourceFilterScope(GlobalSearchScopeUtil.toGlobalSearchScope(useScope, project)))); Collection 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) final Map 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 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) { diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/RelaxedDirectInheritorChecker.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/RelaxedDirectInheritorChecker.java new file mode 100644 index 000000000000..c66bdc6ff477 --- /dev/null +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/RelaxedDirectInheritorChecker.java @@ -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 myClasses; + private final VolatileNotNullLazyValue 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> cache = CachedValuesManager.getManager(project).getCachedValue(project, () -> { + Map> 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 classes) { + int locals = 0; + Set 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 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); + } +} diff --git a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyDirectInheritorsSearcher.java b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyDirectInheritorsSearcher.java index 339c9446a464..c49d32ab5778 100644 --- a/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyDirectInheritorsSearcher.java +++ b/plugins/groovy/groovy-psi/src/org/jetbrains/plugins/groovy/lang/psi/impl/GroovyDirectInheritorsSearcher.java @@ -5,6 +5,7 @@ import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.search.RelaxedDirectInheritorChecker; import com.intellij.psi.impl.search.StubHierarchyInheritorSearcher; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.GlobalSearchScopeUtil; @@ -22,7 +23,6 @@ import org.jetbrains.plugins.groovy.lang.psi.stubs.index.GrAnonymousClassIndex; import org.jetbrains.plugins.groovy.lang.psi.stubs.index.GrDirectInheritorsIndex; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; @@ -44,11 +44,7 @@ public class GroovyDirectInheritorsSearcher implements QueryExecutor classes = - StubIndex.getElements(GrAnonymousClassIndex.KEY, name, clazz.getProject(), scope, GrAnonymousClassDefinition.class); - for (GrAnonymousClassDefinition aClass : classes) { - inheritors.add(aClass); - } + inheritors.addAll(StubIndex.getElements(GrAnonymousClassIndex.KEY, name, clazz.getProject(), scope, GrAnonymousClassDefinition.class)); } return inheritors; } @@ -59,22 +55,24 @@ public class GroovyDirectInheritorsSearcher implements QueryExecutor candidates = DumbService.getInstance(project).runReadActionInSmartMode(() -> { + DumbService dumbService = DumbService.getInstance(project); + List candidates = dumbService.runReadActionInSmartMode(() -> { if (!clazz.isValid()) return Collections.emptyList(); GlobalSearchScope restrictedScope = StubHierarchyInheritorSearcher.restrictScope(globalSearchScope); return getDerivingClassCandidates(clazz, restrictedScope, queryParameters.includeAnonymous()); }); - for (final PsiClass candidate : candidates) { - if (!queryParameters.isCheckInheritance() || isInheritor(clazz, candidate, project)) { - if (!consumer.process(candidate)) { - return false; + + if (!candidates.isEmpty()) { + RelaxedDirectInheritorChecker checker = dumbService.runReadActionInSmartMode(() -> new RelaxedDirectInheritorChecker(clazz)); + + for (PsiClass candidate : candidates) { + if (!queryParameters.isCheckInheritance() || dumbService.runReadActionInSmartMode(() -> checker.checkInheritance(candidate))) { + if (!consumer.process(candidate)) { + return false; + } } } } return true; } - - private static boolean isInheritor(PsiClass clazz, PsiClass candidate, Project project) { - return DumbService.getInstance(project).runReadActionInSmartMode(() -> candidate.isValid() && candidate.isInheritor(clazz, false)); - } }