inspection toolwindow: insert problem nodes with permission of duplication according...
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / InspectionRVContentProvider.java
1 /*
2  * Copyright 2000-2015 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  * User: anna
19  * Date: 10-Jan-2007
20  */
21 package com.intellij.codeInspection.ex;
22
23 import com.intellij.codeInspection.CommonProblemDescriptor;
24 import com.intellij.codeInspection.QuickFix;
25 import com.intellij.codeInspection.offlineViewer.OfflineRefElementNode;
26 import com.intellij.codeInspection.reference.RefEntity;
27 import com.intellij.codeInspection.ui.*;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.module.Module;
30 import com.intellij.openapi.module.ModuleManager;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.Ref;
33 import com.intellij.util.Function;
34 import com.intellij.util.containers.MultiMap;
35 import com.intellij.util.ui.tree.TreeUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import javax.swing.tree.TreeNode;
40 import javax.swing.tree.TreePath;
41 import java.util.*;
42 import java.util.function.Consumer;
43
44 public abstract class InspectionRVContentProvider {
45   private static final Logger LOG = Logger.getInstance("#" + InspectionRVContentProvider.class.getName());
46   private final Project myProject;
47
48   public InspectionRVContentProvider(@NotNull Project project) {
49     myProject = project;
50   }
51
52   protected interface UserObjectContainer<T> {
53     @Nullable
54     UserObjectContainer<T> getOwner();
55
56     @NotNull
57     RefElementNode createNode(@NotNull InspectionToolPresentation presentation);
58
59     @NotNull
60     T getUserObject();
61
62     @Nullable
63     String getModule();
64
65     boolean areEqual(final T o1, final T o2);
66
67     boolean supportStructure();
68   }
69
70   public abstract boolean checkReportedProblems(@NotNull GlobalInspectionContextImpl context, @NotNull InspectionToolWrapper toolWrapper);
71
72   public Iterable<? extends ScopeToolState> getTools(Tools tools) {
73     return tools.getTools();
74   }
75
76   public boolean hasQuickFixes(InspectionTree tree) {
77     final TreePath[] treePaths = tree.getSelectionPaths();
78     if (treePaths == null) return false;
79     for (TreePath selectionPath : treePaths) {
80       if (!TreeUtil.traverseDepth((TreeNode)selectionPath.getLastPathComponent(), node -> {
81         if (!((InspectionTreeNode) node).isValid()) return true;
82         if (node instanceof ProblemDescriptionNode) {
83           ProblemDescriptionNode problemDescriptionNode = (ProblemDescriptionNode)node;
84           if (!problemDescriptionNode.isQuickFixAppliedFromView()) {
85             final CommonProblemDescriptor descriptor = problemDescriptionNode.getDescriptor();
86             final QuickFix[] fixes = descriptor != null ? descriptor.getFixes() : null;
87             return fixes == null || fixes.length == 0;
88           }
89         }
90         return true;
91       })) {
92         return true;
93       }
94     }
95     return false;
96   }
97
98   @Nullable
99   public abstract QuickFixAction[] getQuickFixes(@NotNull InspectionToolWrapper toolWrapper, @NotNull InspectionTree tree);
100
101
102   public void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context,
103                                     @NotNull InspectionNode toolNode,
104                                     @NotNull InspectionTreeNode parentNode,
105                                     final boolean showStructure) {
106     InspectionToolWrapper wrapper = toolNode.getToolWrapper();
107     InspectionToolPresentation presentation = context.getPresentation(wrapper);
108     Map<String, Set<RefEntity>> content = presentation.getContent();
109     Map<RefEntity, CommonProblemDescriptor[]> problems = presentation.getProblemElements();
110     appendToolNodeContent(context, toolNode, parentNode, showStructure, content, problems);
111   }
112
113   public abstract void appendToolNodeContent(@NotNull GlobalInspectionContextImpl context,
114                                              @NotNull InspectionNode toolNode,
115                                              @NotNull InspectionTreeNode parentNode,
116                                              final boolean showStructure,
117                                              @NotNull Map<String, Set<RefEntity>> contents,
118                                              @NotNull Map<RefEntity, CommonProblemDescriptor[]> problems);
119
120   protected abstract void appendDescriptor(@NotNull GlobalInspectionContextImpl context,
121                                            @NotNull InspectionToolWrapper toolWrapper,
122                                            @NotNull UserObjectContainer container,
123                                            @NotNull InspectionTreeNode pNode,
124                                            final boolean canPackageRepeat);
125
126   public boolean isContentLoaded() {
127     return true;
128   }
129
130   protected <T> void buildTree(@NotNull GlobalInspectionContextImpl context,
131                                @NotNull Map<String, Set<T>> packageContents,
132                                final boolean canPackageRepeat,
133                                @NotNull InspectionToolWrapper toolWrapper,
134                                @NotNull Function<T, UserObjectContainer<T>> computeContainer,
135                                final boolean showStructure,
136                                final Consumer<InspectionTreeNode> createdNodesConsumer) {
137     final Map<String, Map<String, InspectionPackageNode>> module2PackageMap = new HashMap<String, Map<String, InspectionPackageNode>>();
138     boolean supportStructure = showStructure;
139     final MultiMap<InspectionPackageNode, UserObjectContainer<T>> packageDescriptors = new MultiMap<>();
140     for (String packageName : packageContents.keySet()) {
141       final Set<T> elements = packageContents.get(packageName);
142       for (T userObject : elements) {
143         final UserObjectContainer<T> container = computeContainer.fun(userObject);
144         supportStructure &= container.supportStructure();
145         final String moduleName = showStructure ? container.getModule() : null;
146         Map<String, InspectionPackageNode> packageNodes = module2PackageMap.get(moduleName);
147         if (packageNodes == null) {
148           packageNodes = new HashMap<String, InspectionPackageNode>();
149           module2PackageMap.put(moduleName, packageNodes);
150         }
151         InspectionPackageNode pNode = packageNodes.get(packageName);
152         if (pNode == null) {
153           pNode = new InspectionPackageNode(packageName);
154           packageNodes.put(packageName, pNode);
155         }
156
157         packageDescriptors.putValue(pNode, container);
158       }
159     }
160
161     if (supportStructure) {
162       final HashMap<String, InspectionModuleNode> moduleNodes = new HashMap<String, InspectionModuleNode>();
163       for (final String moduleName : module2PackageMap.keySet()) {
164         final Map<String, InspectionPackageNode> packageNodes = module2PackageMap.get(moduleName);
165         InspectionModuleNode moduleNode = moduleNodes.get(moduleName);
166
167         if (moduleNode == null) {
168           if (moduleName != null) {
169             final Module module = ModuleManager.getInstance(myProject).findModuleByName(moduleName);
170             if (module != null) {
171               moduleNode = new InspectionModuleNode(module);
172               moduleNodes.put(moduleName, moduleNode);
173             }
174             else { //module content was removed ?
175               continue;
176             }
177           }
178           else {
179             for (InspectionPackageNode packageNode : packageNodes.values()) {
180               createdNodesConsumer.accept(packageNode);
181               for (UserObjectContainer<T> container : packageDescriptors.get(packageNode)) {
182                 appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat);
183               }
184             }
185             continue;
186           }
187         }
188         for (InspectionPackageNode packageNode : packageNodes.values()) {
189           if (packageNode.getPackageName() != null) {
190             moduleNode.insertByOrder(packageNode, false);
191             for (UserObjectContainer<T> container : packageDescriptors.get(packageNode)) {
192               appendDescriptor(context, toolWrapper, container, packageNode, canPackageRepeat);
193             }
194           }
195           else {
196             for (UserObjectContainer<T> container : packageDescriptors.get(packageNode)) {
197               appendDescriptor(context, toolWrapper, container, moduleNode, canPackageRepeat);
198             }
199           }
200         }
201         createdNodesConsumer.accept(moduleNode);
202       }
203     }
204     else {
205       for (Map<String, InspectionPackageNode> packageNodes : module2PackageMap.values()) {
206         for (InspectionPackageNode pNode : packageNodes.values()) {
207           for (UserObjectContainer<T> container : packageDescriptors.get(pNode)) {
208             appendDescriptor(context, toolWrapper, container, pNode, canPackageRepeat);
209           }
210           final int count = pNode.getChildCount();
211           final ArrayList<TreeNode> childNodes = new ArrayList<>(count);
212           for (int i = 0; i < count; i++) {
213             childNodes.add(pNode.getChildAt(i));
214           }
215           for (TreeNode childNode: childNodes) {
216             if (childNode instanceof ProblemDescriptionNode) {
217               createdNodesConsumer.accept(pNode);
218               break;
219             }
220             LOG.assertTrue(childNode instanceof RefElementNode, childNode.getClass().getName());
221             final RefElementNode elementNode = (RefElementNode)childNode;
222             final Set<RefElementNode> parentNodes = new LinkedHashSet<RefElementNode>();
223             if (pNode.getPackageName() != null) {
224               parentNodes.add(elementNode);
225             } else {
226               boolean hasElementNodeUnder = true;
227               for(int e = 0; e < elementNode.getChildCount(); e++) {
228                 final TreeNode grandChildNode = elementNode.getChildAt(e);
229                 if (grandChildNode instanceof ProblemDescriptionNode) {
230                   hasElementNodeUnder = false;
231                   break;
232                 }
233                 LOG.assertTrue(grandChildNode instanceof RefElementNode);
234                 parentNodes.add((RefElementNode)grandChildNode);
235               }
236               if (!hasElementNodeUnder) {
237                 createdNodesConsumer.accept(elementNode);
238                 continue;
239               }
240             }
241             for (RefElementNode parentNode : parentNodes) {
242               final List<ProblemDescriptionNode> nodes = new ArrayList<ProblemDescriptionNode>();
243               TreeUtil.traverse(parentNode, new TreeUtil.Traverse() {
244                 @Override
245                 public boolean accept(final Object node) {
246                   if (node instanceof ProblemDescriptionNode) {
247                     nodes.add((ProblemDescriptionNode)node);
248                   }
249                   return true;
250                 }
251               });
252               if (nodes.isEmpty()) continue;  //FilteringInspectionTool == DeadCode
253               parentNode.removeAllChildren();
254               for (ProblemDescriptionNode node : nodes) {
255                 parentNode.add(node);
256               }
257             }
258             for (RefElementNode node : parentNodes) {
259               createdNodesConsumer.accept(node);
260             }
261           }
262         }
263       }
264     }
265   }
266
267   @NotNull
268   protected static RefElementNode addNodeToParent(@NotNull UserObjectContainer container,
269                                                   @NotNull InspectionToolPresentation presentation,
270                                                   final InspectionTreeNode parentNode) {
271     final RefElementNode nodeToBeAdded = container.createNode(presentation);
272     final Ref<Boolean> firstLevel = new Ref<Boolean>(true);
273     RefElementNode prevNode = null;
274     final Ref<RefElementNode> result = new Ref<RefElementNode>();
275     while (true) {
276       final RefElementNode currentNode = firstLevel.get() ? nodeToBeAdded : container.createNode(presentation);
277       final UserObjectContainer finalContainer = container;
278       final RefElementNode finalPrevNode = prevNode;
279       TreeUtil.traverseDepth(parentNode, new TreeUtil.Traverse() {
280         @Override
281         public boolean accept(Object node) {
282           if (node instanceof RefElementNode) {
283             final RefElementNode refElementNode = (RefElementNode)node;
284             final Object userObject = finalContainer.getUserObject();
285             final Object object = node instanceof OfflineRefElementNode ? ((OfflineRefElementNode) refElementNode).getOfflineDescriptor() : refElementNode.getUserObject();
286             if ((object == null || userObject.getClass().equals(object.getClass())) && finalContainer.areEqual(object, userObject)) {
287               if (firstLevel.get()) {
288                 result.set(refElementNode);
289                 return false;
290               }
291               else {
292                 refElementNode.insertByOrder(finalPrevNode, false);
293                 result.set(nodeToBeAdded);
294                 return false;
295               }
296             }
297           }
298           return true;
299         }
300       });
301       if(!result.isNull()) return result.get();
302
303       if (!firstLevel.get()) {
304         currentNode.insertByOrder(prevNode, false);
305       }
306       final UserObjectContainer owner = container.getOwner();
307       if (owner == null) {
308         parentNode.insertByOrder(currentNode, false);
309         return nodeToBeAdded;
310       }
311       container = owner;
312       prevNode = currentNode;
313       firstLevel.set(false);
314     }
315   }
316
317   @SuppressWarnings({"ConstantConditions"}) //class cast suppression
318   protected static void merge(InspectionTreeNode child, InspectionTreeNode parent, boolean merge) {
319     if (merge) {
320       for (int i = 0; i < parent.getChildCount(); i++) {
321         InspectionTreeNode current = (InspectionTreeNode)parent.getChildAt(i);
322         if (child.getClass() != current.getClass()) {
323           continue;
324         }
325         if (current instanceof InspectionPackageNode) {
326           if (((InspectionPackageNode)current).getPackageName().compareTo(((InspectionPackageNode)child).getPackageName()) == 0) {
327             processDepth(child, current);
328             return;
329           }
330         }
331         else if (current instanceof RefElementNode) {
332           if (((RefElementNode)current).getElement().getName().compareTo(((RefElementNode)child).getElement().getName()) == 0 &&
333               ((RefElementNode)current).getElement().getQualifiedName().compareTo(((RefElementNode)child).getElement().getQualifiedName()) == 0) {
334             processDepth(child, current);
335             return;
336           }
337         }
338         else if (current instanceof InspectionNode) {
339           if (((InspectionNode)current).getToolWrapper().getShortName().compareTo(((InspectionNode)child).getToolWrapper().getShortName()) == 0) {
340             processDepth(child, current);
341             return;
342           }
343         }
344         else if (current instanceof InspectionModuleNode) {
345           if (((InspectionModuleNode)current).getName().compareTo(((InspectionModuleNode)child).getName()) == 0) {
346             processDepth(child, current);
347             return;
348           }
349         }
350       }
351     }
352     parent.insertByOrder(child, false);
353   }
354
355   private static void processDepth(final InspectionTreeNode child, final InspectionTreeNode current) {
356     InspectionTreeNode[] children = new InspectionTreeNode[child.getChildCount()];
357     for (int i = 0; i < children.length; i++) {
358       children[i] = (InspectionTreeNode)child.getChildAt(i);
359     }
360     for (InspectionTreeNode node : children) {
361       merge(node, current, true);
362     }
363   }
364 }