add PsiDocumentManagerImplTest.testPerformLaterWhenAllCommittedFromCommitHandler...
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ui / InspectionTree.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 /*
18  * Created by IntelliJ IDEA.
19  * User: max
20  * Date: Nov 4, 2001
21  * Time: 5:19:35 PM
22  */
23 package com.intellij.codeInspection.ui;
24
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;
46
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;
53 import java.util.*;
54 import java.util.stream.Collectors;
55 import java.util.stream.Stream;
56
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());
63     }
64     return c1.getDescriptionTemplate().compareTo(c2.getDescriptionTemplate());
65   };
66
67   @NotNull private final GlobalInspectionContextImpl myContext;
68   @NotNull private final ExcludedInspectionTreeNodesManager myExcludedManager;
69   @NotNull private InspectionTreeState myState = new InspectionTreeState();
70   private boolean myQueueUpdate;
71
72   public InspectionTree(@NotNull Project project,
73                         @NotNull GlobalInspectionContextImpl context,
74                         @NotNull InspectionResultsView view) {
75     setModel(new DefaultTreeModel(new InspectionRootNode(project, new InspectionTreeUpdater(view))));
76     myContext = context;
77     myExcludedManager = view.getExcludedManager();
78
79     setCellRenderer(new InspectionTreeCellRenderer(view));
80     setRootVisible(false);
81     setShowsRootHandles(true);
82     UIUtil.setLineStyleAngled(this);
83     addTreeWillExpandListener(new ExpandListener());
84
85     myState.getExpandedUserObjects().add(project);
86
87     TreeUtil.installActions(this);
88     new TreeSpeedSearch(this, o -> InspectionsConfigTreeComparator.getDisplayTextToSort(o.getLastPathComponent().toString()));
89
90     addTreeSelectionListener(e -> {
91       TreePath newSelection = e.getNewLeadSelectionPath();
92       if (newSelection != null && !isUnderQueueUpdate()) {
93         myState.setSelectionPath(newSelection);
94       }
95     });
96   }
97
98   public void setQueueUpdate(boolean queueUpdate) {
99     myQueueUpdate = queueUpdate;
100   }
101
102   public boolean isUnderQueueUpdate() {
103     return myQueueUpdate;
104   }
105
106   public void removeAllNodes() {
107     getRoot().removeAllChildren();
108     nodeStructureChanged(getRoot());
109   }
110
111   public InspectionTreeNode getRoot() {
112     return (InspectionTreeNode)getModel().getRoot();
113   }
114
115   @Nullable
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();
123       }
124     }
125     return null;
126   }
127
128   @Nullable
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) {
138           return null;
139         }
140         if (node instanceof InspectionNode) {
141           InspectionToolWrapper wrapper = ((InspectionNode)node).getToolWrapper();
142           if (!allowDummy && getContext().getPresentation(wrapper).isDummy()) {
143             continue;
144           }
145           if (toolWrapper == null) {
146             toolWrapper = wrapper;
147           }
148           else if (toolWrapper != wrapper) {
149             return null;
150           }
151           break;
152         }
153       }
154     }
155
156     return toolWrapper;
157   }
158
159   @Nullable
160   public RefEntity getCommonSelectedElement() {
161     final Object node = getCommonSelectedNode();
162     return node instanceof RefElementNode ? ((RefElementNode)node).getElement() : null;
163   }
164
165   @Nullable
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();
173     }
174
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;
182         }
183       }
184       currentCommonNode = currentNode;
185     }
186     return currentCommonNode;
187   }
188
189   @NotNull
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;
195
196       Set<RefEntity> result = new LinkedHashSet<RefEntity>();
197       for (TreePath selectionPath : selectionPaths) {
198         final InspectionTreeNode node = (InspectionTreeNode)selectionPath.getLastPathComponent();
199         addElementsInNode(node, result);
200       }
201       return ArrayUtil.reverseArray(result.toArray(new RefEntity[result.size()]));
202     }
203     return RefEntity.EMPTY_ELEMENTS_ARRAY;
204   }
205
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();
210       out.add(element);
211     }
212     if (node instanceof ProblemDescriptionNode) {
213       final RefEntity element = ((ProblemDescriptionNode)node).getElement();
214       out.add(element);
215     }
216     final Enumeration children = node.children();
217     while (children.hasMoreElements()) {
218       InspectionTreeNode child = (InspectionTreeNode)children.nextElement();
219       addElementsInNode(child, out);
220     }
221   }
222
223   public CommonProblemDescriptor[] getSelectedDescriptors() {
224     return getSelectedDescriptors(false, null);
225   }
226
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>();
232
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)) {
241           if (length >= 2) {
242             parentToChildNode.putValue(pathAsArray[length - 2], (ProblemDescriptionNode)node);
243           } else {
244             parentToChildNode.putValue(node, (ProblemDescriptionNode)node);
245           }
246         }
247       } else {
248         nonDescriptorNodes.add((InspectionTreeNode)node);
249       }
250     }
251
252     for (InspectionTreeNode node : nonDescriptorNodes) {
253       processChildDescriptorsDeep(node, descriptors, sortedByPosition, readOnlyFilesSink);
254     }
255
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);
266           }
267         }
268       } else {
269         Stream<CommonProblemDescriptor> descriptorStream = siblings.stream().map(ProblemDescriptionNode::getDescriptor);
270         if (sortedByPosition) {
271           descriptorStream = descriptorStream.sorted(DESCRIPTOR_COMPARATOR);
272         }
273         descriptorStream = descriptorStream.filter(descriptors::add);
274         if (readOnlyFilesSink != null) {
275           checkDescriptorsFileIsWritable(descriptorStream.collect(Collectors.toList()), readOnlyFilesSink);
276         }
277       }
278     }
279
280     return descriptors.toArray(new CommonProblemDescriptor[descriptors.size()]);
281   }
282
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)) {
288         return false;
289       }
290     }
291     return true;
292   }
293
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) {
301
302       final InspectionTreeNode node = (InspectionTreeNode)path.getLastPathComponent();
303       final Collection<InspectionTreeNode> visitedChildren = rootDependencies.get(node);
304       for (InspectionTreeNode child : visitedChildren) {
305         result.remove(child);
306       }
307
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)) {
313           needToAdd = false;
314           break;
315         }
316       }
317
318       if (needToAdd) {
319         result.add(node);
320       }
321     }
322
323     int count = 0;
324     for (InspectionTreeNode node : result) {
325       count += node.getProblemCount();
326     }
327     return count;
328   }
329
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<>();
342             }
343             descriptorChildren.add(((ProblemDescriptionNode)child).getDescriptor());
344           } else {
345             descriptors.add(((ProblemDescriptionNode)child).getDescriptor());
346           }
347         }
348       }
349       else {
350         processChildDescriptorsDeep((InspectionTreeNode)child, descriptors, sortedByPosition, readOnlyFilesSink);
351       }
352     }
353
354     if (descriptorChildren != null) {
355       if (descriptorChildren.size() > 1) {
356         Collections.sort(descriptorChildren, DESCRIPTOR_COMPARATOR);
357       }
358       if (readOnlyFilesSink != null) {
359         checkDescriptorsFileIsWritable(descriptorChildren, readOnlyFilesSink);
360       }
361
362       descriptors.addAll(descriptorChildren);
363     }
364   }
365
366   private boolean isNodeValidAndIncluded(ProblemDescriptionNode node) {
367     return node.isValid() &&
368            !node.isExcluded(myExcludedManager) &&
369            !node.isAlreadySuppressedFromView() &&
370            !node.isQuickFixAppliedFromView();
371   }
372
373   private void nodeStructureChanged(InspectionTreeNode node) {
374     ((DefaultTreeModel)getModel()).nodeStructureChanged(node);
375   }
376
377   public void queueUpdate() {
378     ((InspectionRootNode) getRoot()).getUpdater().update(null, true);
379   }
380
381   public void restoreExpansionAndSelection(@Nullable InspectionTreeNode reloadedNode) {
382     myState.restoreExpansionAndSelection(this, reloadedNode);
383   }
384
385   public void setState(@NotNull InspectionTreeState state) {
386     myState = state;
387   }
388
389   public InspectionTreeState getTreeState() {
390     return myState;
391   }
392
393   public void setTreeState(@NotNull InspectionTreeState treeState) {
394     myState = treeState;
395   }
396
397   private class ExpandListener implements TreeWillExpandListener {
398     @Override
399     public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
400       final InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
401       myState.getExpandedUserObjects().add(node.getUserObject());
402     }
403
404     @Override
405     public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
406       InspectionTreeNode node = (InspectionTreeNode)event.getPath().getLastPathComponent();
407       myState.getExpandedUserObjects().remove(node.getUserObject());
408     }
409   }
410
411   @NotNull
412   public GlobalInspectionContextImpl getContext() {
413     return myContext;
414   }
415
416   private static void checkDescriptorsFileIsWritable(@NotNull Collection<CommonProblemDescriptor> descriptors, @NotNull Set<VirtualFile> readOnlySink) {
417     for (CommonProblemDescriptor descriptor : descriptors) {
418       if (checkDescriptorFileIsWritable(descriptor, readOnlySink)) {
419         return;
420       }
421     }
422   }
423
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());
429         return true;
430       }
431     }
432     return false;
433   }
434 }