use a dedicated file-based index for stub hierarchy

This commit is contained in:
peter
2016-06-10 09:04:34 +02:00
parent df50e33a11
commit d412c4d972
10 changed files with 256 additions and 108 deletions
@@ -15,39 +15,28 @@
*/
package com.intellij.psi.stubsHierarchy.impl;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.openapi.vfs.VirtualFileWithId;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.search.DelegatingGlobalSearchScope;
import com.intellij.psi.search.EverythingGlobalScope;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.stubsHierarchy.HierarchyService;
import com.intellij.psi.stubsHierarchy.stubs.Unit;
import com.intellij.psi.util.CachedValue;
import com.intellij.psi.util.CachedValueProvider;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.psi.util.PsiModificationTracker;
import com.intellij.reference.SoftReference;
import com.intellij.util.CachedValueBase;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import com.intellij.util.indexing.FileBasedIndex;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
public class HierarchyServiceImpl extends HierarchyService {
private static final TObjectHashingStrategy<Unit> UNIT_HASHING_STRATEGY = new TObjectHashingStrategy<Unit>() {
@Override
public int computeHashCode(Unit object) {
return object.myClasses[0].myClassAnchor.myFileId;
}
@Override
public boolean equals(Unit o1, Unit o2) {
return computeHashCode(o1) == computeHashCode(o2);
}
};
private static final SingleClassHierarchy EMPTY_HIERARCHY = new SingleClassHierarchy(Symbol.ClassSymbol.EMPTY_ARRAY);
private final Project myProject;
private final ProjectFileIndex myFileIndex;
@@ -99,32 +88,35 @@ public class HierarchyServiceImpl extends HierarchyService {
private void loadSymbols(NameEnvironment names, Symbols symbols) {
StubEnter stubEnter = new StubEnter(names, symbols);
loadUnits(false, names).forEach(stubEnter::unitEnter);
loadUnits(false, names, stubEnter);
stubEnter.connect1();
loadUnits(true, names).forEach(stubEnter::unitEnter);
loadUnits(true, names, stubEnter);
stubEnter.connect2();
}
private Set<Unit> loadUnits(boolean sourceMode, NameEnvironment names) {
Set<Unit> result = new THashSet<>(UNIT_HASHING_STRATEGY);
StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.UNITS, myProject, unit -> {
Unit compact = shouldProcess(sourceMode, unit.myFileId) ? Translator.translate(names, unit) : null;
if (compact != null && compact.myClasses.length > 0) {
result.remove(compact); // there can be several (outdated) stub keys for the same file id, only the last one counts
result.add(compact);
private void loadUnits(boolean sourceMode, NameEnvironment names, StubEnter stubEnter) {
GlobalSearchScope scope = new DelegatingGlobalSearchScope(new EverythingGlobalScope(myProject)) {
@Override
public boolean contains(@NotNull VirtualFile file) {
return sourceMode ? myFileIndex.isInSourceContent(file) : myFileIndex.isInLibraryClasses(file);
}
return true;
});
return result;
}
};
ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
FileBasedIndex index = FileBasedIndex.getInstance();
for (String packageName : index.getAllKeys(StubHierarchyIndex.INDEX_ID, myProject)) {
QualifiedName pkg = StringUtil.isEmpty(packageName) ? null : names.fromString(packageName, true);
index.processValues(StubHierarchyIndex.INDEX_ID, packageName, null, new FileBasedIndex.ValueProcessor<IndexTree.Unit>() {
int count = 0;
private boolean shouldProcess(boolean sourceMode, final int fileId) {
VirtualFile file = PersistentFS.getInstance().findFileById(fileId);
if (file == null) {
return false;
@Override
public boolean process(VirtualFile file, IndexTree.Unit unit) {
if (indicator != null && ++count % 128 == 0) indicator.checkCanceled();
stubEnter.unitEnter(Translator.internNames(names, unit, ((VirtualFileWithId)file).getId(), pkg));
return true;
}
}, scope);
}
return sourceMode ? myFileIndex.isInSourceContent(file) : myFileIndex.isInLibraryClasses(file);
}
}
@@ -0,0 +1,190 @@
/*
* 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.stubsHierarchy.impl;
import com.intellij.ide.highlighter.JavaClassFileType;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiNameHelper;
import com.intellij.psi.PsiReferenceList;
import com.intellij.psi.impl.cache.ModifierFlags;
import com.intellij.psi.impl.java.stubs.*;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree.*;
import com.intellij.psi.impl.java.stubs.impl.PsiClassStubImpl;
import com.intellij.psi.stubs.Stub;
import com.intellij.psi.stubs.StubElement;
import com.intellij.psi.stubs.StubTree;
import com.intellij.psi.stubs.StubTreeBuilder;
import com.intellij.psi.stubsHierarchy.StubHierarchyIndexer;
import com.intellij.util.ArrayUtil;
import com.intellij.util.indexing.FileContent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class JavaStubIndexer extends StubHierarchyIndexer {
@Override
public int getVersion() {
return 0;
}
@Override
public boolean handlesFile(@NotNull VirtualFile file) {
FileType fileType = file.getFileType();
return fileType == JavaFileType.INSTANCE || fileType == JavaClassFileType.INSTANCE;
}
@Nullable
@Override
public List<Pair<String, Unit>> indexFile(@NotNull FileContent content) {
Stub stubTree = StubTreeBuilder.buildStubTree(content);
if (!(stubTree instanceof PsiJavaFileStub)) return null;
PsiJavaFileStub javaFileStub = (PsiJavaFileStub)stubTree;
new StubTree(javaFileStub, false);
ArrayList<ClassDecl> classList = new ArrayList<ClassDecl>();
Set<String> usedNames = new HashSet<String>();
for (StubElement<?> el : javaFileStub.getChildrenStubs()) {
if (el instanceof PsiClassStubImpl) {
ClassDecl classDecl = processClassDecl((PsiClassStubImpl<?>)el, usedNames);
if (classDecl != null) {
classList.add(classDecl);
}
}
}
ArrayList<Import> importList = new ArrayList<Import>();
for (StubElement<?> el : javaFileStub.getChildrenStubs()) {
if (el instanceof PsiImportListStub) {
processImport((PsiImportListStub) el, importList, usedNames);
}
}
ClassDecl[] classes = classList.isEmpty() ? ClassDecl.EMPTY_ARRAY : classList.toArray(new ClassDecl[classList.size()]);
Import[] imports = importList.isEmpty() ? Import.EMPTY_ARRAY : importList.toArray(new Import[importList.size()]);
byte type = javaFileStub.isCompiled() ? IndexTree.BYTECODE : IndexTree.JAVA;
return Collections.singletonList(Pair.create(javaFileStub.getPackageName(), new Unit(type, imports, classes)));
}
@Nullable
private static Decl processMember(StubElement<?> el, Set<String> namesCache) {
if (el instanceof PsiClassStubImpl) {
PsiClassStubImpl classStub = (PsiClassStubImpl)el;
if (!classStub.isAnonymousInQualifiedNew()) {
return processClassDecl(classStub, namesCache);
}
}
ArrayList<Decl> innerList = new ArrayList<Decl>();
for (StubElement childElement : el.getChildrenStubs()) {
Decl innerDef = processMember(childElement, namesCache);
if (innerDef != null) {
innerList.add(innerDef);
}
}
return innerList.isEmpty() ? null : new MemberDecl(innerList.toArray(new Decl[innerList.size()]));
}
@Nullable
private static ClassDecl processClassDecl(PsiClassStubImpl<?> classStub, Set<String> namesCache) {
ArrayList<String> superList = new ArrayList<String>();
ArrayList<Decl> innerList = new ArrayList<Decl>();
int accessModifiers = 0;
if (classStub.isAnonymous()) {
if (classStub.getBaseClassReferenceText() != null) {
superList.add(id(classStub.getBaseClassReferenceText(), true, namesCache));
}
}
for (StubElement el : classStub.getChildrenStubs()) {
if (el instanceof PsiClassReferenceListStub) {
PsiClassReferenceListStub refList = (PsiClassReferenceListStub)el;
if (refList.getRole() == PsiReferenceList.Role.EXTENDS_LIST) {
String[] extendNames = refList.getReferencedNames();
for (String extName : extendNames) {
superList.add(id(extName, true, namesCache));
}
}
if (refList.getRole() == PsiReferenceList.Role.IMPLEMENTS_LIST) {
String[] implementNames = refList.getReferencedNames();
for (String impName : implementNames) {
superList.add(id(impName, true, namesCache));
}
}
}
if (el instanceof PsiModifierListStub) {
accessModifiers = ((PsiModifierListStub)el).getModifiersMask();
}
Decl member = processMember(el, namesCache);
if (member != null) {
innerList.add(member);
}
}
int flags = translateFlags(classStub, accessModifiers);
String[] supers = superList.isEmpty() ? ArrayUtil.EMPTY_STRING_ARRAY : ArrayUtil.toStringArray(superList);
Decl[] inners = innerList.isEmpty() ? Decl.EMPTY_ARRAY : innerList.toArray(new Decl[innerList.size()]);
return new ClassDecl(classStub.id, flags, classStub.getName(), supers, inners);
}
private static int translateFlags(PsiClassStubImpl<?> classStub, int accessModifiers) {
int flags = 0;
if (classStub.isInterface()) {
flags |= IndexTree.INTERFACE;
}
if (classStub.isEnum()) {
flags |= IndexTree.ENUM;
}
if (classStub.isAnnotationType()) {
flags |= IndexTree.ANNOTATION;
}
if (ModifierFlags.hasModifierProperty(PsiModifier.STATIC, accessModifiers)) {
flags |= IndexTree.STATIC;
}
return flags;
}
private static void processImport(PsiImportListStub el, List<Import> imports, Set<String> namesCache) {
for (StubElement<?> importElem : el.getChildrenStubs()) {
PsiImportStatementStub imp = (PsiImportStatementStub)importElem;
String importReferenceText = imp.getImportReferenceText();
if (importReferenceText != null) {
String fullName = PsiNameHelper.getQualifiedClassName(importReferenceText, true);
if (imp.isOnDemand() || namesCache.contains(shortName(fullName))) {
imports.add(new Import(fullName, imp.isStatic(), imp.isOnDemand(), null));
}
}
}
}
private static String id(String s, boolean cacheFirstId, Set<String> namesCache) {
String id = PsiNameHelper.getQualifiedClassName(s, true);
if (cacheFirstId) {
int index = id.indexOf('.');
String firstId = index > 0 ? s.substring(0, index) : id;
namesCache.add(firstId);
}
return id;
}
private static String shortName(String s) {
int dotIndex = s.lastIndexOf('.');
return dotIndex > 0 ? s.substring(dotIndex + 1) : null;
}
}
@@ -0,0 +1,92 @@
/*
* 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.stubsHierarchy.impl;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree;
import com.intellij.psi.impl.java.stubs.index.JavaUnitDescriptor;
import com.intellij.psi.stubsHierarchy.StubHierarchyIndexer;
import com.intellij.util.indexing.*;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.EnumeratorStringDescriptor;
import com.intellij.util.io.KeyDescriptor;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* @author peter
*/
public class StubHierarchyIndex extends FileBasedIndexExtension<String, IndexTree.Unit> implements PsiDependentIndex {
static final ID<String, IndexTree.Unit> INDEX_ID = ID.create("jvm.hierarchy");
private static final StubHierarchyIndexer[] ourIndexers = StubHierarchyIndexer.EP_NAME.getExtensions();
@NotNull
@Override
public ID<String, IndexTree.Unit> getName() {
return INDEX_ID;
}
@NotNull
@Override
public DataIndexer<String, IndexTree.Unit, FileContent> getIndexer() {
return inputData -> {
for (StubHierarchyIndexer indexer : ourIndexers) {
List<Pair<String, IndexTree.Unit>> pairs = indexer.handlesFile(inputData.getFile()) ? indexer.indexFile(inputData) : null;
if (pairs != null && !pairs.isEmpty()) {
Map<String, IndexTree.Unit> answer = new HashMap<>();
for (Pair<String, IndexTree.Unit> entry : pairs) {
if (entry.second.myDecls.length > 0) {
answer.put(StringUtil.notNullize(entry.first), entry.second);
}
}
return answer;
}
}
return Collections.emptyMap();
};
}
@NotNull
@Override
public KeyDescriptor<String> getKeyDescriptor() {
return EnumeratorStringDescriptor.INSTANCE;
}
@NotNull
@Override
public DataExternalizer<IndexTree.Unit> getValueExternalizer() {
return JavaUnitDescriptor.INSTANCE;
}
@Override
public int getVersion() {
return IndexTree.STUB_HIERARCHY_ENABLED ? 1 + Arrays.stream(ourIndexers).mapToInt(StubHierarchyIndexer::getVersion).sum() : 0;
}
@NotNull
@Override
public FileBasedIndex.InputFilter getInputFilter() {
return file -> IndexTree.STUB_HIERARCHY_ENABLED && Arrays.stream(ourIndexers).anyMatch(indexer -> indexer.handlesFile(file));
}
@Override
public boolean dependsOnFileContent() {
return true;
}
}
@@ -21,7 +21,7 @@ import com.intellij.psi.PsiNameHelper;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree;
import com.intellij.psi.stubsHierarchy.stubs.*;
import com.intellij.util.BitUtil;
import gnu.trove.TLongArrayList;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
@@ -75,24 +75,19 @@ public class Translator {
return Import.EMPTY_ARRAY;
}
public static Unit translate(NameEnvironment nameEnvironment, IndexTree.Unit unit) {
if (unit.myDecls.length == 0) {
return null;
}
QualifiedName pid = StringUtil.isEmpty(unit.myPackageId) ? null : nameEnvironment.fromString(unit.myPackageId, true);
ArrayList<ClassDeclaration> classesList = new ArrayList<ClassDeclaration>();
for (IndexTree.ClassDecl def : unit.myDecls) {
ClassDeclaration classDecl = processClassDecl(nameEnvironment, unit.myFileId, def);
classesList.add(classDecl);
}
TLongArrayList importList = new TLongArrayList();
for (IndexTree.Import anImport : unit.imports) {
importList.add(processImport(nameEnvironment, anImport));
@NotNull
public static Unit internNames(NameEnvironment nameEnvironment, IndexTree.Unit unit, int fileId, QualifiedName pkg) {
ClassDeclaration[] classes = new ClassDeclaration[unit.myDecls.length];
for (int i = 0; i < unit.myDecls.length; i++) {
classes[i] = processClassDecl(nameEnvironment, fileId, unit.myDecls[i]);
}
long[] imports = importList.isEmpty() ? Import.EMPTY_ARRAY : importList.toNativeArray();
ClassDeclaration[] classes = classesList.toArray(new ClassDeclaration[classesList.size()]);
return new Unit(pid, imports, classes, unit.myUnitType);
long[] imports = unit.imports.length == 0 ? Import.EMPTY_ARRAY : new long[unit.imports.length];
for (int i = 0; i < unit.imports.length; i++) {
imports[i] = processImport(nameEnvironment, unit.imports[i]);
}
return new Unit(pkg, imports, classes, unit.myUnitType);
}
private static ClassDeclaration processClassDecl(NameEnvironment nameEnvironment, int fileId, IndexTree.ClassDecl def) {