diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/HierarchyService.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/HierarchyService.java index 3c6534b2aec7..21ec1f76302d 100644 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/HierarchyService.java +++ b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/HierarchyService.java @@ -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 myNamesCache = new SoftReference(null); + private static final TObjectHashingStrategy UNIT_HASHING_STRATEGY = new TObjectHashingStrategy() { + @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 myNamesCache = null; + private final CachedValue 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(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(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 loadUnits(boolean sourceMode, NameEnvironment names) { + Set 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); } } diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchyBuilder.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchyBuilder.java deleted file mode 100644 index 3a1344c431be..000000000000 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/SingleClassHierarchyBuilder.java +++ /dev/null @@ -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_HASHING_STRATEGY = new TObjectHashingStrategy() { - @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 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(); - } - }); - } -} diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/BuildStubsHierarchyAction.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/BuildStubsHierarchyAction.java index bea32b575905..f25328639577 100644 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/BuildStubsHierarchyAction.java +++ b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/BuildStubsHierarchyAction.java @@ -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"); } } diff --git a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/ClearSubtypesAction.java b/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/ClearSubtypesAction.java deleted file mode 100644 index 8b6b0f819815..000000000000 --- a/java/java-impl/src/com/intellij/psi/stubsHierarchy/impl/test/ClearSubtypesAction.java +++ /dev/null @@ -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(); - } - } -} diff --git a/resources/src/idea/JavaActions.xml b/resources/src/idea/JavaActions.xml index 1b94879c0a81..af75a20928a0 100644 --- a/resources/src/idea/JavaActions.xml +++ b/resources/src/idea/JavaActions.xml @@ -37,23 +37,19 @@ - + - + - + - - - - - +