diff --git a/java/java-impl/src/com/intellij/codeInspection/deadCode/DeadHTMLComposer.java b/java/java-impl/src/com/intellij/codeInspection/deadCode/DeadHTMLComposer.java index d93fecc6d971..a356ac1d1dc3 100644 --- a/java/java-impl/src/com/intellij/codeInspection/deadCode/DeadHTMLComposer.java +++ b/java/java-impl/src/com/intellij/codeInspection/deadCode/DeadHTMLComposer.java @@ -22,14 +22,11 @@ import com.intellij.codeInspection.ex.DescriptorComposer; import com.intellij.codeInspection.ex.HTMLComposerImpl; import com.intellij.codeInspection.reference.*; import com.intellij.codeInspection.ui.InspectionToolPresentation; -import com.intellij.codeInspection.ui.InspectionTreeNode; -import com.intellij.codeInspection.ui.RefElementNode; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import javax.swing.tree.TreeNode; +import java.util.Collections; import java.util.HashSet; -import java.util.Iterator; import java.util.Set; public class DeadHTMLComposer extends HTMLComposerImpl { @@ -332,7 +329,7 @@ public class DeadHTMLComposer extends HTMLComposerImpl { } private void appendCallesList(RefElement element, StringBuffer buf, Set mentionedElements, boolean appendCallees){ - final Set possibleChildren = getPossibleChildren(new RefElementNode(element, myToolPresentation), element); + final Set possibleChildren = getPossibleChildren(element); if (!possibleChildren.isEmpty()) { if (appendCallees){ appendHeading(buf, InspectionsBundle.message("inspection.export.results.callees")); @@ -359,16 +356,13 @@ public class DeadHTMLComposer extends HTMLComposerImpl { } } - public static Set getPossibleChildren(final RefElementNode refElementNode, RefElement refElement) { - final TreeNode[] pathToRoot = refElementNode.getPath(); + private static Set getPossibleChildren(RefElement refElement) { + if (!refElement.isValid()) return Collections.emptySet(); final HashSet newChildren = new HashSet<>(); - - if (!refElement.isValid()) return newChildren; - for (RefElement refCallee : refElement.getOutReferences()) { if (((RefElementImpl)refCallee).isSuspicious()) { - if (notInPath(pathToRoot, refCallee)) newChildren.add(refCallee); + newChildren.add(refCallee); } } @@ -379,7 +373,7 @@ public class DeadHTMLComposer extends HTMLComposerImpl { if (!refMethod.isStatic() && !refMethod.isConstructor() && (aClass != null && !aClass.isAnonymous())) { for (RefMethod refDerived : refMethod.getDerivedMethods()) { if (((RefMethodImpl)refDerived).isSuspicious()) { - if (notInPath(pathToRoot, refDerived)) newChildren.add(refDerived); + newChildren.add(refDerived); } } } @@ -387,25 +381,16 @@ public class DeadHTMLComposer extends HTMLComposerImpl { RefClass refClass = (RefClass) refElement; for (RefClass subClass : refClass.getSubClasses()) { if ((subClass.isInterface() || subClass.isAbstract()) && ((RefClassImpl)subClass).isSuspicious()) { - if (notInPath(pathToRoot, subClass)) newChildren.add(subClass); + newChildren.add(subClass); } } if (refClass.getDefaultConstructor() instanceof RefImplicitConstructor) { - Set fromConstructor = getPossibleChildren(refElementNode, refClass.getDefaultConstructor()); + Set fromConstructor = getPossibleChildren(refClass.getDefaultConstructor()); newChildren.addAll(fromConstructor); } } return newChildren; } - - private static boolean notInPath(TreeNode[] pathToRoot, RefElement refChild) { - for (TreeNode aPathToRoot : pathToRoot) { - InspectionTreeNode node = (InspectionTreeNode)aPathToRoot; - if (node instanceof RefElementNode && ((RefElementNode)node).getElement() == refChild) return false; - } - - return true; - } } diff --git a/java/java-impl/src/com/intellij/codeInspection/deadCode/DummyEntryPointsPresentation.java b/java/java-impl/src/com/intellij/codeInspection/deadCode/DummyEntryPointsPresentation.java index f5d6ea9e1867..3788ab454dfd 100644 --- a/java/java-impl/src/com/intellij/codeInspection/deadCode/DummyEntryPointsPresentation.java +++ b/java/java-impl/src/com/intellij/codeInspection/deadCode/DummyEntryPointsPresentation.java @@ -9,7 +9,6 @@ import com.intellij.codeInspection.ex.*; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.reference.RefJavaElement; -import com.intellij.codeInspection.ui.InspectionNode; import com.intellij.codeInspection.ui.InspectionResultsView; import com.intellij.codeInspection.ui.InspectionTreeNode; import com.intellij.codeInspection.util.RefFilter; @@ -40,7 +39,7 @@ public class DummyEntryPointsPresentation extends UnusedDeclarationPresentation @Override protected String getSeverityDelegateName() { - return UnusedDeclarationInspection.SHORT_NAME; + return UnusedDeclarationInspectionBase.SHORT_NAME; } private class MoveEntriesToSuspicious extends QuickFixAction { @@ -84,12 +83,10 @@ public class DummyEntryPointsPresentation extends UnusedDeclarationPresentation } @Override - public void createToolNode(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionNode node, - @NotNull InspectionRVContentProvider provider, - @NotNull InspectionTreeNode parentNode, - boolean showStructure, - boolean groupByStructure) { - myToolNode = node; + public void patchToolNode(@NotNull InspectionTreeNode node, + @NotNull InspectionRVContentProvider provider, + boolean showStructure, + boolean groupByStructure) { } @Override diff --git a/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationPresentation.java b/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationPresentation.java index e3bc90342213..e25ff7b879da 100644 --- a/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationPresentation.java +++ b/java/java-impl/src/com/intellij/codeInspection/deadCode/UnusedDeclarationPresentation.java @@ -23,6 +23,7 @@ import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.AtomicNotNullLazyValue; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VfsUtil; @@ -73,6 +74,16 @@ public class UnusedDeclarationPresentation extends DefaultInspectionToolPresenta private WeakUnreferencedFilter myFilter; private DeadHTMLComposer myComposer; + private final AtomicNotNullLazyValue myDummyWrapper = new AtomicNotNullLazyValue() { + @NotNull + @Override + protected InspectionToolWrapper compute() { + InspectionToolWrapper toolWrapper = new GlobalInspectionToolWrapper(new DummyEntryPointsEP()); + toolWrapper.initialize(myContext); + return toolWrapper; + } + }; + @NonNls private static final String DELETE = "delete"; @NonNls private static final String COMMENT = "comment"; @@ -410,24 +421,21 @@ public class UnusedDeclarationPresentation extends DefaultInspectionToolPresenta } @Override - public void createToolNode(@NotNull GlobalInspectionContextImpl context, - @NotNull InspectionNode node, - @NotNull InspectionRVContentProvider provider, - @NotNull InspectionTreeNode parentNode, - boolean showStructure, - boolean groupByStructure) { - final EntryPointsNode entryPointsNode = new EntryPointsNode(context); - InspectionToolWrapper dummyToolWrapper = entryPointsNode.getToolWrapper(); - InspectionToolPresentation presentation = context.getPresentation(dummyToolWrapper); + public void patchToolNode(@NotNull InspectionTreeNode node, + @NotNull InspectionRVContentProvider provider, + boolean showStructure, + boolean groupByStructure) { + InspectionTreeModel model = myContext.getView().getTree().getInspectionTreeModel(); + model.createCustomNode(myDummyWrapper.getValue(), () -> new EntryPointsNode(myDummyWrapper.getValue(), myContext, model), node); + InspectionToolPresentation presentation = myContext.getPresentation(myDummyWrapper.getValue()); presentation.updateContent(); - provider.appendToolNodeContent(context, entryPointsNode, node, showStructure, groupByStructure); - myToolNode = entryPointsNode; + provider.appendToolNodeContent(myContext, myDummyWrapper.getValue(), node, showStructure, groupByStructure); } @NotNull @Override - public RefElementNode createRefNode(@Nullable RefEntity entity) { - return new RefElementNode(entity, this) { + public RefElementNode createRefNode(@Nullable RefEntity entity, @NotNull InspectionTreeModel model) { + return new RefElementNode(entity, this, model) { @Nullable @Override public String getTailText() { @@ -677,7 +685,7 @@ public class UnusedDeclarationPresentation extends DefaultInspectionToolPresenta } @Override - public int getProblemsCount(@NotNull InspectionTree tree) { - return 0; + public boolean showProblemCount() { + return false; } } diff --git a/java/java-impl/src/com/intellij/codeInspection/ui/EntryPointsNode.java b/java/java-impl/src/com/intellij/codeInspection/ui/EntryPointsNode.java index 5d895003035b..71671a88d998 100644 --- a/java/java-impl/src/com/intellij/codeInspection/ui/EntryPointsNode.java +++ b/java/java-impl/src/com/intellij/codeInspection/ui/EntryPointsNode.java @@ -2,9 +2,7 @@ package com.intellij.codeInspection.ui; import com.intellij.codeHighlighting.HighlightDisplayLevel; -import com.intellij.codeInspection.deadCode.DummyEntryPointsEP; import com.intellij.codeInspection.ex.GlobalInspectionContextImpl; -import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper; import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.icons.AllIcons; import gnu.trove.TObjectIntHashMap; @@ -13,18 +11,13 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; -/** - * @author max - */ public class EntryPointsNode extends InspectionNode { - public EntryPointsNode(@NotNull GlobalInspectionContextImpl context) { - super(createDummyWrapper(context), context.getCurrentProfile()); - } + private volatile boolean myExcluded; - private static InspectionToolWrapper createDummyWrapper(@NotNull GlobalInspectionContextImpl context) { - InspectionToolWrapper toolWrapper = new GlobalInspectionToolWrapper(new DummyEntryPointsEP()); - toolWrapper.initialize(context); - return toolWrapper; + public EntryPointsNode(@NotNull InspectionToolWrapper dummyWrapper, + @NotNull GlobalInspectionContextImpl context, + @NotNull InspectionTreeModel model) { + super(dummyWrapper, context.getCurrentProfile(), model); } @Override @@ -44,7 +37,17 @@ public class EntryPointsNode extends InspectionNode { } @Override - public int getProblemCount(boolean allowSuppressed) { - return 0; + public boolean isExcluded() { + return myExcluded; + } + + @Override + public void excludeElement() { + myExcluded = true; + } + + @Override + public void amnestyElement() { + myExcluded = false; } } diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/OfflineInspectionResultViewTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/OfflineInspectionResultViewTest.java index 4e3a3ca78877..f4a4249350eb 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/OfflineInspectionResultViewTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/OfflineInspectionResultViewTest.java @@ -13,8 +13,8 @@ import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.codeInspection.offline.OfflineProblemDescriptor; import com.intellij.codeInspection.offlineViewer.OfflineViewParseUtil; import com.intellij.codeInspection.ui.InspectionResultsView; +import com.intellij.codeInspection.ui.InspectionRootNode; import com.intellij.codeInspection.ui.InspectionTree; -import com.intellij.codeInspection.ui.InspectionTreeNode; import com.intellij.codeInspection.ui.ProblemDescriptionNode; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.PathManagerEx; @@ -129,11 +129,13 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { InspectionTree tree = updateTree(); PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); TreeUtil.expandAll(tree); - PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" + + PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); + PlatformTestUtil.assertTreeEqual(tree, "-InspectionViewTree\n" + " -Java\n" + " -Declaration redundancy\n" + - " -unused\n" + - " -Module: 'testOfflineWithInvalid'\n" + + " -Unused declaration\n" + + " Entry Points\n" + + " -testOfflineWithInvalid\n" + " -\n" + " -Test\n" + " -f()\n" + @@ -141,26 +143,26 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { " -b()\n" + " -anonymous (Runnable)\n" + " -run()\n" + - " Variable i is never used.\n" + - " Variable r is never used.\n" + + " Variable 'i' is never used.\n" + + " Variable 'r' is never used.\n" + " -ff()\n" + - " Variable a is never used.\n" + - " Variable d is never used.\n" + + " Variable 'a' is never used.\n" + + " Variable 'd' is never used.\n" + " -foo()\n" + - " Variable j is never used.\n" + + " Variable 'j' is never used.\n" + " -main(String[])\n" + - " Variable test is never used.\n" + + " Variable 'test' is never used.\n" + " -Probable bugs\n" + - " -EqualsWithItself\n" + - " -Module: 'testOfflineWithInvalid'\n" + + " -'equals()' called on itself\n" + + " -testOfflineWithInvalid\n" + " -\n" + " -Test\n" + " -m()\n" + " 'equals()' called on itself\n" + - " -null\n" + + " -element no longer exists\n" + " '()' called on itself\n" ); - tree.setSelectionRow(28); + tree.setSelectionRow(29); final ProblemDescriptionNode node = (ProblemDescriptionNode)tree.getSelectionModel().getSelectionPath().getLastPathComponent(); assertFalse(node.isValid()); } @@ -170,11 +172,13 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { InspectionTree tree = updateTree(); PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); TreeUtil.expandAll(tree); - PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" + + PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); + PlatformTestUtil.assertTreeEqual(tree, "-InspectionViewTree\n" + " -Java\n" + " -Declaration redundancy\n" + - " -unused\n" + - " -Module: 'testOfflineView'\n" + + " -Unused declaration\n" + + " Entry Points\n" + + " -testOfflineView\n" + " -\n" + " -Test\n" + " -f()\n" + @@ -182,18 +186,18 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { " -b()\n" + " -anonymous (Runnable)\n" + " -run()\n" + - " Variable i is never used.\n" + - " Variable r is never used.\n" + + " Variable 'i' is never used.\n" + + " Variable 'r' is never used.\n" + " -ff()\n" + - " Variable a is never used.\n" + - " Variable d is never used.\n" + + " Variable 'a' is never used.\n" + + " Variable 'd' is never used.\n" + " -foo()\n" + - " Variable j is never used.\n" + + " Variable 'j' is never used.\n" + " -main(String[])\n" + - " Variable test is never used.\n" + + " Variable 'test' is never used.\n" + " -Probable bugs\n" + - " -" + myDataFlowToolWrapper + "\n" + - " -Module: 'testOfflineView'\n" + + " -'equals()' called on itself\n" + + " -testOfflineView\n" + " -\n" + " -Test\n" + " -m()\n" + @@ -206,62 +210,64 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { tree = updateTree(); PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); TreeUtil.expandAll(tree); - PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" + + PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); + PlatformTestUtil.assertTreeEqual(tree, "-InspectionViewTree\n" + " -Java\n" + " -Declaration redundancy\n" + - " -unused\n" + + " -Unused declaration\n" + + " Entry Points\n" + " -Test\n" + " -f()\n" + " -D\n" + " -b()\n" + " -anonymous (Runnable)\n" + " -run()\n" + - " Variable i is never used.\n" + - " Variable r is never used.\n" + + " Variable 'i' is never used.\n" + + " Variable 'r' is never used.\n" + " -ff()\n" + - " Variable a is never used.\n" + - " Variable d is never used.\n" + + " Variable 'a' is never used.\n" + + " Variable 'd' is never used.\n" + " -foo()\n" + - " Variable j is never used.\n" + + " Variable 'j' is never used.\n" + " -main(String[])\n" + - " Variable test is never used.\n" + + " Variable 'test' is never used.\n" + " -Probable bugs\n" + - " -EqualsWithItself\n" + + " -'equals()' called on itself\n" + " -Test\n" + " 'equals()' called on itself\n" + " -Test2\n" + " 'equals()' called on itself\n" ); - TreeUtil.selectNode(tree, tree.getRoot()); - final InspectionTreeNode root = tree.getRoot(); + InspectionRootNode root = tree.getInspectionTreeModel().getRoot(); root.excludeElement(); - TreeUtil.treeNodeTraverser(root).traverse().processEach(node -> { - assertTrue(((InspectionTreeNode)node).isExcluded()); + tree.getInspectionTreeModel().traverse(root, node -> { + assertTrue("node = " + node, node.isExcluded()); return true; }); myView.getGlobalInspectionContext().getUIOptions().FILTER_RESOLVED_ITEMS = false; tree = updateTree(); - PlatformTestUtil.assertTreeEqual(tree, "-" + getProject() + "\n" + PlatformTestUtil.assertTreeEqual(tree, "-InspectionViewTree\n" + " -Java\n" + " -Declaration redundancy\n" + - " -unused\n" + + " -Unused declaration\n" + + " Entry Points\n" + " -Test\n" + " -f()\n" + " -D\n" + " -b()\n" + " -anonymous (Runnable)\n" + " -run()\n" + - " Variable i is never used.\n" + - " Variable r is never used.\n" + + " Variable 'i' is never used.\n" + + " Variable 'r' is never used.\n" + " -ff()\n" + - " Variable a is never used.\n" + - " Variable d is never used.\n" + + " Variable 'a' is never used.\n" + + " Variable 'd' is never used.\n" + " -foo()\n" + - " Variable j is never used.\n" + + " Variable 'j' is never used.\n" + " -main(String[])\n" + - " Variable test is never used.\n" + + " Variable 'test' is never used.\n" + " -Probable bugs\n" + - " -EqualsWithItself\n" + + " -'equals()' called on itself\n" + " -Test\n" + " 'equals()' called on itself\n" + " -Test2\n" + @@ -269,10 +275,11 @@ public class OfflineInspectionResultViewTest extends TestSourceBasedTestCase { ); } - private InspectionTree updateTree() { + private InspectionTree updateTree() throws InterruptedException { myView.update(); final InspectionTree tree = myView.getTree(); TreeUtil.expandAll(tree); + PlatformTestUtil.dispatchAllEventsInIdeEventQueue(); return tree; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/actions/ViewOfflineResultsAction.java b/platform/lang-impl/src/com/intellij/codeInspection/actions/ViewOfflineResultsAction.java index e4fb9a84c834..bc52381b568e 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/actions/ViewOfflineResultsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/actions/ViewOfflineResultsAction.java @@ -199,7 +199,7 @@ public class ViewOfflineResultsAction extends AnAction { context.setCurrentScope(scope); context.initializeTools(new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); final InspectionResultsView view = new InspectionResultsView(context, - new OfflineInspectionRVContentProvider(resMap, project)); + new OfflineInspectionRVContentProvider(resMap)); ((RefManagerImpl)context.getRefManager()).startOfflineView(); context.addView(view, title, true); view.update(); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java index 4318bde685c4..33da4f33ea3b 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/GlobalInspectionContextImpl.java @@ -19,7 +19,6 @@ import com.intellij.codeInspection.reference.RefVisitor; import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation; import com.intellij.codeInspection.ui.InspectionResultsView; import com.intellij.codeInspection.ui.InspectionToolPresentation; -import com.intellij.codeInspection.ui.InspectionTreeState; import com.intellij.codeInspection.ui.actions.ExportHTMLAction; import com.intellij.concurrency.JobLauncher; import com.intellij.concurrency.JobLauncherImpl; @@ -83,7 +82,7 @@ import java.util.function.Predicate; public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext { private static final int MAX_OPEN_GLOBAL_INSPECTION_XML_RESULT_FILES = SystemProperties.getIntProperty("max.open.global.inspection.xml.files", 50); - private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl"); + private static final Logger LOG = Logger.getInstance(GlobalInspectionContextImpl.class); @SuppressWarnings("StaticNonFinalField") public static volatile boolean CREATE_VIEW_FORCE; public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION); @@ -97,7 +96,6 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp @NotNull private AnalysisUIOptions myUIOptions; - private InspectionTreeState myTreeState; public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue contentManager) { super(project); @@ -110,10 +108,6 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp return myContentManager.getValue(); } - public void setTreeState(InspectionTreeState treeState) { - myTreeState = treeState; - } - public void addView(@NotNull InspectionResultsView view, @NotNull String title, boolean isOffline) { @@ -134,9 +128,6 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp if (!isOffline) { myView.setUpdating(true); } - if (myTreeState != null) { - myView.getTree().setTreeState(myTreeState); - } myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false); myContent.setHelpId(InspectionResultsView.HELP_ID); myContent.setDisposer(myView); @@ -387,7 +378,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp LOG.info("Code inspection finished. Took " + elapsed + "ms"); if (getProject().isDisposed()) return; - InspectionResultsView newView = myView == null ? new InspectionResultsView(this, createContentProvider()) : null; + InspectionResultsView newView = myView == null ? new InspectionResultsView(this, new InspectionRVContentProviderImpl()) : null; if (!(myView == null ? newView : myView).hasProblems()) { NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message", scope.getFileCount(), @@ -770,7 +761,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp if (getCurrentScope() == null) return; InspectionResultsView view = getView(); if (view == null) { - view = new InspectionResultsView(this, createContentProvider()); + view = new InspectionResultsView(this, new InspectionRVContentProviderImpl()); addView(view); } }, x -> getCurrentScope() == null); @@ -913,7 +904,7 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp public void refreshViews() { if (myView != null) { - myView.getTree().queueUpdate(); + myView.getTree().getInspectionTreeModel().reload(); } } @@ -1092,15 +1083,11 @@ public class GlobalInspectionContextImpl extends GlobalInspectionContextBase imp return myViewClosed; } - private InspectionRVContentProvider createContentProvider() { - return new InspectionRVContentProviderImpl(getProject()); - } - private void addProblemsToView(List tools) { if (ApplicationManager.getApplication().isHeadlessEnvironment() && !CREATE_VIEW_FORCE) { return; } - if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, createContentProvider())).booleanValue()) { + if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, new InspectionRVContentProviderImpl())).booleanValue()) { return; } initializeViewIfNeed().doWhenDone(() -> myView.addTools(tools)); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java index bda3bba482ab..83ba4b415f84 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProvider.java @@ -14,8 +14,6 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Comparing; -import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.containers.MultiMap; @@ -31,14 +29,11 @@ import javax.swing.tree.TreePath; import java.text.MessageFormat; import java.util.*; import java.util.function.Function; -import java.util.function.UnaryOperator; public abstract class InspectionRVContentProvider { private static final Logger LOG = Logger.getInstance(InspectionRVContentProvider.class); - private final Project myProject; - public InspectionRVContentProvider(@NotNull Project project) { - myProject = project; + public InspectionRVContentProvider() { } protected static class RefEntityContainer { @@ -51,18 +46,22 @@ public abstract class InspectionRVContentProvider { myDescriptors = descriptors; } - @Nullable - public RefEntityContainer getOwner() { - if (myEntity == null) return null; - final RefEntity entity = myEntity.getOwner(); - return entity instanceof RefElement && !(entity instanceof RefDirectory) - ? new RefEntityContainer<>(entity, myDescriptors) - : null; - } - @NotNull - public RefElementNode createNode(@NotNull InspectionToolPresentation presentation) { - return ReadAction.compute(() -> presentation.createRefNode(myEntity)); + public RefElementNode createNode(@NotNull InspectionToolPresentation presentation, + InspectionTreeModel model, + InspectionTreeNode topParent, + boolean showStructure) { + RefEntityContainer owner = getOwner(); + InspectionTreeNode parent; + if (owner == null) { + parent = topParent; + } else { + parent = owner.createNode(presentation, model, topParent, showStructure); + if (!showStructure) { + return (RefElementNode)parent; + } + } + return model.createRefElementNode(myEntity, () -> presentation.createRefNode(myEntity, model), parent); } @Nullable @@ -71,15 +70,18 @@ public abstract class InspectionRVContentProvider { } @Nullable - public String getModule() { + protected String getModuleName() { final RefModule refModule = myEntity instanceof RefElement ? ((RefElement)myEntity).getModule() : myEntity instanceof RefModule ? (RefModule)myEntity : null; return refModule != null ? refModule.getName() : null; } - boolean areEqual(final @NotNull RefEntity o1, final @NotNull RefEntity o2) { - return Comparing.equal(o1, o2); + @Nullable + public Module getModule(Project project) { + String name = getModuleName(); + if (name == null) return null; + return ReadAction.compute(() -> ModuleManager.getInstance(project).findModuleByName(name)); } boolean supportStructure() { @@ -89,6 +91,15 @@ public abstract class InspectionRVContentProvider { public Descriptor[] getDescriptors() { return myDescriptors; } + + @Nullable + private RefEntityContainer getOwner() { + if (myEntity == null) return null; + final RefEntity entity = myEntity.getOwner(); + return entity instanceof RefElement && !(entity instanceof RefDirectory) + ? new RefEntityContainer<>(entity, myDescriptors) + : null; + } } public abstract boolean checkReportedProblems(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionToolWrapper toolWrapper); @@ -197,15 +208,14 @@ public abstract class InspectionRVContentProvider { .toArray(StackTraceElement.class); } - public InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, - @NotNull InspectionNode toolNode, - @NotNull InspectionTreeNode parentNode, - boolean showStructure, - boolean groupBySeverity) { - InspectionToolWrapper wrapper = toolNode.getToolWrapper(); + public void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, + @NotNull InspectionToolWrapper wrapper, + @NotNull InspectionTreeNode parentNode, + boolean showStructure, + boolean groupBySeverity) { InspectionToolPresentation presentation = context.getPresentation(wrapper); Map> content = presentation.getContent(); - return appendToolNodeContent(context, toolNode, parentNode, showStructure, groupBySeverity, content, entity -> { + appendToolNodeContent(context, wrapper, parentNode, showStructure, groupBySeverity, content, entity -> { if (context.getUIOptions().FILTER_RESOLVED_ITEMS) { return presentation.isExcluded(entity) ? null : presentation.getProblemElements().get(entity); } else { @@ -221,19 +231,18 @@ public abstract class InspectionRVContentProvider { }); } - public abstract InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, - @NotNull InspectionNode toolNode, - @NotNull InspectionTreeNode parentNode, - final boolean showStructure, - boolean groupBySeverity, - @NotNull Map> contents, - @NotNull Function problems); + public abstract void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, + @NotNull InspectionToolWrapper wrapper, + @NotNull InspectionTreeNode parentNode, + final boolean showStructure, + boolean groupBySeverity, + @NotNull Map> contents, + @NotNull Function problems); protected abstract void appendDescriptor(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionToolWrapper toolWrapper, @NotNull RefEntityContainer container, - @NotNull InspectionTreeNode pNode, - final boolean canPackageRepeat); + @NotNull InspectionTreeNode parent); public boolean isContentLoaded() { return true; @@ -241,241 +250,37 @@ public abstract class InspectionRVContentProvider { protected void buildTree(@NotNull GlobalInspectionContextImpl context, @NotNull Map> packageContents, - final boolean canPackageRepeat, @NotNull InspectionToolWrapper toolWrapper, @NotNull Function> computeContainer, - final boolean showStructure, - final UnaryOperator createdNodesConsumer) { - final Map> module2PackageMap = new HashMap<>(); - boolean supportStructure = showStructure; - final MultiMap> packageDescriptors = new MultiMap<>(); - for (String packageName : packageContents.keySet()) { - final Set elements = packageContents.get(packageName); - for (T userObject : elements) { - final RefEntityContainer container = computeContainer.apply(userObject); - supportStructure &= container.supportStructure(); - final String moduleName = showStructure ? container.getModule() : null; - Map packageNodes = module2PackageMap.get(moduleName); - if (packageNodes == null) { - packageNodes = new HashMap<>(); - module2PackageMap.put(moduleName, packageNodes); - } - InspectionPackageNode pNode = packageNodes.get(packageName); - if (pNode == null) { - pNode = new InspectionPackageNode(packageName); - packageNodes.put(packageName, pNode); - } - - packageDescriptors.putValue(pNode, container); + boolean showStructure, + final InspectionTreeNode parent, + InspectionTreeModel model) { + MultiMap evaluatedDescriptors = MultiMap.create(); + for (Map.Entry> entry : packageContents.entrySet()) { + String packageName = entry.getKey(); + for (T problemDescriptor : entry.getValue()) { + RefEntityContainer container = computeContainer.apply(problemDescriptor); + evaluatedDescriptors.putValue(packageName, container); + showStructure &= container.supportStructure(); } } - if (supportStructure) { - final HashMap moduleNodes = new HashMap<>(); - for (final String moduleName : module2PackageMap.keySet()) { - final Map packageNodes = module2PackageMap.get(moduleName); - InspectionModuleNode moduleNode = moduleNodes.get(moduleName); - - if (moduleNode == null) { - if (moduleName != null) { - final Module module = ReadAction.compute(() -> ModuleManager.getInstance(myProject).findModuleByName(moduleName)); - if (module != null) { - moduleNode = new InspectionModuleNode(module); - moduleNodes.put(moduleName, moduleNode); - moduleNode = (InspectionModuleNode)createdNodesConsumer.apply(moduleNode); - } - else { //module content was removed ? - continue; - } - } - else { - for (InspectionPackageNode packageNode : packageNodes.values()) { - for (RefEntityContainer container : packageDescriptors.get(packageNode)) { - appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat); - } - createdNodesConsumer.apply(packageNode); - } - continue; - } - } else { - moduleNode = (InspectionModuleNode)createdNodesConsumer.apply(moduleNode); - } - for (InspectionPackageNode packageNode : packageNodes.values()) { - if (packageNode.getPackageName() != null) { - Collection> objectContainers = packageDescriptors.get(packageNode); - packageNode = (InspectionPackageNode)merge(packageNode, moduleNode, true); - for (RefEntityContainer container : objectContainers) { - appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat); - } - } - else { - for (RefEntityContainer container : packageDescriptors.get(packageNode)) { - appendDescriptor(context, toolWrapper, container, moduleNode, canPackageRepeat); - } - } + for (Map.Entry> entry : evaluatedDescriptors.entrySet()) { + for (RefEntityContainer container : entry.getValue()) { + InspectionTreeNode currentParent = parent; + if (showStructure) { + String packageName = entry.getKey(); + Module module = container.getModule(context.getProject()); + InspectionTreeNode moduleNode = module != null ? model.createModuleNode(module, parent) : null; + currentParent = model.createPackageNode(packageName, moduleNode == null ? parent : moduleNode); } + RefElementNode node = container.createNode(context.getPresentation(toolWrapper), + model, + currentParent, + showStructure || HighlightInfoType.UNUSED_SYMBOL_SHORT_NAME.equals(toolWrapper.getShortName())); + appendDescriptor(context, toolWrapper, container, node); } } - else { - for (Map packageNodes : module2PackageMap.values()) { - for (InspectionPackageNode pNode : packageNodes.values()) { - for (RefEntityContainer container : packageDescriptors.get(pNode)) { - appendDescriptor(context, toolWrapper, container, pNode, canPackageRepeat); - } - final int count = pNode.getChildCount(); - final ArrayList childNodes = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - childNodes.add(pNode.getChildAt(i)); - } - for (TreeNode childNode: childNodes) { - if (childNode instanceof ProblemDescriptionNode) { - createdNodesConsumer.apply(pNode); - break; - } - LOG.assertTrue(childNode instanceof RefElementNode, childNode.getClass().getName()); - final RefElementNode elementNode = (RefElementNode)childNode; - final Set parentNodes = new LinkedHashSet<>(); - if (pNode.getPackageName() != null) { - parentNodes.add(elementNode); - } else { - boolean hasElementNodeUnder = true; - for(int e = 0; e < elementNode.getChildCount(); e++) { - final TreeNode grandChildNode = elementNode.getChildAt(e); - if (grandChildNode instanceof ProblemDescriptionNode) { - hasElementNodeUnder = false; - break; - } - LOG.assertTrue(grandChildNode instanceof RefElementNode); - parentNodes.add((RefElementNode)grandChildNode); - } - if (!hasElementNodeUnder) { - createdNodesConsumer.apply(elementNode); - continue; - } - } - - //allow unused declaration to have structure at file level even when there are unused parameters - if (!HighlightInfoType.UNUSED_SYMBOL_SHORT_NAME.equals(toolWrapper.getShortName())) { - for (RefElementNode parentNode : parentNodes) { - final List nodes = new ArrayList<>(); - TreeUtil.traverse(parentNode, node -> { - if (node instanceof ProblemDescriptionNode) { - nodes.add((ProblemDescriptionNode)node); - } - return true; - }); - if (nodes.isEmpty()) continue; - parentNode.removeAllChildren(); - for (ProblemDescriptionNode node : nodes) { - parentNode.insertByOrder(node, false); - } - } - } - for (RefElementNode node : parentNodes) { - createdNodesConsumer.apply(node); - } - } - } - } - } - } - - @NotNull - protected static RefElementNode addNodeToParent(@NotNull RefEntityContainer container, - @NotNull InspectionToolPresentation presentation, - final InspectionTreeNode parentNode) { - final RefElementNode nodeToBeAdded = container.createNode(presentation); - final Ref firstLevel = new Ref<>(true); - RefElementNode prevNode = null; - final Ref result = new Ref<>(); - while (true) { - final RefElementNode currentNode = firstLevel.get() ? nodeToBeAdded : container.createNode(presentation); - final RefEntityContainer finalContainer = container; - final RefElementNode finalPrevNode = prevNode; - TreeUtil.treeNodeTraverser(parentNode).traverse(TreeTraversal.PRE_ORDER_DFS).processEach(node -> { - if (node instanceof RefElementNode) { - final RefElementNode refElementNode = (RefElementNode)node; - final RefEntity userObject = finalContainer.getRefEntity(); - final RefEntity object = refElementNode.getElement(); - if (userObject != null && - object != null && - (userObject.getClass().equals(object.getClass())) && - finalContainer.areEqual(object, userObject)) { - if (firstLevel.get()) { - result.set(refElementNode); - } - else { - refElementNode.insertByOrder(finalPrevNode, false); - result.set(nodeToBeAdded); - } - return false; - } - } - return true; - }); - if(!result.isNull()) return result.get(); - - if (!firstLevel.get()) { - currentNode.insertByOrder(prevNode, false); - } - final RefEntityContainer owner = container.getOwner(); - if (owner == null) { - parentNode.insertByOrder(currentNode, false); - return nodeToBeAdded; - } - container = owner; - prevNode = currentNode; - firstLevel.set(false); - } - } - - //class cast suppression - public static InspectionTreeNode merge(InspectionTreeNode child, InspectionTreeNode parent, boolean merge) { - return ReadAction.compute(() -> { - if (merge) { - for (int i = 0; i < parent.getChildCount(); i++) { - InspectionTreeNode current = (InspectionTreeNode)parent.getChildAt(i); - if (child.getClass() != current.getClass()) { - continue; - } - if (current instanceof InspectionPackageNode) { - if (((InspectionPackageNode)current).getPackageName().compareTo(((InspectionPackageNode)child).getPackageName()) == 0) { - processDepth(child, current); - return current; - } - } - else if (current instanceof RefElementNode) { - if (InspectionResultsViewComparator.getInstance().compare(current, child) == 0) { - processDepth(child, current); - return current; - } - } - else if (current instanceof InspectionNode) { - if (((InspectionNode)current).getToolWrapper().getShortName().compareTo(((InspectionNode)child).getToolWrapper().getShortName()) == 0) { - processDepth(child, current); - return current; - } - } - else if (current instanceof InspectionModuleNode) { - if (((InspectionModuleNode)current).getName().compareTo(((InspectionModuleNode)child).getName()) == 0) { - processDepth(child, current); - return current; - } - } - } - } - return parent.insertByOrder(child, false); - }); - } - - private static void processDepth(final InspectionTreeNode child, final InspectionTreeNode current) { - InspectionTreeNode[] children = new InspectionTreeNode[child.getChildCount()]; - for (int i = 0; i < children.length; i++) { - children[i] = (InspectionTreeNode)child.getChildAt(i); - } - for (InspectionTreeNode node : children) { - merge(node, current, true); - } } @NotNull diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java index 9a8ab854236c..8a1fd8957090 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/InspectionRVContentProviderImpl.java @@ -11,8 +11,6 @@ import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.reference.RefUtil; import com.intellij.codeInspection.ui.*; import com.intellij.codeInspection.ui.util.SynchronizedBidiMultiMap; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -29,10 +27,6 @@ import java.util.Set; import java.util.function.Function; public class InspectionRVContentProviderImpl extends InspectionRVContentProvider { - public InspectionRVContentProviderImpl(final Project project) { - super(project); - } - @Override public boolean checkReportedProblems(@NotNull GlobalInspectionContextImpl context, @NotNull final InspectionToolWrapper toolWrapper) { @@ -81,52 +75,50 @@ public class InspectionRVContentProviderImpl extends InspectionRVContentProvider } @Override - public InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, - @NotNull final InspectionNode toolNode, - @NotNull final InspectionTreeNode parentNode, - final boolean showStructure, - boolean groupBySeverity, - @NotNull final Map> contents, - @NotNull final Function problems) { - final InspectionToolWrapper toolWrapper = toolNode.getToolWrapper(); - InspectionNode mergedToolNode = (InspectionNode)merge(toolNode, parentNode, !groupBySeverity); + public void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, + @NotNull final InspectionToolWrapper wrapper, + @NotNull final InspectionTreeNode toolNode, + final boolean showStructure, + boolean groupBySeverity, + @NotNull final Map> contents, + @NotNull final Function problems) { + + InspectionResultsView view = context.getView(); buildTree(context, contents, - false, - toolWrapper, + wrapper, refElement -> new RefEntityContainer<>(refElement, problems.apply(refElement)), showStructure, - node -> merge(node, mergedToolNode, true)); - return mergedToolNode; + toolNode, + view.getTree().getInspectionTreeModel()); } @Override protected void appendDescriptor(@NotNull GlobalInspectionContextImpl context, @NotNull final InspectionToolWrapper toolWrapper, @NotNull final RefEntityContainer container, - @NotNull final InspectionTreeNode pNode, - final boolean canPackageRepeat) { + @NotNull final InspectionTreeNode parent) { final RefEntity refElement = container.getRefEntity(); + InspectionTreeModel model = context.getView().getTree().getInspectionTreeModel(); InspectionToolPresentation presentation = context.getPresentation(toolWrapper); final CommonProblemDescriptor[] problems = ((RefEntityContainer)container).getDescriptors(); if (problems != null && problems.length != 0) { - final RefElementNode elemNode = addNodeToParent(container, presentation, pNode); for (CommonProblemDescriptor problem : problems) { assert problem != null; - elemNode.insertByOrder(ReadAction.compute(() -> new ProblemDescriptionNode(refElement, problem, presentation)), false); - elemNode.setProblem(elemNode.getChildCount() == 1 ? problems[0] : null); + model.createProblemDescriptorNode(refElement, problem, presentation, parent); } } else { - if (canPackageRepeat && pNode instanceof InspectionPackageNode) { - final Set currentElements = presentation.getContent().get(((InspectionPackageNode) pNode).getPackageName()); + if (false) { + final Set currentElements = presentation.getContent().get(((InspectionPackageNode) parent).getPackageName()); if (currentElements != null) { final Set currentEntities = new HashSet<>(currentElements); if (RefUtil.contains(refElement, currentEntities)) return; } } - addNodeToParent(container, presentation, pNode); + //TODO don't understand + //addNodeToParent(container, presentation, parent, model); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java index df3b55c9f65d..22488c73734a 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ex/QuickFixAction.java @@ -42,6 +42,7 @@ import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import javax.swing.*; +import javax.swing.tree.TreePath; import java.awt.event.MouseEvent; import java.util.*; @@ -110,10 +111,11 @@ public class QuickFixAction extends AnAction implements CustomComponentAction { try { Ref> descriptors = Ref.create(); Set readOnlyFiles = new THashSet<>(); + TreePath[] paths = tree.getSelectionPaths(); if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> ReadAction.run(() -> { final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); indicator.setText("Checking problem descriptors..."); - descriptors.set(tree.getSelectedDescriptorPacks(true, readOnlyFiles, false)); + descriptors.set(tree.getSelectedDescriptorPacks(true, readOnlyFiles, false, paths)); }), InspectionsBundle.message("preparing.for.apply.fix"), true, e.getProject())) { return; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/export/InspectionTreeHtmlWriter.java b/platform/lang-impl/src/com/intellij/codeInspection/export/InspectionTreeHtmlWriter.java index 5796c772db15..d52b84a1659f 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/export/InspectionTreeHtmlWriter.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/export/InspectionTreeHtmlWriter.java @@ -41,19 +41,6 @@ public class InspectionTreeHtmlWriter { serializeTreeToHtml(); } - private void traverseInspectionTree(final InspectionTreeNode node, - final Consumer preAction, - final Consumer postAction) { - if (node.isExcluded()) { - return; - } - preAction.accept(node); - for (int i = 0; i < node.getChildCount(); i++) { - traverseInspectionTree((InspectionTreeNode)node.getChildAt(i), preAction, postAction); - } - postAction.accept(node); - } - private void serializeTreeToHtml() { appendHeader(); appendTree((builder) -> { @@ -68,7 +55,8 @@ public class InspectionTreeHtmlWriter { builder.append(escapeNonBreakingSymbols(text)); } }; - traverseInspectionTree(myTree.getRoot(), + InspectionTreeModel model = myTree.getInspectionTreeModel(); + traverseInspectionTree(model.getRoot(), (n) -> { final int nodeId = System.identityHashCode(n); builder @@ -104,6 +92,19 @@ public class InspectionTreeHtmlWriter { InspectionTreeHtmlExportResources.copyInspectionReportResources(myOutputDir); } + private static void traverseInspectionTree(InspectionTreeNode node, + Consumer preAction, + Consumer postAction) { + if (node.isExcluded()) { + return; + } + preAction.accept(node); + for (InspectionTreeNode child : node.getChildren()) { + traverseInspectionTree(child, preAction, postAction); + } + postAction.accept(node); + } + private String convertNodeToHtml(InspectionTreeNode node) { if (node instanceof InspectionRootNode) { return "'" + escapeNonBreakingSymbols(node) + "' project"; @@ -139,7 +140,7 @@ public class InspectionTreeHtmlWriter { return sb.toString(); } else if (node instanceof RefElementNode) { - final String type = myManager.getType((RefEntity)node.getUserObject()); + final String type = myManager.getType(((RefElementNode)node).getElement()); return type + " " + node.toString() + ""; } else if (node instanceof InspectionNode) { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineDescriptorResolveResult.java b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineDescriptorResolveResult.java index eac90b3ca88b..3d5f708adeb7 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineDescriptorResolveResult.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineDescriptorResolveResult.java @@ -38,7 +38,7 @@ import java.util.stream.Collectors; /** * @author Dmitry Batkovich */ -class OfflineDescriptorResolveResult { +public class OfflineDescriptorResolveResult { private static final Logger LOG = Logger.getInstance(OfflineDescriptorResolveResult.class); private final RefEntity myResolvedEntity; private final CommonProblemDescriptor myResolvedDescriptor; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineInspectionRVContentProvider.java b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineInspectionRVContentProvider.java index fc4e4ca97f65..ce9c0223721d 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineInspectionRVContentProvider.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineInspectionRVContentProvider.java @@ -10,11 +10,8 @@ import com.intellij.codeInspection.offline.OfflineProblemDescriptor; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.ui.*; -import com.intellij.openapi.application.ReadAction; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.util.containers.FactoryMap; -import java.util.HashSet; import gnu.trove.THashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -27,9 +24,7 @@ public class OfflineInspectionRVContentProvider extends InspectionRVContentProvi private final Map> myResolvedDescriptor = FactoryMap.create(key -> new THashMap<>()); - public OfflineInspectionRVContentProvider(@NotNull Map>> content, - @NotNull Project project) { - super(project); + public OfflineInspectionRVContentProvider(@NotNull Map>> content) { myContent = content; } @@ -59,33 +54,28 @@ public class OfflineInspectionRVContentProvider extends InspectionRVContentProvi } @Override - public InspectionNode appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, - @NotNull InspectionNode toolNode, - @NotNull InspectionTreeNode parentNode, - boolean showStructure, - boolean groupBySeverity, @NotNull final Map> contents, - @NotNull Function problems) { - InspectionToolWrapper toolWrapper = toolNode.getToolWrapper(); - final Map> filteredContent = getFilteredContent(context, toolWrapper); + public void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context, + @NotNull InspectionToolWrapper wrapper, + @NotNull InspectionTreeNode parentNode, + boolean showStructure, + boolean groupBySeverity, @NotNull final Map> contents, + @NotNull Function problems) { + final Map> filteredContent = getFilteredContent(context, wrapper); + InspectionResultsView view = context.getView(); if (filteredContent != null && !filteredContent.values().isEmpty()) { - parentNode.insertByOrder(toolNode, false); - buildTree(context, filteredContent, false, toolWrapper, descriptor -> { + buildTree(context, filteredContent, wrapper, descriptor -> { final RefEntity element = descriptor.getRefElement(context.getRefManager()); return new RefEntityContainer(element, new OfflineProblemDescriptor[] {descriptor}) { @Nullable @Override - public String getModule() { - final String module = super.getModule(); + protected String getModuleName() { + final String module = super.getModuleName(); return module == null ? descriptor.getModuleName() : module; } }; }, showStructure, - (newChild) -> { - toolNode.insertByOrder(newChild, false); - return newChild; - }); + parentNode, view.getTree().getInspectionTreeModel()); } - return toolNode; } @Nullable @@ -140,10 +130,9 @@ public class OfflineInspectionRVContentProvider extends InspectionRVContentProvi protected void appendDescriptor(@NotNull GlobalInspectionContextImpl context, @NotNull final InspectionToolWrapper toolWrapper, @NotNull final RefEntityContainer container, - @NotNull final InspectionTreeNode packageNode, - final boolean canPackageRepeat) { + @NotNull final InspectionTreeNode parent) { InspectionToolPresentation presentation = context.getPresentation(toolWrapper); - final RefElementNode elemNode = addNodeToParent(container, presentation, packageNode); + InspectionTreeModel model = context.getView().getTree().getInspectionTreeModel(); for (OfflineProblemDescriptor descriptor : ((RefEntityContainer)container).getDescriptors()) { final OfflineDescriptorResolveResult resolveResult = myResolvedDescriptor.get(toolWrapper.getShortName()) .computeIfAbsent(descriptor, d -> OfflineDescriptorResolveResult.resolve(d, toolWrapper, presentation)); @@ -151,9 +140,9 @@ public class OfflineInspectionRVContentProvider extends InspectionRVContentProvi CommonProblemDescriptor resolvedDescriptor = resolveResult.getResolvedDescriptor(); if (resolvedEntity != null && resolvedDescriptor != null) { presentation.getProblemElements().put(resolvedEntity, resolvedDescriptor); - elemNode.insertByOrder(ReadAction.compute(() -> new ProblemDescriptionNode(resolvedEntity, resolvedDescriptor, presentation)), true); + model.createProblemDescriptorNode(resolvedEntity, resolvedDescriptor, presentation, parent); } else { - elemNode.insertByOrder(ReadAction.compute(() -> OfflineProblemDescriptorNode.create(descriptor, resolveResult, presentation)), true); + model.createOfflineProblemDescriptorNode(descriptor, resolveResult, presentation, parent); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineProblemDescriptorNode.java b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineProblemDescriptorNode.java index 040101721b9e..8eff96563340 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineProblemDescriptorNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/offlineViewer/OfflineProblemDescriptorNode.java @@ -7,37 +7,39 @@ package com.intellij.codeInspection.offlineViewer; import com.intellij.codeInspection.ProblemDescriptorUtil; import com.intellij.codeInspection.offline.OfflineProblemDescriptor; import com.intellij.codeInspection.ui.InspectionToolPresentation; +import com.intellij.codeInspection.ui.InspectionTreeModel; import com.intellij.codeInspection.ui.ProblemDescriptionNode; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; public class OfflineProblemDescriptorNode extends ProblemDescriptionNode { private final OfflineDescriptorResolveResult myDescriptorResolveResult; + private final OfflineProblemDescriptor myOfflineDescriptor; - private OfflineProblemDescriptorNode(OfflineDescriptorResolveResult descriptorResolveResult, - @NotNull InspectionToolPresentation presentation, - @NotNull OfflineProblemDescriptor offlineDescriptor) { - super(descriptorResolveResult.getResolvedEntity(), descriptorResolveResult.getResolvedDescriptor(), presentation, offlineDescriptor::getLine); + public OfflineProblemDescriptorNode(OfflineDescriptorResolveResult descriptorResolveResult, + @NotNull InspectionToolPresentation presentation, + @NotNull OfflineProblemDescriptor offlineDescriptor, + InspectionTreeModel model) { + super(descriptorResolveResult.getResolvedEntity(), descriptorResolveResult.getResolvedDescriptor(), presentation, offlineDescriptor::getLine, model); myDescriptorResolveResult = descriptorResolveResult; - if (descriptorResolveResult.getResolvedDescriptor() == null) { - setUserObject(offlineDescriptor); - } + myOfflineDescriptor = offlineDescriptor; } - static OfflineProblemDescriptorNode create(@NotNull OfflineProblemDescriptor offlineDescriptor, - @NotNull OfflineDescriptorResolveResult resolveResult, - @NotNull InspectionToolPresentation presentation) { + public static OfflineProblemDescriptorNode create(@NotNull OfflineProblemDescriptor offlineDescriptor, + @NotNull OfflineDescriptorResolveResult resolveResult, + @NotNull InspectionToolPresentation presentation, + InspectionTreeModel model) { return new OfflineProblemDescriptorNode(resolveResult, presentation, - offlineDescriptor); + offlineDescriptor, model); } @NotNull @Override protected String calculatePresentableName() { String presentableName = super.calculatePresentableName(); - return presentableName.isEmpty() && getUserObject() instanceof OfflineProblemDescriptor - ? ProblemDescriptorUtil.unescapeTags(StringUtil.notNullize(((OfflineProblemDescriptor)getUserObject()).getDescription())).trim() + return presentableName.isEmpty() && getDescriptor() == null + ? ProblemDescriptorUtil.unescapeTags(StringUtil.notNullize(myOfflineDescriptor.getDescription())).trim() : presentableName; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java index ac3f20e08bf4..a22b44e40962 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/DefaultInspectionToolPresentation.java @@ -56,8 +56,7 @@ public class DefaultInspectionToolPresentation implements InspectionToolPresenta protected static final Logger LOG = Logger.getInstance(DefaultInspectionToolPresentation.class); @NotNull private final InspectionToolWrapper myToolWrapper; - @NotNull private final GlobalInspectionContextImpl myContext; - protected InspectionNode myToolNode; + @NotNull protected final GlobalInspectionContextImpl myContext; private final SynchronizedBidiMultiMap myProblemElements = createBidiMap(); private final SynchronizedBidiMultiMap mySuppressedElements = createBidiMap(); @@ -255,11 +254,6 @@ public class DefaultInspectionToolPresentation implements InspectionToolPresenta } } - @Override - public InspectionNode getToolNode() { - return myToolNode; - } - protected boolean isDisposed() { return isDisposed; } @@ -464,15 +458,6 @@ public class DefaultInspectionToolPresentation implements InspectionToolPresenta return myProblemElements; } - @Override - public void createToolNode(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionNode node, - @NotNull InspectionRVContentProvider provider, - @NotNull InspectionTreeNode parentNode, - boolean showStructure, - boolean groupBySeverity) { - myToolNode = node; - } - @Override @Nullable public IntentionAction findQuickFixes(@NotNull final CommonProblemDescriptor problemDescriptor, final String hint) { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionGroupNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionGroupNode.java index 64107c4ef2cc..461facb17646 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionGroupNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionGroupNode.java @@ -4,20 +4,25 @@ package com.intellij.codeInspection.ui; import org.jetbrains.annotations.NotNull; -/** - * @author max - */ public class InspectionGroupNode extends InspectionTreeNode { - InspectionGroupNode(@NotNull String subGroup) { - super(subGroup); + private final String myGroup; + + InspectionGroupNode(@NotNull String group, @NotNull InspectionTreeModel model) { + super(model); + myGroup = group; } String getSubGroup() { - return (String) getUserObject(); + return myGroup; } @Override public boolean appearsBold() { return true; } + + @Override + public String getPresentableText() { + return myGroup; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionModuleNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionModuleNode.java index a616a1a90045..3461ff3ad405 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionModuleNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionModuleNode.java @@ -17,13 +17,16 @@ package com.intellij.codeInspection.ui; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleType; +import org.jetbrains.annotations.NotNull; -import javax.swing.*; +import javax.swing.Icon; -public class InspectionModuleNode extends InspectionTreeNode{ +public class InspectionModuleNode extends InspectionTreeNode { + @NotNull private final Module myModule; - public InspectionModuleNode(final Module module) { - super(module); + + public InspectionModuleNode(@NotNull Module module, InspectionTreeModel model) { + super(model); myModule = module; } @@ -32,11 +35,12 @@ public class InspectionModuleNode extends InspectionTreeNode{ return myModule.isDisposed() ? null : ModuleType.get(myModule).getIcon(); } - public String getName(){ + public String getName() { return myModule.getName(); } - public String toString() { + @Override + public String getPresentableText() { return getName(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNode.java index 460b06b0ef56..9d5b5d5f4857 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionNode.java @@ -21,24 +21,19 @@ import com.intellij.codeInspection.ex.InspectionToolWrapper; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * @author max - */ public class InspectionNode extends InspectionTreeNode { + @NotNull private final InspectionToolWrapper myToolWrapper; @NotNull private final InspectionProfileImpl myProfile; - public InspectionNode(@NotNull InspectionToolWrapper toolWrapper, @NotNull InspectionProfileImpl profile) { - super(toolWrapper); + public InspectionNode(@NotNull InspectionToolWrapper toolWrapper, @NotNull InspectionProfileImpl profile, @NotNull InspectionTreeModel model) { + super(model); + myToolWrapper = toolWrapper; myProfile = profile; } - public String toString() { - return getToolWrapper().getDisplayName(); - } - @NotNull public InspectionToolWrapper getToolWrapper() { - return (InspectionToolWrapper)getUserObject(); + return myToolWrapper; } @Nullable @@ -47,4 +42,9 @@ public class InspectionNode extends InspectionTreeNode { final String shortName = getToolWrapper().getShortName(); return myProfile.getTools(shortName, null).isEnabled() ? null : "Disabled"; } + + @Override + public String getPresentableText() { + return getToolWrapper().getDisplayName(); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionPackageNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionPackageNode.java index 45a7d4297178..a4a3b15daedd 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionPackageNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionPackageNode.java @@ -18,23 +18,27 @@ package com.intellij.codeInspection.ui; import com.intellij.util.PlatformIcons; -import javax.swing.*; +import javax.swing.Icon; -/** - * @author max - */ public class InspectionPackageNode extends InspectionTreeNode { + private final String myPackageName; - public InspectionPackageNode(String packageName) { - super(packageName); + public InspectionPackageNode(String packageName, InspectionTreeModel model) { + super(model); + myPackageName = packageName; } public String getPackageName() { - return (String) getUserObject(); + return myPackageName; } @Override public Icon getIcon(boolean expanded) { return PlatformIcons.PACKAGE_ICON; } + + @Override + public String getPresentableText() { + return myPackageName; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java index 5edded60b99c..095db9f228fd 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsView.java @@ -14,7 +14,10 @@ import com.intellij.codeInspection.reference.RefEntity; import com.intellij.codeInspection.ui.actions.ExportHTMLAction; import com.intellij.codeInspection.ui.actions.InvokeQuickFixAction; import com.intellij.diff.util.DiffUtil; -import com.intellij.ide.*; +import com.intellij.ide.CommonActionsManager; +import com.intellij.ide.DefaultTreeExpander; +import com.intellij.ide.OccurenceNavigator; +import com.intellij.ide.TreeExpander; import com.intellij.ide.actions.exclusion.ExclusionHandler; import com.intellij.ide.util.PsiNavigationSupport; import com.intellij.injected.editor.VirtualFileWindow; @@ -23,7 +26,6 @@ import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; @@ -43,27 +45,25 @@ import com.intellij.profile.ProfileChangeAdapter; import com.intellij.profile.codeInspection.ProjectInspectionProfileManager; import com.intellij.psi.*; import com.intellij.psi.util.PsiUtilCore; -import com.intellij.ui.*; -import com.intellij.util.EditSourceOnDoubleClickHandler; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.OnePixelSplitter; +import com.intellij.ui.ScrollPaneFactory; +import com.intellij.ui.SideBorder; +import com.intellij.util.Alarm; import com.intellij.util.ObjectUtils; -import com.intellij.util.OpenSourceUtil; import com.intellij.util.concurrency.SequentialTaskExecutor; +import com.intellij.util.ui.EdtInvocationManager; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; -import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.InputEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.util.List; import java.util.*; @@ -77,8 +77,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro @NotNull private final InspectionTree myTree; - @NotNull - private final OccurenceNavigator myOccurenceNavigator; private volatile InspectionProfileImpl myInspectionProfile; private final boolean mySettingsEnabled; @NotNull @@ -91,7 +89,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro private final GlobalInspectionContextImpl myGlobalInspectionContext; private boolean myRerun; private volatile boolean myDisposed; - private int myUpdatingRequestors; //accessed only in edt private boolean myApplyingFix; //accessed only in edt @NotNull @@ -100,11 +97,11 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro private final ExclusionHandler myExclusionHandler; private EditorEx myPreviewEditor; private InspectionTreeLoadingProgressAware myLoadingProgressPreview; + private final Alarm myLoadingProgressPreviewAlarm = new Alarm(this); private final InspectionViewSuppressActionHolder mySuppressActionHolder = new InspectionViewSuppressActionHolder(); - private final Object myTreeStructureUpdateLock = new Object(); - private final ExecutorService myTreeUpdater = SequentialTaskExecutor - .createSequentialApplicationPoolExecutor("Inspection-View-Tree-Updater"); + private final ExecutorService myTreeUpdater = SequentialTaskExecutor.createSequentialApplicationPoolExecutor("Inspection-View-Tree-Updater"); + private volatile boolean myUpdating; public InspectionResultsView(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionRVContentProvider provider) { @@ -114,9 +111,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro myGlobalInspectionContext = globalInspectionContext; myProvider = provider; myTree = new InspectionTree(globalInspectionContext, this); - initTreeListeners(); - - myOccurenceNavigator = initOccurenceNavigator(); mySplitter = new OnePixelSplitter(false, AnalysisUIOptions.getInstance(globalInspectionContext.getProject()).SPLITTER_PROPORTION); mySplitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT)); @@ -160,11 +154,9 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro public void onDone(boolean isExcludeAction) { if (isExcludeAction) { myTree.removeSelectedProblems(); + } else { myTree.repaint(); } - else { - resetTree(); - } syncRightPanel(); } }; @@ -201,98 +193,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro }); } - private void initTreeListeners() { - if (ApplicationManager.getApplication().isUnitTestMode()) { - return; - } - myTree.getSelectionModel().addTreeSelectionListener(e -> { - if (myTree.isUnderQueueUpdate()) return; - syncRightPanel(); - if (isAutoScrollMode()) { - OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(this), false); - } - }); - - EditSourceOnDoubleClickHandler.install(myTree); - - myTree.addKeyListener(new KeyAdapter() { - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(InspectionResultsView.this), false); - } - } - }); - - PopupHandler.installPopupHandler(myTree, IdeActions.INSPECTION_TOOL_WINDOW_TREE_POPUP, ActionPlaces.CODE_INSPECTION); - SmartExpander.installOn(myTree); - } - - @NotNull - private OccurenceNavigatorSupport initOccurenceNavigator() { - return new OccurenceNavigatorSupport(myTree) { - @Override - protected boolean isOccurrenceNode(@NotNull DefaultMutableTreeNode node) { - if (node instanceof InspectionTreeNode && ((InspectionTreeNode)node).isExcluded()) { - return false; - } - if (node instanceof RefElementNode) { - final RefElementNode refNode = (RefElementNode)node; - if (refNode.hasDescriptorsUnder()) return false; - final RefEntity element = refNode.getElement(); - return element != null && element.isValid(); - } - return node instanceof ProblemDescriptionNode; - } - - @Override - @Nullable - protected Navigatable createDescriptorForNode(@NotNull DefaultMutableTreeNode node) { - if (node instanceof InspectionTreeNode && ((InspectionTreeNode)node).isExcluded()) { - return null; - } - if (node instanceof RefElementNode) { - final RefElementNode refNode = (RefElementNode)node; - if (refNode.hasDescriptorsUnder()) return null; - final RefEntity element = refNode.getElement(); - if (element == null || !element.isValid()) return null; - final CommonProblemDescriptor problem = refNode.getDescriptor(); - if (problem != null) { - return navigate(problem); - } - if (element instanceof RefElement) { - return getOpenFileDescriptor((RefElement)element); - } - } - else if (node instanceof ProblemDescriptionNode) { - ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node; - boolean isValid = problemNode.isValid() && (!problemNode.isQuickFixAppliedFromView() || - problemNode.calculateIsValid()); - return isValid - ? navigate(problemNode.getDescriptor()) - : InspectionResultsViewUtil.getNavigatableForInvalidNode(problemNode); - } - return null; - } - - @Nullable - private Navigatable navigate(final CommonProblemDescriptor descriptor) { - return getSelectedNavigatable(descriptor); - } - - @NotNull - @Override - public String getNextOccurenceActionName() { - return InspectionsBundle.message("inspection.action.go.next"); - } - - @NotNull - @Override - public String getPreviousOccurenceActionName() { - return InspectionsBundle.message("inspection.action.go.prev"); - } - }; - } private void createActionsToolbar() { JPanel westPanel = JBUI.Panels.simplePanel() @@ -319,8 +219,8 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro final TreeExpander treeExpander = new DefaultTreeExpander(myTree); group.add(actionsManager.createExpandAllAction(treeExpander, myTree)); group.add(actionsManager.createCollapseAllAction(treeExpander, myTree)); - group.add(actionsManager.createPrevOccurenceAction(myOccurenceNavigator)); - group.add(actionsManager.createNextOccurenceAction(myOccurenceNavigator)); + group.add(actionsManager.createPrevOccurenceAction(myTree.getOccurenceNavigator())); + group.add(actionsManager.createNextOccurenceAction(myTree.getOccurenceNavigator())); group.add(ActionManager.getInstance().getAction("EditInspectionSettings")); return createToolbar(group); @@ -328,34 +228,34 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro @Override public boolean hasNextOccurence() { - return myOccurenceNavigator.hasNextOccurence(); + return myTree.getOccurenceNavigator().hasNextOccurence(); } @Override public boolean hasPreviousOccurence() { - return myOccurenceNavigator.hasPreviousOccurence(); + return myTree.getOccurenceNavigator().hasPreviousOccurence(); } @Override public OccurenceInfo goNextOccurence() { - return myOccurenceNavigator.goNextOccurence(); + return myTree.getOccurenceNavigator().goNextOccurence(); } @Override public OccurenceInfo goPreviousOccurence() { - return myOccurenceNavigator.goPreviousOccurence(); + return myTree.getOccurenceNavigator().goPreviousOccurence(); } @NotNull @Override public String getNextOccurenceActionName() { - return myOccurenceNavigator.getNextOccurenceActionName(); + return myTree.getOccurenceNavigator().getNextOccurenceActionName(); } @NotNull @Override public String getPreviousOccurenceActionName() { - return myOccurenceNavigator.getPreviousOccurenceActionName(); + return myTree.getOccurenceNavigator().getPreviousOccurenceActionName(); } private static JComponent createToolbar(final DefaultActionGroup specialGroup) { @@ -376,39 +276,17 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } } - private boolean isAutoScrollMode() { + boolean isAutoScrollMode() { String activeToolWindowId = ToolWindowManager.getInstance(getProject()).getActiveToolWindowId(); return myGlobalInspectionContext.getUIOptions().AUTOSCROLL_TO_SOURCE && (activeToolWindowId == null || activeToolWindowId.equals(ToolWindowId.INSPECTION)); } - Object getTreeStructureUpdateLock() { - return myTreeStructureUpdateLock; - } - - @Nullable - private static Navigatable getOpenFileDescriptor(final RefElement refElement) { - PsiElement psiElement = refElement.getPsiElement(); - if (psiElement == null) return null; - final PsiFile containingFile = psiElement.getContainingFile(); - if (containingFile == null) return null; - VirtualFile file = containingFile.getVirtualFile(); - if (file == null) return null; - return PsiNavigationSupport.getInstance().createNavigatable(refElement.getRefManager().getProject(), file, - psiElement.getTextOffset()); - } - public void setApplyingFix(boolean applyingFix) { myApplyingFix = applyingFix; syncRightPanel(); } - void openRightPanelIfNeed() { - if (mySplitter.getSecondComponent() == null) { - syncRightPanel(); - } - } - public void syncRightPanel() { final Editor oldEditor = myPreviewEditor; try { @@ -482,7 +360,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro editorPanel.setLayout(new BorderLayout()); final JPanel actionsPanel = new JPanel(new BorderLayout()); editorPanel.add(actionsPanel, BorderLayout.NORTH); - final int problemCount = myTree.getSelectedProblemCount(true); + final int problemCount = myTree.getSelectedProblemCount(); JComponent previewPanel = null; final InspectionToolWrapper tool = myTree.getSelectedToolWrapper(true); if (tool != null) { @@ -597,12 +475,15 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro HighlightDisplayLevel errorLevel, boolean groupedBySeverity, boolean isSingleInspectionRun) { - InspectionTreeNode parentNode = myTree.getToolParentNode(toolWrapper, errorLevel, groupedBySeverity, isSingleInspectionRun); - InspectionNode toolNode = new InspectionNode(toolWrapper, myInspectionProfile); - boolean showStructure = myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE; - toolNode = myProvider.appendToolNodeContent(myGlobalInspectionContext, toolNode, parentNode, showStructure, groupedBySeverity); + InspectionTreeNode toolNode = myTree.getToolProblemsRootNode(toolWrapper, errorLevel, groupedBySeverity, isSingleInspectionRun); + myProvider.appendToolNodeContent(myGlobalInspectionContext, + toolWrapper, + toolNode, + myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE, + groupedBySeverity); InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(toolWrapper); - presentation.createToolNode(myGlobalInspectionContext, toolNode, myProvider, parentNode, showStructure, groupedBySeverity); + presentation.patchToolNode(toolNode, myProvider, + myGlobalInspectionContext.getUIOptions().SHOW_STRUCTURE, groupedBySeverity); registerActionShortcuts(presentation); } @@ -628,39 +509,38 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } void addProblemDescriptors(InspectionToolWrapper wrapper, RefEntity refElement, CommonProblemDescriptor[] descriptors) { - updateTree(() -> { - synchronized (myTreeStructureUpdateLock) { - ReadAction.run(() -> { - if (!isDisposed()) { - ApplicationManager.getApplication().assertReadAccessAllowed(); - final AnalysisUIOptions uiOptions = myGlobalInspectionContext.getUIOptions(); - final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(wrapper); - if (presentation.getToolNode() == null) { - presentation.updateContent(); - addTool(wrapper, HighlightDisplayLevel.find(presentation.getSeverity((RefElement)refElement)), - uiOptions.GROUP_BY_SEVERITY, isSingleInspectionRun()); - return; - } - final InspectionNode toolNode = presentation.getToolNode(); - LOG.assertTrue(toolNode != null); - final Map problems = new HashMap<>(1); - problems.put(refElement, descriptors); - final Map> contents = new HashMap<>(); - final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement); - Set content = contents.computeIfAbsent(groupName, __ -> new HashSet<>()); - content.add(refElement); + updateTree(() -> ReadAction.run(() -> { + if (!isDisposed()) { + ApplicationManager.getApplication().assertReadAccessAllowed(); + final AnalysisUIOptions uiOptions = myGlobalInspectionContext.getUIOptions(); + final InspectionToolPresentation presentation = myGlobalInspectionContext.getPresentation(wrapper); - getProvider().appendToolNodeContent(myGlobalInspectionContext, - toolNode, - (InspectionTreeNode)toolNode.getParent(), - uiOptions.SHOW_STRUCTURE, - true, - contents, - problems::get); + final InspectionTreeNode toolNode = + myTree.getToolProblemsRootNode(wrapper, HighlightDisplayLevel.find(presentation.getSeverity((RefElement)refElement)), + uiOptions.GROUP_BY_SEVERITY, isSingleInspectionRun()); + final Map problems = new HashMap<>(1); + problems.put(refElement, descriptors); + final Map> contents = new HashMap<>(); + final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement); + Set content = contents.computeIfAbsent(groupName, __ -> new HashSet<>()); + content.add(refElement); + + getProvider().appendToolNodeContent(myGlobalInspectionContext, + wrapper, + toolNode, + uiOptions.SHOW_STRUCTURE, + true, + contents, + problems::get); + + myLoadingProgressPreviewAlarm.cancelAllRequests(); + myLoadingProgressPreviewAlarm.addRequest(() -> { + if (myLoadingProgressPreview != null) { + myLoadingProgressPreview.updateLoadingProgress(); } - }); + }, 200); } - }); + })); } public void update() { @@ -670,14 +550,11 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro final Runnable buildAction = () -> { try { setUpdating(true); - synchronized (myTreeStructureUpdateLock) { - myTree.removeAllNodes(); - addToolsSynchronously(tools); - } + myTree.removeAllNodes(); + addToolsSynchronously(tools); } finally { setUpdating(false); - UIUtil.invokeLaterIfNeeded(() -> myTree.restoreExpansionAndSelection(false)); } }; if (app.isUnitTestMode()) { @@ -688,35 +565,16 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } public void setUpdating(boolean isUpdating) { - final Runnable update = () -> { - if (isUpdating) { - myUpdatingRequestors++; - } else { - myUpdatingRequestors--; - } - boolean hasUpdatingRequestors = myUpdatingRequestors > 0; - myTree.setPaintBusy(hasUpdatingRequestors); - if (!hasUpdatingRequestors && myLoadingProgressPreview != null) { - myLoadingProgressPreview.treeLoaded(); - } - //TODO Dmitrii Batkovich it's a hack (preview panel should be created during selection update) - if (!hasUpdatingRequestors && mySplitter.getSecondComponent() == null) { - final int count = myTree.getRoot().getChildCount(); - if (count != 0) { - if (myTree.getSelectionCount() == 0) { - TreeUtil.selectFirstNode(myTree); - } - syncRightPanel(); + myUpdating = isUpdating; + if (!isUpdating) { + myLoadingProgressPreviewAlarm.cancelAllRequests(); + myLoadingProgressPreviewAlarm.addRequest(() -> { + if (myLoadingProgressPreview != null) { + myLoadingProgressPreview.treeLoaded(); } - } - }; - final Application app = ApplicationManager.getApplication(); - if (app.isDispatchThread()) { - update.run(); - } - else { - app.invokeLater(update, ModalityState.any()); + }, 200); } + EdtInvocationManager.getInstance().invokeLater(() -> myTree.setPaintBusy(myUpdating)); } public void addTools(Collection tools) { @@ -725,30 +583,23 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro private void addToolsSynchronously(Collection tools) { if (isDisposed()) return; - synchronized (myTreeStructureUpdateLock) { - InspectionProfileImpl profile = myInspectionProfile; - boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY; - boolean singleInspectionRun = isSingleInspectionRun(); - for (Tools currentTools : tools) { - InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool(); - final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName()); - for (ScopeToolState state : myProvider.getTools(currentTools)) { - InspectionToolWrapper toolWrapper = state.getTool(); - if (ReadAction.compute(() -> myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper))) { - addTool(toolWrapper, - profile.getErrorLevel(key, state.getScope(getProject()), getProject()), - isGroupedBySeverity, - singleInspectionRun); - } + InspectionProfileImpl profile = myInspectionProfile; + boolean isGroupedBySeverity = myGlobalInspectionContext.getUIOptions().GROUP_BY_SEVERITY; + boolean singleInspectionRun = isSingleInspectionRun(); + for (Tools currentTools : tools) { + InspectionToolWrapper defaultToolWrapper = currentTools.getDefaultState().getTool(); + final HighlightDisplayKey key = HighlightDisplayKey.find(defaultToolWrapper.getShortName()); + for (ScopeToolState state : myProvider.getTools(currentTools)) { + InspectionToolWrapper toolWrapper = state.getTool(); + if (ReadAction.compute(() -> myProvider.checkReportedProblems(myGlobalInspectionContext, toolWrapper))) { + addTool(toolWrapper, + profile.getErrorLevel(key, state.getScope(getProject()), getProject()), + isGroupedBySeverity, + singleInspectionRun); } } } - ApplicationManager.getApplication().invokeLater(() -> { - if (myTree.getSelectionCount() == 0) { - TreeUtil.selectFirstNode(myTree); - } - syncRightPanel(); - }); + } @NotNull @@ -761,12 +612,22 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro if (PlatformDataKeys.HELP_ID.is(dataId)) return HELP_ID; if (DATA_KEY.is(dataId)) return this; if (ExclusionHandler.EXCLUSION_HANDLER.is(dataId)) return myExclusionHandler; + if (!ApplicationManager.getApplication().isDispatchThread()) return null; TreePath[] paths = myTree.getSelectionPaths(); if (paths == null || paths.length == 0) return null; if (paths.length > 1) { if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { - return collectPsiElements(); + RefEntity[] refElements = myTree.getSelectedElements(); + List psiElements = new ArrayList<>(); + for (RefEntity refElement : refElements) { + PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getPsiElement() : null; + if (psiElement != null && psiElement.isValid()) { + psiElements.add(psiElement); + } + } + + return PsiUtilCore.toPsiElementArray(psiElements); } return null; } @@ -789,25 +650,11 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro PsiElement psiElement = item instanceof RefElement ? ((RefElement)item).getPsiElement() : null; if (psiElement == null) return null; - final CommonProblemDescriptor problem = refElementNode.getDescriptor(); - if (problem instanceof ProblemDescriptor) { - PsiElement elementFromDescriptor = ((ProblemDescriptor)problem).getPsiElement(); - if (elementFromDescriptor == null && CommonDataKeys.NAVIGATABLE.is(dataId) && refElementNode.getChildCount() != 0) { - final InspectionTreeNode node = (InspectionTreeNode)refElementNode.getChildAt(0); - if (node.isValid()) { - return InspectionResultsViewUtil.getNavigatableForInvalidNode((ProblemDescriptionNode)node); - } - } - else { - psiElement = elementFromDescriptor; - } - } - if (CommonDataKeys.NAVIGATABLE.is(dataId)) { - return getSelectedNavigatable(problem, psiElement); + return getSelectedNavigatable(null, psiElement); } else if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { - return psiElement != null && psiElement.isValid() ? psiElement : null; + return psiElement.isValid() ? psiElement : null; } } else if (selectedNode instanceof ProblemDescriptionNode && CommonDataKeys.NAVIGATABLE.is(dataId)) { @@ -818,28 +665,14 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro return null; } - void resetTree() { - try { - myTree.setQueueUpdate(true); - final TreePath[] selectionPath = myTree.getSelectionPaths(); - final List expandedPaths = TreeUtil.collectExpandedPaths(myTree); - ((DefaultTreeModel)myTree.getModel()).reload(); - TreeUtil.restoreExpandedPaths(myTree, expandedPaths); - myTree.setSelectionPaths(selectionPath); - } - finally { - myTree.setQueueUpdate(false); - } - } - @Nullable - private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) { + public static Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor) { return getSelectedNavigatable(descriptor, descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null); } @Nullable - private Navigatable getSelectedNavigatable(final CommonProblemDescriptor descriptor, final PsiElement psiElement) { + private static Navigatable getSelectedNavigatable(CommonProblemDescriptor descriptor, PsiElement psiElement) { if (descriptor instanceof ProblemDescriptorBase) { Navigatable navigatable = ((ProblemDescriptorBase)descriptor).getNavigatable(); if (navigatable != null) { @@ -861,25 +694,11 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro startOffset = textRange.getStartOffset(); } } - return PsiNavigationSupport.getInstance().createNavigatable(getProject(), virtualFile, startOffset); + return PsiNavigationSupport.getInstance().createNavigatable(psiElement.getProject(), virtualFile, startOffset); } return null; } - @NotNull - private PsiElement[] collectPsiElements() { - RefEntity[] refElements = myTree.getSelectedElements(); - List psiElements = new ArrayList<>(); - for (RefEntity refElement : refElements) { - PsiElement psiElement = refElement instanceof RefElement ? ((RefElement)refElement).getPsiElement() : null; - if (psiElement != null && psiElement.isValid()) { - psiElements.add(psiElement); - } - } - - return PsiUtilCore.toPsiElementArray(psiElements); - } - @NotNull public InspectionTree getTree() { return myTree; @@ -929,13 +748,7 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro } public boolean isUpdating() { - return myUpdatingRequestors > 0; - } - - void updateRightPanelLoading() { - if (!myDisposed && isUpdating() && myLoadingProgressPreview != null) { - myLoadingProgressPreview.updateLoadingProgress(); - } + return myUpdating; } public boolean hasProblems() { @@ -968,7 +781,6 @@ public class InspectionResultsView extends JPanel implements Disposable, DataPro myRerun = true; if (myScope.isValid()) { AnalysisUIOptions.getInstance(getProject()).save(myGlobalInspectionContext.getUIOptions()); - myGlobalInspectionContext.setTreeState(getTree().getTreeState()); myGlobalInspectionContext.doInspections(myScope); } else { GlobalInspectionContextImpl.NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.view.invalid.scope.message"), NotificationType.INFORMATION).notify(getProject()); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java index 3d3e5fe2baeb..701f18c59d4c 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionResultsViewComparator.java @@ -2,7 +2,6 @@ package com.intellij.codeInspection.ui; -import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; import com.intellij.codeInspection.CommonProblemDescriptor; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.reference.RefElement; @@ -26,6 +25,8 @@ import java.util.Comparator; public class InspectionResultsViewComparator implements Comparator { private static final Logger LOG = Logger.getInstance(InspectionResultsViewComparator.class); + public static final InspectionResultsViewComparator INSTANCE = new InspectionResultsViewComparator(); + public boolean areEqual(InspectionTreeNode o1, InspectionTreeNode o2) { return o1.getClass().equals(o2.getClass()) && compare(o1, o2) == 0; } @@ -192,12 +193,4 @@ public class InspectionResultsViewComparator implements Comparator mySeverityGroupNodes = ContainerUtil.newConcurrentMap(); - @NotNull private final ConcurrentMap> myGroups = ContainerUtil.newConcurrentMap(); + private final InspectionTreeModel myModel; - @NotNull private InspectionTreeState myState = new InspectionTreeState(); private boolean myQueueUpdate; + private final OccurenceNavigator myOccurenceNavigator = new MyOccurrenceNavigator(); public InspectionTree(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionResultsView view) { - Project project = context.getProject(); - setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view)))); + myModel = new InspectionTreeModel(); + setModel(new AsyncTreeModel(myModel, view)); myContext = context; setCellRenderer(new InspectionTreeCellRenderer(view)); setRootVisible(false); setShowsRootHandles(true); UIUtil.setLineStyleAngled(this); - addTreeWillExpandListener(new ExpandListener()); + if (!ApplicationManager.getApplication().isUnitTestMode()) { + getSelectionModel().addTreeSelectionListener(e -> { + if (isUnderQueueUpdate()) return; + InspectionResultsView view1 = getContext().getView(); + if (view1 != null && !view1.isDisposed()) { + view1.syncRightPanel(); + if (view1.isAutoScrollMode()) { + OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(this), false); + } + } + }); - myState.getExpandedUserObjects().add(project); + EditSourceOnDoubleClickHandler.install(this); - TreeUtil.installActions(this); - new TreeSpeedSearch(this, o -> InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString())); + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + InspectionResultsView view1 = getContext().getView(); + if (view1 != null && !view1.isDisposed()) { + OpenSourceUtil.openSourcesFrom(DataManager.getInstance().getDataContext(view1), false); + } + } + } + }); + TreeUtil.installActions(this); + PopupHandler.installPopupHandler(this, IdeActions.INSPECTION_TOOL_WINDOW_TREE_POPUP, ActionPlaces.CODE_INSPECTION); + new TreeSpeedSearch(this, o -> InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString())); + } - addTreeSelectionListener(e -> { - TreePath newSelection = e.getNewLeadSelectionPath(); - if (newSelection != null && !isUnderQueueUpdate()) { - myState.setSelectionPath(newSelection); + getModel().addTreeModelListener(new TreeModelAdapter() { + //TODO the same as DiscoveredTestTree (see setRootVisible) + boolean myAlreadyDone; + @Override + protected void process(@NotNull TreeModelEvent event, @NotNull EventType type) { + if (!myAlreadyDone && getSelectionCount() == 0) { + myAlreadyDone = true; + EdtInvocationManager.getInstance().invokeLater(() -> { + expandPath(new TreePath(myModel.getRoot())); + SmartExpander.installOn(InspectionTree.this); + InspectionResultsView v = getContext().getView(); + if (v != null && !v.isDisposed()) { + v.syncRightPanel(); + } + }); + } } }); } + public InspectionTreeNode getRoot() { + return myModel.getRoot(); + } + + public InspectionTreeModel getInspectionTreeModel() { + return myModel; + } + public void setQueueUpdate(boolean queueUpdate) { myQueueUpdate = queueUpdate; } @@ -88,19 +142,7 @@ public class InspectionTree extends Tree { } public void removeAllNodes() { - mySeverityGroupNodes.clear(); - myGroups.clear(); - getRoot().removeAllChildren(); - ApplicationManager.getApplication().invokeLater(() -> { - InspectionResultsView view = myContext.getView(); - if (view != null && !view.isDisposed()) { - nodeStructureChanged(getRoot()); - } - }); - } - - public InspectionTreeNode getRoot() { - return (InspectionTreeNode)getModel().getRoot(); + myModel.clearTree(); } @Nullable @@ -119,8 +161,17 @@ public class InspectionTree extends Tree { @Nullable public InspectionToolWrapper getSelectedToolWrapper(boolean allowDummy) { final TreePath[] paths = getSelectionPaths(); - if (paths == null) return null; - InspectionToolWrapper toolWrapper = null; + if (paths == null) { + InspectionProfileImpl profile = myContext.getView().getCurrentProfile(); + String singleToolName = profile.getSingleTool(); + if (singleToolName != null) { + InspectionToolWrapper tool = profile.getInspectionTool(singleToolName, myContext.getProject()); + LOG.assertTrue(tool != null); + return tool; + } + return null; + } + InspectionToolWrapper resultWrapper = null; for (TreePath path : paths) { Object[] nodes = path.getPath(); for (int j = nodes.length - 1; j >= 0; j--) { @@ -128,23 +179,25 @@ public class InspectionTree extends Tree { if (node instanceof InspectionGroupNode) { return null; } + InspectionToolWrapper wrapper = null; if (node instanceof InspectionNode) { - InspectionToolWrapper wrapper = ((InspectionNode)node).getToolWrapper(); - if (!allowDummy && getContext().getPresentation(wrapper).isDummy()) { - continue; - } - if (toolWrapper == null) { - toolWrapper = wrapper; - } - else if (toolWrapper != wrapper) { - return null; - } - break; + wrapper = ((InspectionNode)node).getToolWrapper(); + } else if (node instanceof SuppressableInspectionTreeNode) { + wrapper = ((SuppressableInspectionTreeNode)node).getPresentation().getToolWrapper(); } + if (wrapper == null || !allowDummy && getContext().getPresentation(wrapper).isDummy()) { + continue; + } + if (resultWrapper == null) { + resultWrapper = wrapper; + } + else if (resultWrapper != wrapper) { + return null; + } + break; } } - - return toolWrapper; + return resultWrapper; } @Override @@ -203,6 +256,15 @@ public class InspectionTree extends Tree { return RefEntity.EMPTY_ELEMENTS_ARRAY; } + public OccurenceNavigator getOccurenceNavigator() { + return myOccurenceNavigator; + } + + public void selectNode(InspectionTreeNode node) { + TreePath path = getPathFor(node); + TreeUtil.selectPath(this, path); + } + private static void addElementsInNode(InspectionTreeNode node, Set out) { if (!node.isValid()) return; if (node instanceof RefElementNode) { @@ -213,45 +275,44 @@ public class InspectionTree extends Tree { final RefEntity element = ((ProblemDescriptionNode)node).getElement(); out.add(element); } - final Enumeration children = node.children(); - while (children.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)children.nextElement(); + + for (InspectionTreeNode child : node.getChildren()) { addElementsInNode(child, out); } } @NotNull public CommonProblemDescriptor[] getAllValidSelectedDescriptors() { - return BatchModeDescriptorsUtil.flattenDescriptors(getSelectedDescriptorPacks(false, null, true)); + return BatchModeDescriptorsUtil.flattenDescriptors(getSelectedDescriptorPacks(false, null, true, null)); } @NotNull public CommonProblemDescriptor[] getSelectedDescriptors() { - return BatchModeDescriptorsUtil.flattenDescriptors(getSelectedDescriptorPacks(false, null, false)); + return BatchModeDescriptorsUtil.flattenDescriptors(getSelectedDescriptorPacks(false, null, false, null)); } @NotNull public List getSelectedDescriptorPacks(boolean sortedByPosition, @Nullable Set readOnlyFilesSink, - boolean allowResolved) { - final TreePath[] paths = getSelectionPaths(); + boolean allowResolved, + TreePath[] paths) { + if (paths == null) { + ApplicationManager.getApplication().assertIsDispatchThread(); + paths = getSelectionPaths(); + } if (paths == null) return Collections.emptyList(); - // key can be node or VirtualFile (if problem descriptor node parent is a file/member RefElementNode). MultiMap parentToChildNode = new MultiMap<>(); - TreeUtil - .treePathTraverser(this) - .withRoots(Arrays.asList(paths)) - .traverse() - .map(p -> p.getLastPathComponent()) + //TODO expected thread + TreeTraversal.PLAIN_BFS.traversal( + ContainerUtil.map(paths, p -> (InspectionTreeNode)p.getLastPathComponent()), + (InspectionTreeNode n) -> myModel.getChildren(n)) .filter(ProblemDescriptionNode.class) .filter(node -> node.getDescriptor() != null && isNodeValidAndIncluded(node, allowResolved)) .consumeEach(node -> { Object key = getVirtualFileOrEntity(node.getElement()); parentToChildNode.putValue(key, node.getDescriptor()); }); - - final List descriptors = new ArrayList<>(); for (Map.Entry> entry : parentToChildNode.entrySet()) { Object key = entry.getKey(); @@ -268,68 +329,53 @@ public class InspectionTree extends Tree { return descriptors; } - @NotNull - InspectionTreeNode getToolParentNode(@NotNull InspectionToolWrapper toolWrapper, - HighlightDisplayLevel errorLevel, - boolean groupedBySeverity, - boolean isSingleInspectionRun) { - //synchronize - if (!groupedBySeverity && isSingleInspectionRun) { - return getRoot(); - } - String[] groupPath = toolWrapper.getGroupPath(); - if (groupPath.length == 0) { - LOG.error("groupPath is empty for tool: " + toolWrapper.getShortName() + ", class: " + toolWrapper.getTool().getClass()); - return getRelativeRootNode(groupedBySeverity, errorLevel); - } - ConcurrentMap map = myGroups.get(errorLevel); - if (map == null) { - map = ConcurrencyUtil.cacheOrGet(myGroups, errorLevel, ConcurrentCollectionFactory.createMap(new TObjectHashingStrategy() { - @Override - public int computeHashCode(String[] object) { - return Arrays.hashCode(object); - } - - @Override - public boolean equals(String[] o1, String[] o2) { - return Arrays.equals(o1, o2); - } - })); - } - InspectionGroupNode group; - if (groupedBySeverity) { - group = map.get(groupPath); - } - else { - group = null; - for (Map groupMap : myGroups.values()) { - if ((group = groupMap.get(groupPath)) != null) break; - } - } - if (group == null) { - if (isSingleInspectionRun) { - return getRelativeRootNode(true, errorLevel); - } - group = map.computeIfAbsent(groupPath, p -> insertGroupNode(p, getRelativeRootNode(groupedBySeverity, errorLevel))); - } - return group; + @Nullable + @Override + public TreePath[] getSelectionPaths() { + ApplicationManager.getApplication().assertIsDispatchThread(); + return super.getSelectionPaths(); } @NotNull - private InspectionTreeNode getRelativeRootNode(boolean isGroupedBySeverity, HighlightDisplayLevel level) { - if (isGroupedBySeverity) { - InspectionSeverityGroupNode severityGroupNode = mySeverityGroupNodes.get(level); - if (severityGroupNode == null) { - InspectionSeverityGroupNode newNode = new InspectionSeverityGroupNode(myContext.getCurrentProfile().getProfileManager().getSeverityRegistrar(), level); - severityGroupNode = ConcurrencyUtil.cacheOrGet(mySeverityGroupNodes, level, newNode); - if (severityGroupNode == newNode) { - InspectionTreeNode root = getRoot(); - root.insertByOrder(severityGroupNode, false); - } - } - return severityGroupNode; + InspectionTreeNode getToolProblemsRootNode(@NotNull InspectionToolWrapper toolWrapper, + @NotNull HighlightDisplayLevel errorLevel, + boolean groupedBySeverity, + boolean isSingleInspectionRun) { + InspectionTreeNode parent = getToolParentNode(toolWrapper, errorLevel, groupedBySeverity, isSingleInspectionRun); + if (isSingleInspectionRun) { + return parent; } - return getRoot(); + return myModel.createInspectionNode(toolWrapper, myContext.getCurrentProfile(), parent); + } + + @NotNull + private InspectionTreeNode getToolParentNode(@NotNull InspectionToolWrapper toolWrapper, + @NotNull HighlightDisplayLevel errorLevel, + boolean groupedBySeverity, + boolean isSingleInspectionRun) { + //synchronize + if (!groupedBySeverity && isSingleInspectionRun) { + return myModel.getRoot(); + } + + InspectionTreeNode currentNode = groupedBySeverity + ? myModel.createSeverityGroupNode(myContext.getCurrentProfile().getProfileManager().getSeverityRegistrar(), + errorLevel, + myModel.getRoot()) + : myModel.getRoot(); + + if (isSingleInspectionRun) return currentNode; + + String[] groupPath = toolWrapper.getGroupPath(); + if (groupPath.length == 0) { + LOG.error("groupPath is empty for tool: " + toolWrapper.getShortName() + ", class: " + toolWrapper.getTool().getClass()); + } + + for (String subGroup : groupPath) { + currentNode = myModel.createGroupNode(subGroup, currentNode); + } + + return currentNode; } public boolean areDescriptorNodesSelected() { @@ -343,10 +389,13 @@ public class InspectionTree extends Tree { return true; } - public int getSelectedProblemCount(boolean allowSuppressed) { + public int getSelectedProblemCount() { int count = 0; for (TreePath path : TreeUtil.selectMaximals(getSelectionPaths())) { - count += ((InspectionTreeNode)path.getLastPathComponent()).getProblemCount(allowSuppressed); + LevelAndCount[] levels = ((InspectionTreeNode)path.getLastPathComponent()).getProblemLevels(); + for (LevelAndCount level : levels) { + count += level.getCount(); + } } return count; } @@ -358,58 +407,57 @@ public class InspectionTree extends Tree { !node.isQuickFixAppliedFromView())); } - private void nodeStructureChanged(InspectionTreeNode node) { - ((DefaultTreeModel)getModel()).nodeStructureChanged(node); - } - - public void queueUpdate() { - ((InspectionRootNode) getRoot()).getUpdater().update(true); - } - - public void restoreExpansionAndSelection(boolean treeNodesMightChange) { - myState.restoreExpansionAndSelection(this, treeNodesMightChange); - } - public void removeSelectedProblems() { + ApplicationManager.getApplication().assertIsDispatchThread(); + TreePath[] selected = getSelectionPaths(); + if (selected == null) return; if (!getContext().getUIOptions().FILTER_RESOLVED_ITEMS) { + for (TreePath path : selected) { + InspectionTreeNode node = (InspectionTreeNode)path.getLastPathComponent(); + myModel.traverse(node, n -> { + n.dropProblemCountCaches(); + return true; + }); + } return; } - synchronized (getContext().getView().getTreeStructureUpdateLock()) { - TreePath[] selected = getSelectionPaths(); - if (selected == null) return; - Set processedNodes = new THashSet<>(); - List toRemove = new ArrayList<>(); - List pathsToSelect = new ArrayList<>(); - for (TreePath path : selected) { - Object[] nodePath = path.getPath(); + Set processedNodes = new THashSet<>(); + List toRemove = new ArrayList<>(); + List pathsToSelect = new ArrayList<>(); + for (TreePath path : selected) { + Object[] nodePath = path.getPath(); - // ignore root - for (int i = 1; i < nodePath.length; i++) { - InspectionTreeNode node = (InspectionTreeNode) nodePath[i]; - if (!processedNodes.add(node)) break; + // ignore root + for (int i = 1; i < nodePath.length; i++) { + InspectionTreeNode node = (InspectionTreeNode) nodePath[i]; + if (!processedNodes.add(node)) continue; - if (shouldDelete(node)) { - toRemove.add(node); - TreePath toSelect = getParentPath(path, nodePath.length - i); - if (toSelect != null) { - pathsToSelect.add(toSelect); - } - break; + if (shouldDelete(node)) { + toRemove.add(node); + TreePath toSelect = getParentPath(path, nodePath.length - i); + if (toSelect != null) { + pathsToSelect.add(toSelect); } + break; } } - - if (toRemove.isEmpty()) return; - DefaultTreeModel model = (DefaultTreeModel)getModel(); - for (InspectionTreeNode node : toRemove) { - TreeNode parent = node.getParent(); - if (parent != null) { - model.removeNodeFromParent(node); - } - } - - TreeUtil.selectPath(this, TreeUtil.findCommonPath(pathsToSelect.toArray(new TreePath[0]))); } + + if (toRemove.isEmpty()) return; + Set parents = new THashSet<>(); + for (InspectionTreeNode node : toRemove) { + InspectionTreeNode parent = node.getParent(); + if (parent != null) { + myModel.remove(node); + parents.add(parent); + } + } + + for (InspectionTreeNode parent : parents) { + parent.dropProblemCountCaches(); + } + TreeUtil.selectPath(this, TreeUtil.findCommonPath(pathsToSelect.toArray(new TreePath[0]))); + revalidate(); repaint(); } @@ -425,10 +473,16 @@ public class InspectionTree extends Tree { private boolean shouldDelete(InspectionTreeNode node) { if (node instanceof RefElementNode) { RefElementNode refElementNode = (RefElementNode)node; - RefEntity refEntity = refElementNode.getElement(); - if (refEntity == null || isEntityExcludedOrResolvedRecursively(refEntity, refElementNode.getPresentation())) { + InspectionToolPresentation presentation = refElementNode.getPresentation(); + RefEntity element = refElementNode.getElement(); + if (element == null || + presentation.isProblemResolved(element) || + presentation.isExcluded(element) || + presentation.isSuppressed(element)) { return true; } + List children = node.getChildren(); + return !children.isEmpty() && children.stream().allMatch(this::shouldDelete); } else if (node instanceof ProblemDescriptionNode) { ProblemDescriptionNode problemDescriptionNode = (ProblemDescriptionNode)node; @@ -439,7 +493,7 @@ public class InspectionTree extends Tree { } } else if (node instanceof InspectionGroupNode || node instanceof InspectionSeverityGroupNode || node instanceof InspectionModuleNode || node instanceof InspectionPackageNode) { - return IntStream.range(0, node.getChildCount()).mapToObj(i -> (InspectionTreeNode)node.getChildAt(i)).allMatch(this::shouldDelete); + return node.getChildren().stream().allMatch(this::shouldDelete); } else if (node instanceof InspectionNode) { InspectionToolPresentation presentation = myContext.getPresentation(((InspectionNode)node).getToolWrapper()); @@ -452,80 +506,16 @@ public class InspectionTree extends Tree { return false; } - private static boolean isEntityExcludedOrResolvedRecursively(RefEntity key, InspectionToolPresentation presentation) { - if (presentation.isProblemResolved(key) || - presentation.isExcluded(key) || - presentation.isSuppressed(key)) { - return true; - } - List children = key.getChildren(); - return !children.isEmpty() && children.stream().allMatch(entity -> isEntityExcludedOrResolvedRecursively(entity, presentation)); - } - - public InspectionTreeState getTreeState() { - return myState; - } - - public void setTreeState(@NotNull InspectionTreeState treeState) { - myState = treeState; - } - - private class ExpandListener implements TreeWillExpandListener { - @Override - public void treeWillExpand(TreeExpansionEvent event) { - final InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent(); - myState.getExpandedUserObjects().add(node.getUserObject()); - } - - @Override - public void treeWillCollapse(TreeExpansionEvent event) { - InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent(); - myState.getExpandedUserObjects().remove(node.getUserObject()); - } - } - @NotNull public GlobalInspectionContextImpl getContext() { return myContext; } - private InspectionGroupNode insertGroupNode(@NotNull String[] groupPath, InspectionTreeNode parent) { - InspectionTreeNode currentNode = parent; - - for (int groupIdx = 0; groupIdx < groupPath.length; groupIdx++) { - String subGroup = groupPath[groupIdx]; - - InspectionTreeNode next = null; - for (int i = 0; i < currentNode.getChildCount(); i++) { - TreeNode child = currentNode.getChildAt(i); - if (child instanceof InspectionGroupNode && ((InspectionGroupNode)child).getSubGroup().equals(subGroup)) { - next = (InspectionTreeNode)child; - break; - } - } - - if (next == null) { - for (int i = groupIdx; i < groupPath.length; i++) { - InspectionResultsView view = getContext().getView(); - if (view != null && !view.isDisposed()) { - currentNode = currentNode.insertByOrder(new InspectionGroupNode(groupPath[i]), false); - } - } - break; - } - else { - currentNode = next; - } - } - - return (InspectionGroupNode)currentNode; - } - @NotNull private static String[] getGroupPath(@NotNull InspectionGroupNode node) { List path = new ArrayList<>(2); while (true) { - TreeNode parent = node.getParent(); + InspectionTreeNode parent = node.getParent(); if (!(parent instanceof InspectionGroupNode)) break; node = (InspectionGroupNode)parent; path.add(node.getSubGroup()); @@ -546,4 +536,138 @@ public class InspectionTree extends Tree { } return entity; } + + private class MyOccurrenceNavigator implements OccurenceNavigator { + @Override + public boolean hasNextOccurence() { + return getNextNode(true) != null; + } + + @Override + public boolean hasPreviousOccurence() { + return getNextNode(false) != null; + } + + @Override + public OccurenceInfo goNextOccurence() { + InspectionTreeNode node = getNextNode(true); + if (node == null) return null; + selectNode(node); + return new OccurenceInfo(createDescriptorForNode(node), -1, -1); + } + + @Override + public OccurenceInfo goPreviousOccurence() { + InspectionTreeNode node = getNextNode(false); + selectNode(node); + return node == null ? null : new OccurenceInfo(createDescriptorForNode(node), -1, -1); + } + + @NotNull + @Override + public String getNextOccurenceActionName() { + return InspectionsBundle.message("inspection.action.go.next"); + } + + @NotNull + @Override + public String getPreviousOccurenceActionName() { + return InspectionsBundle.message("inspection.action.go.prev"); + } + + private InspectionTreeNode getNextNode(boolean next) { + InspectionTreeNode node = getSelectedNode(); + if (node == null) { + return null; + } + return myModel.traverseFrom(node, next).filter(n -> n != node).filter(n -> isOccurrenceNode(n)).first(); + } + + private InspectionTreeNode getSelectedNode() { + TreePath path = getSelectionPath(); + if (path == null) return null; + return (InspectionTreeNode)path.getLastPathComponent(); + } + + private boolean isOccurrenceNode(@NotNull InspectionTreeNode node) { + if (node.isExcluded()) { + return false; + } + if (node instanceof RefElementNode) { + final RefElementNode refNode = (RefElementNode)node; + if (hasDescriptorUnder(refNode)) return false; + final RefEntity element = refNode.getElement(); + return element != null && element.isValid(); + } + return node instanceof ProblemDescriptionNode; + } + + @Nullable + private Navigatable createDescriptorForNode(@NotNull InspectionTreeNode node) { + if (node.isExcluded()) { + return null; + } + if (node instanceof RefElementNode) { + final RefElementNode refNode = (RefElementNode)node; + if (hasDescriptorUnder(refNode)) return null; + final RefEntity element = refNode.getElement(); + if (element == null || !element.isValid()) return null; + if (element instanceof RefElement) { + return getOpenFileDescriptor((RefElement)element); + } + } + else if (node instanceof ProblemDescriptionNode) { + ProblemDescriptionNode problemNode = (ProblemDescriptionNode)node; + boolean isValid = problemNode.isValid() && (!problemNode.isQuickFixAppliedFromView() || + problemNode.calculateIsValid()); + return isValid + ? navigate(problemNode.getDescriptor()) + : InspectionResultsViewUtil.getNavigatableForInvalidNode(problemNode); + } + return null; + } + + + @Nullable + private Navigatable navigate(final CommonProblemDescriptor descriptor) { + return InspectionResultsView.getSelectedNavigatable(descriptor); + } + + @Nullable + private Navigatable getOpenFileDescriptor(final RefElement refElement) { + PsiElement psiElement = refElement.getPsiElement(); + if (psiElement == null) return null; + final PsiFile containingFile = psiElement.getContainingFile(); + if (containingFile == null) return null; + VirtualFile file = containingFile.getVirtualFile(); + if (file == null) return null; + return PsiNavigationSupport.getInstance().createNavigatable(refElement.getRefManager().getProject(), file, + psiElement.getTextOffset()); + } + + private boolean hasDescriptorUnder(RefElementNode node) { + InspectionTreeNode current = node; + while (current != null && current.getChildCount() != 0) { + current = current.getChildAt(0); + if (current instanceof ProblemDescriptionNode) return true; + } + return false; + } + } + + private TreePath getPathFor(InspectionTreeNode node) { + TreePath result = TreePathUtil.pathToTreeNode(node); + + Stack s = new Stack<>(); + TreePath current = result; + while (current != null) { + s.add(current); + current = current.getParentPath(); + } + while (!s.isEmpty()) { + TreePath p = s.pop(); + expandPath(p); + } + return result; + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeCellRenderer.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeCellRenderer.java index fd189d27ce91..824507829796 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeCellRenderer.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeCellRenderer.java @@ -55,7 +55,7 @@ class InspectionTreeCellRenderer extends ColoredTreeCellRenderer { boolean hasFocus) { InspectionTreeNode node = (InspectionTreeNode)value; - append(node.toString(), + append(node.getPresentableText(), patchMainTextAttrs(node, node.appearsBold() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : getMainForegroundAttributes(node))); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeModel.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeModel.java new file mode 100644 index 000000000000..62e141d6f8fb --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeModel.java @@ -0,0 +1,220 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.codeInspection.ui; + +import com.intellij.codeHighlighting.HighlightDisplayLevel; +import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; +import com.intellij.codeInspection.CommonProblemDescriptor; +import com.intellij.codeInspection.ex.InspectionProfileImpl; +import com.intellij.codeInspection.ex.InspectionToolWrapper; +import com.intellij.codeInspection.offline.OfflineProblemDescriptor; +import com.intellij.codeInspection.offlineViewer.OfflineDescriptorResolveResult; +import com.intellij.codeInspection.offlineViewer.OfflineProblemDescriptorNode; +import com.intellij.codeInspection.reference.RefEntity; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ReadAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.module.Module; +import com.intellij.ui.tree.BaseTreeModel; +import com.intellij.ui.tree.TreePathUtil; +import com.intellij.util.ArrayUtil; +import com.intellij.util.ObjectUtils; +import com.intellij.util.Processor; +import com.intellij.util.containers.BidirectionalMap; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.JBIterable; +import com.intellij.util.containers.TreeTraversal; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.tree.TreePath; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +public class InspectionTreeModel extends BaseTreeModel { + private static final Logger LOG = Logger.getInstance(InspectionTreeModel.class); + private final InspectionRootNode myRoot = new InspectionRootNode(this); + private final Map myChildren = new ConcurrentHashMap<>(); + private final Map myParents = new ConcurrentHashMap<>(); + + @Override + public int getIndexOfChild(Object object, Object child) { + return Collections.binarySearch(getChildren(object), (InspectionTreeNode)child, InspectionResultsViewComparator.INSTANCE); + } + + public void reload() { + treeNodesChanged(null, null, null); + } + + private static class Children { + private static final InspectionTreeNode[] EMPTY_ARRAY = new InspectionTreeNode[0]; + + private volatile InspectionTreeNode[] myChildren = EMPTY_ARRAY; + private final BidirectionalMap myUserObject2Node = new BidirectionalMap<>(); + } + + public InspectionTreeModel() {} + + @Override + public List getChildren(Object parent) { + Children nodes = myChildren.get(((InspectionTreeNode)parent)); + return nodes == null ? Collections.emptyList() : Collections.unmodifiableList(Arrays.asList(nodes.myChildren)); + } + + @Override + public InspectionRootNode getRoot() { + return myRoot; + } + + @Nullable + public InspectionTreeNode getParent(InspectionTreeNode node) { + return myParents.get(node); + } + + public void traverse(InspectionTreeNode node, Processor processor) { + TreeTraversal.PRE_ORDER_DFS.traversal(node, n -> getChildren(n)).processEach(processor); + } + + @NotNull + public JBIterable traverseFrom(InspectionTreeNode node, boolean direction) { + return JBIterable.generate(node, n -> getParent(n)).filter(n -> getParent(n) != null).flatMap(n1 -> { + InspectionTreeNode p = getParent(n1); + @SuppressWarnings("ConstantConditions") + List children = p.getChildren(); + int idx = getIndexOfChild(p, n1); + InspectionTreeNode[] arr = children.toArray(InspectionTreeNode.EMPTY_ARRAY); + List sublist = Arrays.asList(arr).subList(idx + ((n1 == node) ? 0 : 1), children.size()); + return TreeTraversal.PRE_ORDER_DFS.traversal(sublist, (InspectionTreeNode n) -> direction ? getChildren(n) : ContainerUtil.reverse(getChildren(n))); + }); + } + + public void removeChild(@NotNull InspectionTreeNode node, int childIndex) { + InspectionTreeNode removed = myChildren.get(node).myChildren[childIndex]; + remove(removed); + treeNodesChanged(null, null, null); + treeStructureChanged(null, null, null); + } + + public void removeChildren(@NotNull InspectionTreeNode node) { + doRemove(node, node); + treeNodesChanged(null, null, null); + treeStructureChanged(null, null, null); + } + + public void remove(@NotNull InspectionTreeNode node) { + doRemove(node, null); + treeNodesChanged(null, null, null); + treeStructureChanged(null, null, null); + } + + private synchronized void doRemove(@NotNull InspectionTreeNode node, @Nullable InspectionTreeNode skip) { + for (InspectionTreeNode child : getChildren(node)) { + doRemove(child, skip); + } + if (node != skip) { + InspectionTreeNode parent = myParents.remove(node); + if (parent != null) { + Children parentChildren = myChildren.get(parent); + if (parentChildren != null) { + parentChildren.myChildren = ArrayUtil.remove(parentChildren.myChildren, node); + parentChildren.myUserObject2Node.removeValue(node); + } + } + } + } + + public synchronized void clearTree() { + myChildren.clear(); + myParents.clear(); + } + + @NotNull + public InspectionModuleNode createModuleNode(@NotNull Module module, @NotNull InspectionTreeNode parent) { + return getOrAdd(module, () -> new InspectionModuleNode(module, this), parent); + } + + @NotNull + public InspectionPackageNode createPackageNode(String packageName, @NotNull InspectionTreeNode parent) { + return getOrAdd(packageName, () -> new InspectionPackageNode(packageName, this), parent); + } + + @NotNull + public InspectionGroupNode createGroupNode(String group, @NotNull InspectionTreeNode parent) { + return getOrAdd(group, () -> new InspectionGroupNode(group, this), parent); + } + + @NotNull + public InspectionSeverityGroupNode createSeverityGroupNode(SeverityRegistrar severityRegistrar, HighlightDisplayLevel level, @NotNull InspectionTreeNode parent) { + return getOrAdd(level, () -> new InspectionSeverityGroupNode(severityRegistrar, level, this), parent); + } + + @NotNull + public RefElementNode createRefElementNode(@Nullable RefEntity entity, + @NotNull Supplier supplier, + @NotNull InspectionTreeNode parent) { + return getOrAdd(entity, () -> ReadAction.compute(supplier::get), parent); + } + + public void createCustomNode(@NotNull Object userObject, @NotNull Supplier supplier, @NotNull InspectionTreeNode parent) { + getOrAdd(userObject, supplier, parent); + } + + @NotNull + public InspectionNode createInspectionNode(@NotNull InspectionToolWrapper toolWrapper, InspectionProfileImpl profile, @NotNull InspectionTreeNode parent) { + return getOrAdd(toolWrapper, () -> new InspectionNode(toolWrapper, profile, this), parent); + } + + public void createProblemDescriptorNode(RefEntity element, + @NotNull CommonProblemDescriptor descriptor, + @NotNull InspectionToolPresentation presentation, + @NotNull InspectionTreeNode parent) { + getOrAdd(descriptor, () -> ReadAction.compute(() -> new ProblemDescriptionNode(element, descriptor, presentation, this)), parent); + } + + public void createOfflineProblemDescriptorNode(@NotNull OfflineProblemDescriptor offlineDescriptor, + @NotNull OfflineDescriptorResolveResult resolveResult, + @NotNull InspectionToolPresentation presentation, + @NotNull InspectionTreeNode parent) { + getOrAdd(offlineDescriptor, + () -> ReadAction.compute(() -> new OfflineProblemDescriptorNode(resolveResult, presentation, offlineDescriptor, this)), + parent); + } + + private synchronized T getOrAdd(Object userObject, Supplier supplier, InspectionTreeNode parent) { + LOG.assertTrue(!ApplicationManager.getApplication().isDispatchThread()); + Children children = myChildren.computeIfAbsent(parent, __ -> new Children()); + if (userObject == null) { + userObject = ObjectUtils.NULL; + } + InspectionTreeNode node = children.myUserObject2Node.get(userObject); + if (node == null) { + node = supplier.get(); + InspectionTreeNode finalNode = node; + int idx = ReadAction.compute(() -> Arrays.binarySearch(children.myChildren, finalNode, InspectionResultsViewComparator.INSTANCE)); + if (idx >= 0) { + InspectionTreeNode finalN = node; + LOG.error(ReadAction.compute(() -> "idx = " + idx + " node = " + finalN + " parent = " + parent + " children " + Arrays.toString(children.myChildren))); + } + int insertionPoint = -idx - 1; + children.myChildren = ArrayUtil.insert(children.myChildren, insertionPoint, node); + myParents.put(node, parent); + children.myUserObject2Node.put(userObject, node); + + LOG.assertTrue(children.myChildren.length == children.myUserObject2Node.size()); + + if (node instanceof SuppressableInspectionTreeNode) { + ((SuppressableInspectionTreeNode)node).nodeAdded(); + } + + TreePath path = TreePathUtil.pathToTreeNode(node); + TreePath parentPath = path.getParentPath(); + treeNodesInserted(parentPath, null, null); + while (parentPath != null) { + treeStructureChanged(parentPath, null, null); + parentPath = parentPath.getParentPath(); + } + } + //noinspection unchecked + return (T)node; + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeNode.java index 59d1489f3784..83347ec10e55 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeNode.java @@ -5,27 +5,23 @@ package com.intellij.codeInspection.ui; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.lang.annotation.HighlightSeverity; -import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.util.AtomicClearableLazyValue; +import com.intellij.util.ObjectUtils; import com.intellij.util.containers.WeakInterner; -import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.TObjectHashingStrategy; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Enumeration; +import java.util.*; /** * @author max */ -public abstract class InspectionTreeNode extends DefaultMutableTreeNode { +public abstract class InspectionTreeNode implements TreeNode { + static final InspectionTreeNode[] EMPTY_ARRAY = new InspectionTreeNode[0]; private static final WeakInterner LEVEL_AND_COUNT_INTERNER = new WeakInterner<>(new TObjectHashingStrategy() { @Override public int computeHashCode(LevelAndCount[] object) { @@ -55,10 +51,11 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { return doesNeedInternProblemLevels() ? LEVEL_AND_COUNT_INTERNER.intern(arr) : arr; } }; - protected volatile InspectionTreeUpdater myUpdater; + @NotNull + private final InspectionTreeModel myModel; - protected InspectionTreeNode(Object userObject) { - super(userObject); + protected InspectionTreeNode(@NotNull InspectionTreeModel model) { + myModel = model; } protected boolean doesNeedInternProblemLevels() { @@ -82,7 +79,7 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { InspectionTreeNode current = this; while (current != null) { current.myProblemLevels.drop(); - current = (InspectionTreeNode)current.getParent(); + current = current.getParent(); } } @@ -91,9 +88,7 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { } protected void visitProblemSeverities(@NotNull TObjectIntHashMap counter) { - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); + for (InspectionTreeNode child : getChildren()) { for (LevelAndCount levelAndCount : child.getProblemLevels()) { if (!counter.adjustValue(levelAndCount.getLevel(), levelAndCount.getCount())) { counter.put(levelAndCount.getLevel(), levelAndCount.getCount()); @@ -102,29 +97,19 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { } } - public int getProblemCount(boolean allowSuppressed) { - int sum = 0; - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); - sum += child.getProblemCount(allowSuppressed); - } - return sum; - } - public boolean isValid() { return true; } public boolean isExcluded() { - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); + List children = getChildren(); + for (InspectionTreeNode child : children) { if (!child.isExcluded()) { return false; } } - return getChildCount() != 0; + + return !children.isEmpty() ; } public boolean appearsBold() { @@ -137,85 +122,27 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { } public void excludeElement() { - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); + for (InspectionTreeNode child : getChildren()) { child.excludeElement(); } dropProblemCountCaches(); } public void amnestyElement() { - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); + for (InspectionTreeNode child : getChildren()) { child.amnestyElement(); } dropProblemCountCaches(); } - public InspectionTreeNode insertByOrder(InspectionTreeNode child, boolean allowDuplication) { - return ReadAction.compute(() -> { - if (!allowDuplication) { - int index = getIndex(child); - if (index != -1) { - return (InspectionTreeNode)getChildAt(index); - } - } - int index = TreeUtil.indexedBinarySearch(this, child, InspectionResultsViewComparator.getInstance()); - if (!allowDuplication && index >= 0) { - return (InspectionTreeNode)getChildAt(index); - } - insert(child, Math.abs(index + 1)); - return child; - }); - } - - @Override - public void add(MutableTreeNode newChild) { - super.add(newChild); - if (myUpdater != null) { - ((InspectionTreeNode)newChild).propagateUpdater(myUpdater); - dropProblemCountCaches(); - myUpdater.updateWithPreviewPanel(); - } - } - - @Override - public void insert(MutableTreeNode newChild, int childIndex) { - super.insert(newChild, childIndex); - if (myUpdater != null) { - ((InspectionTreeNode)newChild).propagateUpdater(myUpdater); - dropProblemCountCaches(); - myUpdater.updateWithPreviewPanel(); - } - } - - @Override public void remove(int childIndex) { - super.remove(childIndex); + myModel.removeChild(this, childIndex); dropProblemCountCaches(); } - protected void nodeAddedToTree() { - } - - private void propagateUpdater(InspectionTreeUpdater updater) { - if (myUpdater != null) return; - myUpdater = updater; - Enumeration enumeration = children(); - while (enumeration.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)enumeration.nextElement(); - child.propagateUpdater(updater); - child.nodeAddedToTree(); - } - } - public RefEntity getContainingFileLocalEntity() { - final Enumeration children = children(); RefEntity current = null; - while (children.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)children.nextElement(); + for (InspectionTreeNode child : getChildren()) { final RefEntity entity = child.getContainingFileLocalEntity(); if (entity == null || current != null) { return null; @@ -226,12 +153,53 @@ public abstract class InspectionTreeNode extends DefaultMutableTreeNode { } @Override - public synchronized TreeNode getParent() { - return super.getParent(); + public boolean isLeaf() { + return getChildren().isEmpty(); + } + + public abstract String getPresentableText(); + + @NotNull + public List getChildren() { + return ObjectUtils.notNull(myModel.getChildren(this), Collections.emptyList()); } @Override - public synchronized void setParent(MutableTreeNode newParent) { - super.setParent(newParent); + public InspectionTreeNode getParent() { + return myModel.getParent(this); + } + + @Override + public int getChildCount() { + return getChildren().size(); + } + + @Override + public InspectionTreeNode getChildAt(int idx) { + return getChildren().get(idx); + } + + public void removeAllChildren() { + myModel.removeChildren(this); + } + + @Override + public int getIndex(TreeNode node) { + return myModel.getIndexOfChild(this, node); + } + + @Override + public boolean getAllowsChildren() { + return true; + } + + @Override + public Enumeration children() { + return Collections.enumeration(getChildren()); + } + + @Override + public String toString() { + return getPresentableText(); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java deleted file mode 100644 index 68dcdf857a2f..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeState.java +++ /dev/null @@ -1,128 +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.codeInspection.ui; - -import com.intellij.util.ui.tree.TreeUtil; - -import javax.swing.tree.TreeNode; -import javax.swing.tree.TreePath; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.Set; - -/** - * @author Dmitry Batkovich - */ -public class InspectionTreeState { - private final Set myExpandedUserObjects = new HashSet<>(); - private InspectionTreeSelectionPath mySelectionPath; - - public Set getExpandedUserObjects() { - return myExpandedUserObjects; - } - - public void setSelectionPath(TreePath selectionPath) { - mySelectionPath = new InspectionTreeSelectionPath(selectionPath); - } - - public void restoreExpansionAndSelection(InspectionTree tree, boolean treeNodeMightChange) { - restoreExpansionStatus((InspectionTreeNode)tree.getModel().getRoot(), tree); - if (mySelectionPath != null) { - mySelectionPath.restore(tree, treeNodeMightChange); - } else { - TreeUtil.selectFirstNode(tree); - } - } - - private void restoreExpansionStatus(InspectionTreeNode node, InspectionTree tree) { - if (getExpandedUserObjects().contains(node.getUserObject())) { - //sortChildren(node); - TreeNode[] pathToNode = node.getPath(); - tree.expandPath(new TreePath(pathToNode)); - Enumeration children = node.children(); - while (children.hasMoreElements()) { - InspectionTreeNode childNode = (InspectionTreeNode)children.nextElement(); - restoreExpansionStatus(childNode, tree); - } - } - } - - private static class InspectionTreeSelectionPath { - private final Object[] myPath; - private final int[] myIndices; - - InspectionTreeSelectionPath(TreePath path) { - myPath = path.getPath(); - myIndices = new int[myPath.length]; - for (int i = 0; i < myPath.length - 1; i++) { - InspectionTreeNode node = (InspectionTreeNode)myPath[i]; - myIndices[i + 1] = getChildIndex(node, (InspectionTreeNode)myPath[i + 1]); - } - } - - private static int getChildIndex(InspectionTreeNode node, InspectionTreeNode child) { - int idx = 0; - Enumeration children = node.children(); - while (children.hasMoreElements()) { - InspectionTreeNode ch = (InspectionTreeNode)children.nextElement(); - if (ch == child) break; - idx++; - } - return idx; - } - - public void restore(InspectionTree tree, boolean treeNodeMightChange) { - tree.getSelectionModel().removeSelectionPaths(tree.getSelectionModel().getSelectionPaths()); - TreeUtil.selectPath(tree, restorePath(tree, treeNodeMightChange)); - } - - private TreePath restorePath(InspectionTree tree, boolean treeNodeMightChange) { - ArrayList newPath = new ArrayList<>(); - - newPath.add(tree.getModel().getRoot()); - restorePath(newPath, 1, treeNodeMightChange); - return new TreePath(newPath.toArray()); - } - - private void restorePath(ArrayList newPath, int idx, boolean treeNodeMightChange) { - if (idx >= myPath.length) return; - InspectionTreeNode oldNode = (InspectionTreeNode)myPath[idx]; - InspectionTreeNode newRoot = (InspectionTreeNode)newPath.get(idx - 1); - - Enumeration children = newRoot.children(); - while (children.hasMoreElements()) { - InspectionTreeNode child = (InspectionTreeNode)children.nextElement(); - if (treeNodeMightChange ? InspectionResultsViewComparator.getInstance().areEqual(child, oldNode) : child == oldNode) { - newPath.add(child); - restorePath(newPath, idx + 1, treeNodeMightChange); - return; - } - } - - // Exactly same element not found. Trying to select somewhat near. - int count = newRoot.getChildCount(); - if (count > 0) { - if (myIndices[idx] < count) { - newPath.add(newRoot.getChildAt(myIndices[idx])); - } - else { - newPath.add(newRoot.getChildAt(count - 1)); - } - } - } - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeUpdater.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeUpdater.java deleted file mode 100644 index 060e33a442ee..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionTreeUpdater.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.codeInspection.ui; - -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.ui.update.MergingUpdateQueue; -import com.intellij.util.ui.update.Update; - -import javax.swing.tree.DefaultTreeModel; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * @author Dmitry Batkovich - */ -public class InspectionTreeUpdater { - private final MergingUpdateQueue myUpdateQueue; - private final InspectionResultsView myView; - private final AtomicBoolean myDoUpdatePreviewPanel = new AtomicBoolean(false); - - public InspectionTreeUpdater(InspectionResultsView view) { - myView = view; - myUpdateQueue = new MergingUpdateQueue("InspectionView", 100, true, view, view); - myUpdateQueue.setPassThrough(false); - } - - public void updateWithPreviewPanel() { - myDoUpdatePreviewPanel.set(true); - update(false); - } - - public void update(boolean force) { - if (ApplicationManager.getApplication().isDispatchThread() && !force) { - return; - } - myUpdateQueue.queue(new MyTreeUpdate()); - } - - private class MyTreeUpdate extends Update { - MyTreeUpdate() { - super("inspection.view.update"); - } - - @Override - public void run() { - if (myView.isDisposed()) return; - final InspectionTree tree = myView.getTree(); - try { - tree.setQueueUpdate(true); - ((DefaultTreeModel)tree.getModel()).reload(); - tree.restoreExpansionAndSelection(true); - myView.openRightPanelIfNeed(); - if (myDoUpdatePreviewPanel.compareAndSet(true, false)) { - myView.updateRightPanelLoading(); - } - } finally { - tree.setQueueUpdate(false); - } - } - - @Override - public boolean canEat(Update update) { - return true; - } - } -} diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewChangeAdapter.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewChangeAdapter.java index a58c1fa9ed7b..e0e5120b32ff 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewChangeAdapter.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewChangeAdapter.java @@ -5,7 +5,6 @@ package com.intellij.codeInspection.ui; import com.intellij.codeInspection.reference.RefElement; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.util.Alarm; @@ -17,7 +16,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; -import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -121,7 +119,7 @@ class InspectionViewChangeAdapter extends PsiTreeChangeAdapter { if (pointer != null) { VirtualFile vFile = pointer.getVirtualFile(); if (vFile == null || !vFile.isValid()) { - dropNodeCache((SuppressableInspectionTreeNode)node); + ((SuppressableInspectionTreeNode)node).dropCache(); if (!needUpdateUI[0]) { needUpdateUI[0] = true; } @@ -154,7 +152,7 @@ class InspectionViewChangeAdapter extends PsiTreeChangeAdapter { VirtualFile vFile = pointer.getVirtualFile(); if (filesToCheck.contains(vFile)) { unPresentFiles.remove(vFile); - dropNodeCache((SuppressableInspectionTreeNode)node); + ((SuppressableInspectionTreeNode)node).dropCache(); if (!needUpdateUI[0]) { needUpdateUI[0] = true; } @@ -168,9 +166,7 @@ class InspectionViewChangeAdapter extends PsiTreeChangeAdapter { nodeProcessor = CompositeProcessor.combine(fileCheckProcessor, nodeProcessor); } - synchronized (myView.getTreeStructureUpdateLock()) { - processNodesIfNeed(myView.getTree().getRoot(), Objects.requireNonNull(nodeProcessor)); - } + myView.getTree().getInspectionTreeModel().traverse(myView.getTree().getInspectionTreeModel().getRoot(), nodeProcessor); if (!unPresentFiles.isEmpty()) { myUnPresentEditedFiles.addAll(unPresentFiles); @@ -178,24 +174,12 @@ class InspectionViewChangeAdapter extends PsiTreeChangeAdapter { if (needUpdateUI[0] && !myAlarm.isDisposed()) { myAlarm.cancelAllRequests(); - myAlarm.addRequest(() -> myView.resetTree(), 100, ModalityState.NON_MODAL); + //TODO replace with more accurate + myAlarm.addRequest(() -> myView.getTree().getInspectionTreeModel().reload(), 100, ModalityState.NON_MODAL); } }, 200); } - private static void dropNodeCache(SuppressableInspectionTreeNode node) { - ReadAction.run(() -> node.dropCache()); - } - - private static void processNodesIfNeed(InspectionTreeNode node, Processor processor) { - if (processor.process(node)) { - final int count = node.getChildCount(); - for (int i = 0; i < count; i++) { - processNodesIfNeed((InspectionTreeNode)node.getChildAt(i), processor); - } - } - } - private static class CompositeProcessor implements Processor { private final Processor myFirstProcessor; private boolean myFirstFinished; @@ -207,7 +191,6 @@ class InspectionViewChangeAdapter extends PsiTreeChangeAdapter { mySecondProcessor = secondProcessor; } - @Override public boolean process(X x) { if (!myFirstFinished && !myFirstProcessor.process(x)) { diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java index 35ad08107051..b4a7909d8c06 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewNavigationPanel.java @@ -18,12 +18,9 @@ package com.intellij.codeInspection.ui; import com.intellij.ui.components.JBLabel; import com.intellij.ui.components.labels.LinkLabel; import com.intellij.util.ui.JBUI; -import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreeNode; import java.awt.*; /** @@ -70,11 +67,11 @@ public class InspectionViewNavigationPanel extends JPanel implements InspectionT myLinks.removeAll(); myLinks.add(Box.createVerticalStrut(JBUI.scale(10))); for (int i = 0; i < currentChildrenCount; i++) { - final TreeNode child = myNode.getChildAt(i); - final LinkLabel link = new LinkLabel(child.toString(), null) { + final InspectionTreeNode child = myNode.getChildAt(i); + final LinkLabel link = new LinkLabel(child.getPresentableText(), null) { @Override public void doClick() { - TreeUtil.selectInTree((DefaultMutableTreeNode)child, true, myTree); + myTree.selectNode(child); } }; link.setBorder(JBUI.Borders.empty(1, 17, 3, 1)); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewSuppressActionHolder.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewSuppressActionHolder.java index d1e2b48c2b4c..e5fa0cd41a2f 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewSuppressActionHolder.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/InspectionViewSuppressActionHolder.java @@ -34,6 +34,7 @@ import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -115,7 +116,7 @@ public class InspectionViewSuppressActionHolder { if (!myElementLanguage.equals(that.myElementLanguage)) return false; if (!myFileBaseLanguage.equals(that.myFileBaseLanguage)) return false; if (!myFileLanguages.equals(that.myFileLanguages)) return false; - if (myInjectionDescriptor != null ? !myInjectionDescriptor.equals(that.myInjectionDescriptor) : that.myInjectionDescriptor != null) { + if (!Objects.equals(myInjectionDescriptor, that.myInjectionDescriptor)) { return false; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/ProblemDescriptionNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/ProblemDescriptionNode.java index a2af6b8b1102..a1607ef8ffa2 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/ProblemDescriptionNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/ProblemDescriptionNode.java @@ -13,6 +13,7 @@ import com.intellij.codeInspection.ex.InspectionToolWrapper; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.profile.codeInspection.InspectionProfileManager; import com.intellij.psi.PsiElement; @@ -35,15 +36,17 @@ public class ProblemDescriptionNode extends SuppressableInspectionTreeNode { public ProblemDescriptionNode(RefEntity element, @NotNull CommonProblemDescriptor descriptor, - @NotNull InspectionToolPresentation presentation) { - this(element, descriptor, presentation, null); + @NotNull InspectionToolPresentation presentation, + @NotNull InspectionTreeModel model) { + this(element, descriptor, presentation, null, model); } protected ProblemDescriptionNode(@Nullable RefEntity element, CommonProblemDescriptor descriptor, @NotNull InspectionToolPresentation presentation, - @Nullable IntSupplier lineNumberCounter) { - super(descriptor, presentation); + @Nullable IntSupplier lineNumberCounter, + @NotNull InspectionTreeModel model) { + super(presentation, model); myElement = element; myDescriptor = descriptor; myLevel = ObjectUtils.notNull(calculatePreciseLevel(element, descriptor, presentation), () -> { @@ -111,7 +114,6 @@ public class ProblemDescriptionNode extends SuppressableInspectionTreeNode { return myElement; } - @Override @Nullable public CommonProblemDescriptor getDescriptor() { return myDescriptor; @@ -180,4 +182,17 @@ public class ProblemDescriptionNode extends SuppressableInspectionTreeNode { final String text = super.getTailText(); return text == null ? "" : text; } + + @NotNull + @Override + public Pair getSuppressContent() { + RefEntity refElement = getElement(); + CommonProblemDescriptor descriptor = getDescriptor(); + PsiElement element = descriptor instanceof ProblemDescriptor + ? ((ProblemDescriptor)descriptor).getPsiElement() + : refElement instanceof RefElement + ? ((RefElement)refElement).getPsiElement() + : null; + return Pair.create(element, descriptor); + } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java index 397ded6da80f..19d0899dc41f 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/QuickFixPreviewPanelFactory.java @@ -70,10 +70,16 @@ public class QuickFixPreviewPanelFactory { setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS)); boolean hasComponents = false; - final int actualProblemCount = myView.getTree().getContext().getPresentation(myWrapper).getProblemsCount(myView.getTree()); - if (actualProblemCount > 1 || (actualProblemCount == 1 && multipleDescriptors)) { - add(getLabel(actualProblemCount)); - hasComponents = true; + InspectionTree tree = myView.getTree(); + InspectionToolPresentation presentation = tree.getContext().getPresentation(myWrapper); + final boolean showProblemCount = presentation.showProblemCount(); + + if (showProblemCount) { + final int actualProblemCount = tree.getSelectedProblemCount(); + if (actualProblemCount > 1 || (actualProblemCount == 1 && multipleDescriptors)) { + add(getLabel(actualProblemCount)); + hasComponents = true; + } } final DefaultActionGroup actions = new DefaultActionGroup(); @@ -197,7 +203,7 @@ public class QuickFixPreviewPanelFactory { setBorder(JBUI.Borders.empty(16, 9, 13, 0)); AsyncProcessIcon waitingIcon = new AsyncProcessIcon("Inspection preview panel updating..."); Disposer.register(this, waitingIcon); - myWaitingLabel = getLabel(myView.getTree().getSelectedProblemCount(false)); + myWaitingLabel = getLabel(myView.getTree() .getSelectedProblemCount()); add(myWaitingLabel); add(waitingIcon); } @@ -207,7 +213,7 @@ public class QuickFixPreviewPanelFactory { if (myWaitingLabel != null) { myWaitingLabel.clear(); final InspectionTree tree = myView.getTree(); - appendTextToLabel(myWaitingLabel, tree.getSelectedProblemCount(false)); + appendTextToLabel(myWaitingLabel, tree.getSelectedProblemCount()); } } @@ -225,7 +231,7 @@ public class QuickFixPreviewPanelFactory { private static SimpleColoredComponent getLabel(int problemsCount) { SimpleColoredComponent label = new SimpleColoredComponent(); appendTextToLabel(label, problemsCount); - label.setBorder(JBUI.Borders.empty(0, 0, 0, 2)); + label.setBorder(JBUI.Borders.emptyRight(2)); return label; } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java index b5793a7eaa0a..f1559c048ae7 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/RefElementNode.java @@ -9,24 +9,24 @@ import com.intellij.codeInspection.reference.RefDirectory; import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.lang.annotation.HighlightSeverity; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.PsiElement; import gnu.trove.TObjectIntHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.MutableTreeNode; -import javax.swing.tree.TreeNode; /** * @author max */ public class RefElementNode extends SuppressableInspectionTreeNode { - private volatile boolean myHasDescriptorsUnder; - private volatile CommonProblemDescriptor mySingleDescriptor; private final Icon myIcon; - public RefElementNode(@Nullable RefEntity userObject, @NotNull InspectionToolPresentation presentation) { - super(userObject, presentation); - final RefEntity refEntity = getElement(); + @Nullable private final RefEntity myRefEntity; + + public RefElementNode(@Nullable RefEntity refEntity, @NotNull InspectionToolPresentation presentation, InspectionTreeModel model) { + super(presentation, model); + myRefEntity = refEntity; myIcon = refEntity == null ? null : refEntity.getIcon(false); } @@ -35,14 +35,10 @@ public class RefElementNode extends SuppressableInspectionTreeNode { return getElement() != null && getPresentation().isSuppressed(getElement()); } - boolean hasDescriptorsUnder() { - return myHasDescriptorsUnder; - } - @Override @Nullable public RefEntity getElement() { - return (RefEntity)getUserObject(); + return myRefEntity; } @Override @@ -95,28 +91,6 @@ public class RefElementNode extends SuppressableInspectionTreeNode { super.amnestyElement(); } - @Override - public void add(MutableTreeNode newChild) { - checkHasDescriptorUnder(newChild); - super.add(newChild); - } - - @Override - public InspectionTreeNode insertByOrder(InspectionTreeNode child, boolean allowDuplication) { - checkHasDescriptorUnder(child); - return super.insertByOrder(child, allowDuplication); - } - - public void setProblem(CommonProblemDescriptor descriptor) { - mySingleDescriptor = descriptor; - } - - @Nullable - @Override - public CommonProblemDescriptor getDescriptor() { - return mySingleDescriptor; - } - @Override public RefEntity getContainingFileLocalEntity() { final RefEntity element = getElement(); @@ -156,16 +130,11 @@ public class RefElementNode extends SuppressableInspectionTreeNode { return isLeaf() ? "" : null; } - private void checkHasDescriptorUnder(MutableTreeNode newChild) { - if (myHasDescriptorsUnder) return; - if (newChild instanceof ProblemDescriptionNode || - newChild instanceof RefElementNode && ((RefElementNode)newChild).hasDescriptorsUnder()) { - myHasDescriptorsUnder = true; - TreeNode parent = getParent(); - while (parent instanceof RefElementNode) { - ((RefElementNode)parent).myHasDescriptorsUnder = true; - parent = parent.getParent(); - } - } + @NotNull + @Override + public Pair getSuppressContent() { + RefEntity refElement = getElement(); + PsiElement element = refElement instanceof RefElement ? ((RefElement)refElement).getPsiElement() : null; + return Pair.create(element, null); } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/SuppressableInspectionTreeNode.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/SuppressableInspectionTreeNode.java index fee3caaa0338..57cbed707ff3 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/SuppressableInspectionTreeNode.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/SuppressableInspectionTreeNode.java @@ -4,11 +4,10 @@ package com.intellij.codeInspection.ui; import com.intellij.codeInspection.CommonProblemDescriptor; -import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.SuppressIntentionAction; -import com.intellij.codeInspection.reference.RefElement; import com.intellij.codeInspection.reference.RefEntity; import com.intellij.concurrency.ConcurrentCollectionFactory; +import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; @@ -17,7 +16,6 @@ import com.intellij.util.containers.Interner; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.swing.tree.TreeNode; import java.util.Arrays; import java.util.Collections; import java.util.Set; @@ -31,11 +29,20 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode private volatile Boolean myValid; private volatile NodeState myPreviousState; - SuppressableInspectionTreeNode(Object userObject, @NotNull InspectionToolPresentation presentation) { - super(userObject); + SuppressableInspectionTreeNode(@NotNull InspectionToolPresentation presentation, @NotNull InspectionTreeModel model) { + super(model); myPresentation = presentation; } + void nodeAdded() { + dropProblemCountCaches(); + ReadAction.run(() -> { + myPresentableName = calculatePresentableName(); + myValid = calculateIsValid(); + myAvailableSuppressActions = calculateAvailableSuppressActions(); + }); + } + @Override protected boolean doesNeedInternProblemLevels() { return true; @@ -47,7 +54,7 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode } public boolean canSuppress() { - return isLeaf(); + return getChildren().isEmpty(); } public abstract boolean isAlreadySuppressedFromView(); @@ -64,11 +71,6 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode return true; } - @Override - public int getProblemCount(boolean allowSuppressed) { - return !isExcluded() && isValid() && !isQuickFixAppliedFromView() && (allowSuppressed || !isAlreadySuppressedFromView()) ? 1 : 0; - } - @NotNull public synchronized Set getAvailableSuppressActions() { Set actions = myAvailableSuppressActions; @@ -86,9 +88,6 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode @Nullable public abstract RefEntity getElement(); - @Nullable - public abstract CommonProblemDescriptor getDescriptor(); - @Override public final synchronized boolean isValid() { Boolean valid = myValid; @@ -100,7 +99,7 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode } @Override - public final synchronized String toString() { + public final synchronized String getPresentableText() { String name = myPresentableName; if (name == null) { name = calculatePresentableName(); @@ -121,13 +120,6 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode return !isValid() ? "No longer valid" : null; } - @Override - protected void nodeAddedToTree() { - myPresentableName = calculatePresentableName(); - myValid = calculateIsValid(); - myAvailableSuppressActions = calculateAvailableSuppressActions(); - } - @NotNull private Set calculateAvailableSuppressActions() { return getElement() == null @@ -136,16 +128,7 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode } @NotNull - public final Pair getSuppressContent() { - RefEntity refElement = getElement(); - CommonProblemDescriptor descriptor = getDescriptor(); - PsiElement element = descriptor instanceof ProblemDescriptor - ? ((ProblemDescriptor)descriptor).getPsiElement() - : refElement instanceof RefElement - ? ((RefElement)refElement).getPsiElement() - : null; - return Pair.create(element, descriptor); - } + public abstract Pair getSuppressContent(); @NotNull private Set calculateAvailableSuppressActions(@NotNull Project project) { @@ -168,14 +151,19 @@ public abstract class SuppressableInspectionTreeNode extends InspectionTreeNode protected abstract boolean calculateIsValid(); protected void dropCache() { + ReadAction.run(() -> doDropCache()); + } + + private void doDropCache() { myProblemLevels.drop(); if (isQuickFixAppliedFromView() || isAlreadySuppressedFromView()) return; + // calculate all data on background thread myValid = calculateIsValid(); myPresentableName = calculatePresentableName(); - for (int i = 0; i < getChildCount(); i++) { - TreeNode child = getChildAt(i); + + for (InspectionTreeNode child : getChildren()) { if (child instanceof SuppressableInspectionTreeNode) { - ((SuppressableInspectionTreeNode)child).dropCache(); + ((SuppressableInspectionTreeNode)child).doDropCache(); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/ExportHTMLAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/ExportHTMLAction.java index 1d8bbcfe6bf3..f08b32604c07 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/ExportHTMLAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/ExportHTMLAction.java @@ -31,7 +31,6 @@ import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashSet; import org.jdom.Element; import org.jdom.output.Format; @@ -120,7 +119,7 @@ public class ExportHTMLAction extends AnAction implements DumbAware { if (!outputDir.exists() && !outputDir.mkdirs()) { throw new IOException("Cannot create \'" + outputDir + "\'"); } - final InspectionTreeNode root = myView.getTree().getRoot(); + final InspectionTreeModel model = myView.getTree().getInspectionTreeModel(); final Exception[] ex = new Exception[1]; final Set visitedTools = new THashSet<>(); @@ -128,7 +127,7 @@ public class ExportHTMLAction extends AnAction implements DumbAware { Format format = JDOMUtil.createFormat("\n"); XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newInstance(); - TreeUtil.treeNodeTraverser(root).traverse().processEach(node -> { + model.traverse(model.getRoot(), node -> { if (node instanceof InspectionNode) { InspectionNode toolNode = (InspectionNode)node; if (toolNode.isExcluded()) return true; diff --git a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/InvokeQuickFixAction.java b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/InvokeQuickFixAction.java index 78d63843b674..2d71d080aa10 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/InvokeQuickFixAction.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/ui/actions/InvokeQuickFixAction.java @@ -66,6 +66,6 @@ public class InvokeQuickFixAction extends AnAction { } static boolean cantApplyFixes(InspectionResultsView view) { - return view.isUpdating() && !view.getTree().areDescriptorNodesSelected(); + return !view.getTree().areDescriptorNodesSelected(); } } diff --git a/platform/platform-api/src/com/intellij/ide/actions/exclusion/ExclusionHandler.java b/platform/platform-api/src/com/intellij/ide/actions/exclusion/ExclusionHandler.java index 102a1e492e2f..16f36bd6c972 100644 --- a/platform/platform-api/src/com/intellij/ide/actions/exclusion/ExclusionHandler.java +++ b/platform/platform-api/src/com/intellij/ide/actions/exclusion/ExclusionHandler.java @@ -18,21 +18,16 @@ package com.intellij.ide.actions.exclusion; import com.intellij.openapi.actionSystem.DataKey; import org.jetbrains.annotations.NotNull; -import javax.swing.tree.MutableTreeNode; - -/** - * @author Dmitry Batkovich - */ -public interface ExclusionHandler { +public interface ExclusionHandler { DataKey EXCLUSION_HANDLER = DataKey.create("tree.exclusion.handler"); - boolean isNodeExclusionAvailable(@NotNull T node); + boolean isNodeExclusionAvailable(@NotNull Node node); - boolean isNodeExcluded(@NotNull T node); + boolean isNodeExcluded(@NotNull Node node); - void excludeNode(@NotNull T node); + void excludeNode(@NotNull Node node); - void includeNode(@NotNull T node); + void includeNode(@NotNull Node node); boolean isActionEnabled(boolean isExcludeAction); diff --git a/platform/platform-api/src/com/intellij/ide/actions/exclusion/TreeNodeExclusionAction.java b/platform/platform-api/src/com/intellij/ide/actions/exclusion/TreeNodeExclusionAction.java index 69b7235bc700..78cefa781f37 100644 --- a/platform/platform-api/src/com/intellij/ide/actions/exclusion/TreeNodeExclusionAction.java +++ b/platform/platform-api/src/com/intellij/ide/actions/exclusion/TreeNodeExclusionAction.java @@ -24,14 +24,11 @@ import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; -import javax.swing.tree.MutableTreeNode; +import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; -/** - * @author Dmitry Batkovich. - */ -abstract class TreeNodeExclusionAction extends AnAction { +abstract class TreeNodeExclusionAction extends AnAction { private final static Logger LOG = Logger.getInstance(TreeNodeExclusionAction.class); private final boolean myIsExclude; @@ -65,7 +62,7 @@ abstract class TreeNodeExclusionAction extends AnActi final T node = (T)path.getLastPathComponent(); TreeUtil.traverse(node, n -> { if (!exclusionProcessor.isNodeExclusionAvailable((T)n)) return true; - final Boolean isNodeExcluded = exclusionProcessor.isNodeExcluded((T)n); + boolean isNodeExcluded = exclusionProcessor.isNodeExcluded((T)n); if (myIsExclude != isNodeExcluded) { isEnabled[0] = true; return false; @@ -95,7 +92,7 @@ abstract class TreeNodeExclusionAction extends AnActi final T node = (T)path.getLastPathComponent(); TreeUtil.traverse(node, n -> { if (!exclusionProcessor.isNodeExclusionAvailable((T)n)) return true; - if (Boolean.valueOf(myIsExclude) != exclusionProcessor.isNodeExcluded((T)n)) { + if (myIsExclude != exclusionProcessor.isNodeExcluded((T)n)) { if (myIsExclude) { exclusionProcessor.excludeNode(node); } else { diff --git a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/InspectionsFixture.java b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/InspectionsFixture.java index 9bf1ac62d763..02265ed02bb2 100644 --- a/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/InspectionsFixture.java +++ b/platform/testGuiFramework/src/com/intellij/testGuiFramework/fixtures/InspectionsFixture.java @@ -22,10 +22,10 @@ import com.intellij.openapi.wm.ToolWindowId; import org.fest.swing.core.Robot; import org.fest.swing.edt.GuiQuery; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.List; import static org.fest.swing.edt.GuiActionRunner.execute; @@ -47,10 +47,10 @@ public class InspectionsFixture extends ToolWindowFixture { return execute(new GuiQuery() { @Override - @Nullable - protected String executeInEDT() throws Throwable { + @NotNull + protected String executeInEDT() { StringBuilder sb = new StringBuilder(); - InspectionsFixture.describe(myTree.getRoot(), sb, 0); + describe(myTree.getInspectionTreeModel().getRoot(), sb, 0); return sb.toString(); } }); @@ -65,11 +65,8 @@ public class InspectionsFixture extends ToolWindowFixture { // The exact order of the results sometimes varies so sort the children alphabetically // instead to ensure stable test output - List children = new ArrayList<>(node.getChildCount()); - for (int i = 0, n = node.getChildCount(); i < n; i++) { - children.add((InspectionTreeNode)node.getChildAt(i)); - } - Collections.sort(children, (node1, node2) -> node1.toString().compareTo(node2.toString())); + List children = new ArrayList<>(node.getChildren()); + Collections.sort(children, Comparator.comparing(Object::toString)); for (InspectionTreeNode child : children) { describe(child, sb, depth + 1); }