diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchy.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchy.java index 125216dbef74..0bd87dde71f2 100644 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchy.java +++ b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchy.java @@ -15,14 +15,17 @@ */ package com.intellij.psi.stubsHierarchy.impl; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileWithId; import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiElement; +import com.intellij.psi.search.DelegatingGlobalSearchScope; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubsHierarchy.ClassHierarchy; import com.intellij.psi.stubsHierarchy.SmartClassAnchor; import com.intellij.psi.stubsHierarchy.impl.Symbol.ClassSymbol; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.indexing.FileBasedIndex; -import gnu.trove.TIntHashSet; -import gnu.trove.TIntStack; import org.jetbrains.annotations.NotNull; import java.util.*; @@ -32,6 +35,7 @@ import java.util.*; */ public class SingleClassHierarchy extends ClassHierarchy { private final BitSet myCoveredFiles; + private final BitSet myAmbiguousSupers; private final List myCoveredClasses; private final StubClassAnchor[] myClassAnchors; private final StubClassAnchor[] myClassAnchorsByFileIds; @@ -42,10 +46,23 @@ public class SingleClassHierarchy extends ClassHierarchy { myCoveredFiles = calcCoveredFiles(classSymbols); myClassAnchors = ContainerUtil.map2Array(classSymbols, StubClassAnchor.class, symbol -> symbol.myClassAnchor); myClassAnchorsByFileIds = mkByFileId(myClassAnchors); + excludeUncoveredFiles(classSymbols); connectSubTypes(classSymbols); + myAmbiguousSupers = calcAmbiguousSupers(classSymbols); myCoveredClasses = Collections.unmodifiableList(ContainerUtil.filter(myClassAnchors, this::isCovered)); } + @NotNull + private static BitSet calcAmbiguousSupers(ClassSymbol[] classSymbols) { + BitSet ambiguousSupers = new BitSet(); + for (ClassSymbol symbol : classSymbols) { + if (!symbol.isHierarchyIncomplete() && symbol.hasAmbiguousSupers()) { + ambiguousSupers.set(symbol.myClassAnchor.myId); + } + } + return ambiguousSupers; + } + @NotNull private static BitSet calcCoveredFiles(ClassSymbol[] classSymbols) { BitSet problematicFiles = new BitSet(); @@ -79,6 +96,10 @@ public class SingleClassHierarchy extends ClassHierarchy { @NotNull @Override public SmartClassAnchor[] getDirectSubtypeCandidates(@NotNull PsiClass psiClass) { + PsiElement original = psiClass.getOriginalElement(); + if (original instanceof PsiClass) { + psiClass = (PsiClass)original; + } int fileId = Math.abs(FileBasedIndex.getFileId(psiClass.getContainingFile().getVirtualFile())); SmartClassAnchor anchor = forPsiClass(fileId, psiClass); return anchor == null ? StubClassAnchor.EMPTY_ARRAY : getDirectSubtypeCandidates(anchor); @@ -101,39 +122,28 @@ public class SingleClassHierarchy extends ClassHierarchy { return result; } - public StubClassAnchor[] getAllSubtypes(PsiClass base) { - int fileId = Math.abs(FileBasedIndex.getFileId(base.getContainingFile().getVirtualFile())); + @Override + public boolean hasAmbiguousSupers(@NotNull SmartClassAnchor anchor) { + return myAmbiguousSupers.get(((StubClassAnchor)anchor).myId); + } - TIntHashSet resultIds = new TIntHashSet(); - TIntHashSet processed = new TIntHashSet(); - TIntStack queue = new TIntStack(); - - StubClassAnchor baseAnchor = forPsiClass(fileId, base); - if (baseAnchor == null) { - return StubClassAnchor.EMPTY_ARRAY; + @NotNull + @Override + public GlobalSearchScope restrictToUncovered(@NotNull GlobalSearchScope scope) { + if (myCoveredClasses.isEmpty()) { + return scope; } - queue.push(baseAnchor.myId); - while (queue.size() > 0) { - int id = queue.pop(); - if (processed.add(id)) { - int start = subtypeStart(id); - int end = subtypeEnd(id); - for (int i = start; i < end; i++) { - int subtypeId = mySubtypes[i]; - resultIds.add(subtypeId); - if (!processed.contains(subtypeId)) { - queue.push(subtypeId); - } + return new DelegatingGlobalSearchScope(scope, this) { + @Override + public boolean contains(@NotNull VirtualFile file) { + if (file instanceof VirtualFileWithId && myCoveredFiles.get(((VirtualFileWithId)file).getId())) { + return false; } + + return super.contains(file); } - } - int[] allIds = resultIds.toArray(); - StubClassAnchor[] result = new StubClassAnchor[allIds.length]; - for (int i = 0; i < result.length; i++) { - result[i] = myClassAnchors[allIds[i]]; - } - return result; + }; } private static StubClassAnchor[] mkByFileId(final StubClassAnchor[] classAnchors) { @@ -154,8 +164,6 @@ public class SingleClassHierarchy extends ClassHierarchy { } private void connectSubTypes(ClassSymbol[] classSymbols) { - excludeUncoveredFiles(classSymbols); - int[] sizes = calculateSizes(classSymbols); int[] starts = new int[classSymbols.length]; diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/Symbol.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/Symbol.java index 22903d1ad0c9..bb1505d342ec 100644 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/Symbol.java +++ b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/Symbol.java @@ -18,6 +18,7 @@ package com.intellij.psi.stubsHierarchy.impl; import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree; import com.intellij.psi.stubsHierarchy.stubs.UnitInfo; import com.intellij.util.BitUtil; +import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; import java.util.Comparator; @@ -159,6 +160,20 @@ public abstract class Symbol { boolean isHierarchyIncomplete() { return myHierarchyIncomplete; } + + boolean hasAmbiguousSupers() { + ClassSymbol[] superClasses = rawSuperClasses(); + if (superClasses.length < 2) return false; + + TIntHashSet superNames = new TIntHashSet(); + for (ClassSymbol symbol : superClasses) { + if (!superNames.add(symbol.myShortName)) { + return true; + } + } + + return false; + } } /** 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 4820dfe0859b..f52a56955e4b 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 @@ -17,6 +17,7 @@ package com.intellij.psi.impl.search; import com.intellij.concurrency.JobLauncher; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -175,7 +176,7 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor)()-> toGlobal(useScope, project)); + GlobalSearchScope globalUseScope = ReadAction.compute(() -> StubHierarchyInheritorSearcher.restrictScope(toGlobal(useScope, project))); Collection candidates = MethodUsagesSearcher.resolveInReadAction(project, () -> JavaSuperClassNameOccurenceIndex.getInstance().get(baseClassName, project, globalUseScope)); diff --git a/java/java-indexing-impl/src/com/intellij/psi/impl/search/StubHierarchyInheritorSearcher.java b/java/java-indexing-impl/src/com/intellij/psi/impl/search/StubHierarchyInheritorSearcher.java new file mode 100644 index 000000000000..51a2fc51c7e1 --- /dev/null +++ b/java/java-indexing-impl/src/com/intellij/psi/impl/search/StubHierarchyInheritorSearcher.java @@ -0,0 +1,80 @@ +/* + * Copyright 2000-2016 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.psi.impl.search; + +import com.intellij.openapi.application.QueryExecutorBase; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchScopeUtil; +import com.intellij.psi.search.searches.DirectClassInheritorsSearch; +import com.intellij.psi.stubsHierarchy.ClassHierarchy; +import com.intellij.psi.stubsHierarchy.HierarchyService; +import com.intellij.psi.stubsHierarchy.SmartClassAnchor; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NotNull; + +/** + * @author peter + */ +public class StubHierarchyInheritorSearcher extends QueryExecutorBase { + public StubHierarchyInheritorSearcher() { + super(true); + } + + private static boolean isSearching() { + return Registry.is("java.use.stub.hierarchy.in.inheritor.search"); + } + + @NotNull + public static GlobalSearchScope restrictScope(@NotNull GlobalSearchScope scope) { + if (!isSearching()) return scope; + + Project project = scope.getProject(); + return project == null ? scope : HierarchyService.getHierarchy(project).restrictToUncovered(scope); + } + + @Override + public void processQuery(@NotNull DirectClassInheritorsSearch.SearchParameters queryParameters, @NotNull Processor consumer) { + if (!(queryParameters.getScope() instanceof GlobalSearchScope) || !isSearching()) return; + + PsiClass base = queryParameters.getClassToProcess(); + GlobalSearchScope scope = (GlobalSearchScope)queryParameters.getScope(); + ClassHierarchy hierarchy = HierarchyService.getHierarchy(base.getProject()); + for (SmartClassAnchor anchor : hierarchy.getDirectSubtypeCandidates(base)) { + if (!processCandidate(consumer, base, scope, hierarchy, anchor)) { + return; + } + } + } + + private static boolean processCandidate(Processor consumer, + PsiClass base, + GlobalSearchScope scope, + ClassHierarchy hierarchy, SmartClassAnchor anchor) { + VirtualFile file = anchor.retrieveFile(); + if (!scope.contains(file)) return true; + + PsiClass candidate = anchor.retrieveClass(base.getProject()); + if (!PsiSearchScopeUtil.isInScope(candidate.getResolveScope(), base)) return true; + + if (hierarchy.hasAmbiguousSupers(anchor) && !candidate.isInheritor(base, false)) return true; + + return consumer.process(candidate); + } +} diff --git a/java/java-indexing-impl/src/com/intellij/psi/stubsHierarchy/ClassHierarchy.java b/java/java-indexing-impl/src/com/intellij/psi/stubsHierarchy/ClassHierarchy.java index 3de5d815060e..74c5cccedc2a 100644 --- a/java/java-indexing-impl/src/com/intellij/psi/stubsHierarchy/ClassHierarchy.java +++ b/java/java-indexing-impl/src/com/intellij/psi/stubsHierarchy/ClassHierarchy.java @@ -16,6 +16,7 @@ package com.intellij.psi.stubsHierarchy; import com.intellij.psi.PsiClass; +import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; import java.util.List; @@ -41,4 +42,15 @@ public abstract class ClassHierarchy { @NotNull public abstract SmartClassAnchor[] getDirectSubtypeCandidates(@NotNull SmartClassAnchor anchor); + + /** + * @return whether stub hierarchy resolver couldn't determine the super class exactly because there were several possible candidates + */ + public abstract boolean hasAmbiguousSupers(@NotNull SmartClassAnchor anchor); + + /** + * @return the given scope restricted to the files not covered by this hierarchy, to use usual PSI/resolve-based inheritor search in it + */ + @NotNull + public abstract GlobalSearchScope restrictToUncovered(@NotNull GlobalSearchScope scope); } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 0b60dad43da4..462bff4781d1 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -312,7 +312,11 @@ java.annotations.inference.nullable.method.transitivity=true java.annotations.inference.nullable.method.transitivity.description=Restart is required; if a method result is a call to a @Nullable method, reports the caller as @Nullable as well java.hierarchy.service=false -java.hierarchy.service.description=Restart is required; calculate class hierarchy in background +java.hierarchy.service.description=Enable special indexing for quick JVM class hierarchy calculation +java.hierarchy.service.restartRequired=true + +java.use.stub.hierarchy.in.inheritor.search=false +java.use.stub.hierarchy.in.inheritor.search.description=Use stub hierarchy (enabled by java.hierarchy.service key) in inheritor searches java.max.package.name.length=1000 java.max.package.name.length.description=An upper length limit on string that the IDE considers worth checking for being package names 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 fcc4e61c4494..993783d5a464 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 @@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.util.Computable; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.impl.search.StubHierarchyInheritorSearcher; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.SearchScope; import com.intellij.psi.search.searches.DirectClassInheritorsSearch; @@ -74,7 +75,8 @@ public class GroovyDirectInheritorsSearcher implements QueryExecutor compute() { if (!clazz.isValid()) return Collections.emptyList(); - return getDerivingClassCandidates(clazz, (GlobalSearchScope)scope, queryParameters.includeAnonymous()); + GlobalSearchScope restrictedScope = StubHierarchyInheritorSearcher.restrictScope((GlobalSearchScope)scope); + return getDerivingClassCandidates(clazz, restrictedScope, queryParameters.includeAnonymous()); } }); for (final PsiClass candidate : candidates) { diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 949e72413475..3075fc008d19 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -353,6 +353,7 @@ +