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