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.diagnostic.Logger;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.vfs.VirtualFile;
33 import com.intellij.profile.codeInspection.ui.inspectionsTree.InspectionsConfigTreeComparator;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.util.PsiUtilCore;
36 import com.intellij.ui.TreeSpeedSearch;
37 import com.intellij.ui.treeStructure.Tree;
38 import com.intellij.util.ArrayUtil;
39 import com.intellij.util.SmartList;
40 import com.intellij.util.containers.ContainerUtil;
41 import com.intellij.util.containers.MultiMap;
42 import com.intellij.util.ui.UIUtil;
43 import com.intellij.util.ui.tree.TreeUtil;
44 import org.jetbrains.annotations.NotNull;
45 import org.jetbrains.annotations.Nullable;
47 import javax.swing.event.TreeExpansionEvent;
48 import javax.swing.event.TreeWillExpandListener;
49 import javax.swing.tree.DefaultTreeModel;
50 import javax.swing.tree.ExpandVetoException;
51 import javax.swing.tree.TreeNode;
52 import javax.swing.tree.TreePath;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
57 public class InspectionTree extends Tree {
58 private static final Logger LOG = Logger.getInstance(InspectionTree.class);
59 private static final Comparator<CommonProblemDescriptor> DESCRIPTOR_COMPARATOR = (c1, c2) -> {
60 if (c1 instanceof ProblemDescriptor && c2 instanceof ProblemDescriptor) {
61 return PsiUtilCore.compareElementsByPosition(((ProblemDescriptor)c2).getPsiElement(),
62 ((ProblemDescriptor)c1).getPsiElement());
64 return c1.getDescriptionTemplate().compareTo(c2.getDescriptionTemplate());
67 @NotNull private final GlobalInspectionContextImpl myContext;
68 @NotNull private final ExcludedInspectionTreeNodesManager myExcludedManager;
69 @NotNull private InspectionTreeState myState = new InspectionTreeState();
70 private boolean myQueueUpdate;
72 public InspectionTree(@NotNull Project project,
73 @NotNull GlobalInspectionContextImpl context,
74 @NotNull InspectionResultsView view) {
75 setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view))));
77 myExcludedManager = view.getExcludedManager();
79 setCellRenderer(new InspectionTreeCellRenderer(view));
80 setRootVisible(false);
81 setShowsRootHandles(true);
82 UIUtil.setLineStyleAngled(this);
83 addTreeWillExpandListener(new ExpandListener());
85 myState.getExpandedUserObjects().add(project);
87 TreeUtil.installActions(this);
88 new TreeSpeedSearch(this, o -> InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString()));
90 addTreeSelectionListener(e -> {
91 TreePath newSelection = e.getNewLeadSelectionPath();
92 if (newSelection != null && !isUnderQueueUpdate()) {
93 myState.setSelectionPath(newSelection);
98 public void setQueueUpdate(boolean queueUpdate) {
99 myQueueUpdate = queueUpdate;
102 public boolean isUnderQueueUpdate() {
103 return myQueueUpdate;
106 public void removeAllNodes() {
107 getRoot().removeAllChildren();
108 nodeStructureChanged(getRoot());
111 public InspectionTreeNode getRoot() {
112 return (InspectionTreeNode)getModel().getRoot();
116 public String[] getSelectedGroupPath() {
117 final TreePath[] paths = getSelectionPaths();
118 if (paths == null) return null;
119 final TreePath commonPath = TreeUtil.findCommonPath(paths);
120 for (Object n : commonPath.getPath()) {
121 if (n instanceof InspectionGroupNode) {
122 return ((InspectionGroupNode)n).getGroupPath();
129 public InspectionToolWrapper getSelectedToolWrapper(boolean allowDummy) {
130 final TreePath[] paths = getSelectionPaths();
131 if (paths == null) return null;
132 InspectionToolWrapper toolWrapper = null;
133 for (TreePath path : paths) {
134 Object[] nodes = path.getPath();
135 for (int j = nodes.length - 1; j >= 0; j--) {
136 Object node = nodes[j];
137 if (node instanceof InspectionGroupNode) {
140 if (node instanceof InspectionNode) {
141 InspectionToolWrapper wrapper = ((InspectionNode)node).getToolWrapper();
142 if (!allowDummy && getContext().getPresentation(wrapper).isDummy()) {
145 if (toolWrapper == null) {
146 toolWrapper = wrapper;
148 else if (toolWrapper != wrapper) {
160 public RefEntity getCommonSelectedElement() {
161 final Object node = getCommonSelectedNode();
162 return node instanceof RefElementNode ? ((RefElementNode)node).getElement() : null;
166 private Object getCommonSelectedNode() {
167 final TreePath[] paths = getSelectionPaths();
168 if (paths == null) return null;
169 final Object[][] resolvedPaths = new Object[paths.length][];
170 for (int i = 0; i < paths.length; i++) {
171 TreePath path = paths[i];
172 resolvedPaths[i] = path.getPath();
175 Object currentCommonNode = null;
176 for (int i = 0; i < resolvedPaths[0].length; i++) {
177 final Object currentNode = resolvedPaths[0][i];
178 for (int j = 1; j < resolvedPaths.length; j++) {
179 final Object o = resolvedPaths[j][i];
180 if (!o.equals(currentNode)) {
181 return currentCommonNode;
184 currentCommonNode = currentNode;
186 return currentCommonNode;
190 public RefEntity[] getSelectedElements() {
191 TreePath[] selectionPaths = getSelectionPaths();
192 if (selectionPaths != null) {
193 InspectionToolWrapper toolWrapper = getSelectedToolWrapper(true);
194 if (toolWrapper == null) return RefEntity.EMPTY_ELEMENTS_ARRAY;
196 Set<RefEntity> result = new LinkedHashSet<RefEntity>();
197 for (TreePath selectionPath : selectionPaths) {
198 final InspectionTreeNode node = (InspectionTreeNode)selectionPath.getLastPathComponent();
199 addElementsInNode(node, result);
201 return ArrayUtil.reverseArray(result.toArray(new RefEntity[result.size()]));
203 return RefEntity.EMPTY_ELEMENTS_ARRAY;
206 private static void addElementsInNode(InspectionTreeNode node, Set<RefEntity> out) {
207 if (!node.isValid()) return;
208 if (node instanceof RefElementNode) {
209 final RefEntity element = ((RefElementNode)node).getElement();
212 if (node instanceof ProblemDescriptionNode) {
213 final RefEntity element = ((ProblemDescriptionNode)node).getElement();
216 final Enumeration children = node.children();
217 while (children.hasMoreElements()) {
218 InspectionTreeNode child = (InspectionTreeNode)children.nextElement();
219 addElementsInNode(child, out);
223 public CommonProblemDescriptor[] getSelectedDescriptors() {
224 return getSelectedDescriptors(false, null);
227 public CommonProblemDescriptor[] getSelectedDescriptors(boolean sortedByPosition, @Nullable Set<VirtualFile> readOnlyFilesSink) {
228 final TreePath[] paths = getSelectionPaths();
229 if (paths == null) return CommonProblemDescriptor.EMPTY_ARRAY;
230 final TreePath[] selectionPaths = TreeUtil.selectMaximals(paths);
231 final LinkedHashSet<CommonProblemDescriptor> descriptors = new LinkedHashSet<CommonProblemDescriptor>();
233 MultiMap<Object, ProblemDescriptionNode> parentToChildNode = new MultiMap<>();
234 final List<InspectionTreeNode> nonDescriptorNodes = new SmartList<>();
235 for (TreePath path : selectionPaths) {
236 final Object[] pathAsArray = path.getPath();
237 final int length = pathAsArray.length;
238 final Object node = pathAsArray[length - 1];
239 if (node instanceof ProblemDescriptionNode) {
240 if (isNodeValidAndIncluded((ProblemDescriptionNode)node)) {
242 parentToChildNode.putValue(pathAsArray[length - 2], (ProblemDescriptionNode)node);
244 parentToChildNode.putValue(node, (ProblemDescriptionNode)node);
248 nonDescriptorNodes.add((InspectionTreeNode)node);
252 for (InspectionTreeNode node : nonDescriptorNodes) {
253 processChildDescriptorsDeep(node, descriptors, sortedByPosition, readOnlyFilesSink);
256 for (Map.Entry<Object, Collection<ProblemDescriptionNode>> entry : parentToChildNode.entrySet()) {
257 final Collection<ProblemDescriptionNode> siblings = entry.getValue();
258 if (siblings.size() == 1) {
259 final ProblemDescriptionNode descriptorNode = ContainerUtil.getFirstItem(siblings);
260 LOG.assertTrue(descriptorNode != null);
261 CommonProblemDescriptor descriptor = descriptorNode.getDescriptor();
262 if (descriptor != null) {
263 descriptors.add(descriptor);
264 if (readOnlyFilesSink != null) {
265 checkDescriptorFileIsWritable(descriptor, readOnlyFilesSink);
269 Stream<CommonProblemDescriptor> descriptorStream = siblings.stream().map(ProblemDescriptionNode::getDescriptor);
270 if (sortedByPosition) {
271 descriptorStream = descriptorStream.sorted(DESCRIPTOR_COMPARATOR);
273 descriptorStream = descriptorStream.filter(descriptors::add);
274 if (readOnlyFilesSink != null) {
275 checkDescriptorsFileIsWritable(descriptorStream.collect(Collectors.toList()), readOnlyFilesSink);
280 return descriptors.toArray(new CommonProblemDescriptor[descriptors.size()]);
283 public boolean areDescriptorNodesSelected() {
284 final TreePath[] paths = getSelectionPaths();
285 if (paths == null) return false;
286 for (TreePath path : paths) {
287 if (!(path.getLastPathComponent() instanceof ProblemDescriptionNode)) {
294 public int getSelectedProblemCount() {
295 if (getSelectionCount() == 0) return 0;
296 final TreePath[] paths = getSelectionPaths();
297 LOG.assertTrue(paths != null);
298 Set<InspectionTreeNode> result = new HashSet<>();
299 MultiMap<InspectionTreeNode, InspectionTreeNode> rootDependencies = new MultiMap<>();
300 for (TreePath path : paths) {
302 final InspectionTreeNode node = (InspectionTreeNode)path.getLastPathComponent();
303 final Collection<InspectionTreeNode> visitedChildren = rootDependencies.get(node);
304 for (InspectionTreeNode child : visitedChildren) {
305 result.remove(child);
308 boolean needToAdd = true;
309 for (int i = 0; i < path.getPathCount() - 1; i++) {
310 final InspectionTreeNode parent = (InspectionTreeNode) path.getPathComponent(i);
311 rootDependencies.putValue(parent, node);
312 if (result.contains(parent)) {
324 for (InspectionTreeNode node : result) {
325 count += node.getProblemCount();
330 private void processChildDescriptorsDeep(InspectionTreeNode node,
331 LinkedHashSet<CommonProblemDescriptor> descriptors,
332 boolean sortedByPosition,
333 @Nullable Set<VirtualFile> readOnlyFilesSink) {
334 List<CommonProblemDescriptor> descriptorChildren = null;
335 for (int i = 0; i < node.getChildCount(); i++) {
336 final TreeNode child = node.getChildAt(i);
337 if (child instanceof ProblemDescriptionNode) {
338 if (isNodeValidAndIncluded((ProblemDescriptionNode)child)) {
339 if (sortedByPosition) {
340 if (descriptorChildren == null) {
341 descriptorChildren = new ArrayList<>();
343 descriptorChildren.add(((ProblemDescriptionNode)child).getDescriptor());
345 descriptors.add(((ProblemDescriptionNode)child).getDescriptor());
350 processChildDescriptorsDeep((InspectionTreeNode)child, descriptors, sortedByPosition, readOnlyFilesSink);
354 if (descriptorChildren != null) {
355 if (descriptorChildren.size() > 1) {
356 Collections.sort(descriptorChildren, DESCRIPTOR_COMPARATOR);
358 if (readOnlyFilesSink != null) {
359 checkDescriptorsFileIsWritable(descriptorChildren, readOnlyFilesSink);
362 descriptors.addAll(descriptorChildren);
366 private boolean isNodeValidAndIncluded(ProblemDescriptionNode node) {
367 return node.isValid() &&
368 !node.isExcluded(myExcludedManager) &&
369 !node.isAlreadySuppressedFromView() &&
370 !node.isQuickFixAppliedFromView();
373 private void nodeStructureChanged(InspectionTreeNode node) {
374 ((DefaultTreeModel)getModel()).nodeStructureChanged(node);
377 public void queueUpdate() {
378 ((InspectionRootNode) getRoot()).getUpdater().update(null, true);
381 public void restoreExpansionAndSelection(@Nullable InspectionTreeNode reloadedNode) {
382 myState.restoreExpansionAndSelection(this, reloadedNode);
385 public void setState(@NotNull InspectionTreeState state) {
389 public InspectionTreeState getTreeState() {
393 public void setTreeState(@NotNull InspectionTreeState treeState) {
397 private class ExpandListener implements TreeWillExpandListener {
399 public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
400 final InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
401 myState.getExpandedUserObjects().add(node.getUserObject());
405 public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
406 InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
407 myState.getExpandedUserObjects().remove(node.getUserObject());
412 public GlobalInspectionContextImpl getContext() {
416 private static void checkDescriptorsFileIsWritable(@NotNull Collection<CommonProblemDescriptor> descriptors, @NotNull Set<VirtualFile> readOnlySink) {
417 for (CommonProblemDescriptor descriptor : descriptors) {
418 if (checkDescriptorFileIsWritable(descriptor, readOnlySink)) {
424 private static boolean checkDescriptorFileIsWritable(@NotNull CommonProblemDescriptor descriptor, @NotNull Set<VirtualFile> readOnlySink) {
425 if (descriptor instanceof ProblemDescriptor) {
426 PsiElement psiElement = ((ProblemDescriptor)descriptor).getPsiElement();
427 if (psiElement != null && !psiElement.isWritable()) {
428 readOnlySink.add(psiElement.getContainingFile().getVirtualFile());