2 * Copyright 2000-2016 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * Created by IntelliJ IDEA.
23 package com.intellij.codeInspection.ui;
25 import com.intellij.codeInspection.CommonProblemDescriptor;
26 import com.intellij.codeInspection.ProblemDescriptor;
27 import com.intellij.codeInspection.ex.GlobalInspectionContextImpl;
28 import com.intellij.codeInspection.ex.InspectionToolWrapper;
29 import com.intellij.codeInspection.reference.RefEntity;
30 import com.intellij.openapi.application.ApplicationManager;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.project.Project;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator;
35 import com.intellij.psi.PsiElement;
36 import com.intellij.psi.util.PsiUtilCore;
37 import com.intellij.ui.TreeSpeedSearch;
38 import com.intellij.ui.treeStructure.Tree;
39 import com.intellij.util.ArrayUtil;
40 import com.intellij.util.SmartList;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.containers.MultiMap;
43 import com.intellij.util.ui.UIUtil;
44 import com.intellij.util.ui.tree.TreeUtil;
45 import org.jetbrains.annotations.NotNull;
46 import org.jetbrains.annotations.Nullable;
48 import javax.swing.event.TreeExpansionEvent;
49 import javax.swing.event.TreeWillExpandListener;
50 import javax.swing.tree.DefaultTreeModel;
51 import javax.swing.tree.ExpandVetoException;
52 import javax.swing.tree.TreeNode;
53 import javax.swing.tree.TreePath;
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
58 public class InspectionTree extends Tree {
59 private static final Logger LOG = Logger.getInstance(InspectionTree.class);
60 private static final Comparator<CommonProblemDescriptor> DESCRIPTOR_COMPARATOR = (c1, c2) -> {
61 if (c1 instanceof ProblemDescriptor && c2 instanceof ProblemDescriptor) {
62 return PsiUtilCore.compareElementsByPosition(((ProblemDescriptor)c2).getPsiElement(),
63 ((ProblemDescriptor)c1).getPsiElement());
65 return c1.getDescriptionTemplate().compareTo(c2.getDescriptionTemplate());
68 @NotNull private final GlobalInspectionContextImpl myContext;
69 @NotNull private final ExcludedInspectionTreeNodesManager myExcludedManager;
70 @NotNull private InspectionTreeState myState = new InspectionTreeState();
71 private boolean myQueueUpdate;
73 public InspectionTree(@NotNull Project project,
74 @NotNull GlobalInspectionContextImpl context,
75 @NotNull InspectionResultsView view) {
76 setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view))));
78 myExcludedManager = view.getExcludedManager();
80 setCellRenderer(new InspectionTreeCellRenderer(view));
81 setRootVisible(false);
82 setShowsRootHandles(true);
83 UIUtil.setLineStyleAngled(this);
84 addTreeWillExpandListener(new ExpandListener());
86 myState.getExpandedUserObjects().add(project);
88 TreeUtil.installActions(this);
89 new TreeSpeedSearch(this, o -> InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString()));
91 addTreeSelectionListener(e -> {
92 TreePath newSelection = e.getNewLeadSelectionPath();
93 if (newSelection != null && !isUnderQueueUpdate()) {
94 myState.setSelectionPath(newSelection);
99 public void setQueueUpdate(boolean queueUpdate) {
100 myQueueUpdate = queueUpdate;
103 public boolean isUnderQueueUpdate() {
104 return myQueueUpdate;
107 public void removeAllNodes() {
108 getRoot().removeAllChildren();
109 ApplicationManager.getApplication().invokeLater(() -> nodeStructureChanged(getRoot()));
112 public InspectionTreeNode getRoot() {
113 return (InspectionTreeNode)getModel().getRoot();
117 public String[] getSelectedGroupPath() {
118 final TreePath[] paths = getSelectionPaths();
119 if (paths == null) return null;
120 final TreePath commonPath = TreeUtil.findCommonPath(paths);
121 for (Object n : commonPath.getPath()) {
122 if (n instanceof InspectionGroupNode) {
123 return ((InspectionGroupNode)n).getGroupPath();
130 public InspectionToolWrapper getSelectedToolWrapper(boolean allowDummy) {
131 final TreePath[] paths = getSelectionPaths();
132 if (paths == null) return null;
133 InspectionToolWrapper toolWrapper = null;
134 for (TreePath path : paths) {
135 Object[] nodes = path.getPath();
136 for (int j = nodes.length - 1; j >= 0; j--) {
137 Object node = nodes[j];
138 if (node instanceof InspectionGroupNode) {
141 if (node instanceof InspectionNode) {
142 InspectionToolWrapper wrapper = ((InspectionNode)node).getToolWrapper();
143 if (!allowDummy && getContext().getPresentation(wrapper).isDummy()) {
146 if (toolWrapper == null) {
147 toolWrapper = wrapper;
149 else if (toolWrapper != wrapper) {
161 public RefEntity getCommonSelectedElement() {
162 final Object node = getCommonSelectedNode();
163 return node instanceof RefElementNode ? ((RefElementNode)node).getElement() : null;
167 private Object getCommonSelectedNode() {
168 final TreePath[] paths = getSelectionPaths();
169 if (paths == null) return null;
170 final Object[][] resolvedPaths = new Object[paths.length][];
171 for (int i = 0; i < paths.length; i++) {
172 TreePath path = paths[i];
173 resolvedPaths[i] = path.getPath();
176 Object currentCommonNode = null;
177 for (int i = 0; i < resolvedPaths[0].length; i++) {
178 final Object currentNode = resolvedPaths[0][i];
179 for (int j = 1; j < resolvedPaths.length; j++) {
180 final Object o = resolvedPaths[j][i];
181 if (!o.equals(currentNode)) {
182 return currentCommonNode;
185 currentCommonNode = currentNode;
187 return currentCommonNode;
191 public RefEntity[] getSelectedElements() {
192 TreePath[] selectionPaths = getSelectionPaths();
193 if (selectionPaths != null) {
194 InspectionToolWrapper toolWrapper = getSelectedToolWrapper(true);
195 if (toolWrapper == null) return RefEntity.EMPTY_ELEMENTS_ARRAY;
197 Set<RefEntity> result = new LinkedHashSet<RefEntity>();
198 for (TreePath selectionPath : selectionPaths) {
199 final InspectionTreeNode node = (InspectionTreeNode)selectionPath.getLastPathComponent();
200 addElementsInNode(node, result);
202 return ArrayUtil.reverseArray(result.toArray(new RefEntity[result.size()]));
204 return RefEntity.EMPTY_ELEMENTS_ARRAY;
207 private static void addElementsInNode(InspectionTreeNode node, Set<RefEntity> out) {
208 if (!node.isValid()) return;
209 if (node instanceof RefElementNode) {
210 final RefEntity element = ((RefElementNode)node).getElement();
213 if (node instanceof ProblemDescriptionNode) {
214 final RefEntity element = ((ProblemDescriptionNode)node).getElement();
217 final Enumeration children = node.children();
218 while (children.hasMoreElements()) {
219 InspectionTreeNode child = (InspectionTreeNode)children.nextElement();
220 addElementsInNode(child, out);
224 public CommonProblemDescriptor[] getSelectedDescriptors() {
225 return getSelectedDescriptors(false, null);
228 public CommonProblemDescriptor[] getSelectedDescriptors(boolean sortedByPosition, @Nullable Set<VirtualFile> readOnlyFilesSink) {
229 final TreePath[] paths = getSelectionPaths();
230 if (paths == null) return CommonProblemDescriptor.EMPTY_ARRAY;
231 final TreePath[] selectionPaths = TreeUtil.selectMaximals(paths);
232 final LinkedHashSet<CommonProblemDescriptor> descriptors = new LinkedHashSet<CommonProblemDescriptor>();
234 MultiMap<Object, ProblemDescriptionNode> parentToChildNode = new MultiMap<>();
235 final List<InspectionTreeNode> nonDescriptorNodes = new SmartList<>();
236 for (TreePath path : selectionPaths) {
237 final Object[] pathAsArray = path.getPath();
238 final int length = pathAsArray.length;
239 final Object node = pathAsArray[length - 1];
240 if (node instanceof ProblemDescriptionNode) {
241 if (isNodeValidAndIncluded((ProblemDescriptionNode)node)) {
243 parentToChildNode.putValue(pathAsArray[length - 2], (ProblemDescriptionNode)node);
245 parentToChildNode.putValue(node, (ProblemDescriptionNode)node);
249 nonDescriptorNodes.add((InspectionTreeNode)node);
253 for (InspectionTreeNode node : nonDescriptorNodes) {
254 processChildDescriptorsDeep(node, descriptors, sortedByPosition, readOnlyFilesSink);
257 for (Map.Entry<Object, Collection<ProblemDescriptionNode>> entry : parentToChildNode.entrySet()) {
258 final Collection<ProblemDescriptionNode> siblings = entry.getValue();
259 if (siblings.size() == 1) {
260 final ProblemDescriptionNode descriptorNode = ContainerUtil.getFirstItem(siblings);
261 LOG.assertTrue(descriptorNode != null);
262 CommonProblemDescriptor descriptor = descriptorNode.getDescriptor();
263 if (descriptor != null) {
264 descriptors.add(descriptor);
265 if (readOnlyFilesSink != null) {
266 checkDescriptorFileIsWritable(descriptor, readOnlyFilesSink);
270 Stream<CommonProblemDescriptor> descriptorStream = siblings.stream().map(ProblemDescriptionNode::getDescriptor);
271 if (sortedByPosition) {
272 descriptorStream = descriptorStream.sorted(DESCRIPTOR_COMPARATOR);
274 descriptorStream = descriptorStream.filter(descriptors::add);
275 if (readOnlyFilesSink != null) {
276 checkDescriptorsFileIsWritable(descriptorStream.collect(Collectors.toList()), readOnlyFilesSink);
281 return descriptors.toArray(new CommonProblemDescriptor[descriptors.size()]);
284 public boolean areDescriptorNodesSelected() {
285 final TreePath[] paths = getSelectionPaths();
286 if (paths == null) return false;
287 for (TreePath path : paths) {
288 if (!(path.getLastPathComponent() instanceof ProblemDescriptionNode)) {
295 public int getSelectedProblemCount() {
296 if (getSelectionCount() == 0) return 0;
297 final TreePath[] paths = getSelectionPaths();
298 LOG.assertTrue(paths != null);
299 Set<InspectionTreeNode> result = new HashSet<>();
300 MultiMap<InspectionTreeNode, InspectionTreeNode> rootDependencies = new MultiMap<>();
301 for (TreePath path : paths) {
303 final InspectionTreeNode node = (InspectionTreeNode)path.getLastPathComponent();
304 final Collection<InspectionTreeNode> visitedChildren = rootDependencies.get(node);
305 for (InspectionTreeNode child : visitedChildren) {
306 result.remove(child);
309 boolean needToAdd = true;
310 for (int i = 0; i < path.getPathCount() - 1; i++) {
311 final InspectionTreeNode parent = (InspectionTreeNode) path.getPathComponent(i);
312 rootDependencies.putValue(parent, node);
313 if (result.contains(parent)) {
325 for (InspectionTreeNode node : result) {
326 count += node.getProblemCount();
331 private void processChildDescriptorsDeep(InspectionTreeNode node,
332 LinkedHashSet<CommonProblemDescriptor> descriptors,
333 boolean sortedByPosition,
334 @Nullable Set<VirtualFile> readOnlyFilesSink) {
335 List<CommonProblemDescriptor> descriptorChildren = null;
336 for (int i = 0; i < node.getChildCount(); i++) {
337 final TreeNode child = node.getChildAt(i);
338 if (child instanceof ProblemDescriptionNode) {
339 if (isNodeValidAndIncluded((ProblemDescriptionNode)child)) {
340 if (sortedByPosition) {
341 if (descriptorChildren == null) {
342 descriptorChildren = new ArrayList<>();
344 descriptorChildren.add(((ProblemDescriptionNode)child).getDescriptor());
346 descriptors.add(((ProblemDescriptionNode)child).getDescriptor());
351 processChildDescriptorsDeep((InspectionTreeNode)child, descriptors, sortedByPosition, readOnlyFilesSink);
355 if (descriptorChildren != null) {
356 if (descriptorChildren.size() > 1) {
357 Collections.sort(descriptorChildren, DESCRIPTOR_COMPARATOR);
359 if (readOnlyFilesSink != null) {
360 checkDescriptorsFileIsWritable(descriptorChildren, readOnlyFilesSink);
363 descriptors.addAll(descriptorChildren);
367 private boolean isNodeValidAndIncluded(ProblemDescriptionNode node) {
368 return node.isValid() &&
369 !node.isExcluded(myExcludedManager) &&
370 !node.isAlreadySuppressedFromView() &&
371 !node.isQuickFixAppliedFromView();
374 private void nodeStructureChanged(InspectionTreeNode node) {
375 ((DefaultTreeModel)getModel()).nodeStructureChanged(node);
378 public void queueUpdate() {
379 ((InspectionRootNode) getRoot()).getUpdater().update(null, true);
382 public void restoreExpansionAndSelection(boolean treeNodesMightChange) {
383 myState.restoreExpansionAndSelection(this, treeNodesMightChange);
386 public void setState(@NotNull InspectionTreeState state) {
390 public InspectionTreeState getTreeState() {
394 public void setTreeState(@NotNull InspectionTreeState treeState) {
398 private class ExpandListener implements TreeWillExpandListener {
400 public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
401 final InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
402 myState.getExpandedUserObjects().add(node.getUserObject());
406 public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
407 InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
408 myState.getExpandedUserObjects().remove(node.getUserObject());
413 public GlobalInspectionContextImpl getContext() {
417 private static void checkDescriptorsFileIsWritable(@NotNull Collection<CommonProblemDescriptor> descriptors, @NotNull Set<VirtualFile> readOnlySink) {
418 for (CommonProblemDescriptor descriptor : descriptors) {
419 if (checkDescriptorFileIsWritable(descriptor, readOnlySink)) {
425 private static boolean checkDescriptorFileIsWritable(@NotNull CommonProblemDescriptor descriptor, @NotNull Set<VirtualFile> readOnlySink) {
426 if (descriptor instanceof ProblemDescriptor) {
427 PsiElement psiElement = ((ProblemDescriptor)descriptor).getPsiElement();
428 if (psiElement != null && !psiElement.isWritable()) {
429 readOnlySink.add(psiElement.getContainingFile().getVirtualFile());