export html action: do not traverse tree for excluded elements
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ui / DefaultInspectionToolPresentation.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 package com.intellij.codeInspection.ui;
17
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
20 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
21 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
22 import com.intellij.codeInsight.intention.IntentionAction;
23 import com.intellij.codeInspection.*;
24 import com.intellij.codeInspection.ex.*;
25 import com.intellij.codeInspection.reference.*;
26 import com.intellij.lang.annotation.HighlightSeverity;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.components.PathMacroManager;
29 import com.intellij.openapi.diagnostic.Logger;
30 import com.intellij.openapi.editor.Editor;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.Comparing;
33 import com.intellij.openapi.util.JDOMUtil;
34 import com.intellij.openapi.util.text.StringUtil;
35 import com.intellij.openapi.vcs.FileStatus;
36 import com.intellij.openapi.vfs.CharsetToolkit;
37 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
38 import com.intellij.profile.codeInspection.InspectionProjectProfileManagerImpl;
39 import com.intellij.psi.PsiElement;
40 import com.intellij.psi.PsiFile;
41 import com.intellij.util.ArrayUtil;
42 import com.intellij.util.IncorrectOperationException;
43 import com.intellij.util.containers.ContainerUtil;
44 import com.intellij.util.containers.HashSet;
45 import gnu.trove.Equality;
46 import gnu.trove.THashMap;
47 import gnu.trove.THashSet;
48 import gnu.trove.TObjectHashingStrategy;
49 import org.jdom.Element;
50 import org.jdom.IllegalDataException;
51 import org.jetbrains.annotations.NonNls;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54
55 import java.io.*;
56 import java.util.*;
57 import java.util.function.Predicate;
58 import java.util.regex.Matcher;
59 import java.util.regex.Pattern;
60
61 public class DefaultInspectionToolPresentation implements ProblemDescriptionsProcessor, InspectionToolPresentation {
62   @NotNull private final InspectionToolWrapper myToolWrapper;
63
64   @NotNull
65   private final GlobalInspectionContextImpl myContext;
66   private static String ourOutputPath;
67   private InspectionNode myToolNode;
68
69   private static final Object lock = new Object();
70   private final Map<RefEntity, CommonProblemDescriptor[]> myProblemElements = Collections.synchronizedMap(new THashMap<RefEntity, CommonProblemDescriptor[]>(
71     TObjectHashingStrategy.IDENTITY));
72   private final Map<String, Set<RefEntity>> myContents = Collections.synchronizedMap(new HashMap<String, Set<RefEntity>>(1)); // keys can be null
73   private final Set<RefModule> myModulesProblems = Collections.synchronizedSet(new THashSet<RefModule>(TObjectHashingStrategy.IDENTITY));
74   private final Map<CommonProblemDescriptor, RefEntity> myProblemToElements = Collections.synchronizedMap(new THashMap<CommonProblemDescriptor, RefEntity>(TObjectHashingStrategy.IDENTITY));
75   private DescriptorComposer myComposer;
76   private final Map<RefEntity, Set<QuickFix>> myQuickFixActions = Collections.synchronizedMap(new THashMap<RefEntity, Set<QuickFix>>(TObjectHashingStrategy.IDENTITY));
77   private final Map<RefEntity, CommonProblemDescriptor[]> myIgnoredElements = Collections.synchronizedMap(new THashMap<RefEntity, CommonProblemDescriptor[]>(TObjectHashingStrategy.IDENTITY));
78
79   protected static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.DescriptorProviderInspection");
80   private volatile boolean isDisposed;
81
82   public DefaultInspectionToolPresentation(@NotNull InspectionToolWrapper toolWrapper, @NotNull GlobalInspectionContextImpl context) {
83     myToolWrapper = toolWrapper;
84     myContext = context;
85   }
86
87   @NotNull
88   protected static FileStatus calcStatus(boolean old, boolean current) {
89     if (old) {
90       if (!current) {
91         return FileStatus.DELETED;
92       }
93     }
94     else if (current) {
95       return FileStatus.ADDED;
96     }
97     return FileStatus.NOT_CHANGED;
98   }
99
100   public static String stripUIRefsFromInspectionDescription(String description) {
101     final int descriptionEnd = description.indexOf("<!-- tooltip end -->");
102     if (descriptionEnd < 0) {
103       final Pattern pattern = Pattern.compile(".*Use.*(the (panel|checkbox|checkboxes|field|button|controls).*below).*", Pattern.DOTALL);
104       final Matcher matcher = pattern.matcher(description);
105       int startFindIdx = 0;
106       while (matcher.find(startFindIdx)) {
107         final int end = matcher.end(1);
108         startFindIdx = end;
109         description = description.substring(0, matcher.start(1)) + " inspection settings " + description.substring(end);
110       }
111     } else {
112       description = description.substring(0, descriptionEnd);
113     }
114     return description;
115   }
116
117   protected HighlightSeverity getSeverity(@NotNull RefElement element) {
118     final PsiElement psiElement = element.getPointer().getContainingFile();
119     if (psiElement != null) {
120       final GlobalInspectionContextImpl context = getContext();
121       final String shortName = getSeverityDelegateName();
122       final Tools tools = context.getTools().get(shortName);
123       if (tools != null) {
124         for (ScopeToolState state : tools.getTools()) {
125           InspectionToolWrapper toolWrapper = state.getTool();
126           if (toolWrapper == getToolWrapper()) {
127             return context.getCurrentProfile().getErrorLevel(HighlightDisplayKey.find(shortName), psiElement).getSeverity();
128           }
129         }
130       }
131
132       final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getInspectionProfile();
133       final HighlightDisplayLevel level = profile.getErrorLevel(HighlightDisplayKey.find(shortName), psiElement);
134       return level.getSeverity();
135     }
136     return null;
137   }
138
139   protected String getSeverityDelegateName() {
140     return getToolWrapper().getShortName();
141   }
142
143   protected static String getTextAttributeKey(@NotNull Project project,
144                                               @NotNull HighlightSeverity severity,
145                                               @NotNull ProblemHighlightType highlightType) {
146     if (highlightType == ProblemHighlightType.LIKE_DEPRECATED) {
147       return HighlightInfoType.DEPRECATED.getAttributesKey().getExternalName();
148     }
149     if (highlightType == ProblemHighlightType.LIKE_UNKNOWN_SYMBOL && severity == HighlightSeverity.ERROR) {
150       return HighlightInfoType.WRONG_REF.getAttributesKey().getExternalName();
151     }
152     if (highlightType == ProblemHighlightType.LIKE_UNUSED_SYMBOL) {
153       return HighlightInfoType.UNUSED_SYMBOL.getAttributesKey().getExternalName();
154     }
155     SeverityRegistrar registrar = InspectionProjectProfileManagerImpl.getInstanceImpl(project).getSeverityRegistrar();
156     return registrar.getHighlightInfoTypeBySeverity(severity).getAttributesKey().getExternalName();
157   }
158
159   @NotNull
160   public InspectionToolWrapper getToolWrapper() {
161     return myToolWrapper;
162   }
163
164   @NotNull
165   public RefManager getRefManager() {
166     return getContext().getRefManager();
167   }
168
169   @NotNull
170   @Override
171   public GlobalInspectionContextImpl getContext() {
172     return myContext;
173   }
174
175   @Override
176   public void exportResults(@NotNull final Element parentNode,
177                             @NotNull final Predicate<RefEntity> excludedEntities,
178                             @NotNull final Predicate<CommonProblemDescriptor> excludedDescriptors) {
179     getRefManager().iterate(new RefVisitor(){
180       @Override
181       public void visitElement(@NotNull RefEntity elem) {
182         if (!excludedEntities.test(elem)) {
183           exportResults(parentNode, elem, excludedDescriptors);
184         }
185       }
186     });
187   }
188
189   @Override
190   public void addProblemElement(RefEntity refElement, @NotNull CommonProblemDescriptor... descriptions){
191     addProblemElement(refElement, true, descriptions);
192   }
193
194   @Override
195   public void addProblemElement(final RefEntity refElement, boolean filterSuppressed, @NotNull final CommonProblemDescriptor... descriptors) {
196     if (refElement == null) return;
197     if (descriptors.length == 0) return;
198     if (filterSuppressed) {
199       if (!isOutputPathSet() || !(myToolWrapper instanceof LocalInspectionToolWrapper)) {
200         synchronized (lock) {
201           Map<RefEntity, CommonProblemDescriptor[]> problemElements = getProblemElements();
202           CommonProblemDescriptor[] problems = problemElements.get(refElement);
203           problems = problems == null ? descriptors : mergeDescriptors(problems, descriptors);
204           problemElements.put(refElement, problems);
205         }
206         for (CommonProblemDescriptor description : descriptors) {
207           getProblemToElements().put(description, refElement);
208           collectQuickFixes(description.getFixes(), refElement);
209         }
210       }
211       else {
212         writeOutput(descriptors, refElement);
213       }
214     }
215     else { //just need to collect problems
216       for (CommonProblemDescriptor descriptor : descriptors) {
217         getProblemToElements().put(descriptor, refElement);
218       }
219     }
220
221     final GlobalInspectionContextImpl context = getContext();
222     if (context.isViewClosed() || !(refElement instanceof RefElement)) {
223       return;
224     }
225     if (myToolWrapper instanceof LocalInspectionToolWrapper && !ApplicationManager.getApplication().isUnitTestMode()) {
226       InspectionResultsView view = context.createViewIfNeed();
227       if (!isDisposed()) {
228         ApplicationManager.getApplication().assertReadAccessAllowed();
229         synchronized (view.getTreeStructureUpdateLock()) {
230           final InspectionNode toolNode;
231           toolNode = myToolNode == null ?
232                      view.addTool(myToolWrapper, HighlightDisplayLevel.find(getSeverity((RefElement)refElement)),
233                                   context.getUIOptions().GROUP_BY_SEVERITY, context.isSingleInspectionRun()) : myToolNode;
234
235           final Map<RefEntity, CommonProblemDescriptor[]> problems = new HashMap<RefEntity, CommonProblemDescriptor[]>();
236           problems.put(refElement, descriptors);
237           final Map<String, Set<RefEntity>> contents = new HashMap<String, Set<RefEntity>>();
238           final String groupName = refElement.getRefManager().getGroupName((RefElement)refElement);
239           Set<RefEntity> content = contents.get(groupName);
240           if (content == null) {
241             content = new HashSet<RefEntity>();
242             contents.put(groupName, content);
243           }
244           content.add(refElement);
245
246           view.getProvider().appendToolNodeContent(context, toolNode,
247                                                    (InspectionTreeNode)toolNode.getParent(), context.getUIOptions().SHOW_STRUCTURE,
248                                                    contents, problems);
249
250         }
251       }
252     }
253   }
254
255   @NotNull
256   public static CommonProblemDescriptor[] mergeDescriptors(@NotNull CommonProblemDescriptor[] problems1,
257                                                             @NotNull CommonProblemDescriptor[] problems2) {
258     CommonProblemDescriptor[] out = new CommonProblemDescriptor[problems1.length + problems2.length];
259     int o = problems1.length;
260     Equality<CommonProblemDescriptor> equality = new Equality<CommonProblemDescriptor>() {
261       @Override
262       public boolean equals(CommonProblemDescriptor o1, CommonProblemDescriptor o2) {
263         if (o1 instanceof ProblemDescriptor) {
264           ProblemDescriptorBase p1 = (ProblemDescriptorBase)o1;
265           ProblemDescriptorBase p2 = (ProblemDescriptorBase)o2;
266           if (!Comparing.equal(p1.getDescriptionTemplate(), p2.getDescriptionTemplate())) return false;
267           if (!Comparing.equal(p1.getTextRange(), p2.getTextRange())) return false;
268           if (!Comparing.equal(p1.getHighlightType(), p2.getHighlightType())) return false;
269           if (!Comparing.equal(p1.getProblemGroup(), p2.getProblemGroup())) return false;
270           if (!Comparing.equal(p1.getStartElement(), p2.getStartElement())) return false;
271           if (!Comparing.equal(p1.getEndElement(), p2.getEndElement())) return false;
272         }
273         else {
274           if (!o1.toString().equals(o2.toString())) return false;
275         }
276         return true;
277       }
278     };
279     for (CommonProblemDescriptor descriptor : problems2) {
280       if (ArrayUtil.indexOf(problems1, descriptor, equality) == -1) {
281         out[o++] = descriptor;
282       }
283     }
284     System.arraycopy(problems1, 0, out, 0, problems1.length);
285     return Arrays.copyOfRange(out, 0, o);
286   }
287
288   public void setToolNode(InspectionNode toolNode) {
289     myToolNode = toolNode;
290   }
291   
292   protected boolean isDisposed() {
293     return isDisposed;
294   }
295
296   private synchronized void writeOutput(@NotNull final CommonProblemDescriptor[] descriptions, @NotNull RefEntity refElement) {
297     final Element parentNode = new Element(InspectionsBundle.message("inspection.problems"));
298     exportResults(descriptions, refElement, parentNode, d -> false);
299     final List list = parentNode.getChildren();
300
301     @NonNls final String ext = ".xml";
302     final String fileName = ourOutputPath + File.separator + myToolWrapper.getShortName() + ext;
303     final PathMacroManager pathMacroManager = PathMacroManager.getInstance(getContext().getProject());
304     PrintWriter printWriter = null;
305     try {
306       new File(ourOutputPath).mkdirs();
307       final File file = new File(fileName);
308       final CharArrayWriter writer = new CharArrayWriter();
309       if (!file.exists()) {
310         writer.append("<").append(InspectionsBundle.message("inspection.problems")).append(" " + GlobalInspectionContextBase.LOCAL_TOOL_ATTRIBUTE + "=\"")
311           .append(Boolean.toString(myToolWrapper instanceof LocalInspectionToolWrapper)).append("\">\n");
312       }
313       for (Object o : list) {
314         final Element element = (Element)o;
315         pathMacroManager.collapsePaths(element);
316         JDOMUtil.writeElement(element, writer, "\n");
317       }
318       printWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, true), CharsetToolkit.UTF8_CHARSET)));
319       printWriter.append("\n");
320       printWriter.append(writer.toString());
321     }
322     catch (IOException e) {
323       LOG.error(e);
324     }
325     finally {
326       if (printWriter != null) {
327         printWriter.close();
328       }
329     }
330   }
331
332   @Override
333   @NotNull
334   public Collection<CommonProblemDescriptor> getProblemDescriptors() {
335     return getProblemToElements().keySet();
336   }
337
338   private void collectQuickFixes(final QuickFix[] fixes, @NotNull RefEntity refEntity) {
339     if (fixes != null && fixes.length != 0) {
340       Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
341       if (localQuickFixes == null) {
342         localQuickFixes = new HashSet<QuickFix>();
343         getQuickFixActions().put(refEntity, localQuickFixes);
344       }
345       ContainerUtil.addAll(localQuickFixes, fixes);
346     }
347   }
348
349   @Override
350   public void ignoreElement(@NotNull final RefEntity refEntity) {
351     getProblemElements().remove(refEntity);
352     getQuickFixActions().remove(refEntity);
353   }
354
355   @Override
356   public void ignoreCurrentElement(RefEntity refEntity) {
357     if (refEntity == null) return;
358     getIgnoredElements().put(refEntity, getProblemElements().get(refEntity));
359   }
360
361   @Override
362   public void amnesty(RefEntity refEntity) {
363     getIgnoredElements().remove(refEntity);
364   }
365
366   @Override
367   public void ignoreProblem(RefEntity refEntity, CommonProblemDescriptor problem, int idx) {
368     if (refEntity == null) return;
369     final Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
370     final QuickFix[] fixes = problem.getFixes();
371     if (isIgnoreProblem(fixes, localQuickFixes, idx)){
372       getProblemToElements().remove(problem);
373       Map<RefEntity, CommonProblemDescriptor[]> problemElements = getProblemElements();
374       synchronized (lock) {
375         CommonProblemDescriptor[] descriptors = problemElements.get(refEntity);
376         if (descriptors != null) {
377           ArrayList<CommonProblemDescriptor> newDescriptors = new ArrayList<CommonProblemDescriptor>(Arrays.asList(descriptors));
378           newDescriptors.remove(problem);
379           CommonProblemDescriptor[] newDescriptorsAsArray = newDescriptors.toArray(new CommonProblemDescriptor[newDescriptors.size()]);
380           getQuickFixActions().put(refEntity, null);
381           if (!newDescriptors.isEmpty()) {
382             problemElements.put(refEntity, newDescriptorsAsArray);
383             for (CommonProblemDescriptor descriptor : newDescriptors) {
384               collectQuickFixes(descriptor.getFixes(), refEntity);
385             }
386           }
387           ignoreProblemElement(refEntity, newDescriptorsAsArray, problem);
388         }
389       }
390     }
391   }
392
393   private void ignoreProblemElement(RefEntity refEntity, CommonProblemDescriptor[] newDescriptors, CommonProblemDescriptor toIgnore){
394     if (newDescriptors != null && newDescriptors.length == 0) {
395       newDescriptors = null;
396     }
397     if (newDescriptors == null) {
398       getProblemElements().remove(refEntity);
399     } else {
400       getProblemElements().put(refEntity, newDescriptors);
401     }
402     CommonProblemDescriptor[] oldIgnored = getIgnoredElements().getOrDefault(refEntity, CommonProblemDescriptor.EMPTY_ARRAY);
403     CommonProblemDescriptor[] update = new CommonProblemDescriptor[oldIgnored.length + 1];
404     System.arraycopy(oldIgnored, 0, update, 0, oldIgnored.length);
405     update[update.length - 1] = toIgnore;
406     getIgnoredElements().put(refEntity, update);
407   }
408
409   @Override
410   public void ignoreCurrentElementProblem(RefEntity refEntity, CommonProblemDescriptor descriptor) {
411     CommonProblemDescriptor[] descriptors = getIgnoredElements().get(refEntity);
412     if (descriptors == null) {
413       descriptors = new CommonProblemDescriptor[0];
414     }
415     getIgnoredElements().put(refEntity, ArrayUtil.append(descriptors, descriptor));
416   }
417
418   private static boolean isIgnoreProblem(QuickFix[] problemFixes, Set<QuickFix> fixes, int idx){
419     if (problemFixes == null || fixes == null) {
420       return true;
421     }
422     if (problemFixes.length <= idx){
423       return true;
424     }
425     for (QuickFix fix : problemFixes) {
426       if (fix != problemFixes[idx] && !fixes.contains(fix)){
427         return false;
428       }
429     }
430     return true;
431   }
432
433   @Override
434   public void cleanup() {
435     synchronized (lock) {
436       myProblemElements.clear();
437       myProblemToElements.clear();
438       myQuickFixActions.clear();
439       myIgnoredElements.clear();
440       myContents.clear();
441       myModulesProblems.clear();
442     }
443
444     isDisposed = true;
445   }
446
447   @Override
448   public void finalCleanup() {
449     cleanup();
450   }
451
452   @Override
453   @Nullable
454   public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) {
455     final CommonProblemDescriptor[] problems = getProblemElements().get(refEntity);
456     if (problems == null) return null;
457
458     if (!refEntity.isValid()) {
459       ignoreElement(refEntity);
460       return null;
461     }
462
463     return problems;
464   }
465
466   @NotNull
467   @Override
468   public HTMLComposerImpl getComposer() {
469     if (myComposer == null) {
470       myComposer = new DescriptorComposer(this);
471     }
472     return myComposer;
473   }
474
475   @Override
476   public void exportResults(@NotNull final Element parentNode,
477                             @NotNull RefEntity refEntity,
478                             @NotNull Predicate<CommonProblemDescriptor> isDescriptorExcluded) {
479     synchronized (lock) {
480       if (getProblemElements().containsKey(refEntity)) {
481         CommonProblemDescriptor[] descriptions = getDescriptions(refEntity);
482         if (descriptions != null) {
483           exportResults(descriptions, refEntity, parentNode, isDescriptorExcluded);
484         }
485       }
486     }
487   }
488
489   private void exportResults(@NotNull final CommonProblemDescriptor[] descriptors,
490                              @NotNull RefEntity refEntity,
491                              @NotNull Element parentNode,
492                              @NotNull Predicate<CommonProblemDescriptor> isDescriptorExcluded) {
493     for (CommonProblemDescriptor descriptor : descriptors) {
494       if (isDescriptorExcluded.test(descriptor)) continue;
495       @NonNls final String template = descriptor.getDescriptionTemplate();
496       int line = descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getLineNumber() : -1;
497       final PsiElement psiElement = descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null;
498       @NonNls String problemText = StringUtil.replace(StringUtil.replace(template, "#ref", psiElement != null ? ProblemDescriptorUtil
499         .extractHighlightedText(descriptor, psiElement) : ""), " #loc ", " ");
500
501       Element element = refEntity.getRefManager().export(refEntity, parentNode, line);
502       if (element == null) return;
503       @NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag"));
504       problemClassElement.addContent(myToolWrapper.getDisplayName());
505
506       final HighlightSeverity severity;
507       if (refEntity instanceof RefElement){
508         final RefElement refElement = (RefElement)refEntity;
509         severity = getSeverity(refElement);
510       }
511       else {
512         final InspectionProfile profile = InspectionProjectProfileManager.getInstance(getContext().getProject()).getInspectionProfile();
513         final HighlightDisplayLevel level = profile.getErrorLevel(HighlightDisplayKey.find(myToolWrapper.getShortName()), psiElement);
514         severity = level.getSeverity();
515       }
516
517       if (severity != null) {
518         ProblemHighlightType problemHighlightType = descriptor instanceof ProblemDescriptor
519                                                     ? ((ProblemDescriptor)descriptor).getHighlightType()
520                                                     : ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
521         final String attributeKey = getTextAttributeKey(getRefManager().getProject(), severity, problemHighlightType);
522         problemClassElement.setAttribute("severity", severity.myName);
523         problemClassElement.setAttribute("attribute_key", attributeKey);
524       }
525
526       element.addContent(problemClassElement);
527       if (myToolWrapper instanceof GlobalInspectionToolWrapper) {
528         final GlobalInspectionTool globalInspectionTool = ((GlobalInspectionToolWrapper)myToolWrapper).getTool();
529         final QuickFix[] fixes = descriptor.getFixes();
530         if (fixes != null) {
531           @NonNls Element hintsElement = new Element("hints");
532           for (QuickFix fix : fixes) {
533             final String hint = globalInspectionTool.getHint(fix);
534             if (hint != null) {
535               @NonNls Element hintElement = new Element("hint");
536               hintElement.setAttribute("value", hint);
537               hintsElement.addContent(hintElement);
538             }
539           }
540           element.addContent(hintsElement);
541         }
542       }
543       try {
544         Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
545         descriptionElement.addContent(problemText);
546         element.addContent(descriptionElement);
547       }
548       catch (IllegalDataException e) {
549         //noinspection HardCodedStringLiteral,UseOfSystemOutOrSystemErr
550         System.out.println("Cannot save results for " + refEntity.getName() + ", inspection which caused problem: " + myToolWrapper.getShortName());
551       }
552     }
553   }
554
555   @Override
556   public boolean isGraphNeeded() {
557     return false;
558   }
559
560   @Override
561   public boolean hasReportedProblems() {
562     return !getProblemElements().isEmpty();
563   }
564
565   @Override
566   public void updateContent() {
567     myContents.clear();
568     myModulesProblems.clear();
569     final Set<RefEntity> elements = getProblemElements().keySet();
570     for (RefEntity element : elements) {
571       if (getContext().getUIOptions().FILTER_RESOLVED_ITEMS && getIgnoredElements().containsKey(element)) continue;
572       if (element instanceof RefModule) {
573         myModulesProblems.add((RefModule)element);
574       }
575       else {
576         String groupName = element instanceof RefElement ? element.getRefManager().getGroupName((RefElement)element) : element.getQualifiedName() ;
577         Set<RefEntity> content = myContents.get(groupName);
578         if (content == null) {
579           content = new HashSet<RefEntity>();
580           myContents.put(groupName, content);
581         }
582         content.add(element);
583       }
584     }
585   }
586
587   @NotNull
588   @Override
589   public Map<String, Set<RefEntity>> getContent() {
590     return myContents;
591   }
592
593   @NotNull
594   @Override
595   public Set<RefModule> getModuleProblems() {
596     return myModulesProblems;
597   }
598
599   @Override
600   @Nullable
601   public QuickFixAction[] getQuickFixes(@NotNull final RefEntity[] refElements, CommonProblemDescriptor[] allowedDescriptors) {
602     return extractActiveFixes(refElements, getProblemElements(), allowedDescriptors);
603   }
604
605   @Override
606   @Nullable
607   public QuickFixAction[] extractActiveFixes(@NotNull RefEntity[] refElements,
608                                              @NotNull Map<RefEntity, CommonProblemDescriptor[]> descriptorMap,
609                                              @Nullable CommonProblemDescriptor[] allowedDescriptors) {
610     final Set<CommonProblemDescriptor> allowedDescriptorSet = allowedDescriptors == null ? null : ContainerUtil.newHashSet(allowedDescriptors);
611     Map<Class, QuickFixAction> result = new com.intellij.util.containers.HashMap<>();
612     boolean isFirst = true;
613     for (RefEntity refElement : refElements) {
614       final CommonProblemDescriptor[] descriptors = descriptorMap.get(refElement);
615       if (descriptors == null) continue;
616       for (CommonProblemDescriptor d : descriptors) {
617         if (allowedDescriptorSet != null && !allowedDescriptorSet.contains(d)) {
618           continue;
619         }
620         QuickFix[] fixes = d.getFixes();
621         if (fixes != null) {
622           if (isFirst) {
623             for (QuickFix fix : fixes) {
624               if (fix == null) continue;
625               final Class klass = getFixClass(fix);
626               LocalQuickFixWrapper quickFixWrapper = new LocalQuickFixWrapper(fix, myToolWrapper);
627               result.put(klass, quickFixWrapper);
628             }
629             isFirst = false;
630           }
631           else {
632             for (Class clazz : new ArrayList<>(result.keySet())) {
633               boolean isFound = false;
634               for (QuickFix fix : fixes) {
635                 if (fix == null) continue;
636                 final Class klass = getFixClass(fix);
637                 if (clazz.equals(klass)) {
638                   isFound = true;
639                   final QuickFixAction quickFixAction = result.get(clazz);
640                   try {
641                     String familyName = fix.getFamilyName();
642                     ((LocalQuickFixWrapper)quickFixAction).setText(familyName);
643                   }
644                   catch (AbstractMethodError e) {
645                     //for plugin compatibility
646                     ((LocalQuickFixWrapper)quickFixAction).setText("Name is not available");
647                   }
648                   break;
649                 }
650               }
651               if (!isFound) {
652                 result.remove(clazz);
653                 if (result.isEmpty()) {
654                   return QuickFixAction.EMPTY;
655                 }
656               }
657             }
658           }
659         }
660       }
661     }
662     return result.values().isEmpty() ? null : result.values().toArray(new QuickFixAction[result.size()]);
663   }
664
665   private static Class getFixClass(QuickFix fix) {
666     return fix instanceof ActionClassHolder ? ((ActionClassHolder)fix).getActionClass() : fix.getClass();
667   }
668
669   @Override
670   public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) {
671     return getProblemToElements().get(descriptor);
672   }
673
674   @Override
675   public void ignoreProblem(@NotNull CommonProblemDescriptor descriptor, @NotNull QuickFix fix) {
676     RefEntity refElement = getProblemToElements().get(descriptor);
677     if (refElement != null) {
678       final QuickFix[] fixes = descriptor.getFixes();
679       for (int i = 0; i < fixes.length; i++) {
680         if (fixes[i] == fix){
681           ignoreProblem(refElement, descriptor, i);
682           return;
683         }
684       }
685     }
686   }
687
688
689   @Override
690   public boolean isElementIgnored(final RefEntity element) {
691     return getIgnoredElements().containsKey(element);
692   }
693
694   @Override
695   public boolean isProblemResolved(RefEntity refEntity, CommonProblemDescriptor descriptor) {
696     if (descriptor == null) return true;
697     CommonProblemDescriptor[] descriptors = getIgnoredElements().get(refEntity);
698     return descriptors != null && ArrayUtil.contains(descriptor, descriptors);
699   }
700
701   @Override
702   @NotNull
703   public FileStatus getProblemStatus(@NotNull final CommonProblemDescriptor descriptor) {
704     return FileStatus.NOT_CHANGED;
705   }
706
707   @NotNull
708   @Override
709   public FileStatus getElementStatus(final RefEntity element) {
710     return FileStatus.NOT_CHANGED;
711   }
712
713   @NotNull
714   @Override
715   public Set<RefEntity> getIgnoredRefElements() {
716     return getIgnoredElements().keySet();
717   }
718
719   @Override
720   @NotNull
721   public Map<RefEntity, CommonProblemDescriptor[]> getProblemElements() {
722     return myProblemElements;
723   }
724
725   @NotNull
726   private Map<CommonProblemDescriptor, RefEntity> getProblemToElements() {
727     return myProblemToElements;
728   }
729
730   @NotNull
731   private Map<RefEntity, Set<QuickFix>> getQuickFixActions() {
732     return myQuickFixActions;
733   }
734
735   @NotNull
736   private Map<RefEntity, CommonProblemDescriptor[]> getIgnoredElements() {
737     return myIgnoredElements;
738   }
739
740   @NotNull
741   @Override
742   public InspectionNode createToolNode(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionNode node,
743                                        @NotNull InspectionRVContentProvider provider,
744                                        @NotNull InspectionTreeNode parentNode,
745                                        boolean showStructure) {
746     return node;
747   }
748
749
750   @Override
751   @Nullable
752   public IntentionAction findQuickFixes(@NotNull final CommonProblemDescriptor problemDescriptor, final String hint) {
753     InspectionProfileEntry tool = getToolWrapper().getTool();
754     if (!(tool instanceof GlobalInspectionTool)) return null;
755     final QuickFix fix = ((GlobalInspectionTool)tool).getQuickFix(hint);
756     if (fix == null) {
757       return null;
758     }
759     if (problemDescriptor instanceof ProblemDescriptor) {
760       final ProblemDescriptor descriptor = new ProblemDescriptorImpl(((ProblemDescriptor)problemDescriptor).getStartElement(),
761                                                                      ((ProblemDescriptor)problemDescriptor).getEndElement(),
762                                                                      problemDescriptor.getDescriptionTemplate(),
763                                                                      new LocalQuickFix[]{(LocalQuickFix)fix},
764                                                                      ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false, null, false);
765       return QuickFixWrapper.wrap(descriptor, 0);
766     }
767     return new IntentionAction() {
768       @Override
769       @NotNull
770       public String getText() {
771         return fix.getName();
772       }
773
774       @Override
775       @NotNull
776       public String getFamilyName() {
777         return fix.getFamilyName();
778       }
779
780       @Override
781       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
782         return true;
783       }
784
785       @Override
786       public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
787         fix.applyFix(project, problemDescriptor); //todo check type consistency
788       }
789
790       @Override
791       public boolean startInWriteAction() {
792         return true;
793       }
794     };
795   }
796
797   public synchronized static void setOutputPath(final String output) {
798     ourOutputPath = output;
799   }
800
801   private synchronized static boolean isOutputPathSet() {
802     return ourOutputPath != null;
803   }
804 }