diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceReader.java b/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceReader.java index 2ae12eabd2e4..fb2dd4fd7a2d 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceReader.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceReader.java @@ -18,11 +18,20 @@ package com.intellij.compiler; import com.intellij.compiler.backwardRefs.LanguageLightUsageConverter; import com.intellij.compiler.server.BuildManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Couple; import com.intellij.openapi.vfs.VfsUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileWithId; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SmartList; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Queue; +import com.sun.tools.javac.util.Convert; +import gnu.trove.THashMap; import gnu.trove.THashSet; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; @@ -32,10 +41,12 @@ import org.jetbrains.jps.backwardRefs.LightUsage; import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; -public class CompilerReferenceReader { +import static java.util.stream.Collectors.*; + +class CompilerReferenceReader { private final static Logger LOG = Logger.getInstance(CompilerReferenceReader.class); private final CompilerBackwardReferenceIndex myIndex; @@ -45,7 +56,7 @@ public class CompilerReferenceReader { } @Nullable - public TIntHashSet findReferentFileIds(@NotNull CompilerElement element, + TIntHashSet findReferentFileIds(@NotNull CompilerElement element, @NotNull CompilerSearchAdapter adapter, boolean checkBaseClassAmbiguity) { LightUsage usage = asLightUsage(element); @@ -64,23 +75,61 @@ public class CompilerReferenceReader { return set; } - public void addUsages(LightUsage usage, TIntHashSet sink) { - final Collection usageFiles = myIndex.getBackwardReferenceMap().get(usage); - if (usageFiles != null) { - for (int fileId : usageFiles) { - final VirtualFile file = findFile(fileId); - if (file != null) { - sink.add(((VirtualFileWithId)file).getId()); - } + @NotNull + Couple> getDirectInheritors(@NotNull CompilerElement element, + @NotNull PsiNamedElement psiElement, + @NotNull CompilerDirectInheritorSearchAdapter adapter, + @NotNull GlobalSearchScope searchScope, + @NotNull GlobalSearchScope dirtyScope, + @NotNull Project project, + FileType... fileTypes) { + final LightUsage aClass = asLightUsage(element); + Collection candidates = myIndex.getBackwardHierarchyMap().get(aClass); + if (candidates == null) return Couple.of(Collections.emptyMap(), Collections.emptyMap()); + + final Set fileTypeSet = ContainerUtil.set(fileTypes); + + final Set> suitableClasses = new THashSet<>(); + for (LanguageLightUsageConverter converter : LanguageLightUsageConverter.INSTANCES) { + if (fileTypeSet.contains(converter.getFileSourceType())) { + suitableClasses.addAll(converter.getLanguageLightUsageClasses()); } } + + final GlobalSearchScope effectiveSearchScope = GlobalSearchScope.notScope(dirtyScope).intersectWith(searchScope); + + Map> perFileCandidates = candidates + .stream() + .filter(def -> suitableClasses.contains(def.getUsage().getClass())) + .map(definition -> { + final VirtualFile file = findFile(definition.getFileId()); + return file != null && effectiveSearchScope.contains(file) ? new DecodedInheritorCandidate(getName(definition), file) : null; + }) + .filter(Objects::nonNull) + .collect(groupingBy(DecodedInheritorCandidate::getDeclarationFile, mapping(DecodedInheritorCandidate::getQName, toCollection(SmartList::new)))); + + if (perFileCandidates.isEmpty()) return Couple.of(Collections.emptyMap(), Collections.emptyMap()); + + Map inheritors = new THashMap<>(perFileCandidates.size()); + Map inheritorCandidates = new THashMap<>(); + + perFileCandidates.forEach((file, directInheritors) -> { + final T[] currInheritors = adapter.getCandidatesFromFile(directInheritors, psiElement, file, project); + if (currInheritors.length == directInheritors.size()) { + inheritors.put(file, currInheritors); + } else { + inheritorCandidates.put(file, currInheritors); + } + }); + + return Couple.of(inheritors, inheritorCandidates); } - public void close() { + void close() { myIndex.close(); } - public static CompilerReferenceReader create(Project project) { + static CompilerReferenceReader create(Project project) { File buildDir = BuildManager.getInstance().getProjectSystemDirectory(project); if (buildDir == null || CompilerBackwardReferenceIndex.versionDiffers(buildDir)) { return null; @@ -93,6 +142,18 @@ public class CompilerReferenceReader { } } + private void addUsages(LightUsage usage, TIntHashSet sink) { + final Collection usageFiles = myIndex.getBackwardReferenceMap().get(usage); + if (usageFiles != null) { + for (int fileId : usageFiles) { + final VirtualFile file = findFile(fileId); + if (file != null) { + sink.add(((VirtualFileWithId)file).getId()); + } + } + } + } + @NotNull private LightUsage asLightUsage(@NotNull CompilerElement element) { LightUsage usage = null; @@ -141,4 +202,32 @@ public class CompilerReferenceReader { } return result.toArray(new LightUsage[result.size()]); } + + @NotNull + private String getName(CompilerBackwardReferenceIndex.LightDefinition def) { + try { + return Convert.utf2string(ObjectUtils.notNull(myIndex.getByteSeqEum().valueOf(def.getUsage().getName()))); + } + catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static class DecodedInheritorCandidate { + private final String qName; + private final VirtualFile declarationFile; + + private DecodedInheritorCandidate(String name, VirtualFile file) { + qName = name; + declarationFile = file; + } + + public VirtualFile getDeclarationFile() { + return declarationFile; + } + + public String getQName() { + return qName; + } + } } \ No newline at end of file diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceServiceImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceServiceImpl.java index f7c19b92563b..b56eca2d6daf 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceServiceImpl.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompilerReferenceServiceImpl.java @@ -15,8 +15,8 @@ */ package com.intellij.compiler; +import com.intellij.compiler.backwardRefs.LanguageLightUsageConverter; import com.intellij.compiler.server.BuildManagerListener; -import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.compiler.*; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.module.Module; @@ -24,12 +24,16 @@ import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.ModificationTracker; import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.openapi.vfs.*; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.impl.PsiImplUtil; import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.search.PsiSearchScopeUtil; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; @@ -42,9 +46,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; -import java.util.Collections; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.atomic.LongAdder; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -65,8 +67,10 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple super(project); myDirtyModulesHolder = new DirtyModulesHolder(); - myFileTypes = Collections.unmodifiableSet(ContainerUtil.set(JavaFileType.INSTANCE)); myProjectFileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + myFileTypes = Collections.unmodifiableSet(Stream.of(LanguageLightUsageConverter.INSTANCES) + .map(LanguageLightUsageConverter::getFileSourceType) + .collect(Collectors.toSet())); } @Override @@ -177,7 +181,39 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple return calculateScopeWithoutReferences(element, key); } }, - PsiModificationTracker.MODIFICATION_COUNT, this)).get(adapter); + PsiModificationTracker.MODIFICATION_COUNT)).get(adapter); + } + + @Nullable + @Override + public CompilerDirectInheritorInfo getDirectInheritors(@NotNull PsiNamedElement aClass, + @NotNull GlobalSearchScope useScope, + @NotNull GlobalSearchScope searchScope, + @NotNull CompilerSearchAdapter compilerSearchAdapter, + @NotNull CompilerDirectInheritorSearchAdapter inheritorSearchAdapter, + @NotNull FileType... searchFileTypes) { + if (!isServiceEnabled()) return null; + + //TODO should be available to search inheritors of lib classes + final VirtualFile file = aClass.getContainingFile().getVirtualFile(); + if (!myProjectFileIndex.isInSourceContent(file)) return null; + + final CompilerElement compilerElement = compilerSearchAdapter.asCompilerElement(aClass); + if (compilerElement == null) return null; + + final GlobalSearchScope dirtyScope = myDirtyModulesHolder.getDirtyScope(); + final Couple> directInheritorsAndCandidates = myReader.getDirectInheritors(compilerElement, + aClass, + inheritorSearchAdapter, + useScope, + dirtyScope, + myProject, + searchFileTypes); + + + return new CompilerDirectInheritorInfo<>(selectClassesInScope(directInheritorsAndCandidates.getFirst(), searchScope), + selectClassesInScope(directInheritorsAndCandidates.getSecond(), searchScope), + dirtyScope); } private boolean isServiceEnabled() { @@ -247,6 +283,15 @@ public class CompilerReferenceServiceImpl extends CompilerReferenceService imple } } + private static List selectClassesInScope(Map classesPerFile, GlobalSearchScope searchScope) { + return classesPerFile + .entrySet() + .stream() + .filter(e -> searchScope.contains(e.getKey())) + .flatMap(e -> Stream.of(e.getValue())) + .collect(Collectors.toList()); + } + @TestOnly @Nullable public Set getReferentFiles(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter) { diff --git a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/LanguageLightUsageConverter.java b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/LanguageLightUsageConverter.java index a28ac29dd1ab..43771481478c 100644 --- a/java/compiler/impl/src/com/intellij/compiler/backwardRefs/LanguageLightUsageConverter.java +++ b/java/compiler/impl/src/com/intellij/compiler/backwardRefs/LanguageLightUsageConverter.java @@ -16,16 +16,28 @@ package com.intellij.compiler.backwardRefs; import com.intellij.compiler.CompilerElement; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.util.containers.ContainerUtil; import com.sun.tools.javac.util.Convert; import org.jetbrains.jps.backwardRefs.ByteArrayEnumerator; import org.jetbrains.jps.backwardRefs.LightUsage; +import java.util.Set; + public interface LanguageLightUsageConverter { LanguageLightUsageConverter[] INSTANCES = new LanguageLightUsageConverter[]{new Java()}; LightUsage asLightUsage(CompilerElement element, ByteArrayEnumerator names); + FileType getFileSourceType(); + + Set> getLanguageLightUsageClasses(); + class Java implements LanguageLightUsageConverter { + private static final Set> JAVA_LIGHT_USAGE_CLASSES = + ContainerUtil.set(LightUsage.LightClassUsage.class, LightUsage.LightMethodUsage.class, LightUsage.LightFieldUsage.class); + @Override public LightUsage asLightUsage(CompilerElement element, ByteArrayEnumerator names) { if (element instanceof CompilerElement.CompilerClass) { @@ -53,6 +65,16 @@ public interface LanguageLightUsageConverter { return null; } + @Override + public FileType getFileSourceType() { + return StdFileTypes.JAVA; + } + + @Override + public Set> getLanguageLightUsageClasses() { + return JAVA_LIGHT_USAGE_CLASSES; + } + private static int id(String name, ByteArrayEnumerator names) { return names.enumerate(Convert.string2utf(name)); } diff --git a/java/java-indexing-impl/src/com/intellij/compiler/CompilerDirectInheritorSearchAdapter.java b/java/java-indexing-impl/src/com/intellij/compiler/CompilerDirectInheritorSearchAdapter.java new file mode 100644 index 000000000000..b6dc1e91438d --- /dev/null +++ b/java/java-indexing-impl/src/com/intellij/compiler/CompilerDirectInheritorSearchAdapter.java @@ -0,0 +1,36 @@ +/* + * 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.compiler; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiNamedElement; +import com.intellij.psi.search.GlobalSearchScope; +import org.jetbrains.annotations.NotNull; + +import java.util.Collection; + +public interface CompilerDirectInheritorSearchAdapter { + + /** + * @param classInternalNames - collection compiler internal name of classes (e.g. org.some.Main$1 for java anonymous class) + */ + @NotNull + T[] getCandidatesFromFile(@NotNull Collection classInternalNames, + @NotNull PsiNamedElement superClass, + @NotNull VirtualFile containingFile, + @NotNull Project project); +} diff --git a/java/java-indexing-impl/src/com/intellij/compiler/CompilerReferenceService.java b/java/java-indexing-impl/src/com/intellij/compiler/CompilerReferenceService.java index c8e6c0612fbd..e0861146f8dd 100644 --- a/java/java-indexing-impl/src/com/intellij/compiler/CompilerReferenceService.java +++ b/java/java-indexing-impl/src/com/intellij/compiler/CompilerReferenceService.java @@ -16,14 +16,18 @@ package com.intellij.compiler; import com.intellij.openapi.components.AbstractProjectComponent; +import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.registry.RegistryValue; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiNamedElement; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; + public abstract class CompilerReferenceService extends AbstractProjectComponent { public static final RegistryValue IS_ENABLED_KEY = Registry.get("bytecode.ref.index"); @@ -38,7 +42,45 @@ public abstract class CompilerReferenceService extends AbstractProjectComponent @Nullable public abstract GlobalSearchScope getScopeWithoutCodeReferences(@NotNull PsiElement element, @NotNull CompilerSearchAdapter adapter); + @Nullable + public abstract CompilerDirectInheritorInfo getDirectInheritors(@NotNull PsiNamedElement aClass, + @NotNull GlobalSearchScope useScope, + @NotNull GlobalSearchScope searchScope, + @NotNull CompilerSearchAdapter compilerSearchAdapter, + @NotNull CompilerDirectInheritorSearchAdapter inheritorSearchAdapter, + @NotNull FileType... searchFileTypes); + + public static boolean isEnabled() { return IS_ENABLED_KEY.asBoolean(); } + + public static class CompilerDirectInheritorInfo { + private final Collection myDirectInheritors; + private final Collection myDirectInheritorCandidates; + private final GlobalSearchScope myDirtyScope; + + CompilerDirectInheritorInfo(Collection directInheritors, + Collection directInheritorCandidates, + GlobalSearchScope dirtyScope) { + myDirectInheritors = directInheritors; + myDirectInheritorCandidates = directInheritorCandidates; + myDirtyScope = dirtyScope; + } + + @NotNull + public Collection getDirectInheritors() { + return myDirectInheritors; + } + + @NotNull + public Collection getDirectInheritorCandidates() { + return myDirectInheritorCandidates; + } + + @NotNull + public GlobalSearchScope getDirtyScope() { + return myDirtyScope; + } + } } diff --git a/java/java-indexing-impl/src/com/intellij/compiler/JavaCompilerDirectInheritorSearchAdapter.java b/java/java-indexing-impl/src/com/intellij/compiler/JavaCompilerDirectInheritorSearchAdapter.java new file mode 100644 index 000000000000..ce07fd4640dc --- /dev/null +++ b/java/java-indexing-impl/src/com/intellij/compiler/JavaCompilerDirectInheritorSearchAdapter.java @@ -0,0 +1,116 @@ +/* + * 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.compiler; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.impl.java.stubs.PsiClassStub; +import com.intellij.psi.impl.source.PsiFileWithStubSupport; +import com.intellij.psi.stubs.StubElement; +import com.intellij.psi.stubs.StubTree; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.SmartList; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class JavaCompilerDirectInheritorSearchAdapter implements CompilerDirectInheritorSearchAdapter { + public static final JavaCompilerDirectInheritorSearchAdapter INSTANCE = new JavaCompilerDirectInheritorSearchAdapter(); + + @NotNull + @Override + public PsiClass[] getCandidatesFromFile(@NotNull Collection classInternalNames, + @NotNull PsiNamedElement superClass, + @NotNull VirtualFile containingFile, + @NotNull Project project) { + final PsiClass[] result = new PsiClass[classInternalNames.size()]; + int i = 0; + boolean anonymousClassesAdded = false; + for (String classInternalName : classInternalNames) { + String name; + + boolean isAnonymous = isAnonymousClass(classInternalName); + if (isAnonymous) { + if (anonymousClassesAdded) { + continue; + } + anonymousClassesAdded = true; + name = ObjectUtils.notNull(superClass.getName()); + } + else { + name = StringUtil.replace(classInternalName, "$", "."); + } + for (PsiClass c : findClassByStub(containingFile, name, isAnonymous, project)) { + result[i++] = c; + } + } + return result; + } + + private static boolean isAnonymousClass(@NotNull String name) { + int lastIndex = name.lastIndexOf('$'); + return lastIndex != -1 && lastIndex < name.length() - 1 && Character.isDigit(name.charAt(lastIndex + 1)); + } + + private static Collection findClassByStub(VirtualFile file, String name, boolean isAnonymous, Project project) { + final List result = new SmartList<>(); + PsiFileWithStubSupport psiFile = ObjectUtils.notNull((PsiFileWithStubSupport)PsiManager.getInstance(project).findFile(file)); + StubTree tree = psiFile.getStubTree(); + if (tree != null) { + for (StubElement element : tree.getPlainListFromAllRoots()) { + if (element instanceof PsiClassStub) { + if (isAnonymous) { + String baseClassRef = ((PsiClassStub)element).getBaseClassReferenceText(); + if (baseClassRef != null && ((PsiClassStub)element).isAnonymous() && name.equals(PsiNameHelper.getShortClassName(baseClassRef))) { + result.add((PsiClass)element.getPsi()); + } + } else { + if (!((PsiClassStub)element).isAnonymous() && name.equals(((PsiClassStub)element).getQualifiedName())) { + result.add((PsiClass)element.getPsi()); + } + } + } + } + } else { + PsiTreeUtil.processElements(psiFile, e -> { + if (e instanceof PsiAnonymousClass) { + if (isAnonymous) { + String baseClassRefText = ((PsiAnonymousClass)e).getBaseClassReference().getText(); + if (name.equals(PsiNameHelper.getShortClassName(baseClassRefText))) { + result.add((PsiClass)e); + } + } + return true; + } + else if (e instanceof PsiClass) { + if (!isAnonymous) { + if (name.equals(((PsiClass)e).getQualifiedName())) { + result.add((PsiClass)e); + } + } + } + return true; + }); + } + return result; + } +} 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 e667349ffcff..2352b4cb9335 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 @@ -15,7 +15,11 @@ */ package com.intellij.psi.impl.search; +import com.intellij.compiler.CompilerReferenceService; +import com.intellij.compiler.JavaBaseCompilerSearchAdapter; +import com.intellij.compiler.JavaCompilerDirectInheritorSearchAdapter; import com.intellij.concurrency.JobLauncher; +import com.intellij.ide.highlighter.JavaFileType; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProgressIndicatorProvider; import com.intellij.openapi.progress.ProgressManager; @@ -43,10 +47,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.ConcurrentMap; /** @@ -58,7 +59,7 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor)baseClass::getUseScope); + SearchScope useScope = ApplicationManager.getApplication().runReadAction((Computable)baseClass::getUseScope); final Project project = PsiUtilCore.getProjectInReadAction(baseClass); if (JavaClassInheritorsSearcher.isJavaLangObject(baseClass)) { @@ -72,8 +73,34 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor anonymousCandidatesFromCompilerSearchIndex = Collections.emptyList(); SearchScope scope = parameters.getScope(); - PsiClass[] cache = getOrCalculateDirectSubClasses(project, baseClass, useScope); + if (useScope instanceof GlobalSearchScope && scope instanceof GlobalSearchScope) { + PsiClass searchClass = baseClass; + PsiElement original = baseClass.getOriginalElement(); + if (original instanceof PsiClass) { + searchClass = (PsiClass)original; + } + final CompilerReferenceService compilerReferenceService = CompilerReferenceService.getInstance(project); + final CompilerReferenceService.CompilerDirectInheritorInfo compilerDirectInheritorInfo = + compilerReferenceService.getDirectInheritors(searchClass, + (GlobalSearchScope)useScope, + (GlobalSearchScope)scope, + JavaBaseCompilerSearchAdapter.INSTANCE, + JavaCompilerDirectInheritorSearchAdapter.INSTANCE, + JavaFileType.INSTANCE); + if (compilerDirectInheritorInfo != null) { + for (PsiClass aClass : compilerDirectInheritorInfo.getDirectInheritors()) { + consumer.process(aClass); + } + // here candidates is only anonymous classes whose containing file has more than one anonymous inheritor of baseClass + anonymousCandidatesFromCompilerSearchIndex = compilerDirectInheritorInfo.getDirectInheritorCandidates(); + scope = ((GlobalSearchScope)scope).intersectWith(compilerDirectInheritorInfo.getDirtyScope()); + useScope = ((GlobalSearchScope)useScope).intersectWith(compilerDirectInheritorInfo.getDirtyScope()); + } + } + + PsiClass[] cache = getOrCalculateDirectSubClasses(project, baseClass, useScope, anonymousCandidatesFromCompilerSearchIndex); if (cache.length == 0) { return true; @@ -139,7 +166,10 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor anonymousCandidates) { ConcurrentMap map = HighlightingCaches.getInstance(project).DIRECT_SUB_CLASSES; PsiClass[] cache = map.get(baseClass); if (cache != null) { @@ -150,7 +180,7 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor)baseClass::isPhysical)) { cache = ConcurrencyUtil.cacheOrGet(map, baseClass, cache); @@ -173,7 +203,8 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor anonymousCandidatesFromCompilerIndicesSearch) { DumbService dumbService = DumbService.getInstance(project); GlobalSearchScope globalUseScope = dumbService.runReadActionInSmartMode( () -> StubHierarchyInheritorSearcher.restrictScope(GlobalSearchScopeUtil.toGlobalSearchScope(useScope, project))); @@ -228,6 +259,9 @@ public class JavaDirectInheritorsSearcher implements QueryExecutor anonymousCandidates = dumbService.runReadActionInSmartMode(() -> JavaAnonymousClassBaseRefOccurenceIndex.getInstance().get(baseClassName, project, globalUseScope)); + for (PsiClass candidate : anonymousCandidatesFromCompilerIndicesSearch) { + anonymousCandidates.add((PsiAnonymousClass)candidate); + } processConcurrentlyIfTooMany(anonymousCandidates, candidate-> { diff --git a/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Bar.java b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Bar.java new file mode 100644 index 000000000000..b0eedd43a534 --- /dev/null +++ b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Bar.java @@ -0,0 +1,15 @@ +class Bar { + + void m() { + Foo f = new Foo() { + + }; + } + + void m2() { + Foo f2 = new Foo() { + + }; + } + +} \ No newline at end of file diff --git a/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Foo.java b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Foo.java new file mode 100644 index 000000000000..c59fcc4a8339 --- /dev/null +++ b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/Foo.java @@ -0,0 +1,3 @@ +public class Foo { + +} \ No newline at end of file diff --git a/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/FooImpl.java b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/FooImpl.java new file mode 100644 index 000000000000..3fb32e15cae8 --- /dev/null +++ b/java/java-tests/testData/compiler/bytecodeReferences/testHierarchy/FooImpl.java @@ -0,0 +1,9 @@ +class FooImpl extends Foo { + + void m() { + Foo f = new Foo() { + + }; + } + +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/compiler/CompilerReferencesTest.java b/java/java-tests/testSrc/com/intellij/compiler/CompilerReferencesTest.java index 7daff8ab7220..94f513a39050 100644 --- a/java/java-tests/testSrc/com/intellij/compiler/CompilerReferencesTest.java +++ b/java/java-tests/testSrc/com/intellij/compiler/CompilerReferencesTest.java @@ -17,15 +17,20 @@ package com.intellij.compiler; import com.intellij.JavaTestUtil; import com.intellij.codeInsight.completion.AbstractCompilerAwareTest; +import com.intellij.openapi.fileTypes.StdFileTypes; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.java.LanguageLevel; +import com.intellij.psi.PsiAnonymousClass; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMember; +import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.SkipSlowTestLocally; import com.intellij.testFramework.builders.JavaModuleFixtureBuilder; import com.intellij.util.containers.ContainerUtil; +import java.util.Collection; import java.util.Set; import java.util.stream.Collectors; @@ -77,6 +82,39 @@ public class CompilerReferencesTest extends AbstractCompilerAwareTest { assertEquals(filesWithReferences, ContainerUtil.set("Bar.java", "BarRef.java")); } + public void testHierarchy() { + myFixture.configureByFiles(getName() + "/Foo.java", getName() + "/FooImpl.java", getName() + "/Bar.java"); + rebuildProject(); + CompilerReferenceService.CompilerDirectInheritorInfo directInheritorInfo = getHierarchyUnderForElementCaret(); + + Collection inheritors = directInheritorInfo.getDirectInheritors(); + assertSize(4, inheritors); + for (PsiClass inheritor : inheritors) { + if (inheritor instanceof PsiAnonymousClass) { + assertOneOf(inheritor.getTextOffset(), 58, 42, 94); + } else { + assertEquals("FooImpl", inheritor.getQualifiedName()); + } + } + + Collection candidates = directInheritorInfo.getDirectInheritorCandidates(); + assertEmpty(candidates); + } + + private CompilerReferenceService.CompilerDirectInheritorInfo getHierarchyUnderForElementCaret() { + final PsiElement atCaret = myFixture.getElementAtCaret(); + assertNotNull(atCaret); + final PsiClass classAtCaret = PsiTreeUtil.getParentOfType(atCaret, PsiClass.class, false); + assertNotNull(classAtCaret); + return CompilerReferenceService.getInstance(myFixture.getProject()).getDirectInheritors(classAtCaret, + assertInstanceOf(classAtCaret.getUseScope(), GlobalSearchScope.class), + assertInstanceOf(classAtCaret.getUseScope(), GlobalSearchScope.class), + JavaBaseCompilerSearchAdapter.INSTANCE, + JavaCompilerDirectInheritorSearchAdapter.INSTANCE, + StdFileTypes.JAVA); + + } + private Set getReferentFilesForElementUnderCaret(CompilerSearchAdapter adapter) { final PsiElement atCaret = myFixture.getElementAtCaret(); assertNotNull(atCaret);