mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-17 21:43:33 +07:00
build stub hierarchy on demand, drop cache on java structure change
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
-135
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+14
-4
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user