build stub hierarchy on demand, drop cache on java structure change

This commit is contained in:
peter
2016-06-09 09:12:51 +02:00
parent f31f390cae
commit b288be92fe
5 changed files with 105 additions and 219 deletions
@@ -17,63 +17,109 @@ package com.intellij.psi.stubsHierarchy.impl;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.impl.java.stubs.hierarchy.IndexTree;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.stubs.StubIndex;
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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Set;
public class HierarchyService {
private NameEnvironment myNameEnvironment;
private SoftReference<NameEnvironment> myNamesCache = new SoftReference<NameEnvironment>(null);
private static final TObjectHashingStrategy<Unit> UNIT_HASHING_STRATEGY = new TObjectHashingStrategy<Unit>() {
@Override
public int computeHashCode(Unit object) {
return object.myClasses[0].myClassAnchor.myFileId;
}
private SingleClassHierarchy mySingleClassHierarchy;
private Symbols mySymbols;
private StubEnter myStubEnter;
@Override
public boolean equals(Unit o1, Unit o2) {
return computeHashCode(o1) == computeHashCode(o2);
}
};
private final Project myProject;
private final ProjectFileIndex myFileIndex;
public static HierarchyService instance(Project project) {
private volatile SoftReference<NameEnvironment> myNamesCache = null;
private final CachedValue<SingleClassHierarchy> myHierarchy ;
public static HierarchyService instance(@NotNull Project project) {
return ServiceManager.getService(project, HierarchyService.class);
}
public HierarchyService() {
clear();
}
void processUnit(@NotNull Unit compUnit) {
myStubEnter.unitEnter(compUnit);
}
@Nullable Unit compact(@NotNull IndexTree.Unit unit) {
return Translator.translate(myNameEnvironment, unit);
}
public void connect1() {
myStubEnter.connect1();
}
public void complete2() {
myStubEnter.connect2();
myStubEnter = null;
}
public void connectSubtypes() {
this.mySingleClassHierarchy = mySymbols.createHierarchy();
mySymbols = null;
public HierarchyService(Project project) {
myProject = project;
myFileIndex = ProjectFileIndex.SERVICE.getInstance(project);
myHierarchy = CachedValuesManager.getManager(project).createCachedValue(
() -> CachedValueProvider.Result.create(buildHierarchy(), PsiModificationTracker.JAVA_STRUCTURE_MODIFICATION_COUNT),
false);
}
public SingleClassHierarchy getSingleClassHierarchy() {
return mySingleClassHierarchy;
synchronized (myHierarchy) { //the calculation is memory-intensive, don't allow multiple threads to do it
return myHierarchy.getValue();
}
}
public void clear() {
myNameEnvironment = myNamesCache.get();
if (myNameEnvironment == null) {
myNameEnvironment = new NameEnvironment();
myNamesCache = new SoftReference<NameEnvironment>(myNameEnvironment);
public void clearHierarchy() {
((CachedValueBase)myHierarchy).clear();
}
private SingleClassHierarchy buildHierarchy() {
NameEnvironment names = obtainNames();
Symbols symbols = new Symbols(names);
loadSymbols(names, symbols);
return symbols.createHierarchy();
}
@NotNull
private NameEnvironment obtainNames() {
NameEnvironment names = SoftReference.dereference(myNamesCache);
if (names == null) {
myNamesCache = new SoftReference<NameEnvironment>(names = new NameEnvironment());
}
mySymbols = new Symbols(myNameEnvironment);
myStubEnter = new StubEnter(myNameEnvironment, mySymbols);
mySingleClassHierarchy = null;
return names;
}
private void loadSymbols(NameEnvironment names, Symbols symbols) {
StubEnter stubEnter = new StubEnter(names, symbols);
loadUnits(false, names).forEach(stubEnter::unitEnter);
stubEnter.connect1();
loadUnits(true, names).forEach(stubEnter::unitEnter);
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);
}
return true;
});
return result;
}
private boolean shouldProcess(boolean sourceMode, final int fileId) {
VirtualFile file = PersistentFS.getInstance().findFileById(fileId);
if (file == null) {
return false;
}
return sourceMode ? myFileIndex.isInSourceContent(file) : myFileIndex.isInLibraryClasses(file);
}
}
@@ -1,135 +0,0 @@
/*
* 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.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.ui.MessageDialogBuilder;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
import com.intellij.psi.impl.java.stubs.index.JavaStubIndexKeys;
import com.intellij.psi.stubs.StubIndex;
import com.intellij.psi.stubsHierarchy.stubs.Unit;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.NotNull;
import java.util.Set;
public class SingleClassHierarchyBuilder {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubsHierarchy.HierarchyBuilder");
private static final boolean TEST_MEMORY_USAGE = false;
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);
}
};
public static void build(@NotNull Project project) {
ProgressManager progress = ProgressManager.getInstance();
progress.runProcessWithProgressSynchronously(() -> ReadAction.run(() -> new BuildSingleClassHierarchy(project).run()),
"Building Hierarchy", false, project);
}
private static class BuildSingleClassHierarchy implements Runnable {
Project myProject;
private ProjectFileIndex myFileIndex;
BuildSingleClassHierarchy(Project project) {
myProject = project;
myFileIndex = ProjectFileIndex.SERVICE.getInstance(myProject);
}
@Override
public void run() {
HierarchyService service = HierarchyService.instance(myProject);
service.clear();
LOG.info("BuildHierarchy started");
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
// 1. entering classes
LOG.info("read classes start");
processUnits(service, false);
indicator.setFraction(0.3);
testMemory("0");
// 2. completing classes
LOG.info("complete classes start");
service.connect1();
indicator.setFraction(0.4);
testMemory("1");
// 3. reading sources
LOG.info("read sources start");
processUnits(service, true);
indicator.setFraction(0.7);
// 4. completing sources
service.complete2();
LOG.info("Complete end");
testMemory("2");
indicator.setFraction(0.9);
// 5. connect subtypes
service.connectSubtypes();
LOG.info("Subtypes connected");
}
private void processUnits(HierarchyService service, final boolean sourceMode) {
Set<Unit> result = new THashSet<>(UNIT_HASHING_STRATEGY);
StubIndex.getInstance().processAllKeys(JavaStubIndexKeys.UNITS, myProject, unit -> {
Unit compact = shouldProcess(sourceMode, unit.myFileId) ? service.compact(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);
}
return true;
});
result.forEach(service::processUnit);
}
private boolean shouldProcess(boolean sourceMode, final int fileId) {
VirtualFile file = PersistentFS.getInstance().findFileById(fileId);
if (file == null) {
return false;
}
return sourceMode ? myFileIndex.isInSourceContent(file) : myFileIndex.isInLibraryClasses(file);
}
}
private static void testMemory(final String msg) {
if (!TEST_MEMORY_USAGE)
return;
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
MessageDialogBuilder.yesNo(msg, msg).show();
}
});
}
}
@@ -17,15 +17,25 @@ package com.intellij.psi.stubsHierarchy.impl.test;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.psi.stubsHierarchy.impl.SingleClassHierarchyBuilder;
import com.intellij.psi.stubsHierarchy.impl.HierarchyService;
public class BuildStubsHierarchyAction extends InheritanceAction {
private static final Logger LOG = Logger.getInstance("#com.intellij.psi.stubsHierarchy.impl.test.BuildStubsHierarchyAction");
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
if (project != null) {
SingleClassHierarchyBuilder.build(project);
}
if (project == null) return;
HierarchyService service = HierarchyService.instance(project);
service.clearHierarchy();
long start = System.currentTimeMillis();
ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ReadAction.run(service::getSingleClassHierarchy),
"Building Hierarchy", false, project);
LOG.info("Building stub hierarchy took " + (System.currentTimeMillis() - start) + " ms");
}
}
@@ -1,31 +0,0 @@
/*
* Copyright 2000-2015 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.test;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.project.Project;
import com.intellij.psi.stubsHierarchy.impl.HierarchyService;
public class ClearSubtypesAction extends InheritanceAction {
@Override
public void actionPerformed(AnActionEvent e) {
final Project project = e.getData(CommonDataKeys.PROJECT);
if (project != null) {
HierarchyService.instance(project).clear();
}
}
}