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.ProjectInspectionProfileManagerImpl;
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 = ProjectInspectionProfileManagerImpl.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       getIgnoredElements().put(refEntity, ArrayUtil.remove(ignoredDescriptors, descriptor));
368     }
369   }
370
371   @Override
372   public void ignoreProblem(RefEntity refEntity, CommonProblemDescriptor problem, int idx) {
373     if (refEntity == null) return;
374     final Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
375     final QuickFix[] fixes = problem.getFixes();
376     if (isIgnoreProblem(fixes, localQuickFixes, idx)){
377       getProblemToElements().remove(problem);
378       Map<RefEntity, CommonProblemDescriptor[]> problemElements = getProblemElements();
379       synchronized (lock) {
380         CommonProblemDescriptor[] descriptors = problemElements.get(refEntity);
381         if (descriptors != null) {
382           ArrayList<CommonProblemDescriptor> newDescriptors = new ArrayList<CommonProblemDescriptor>(Arrays.asList(descriptors));
383           newDescriptors.remove(problem);
384           CommonProblemDescriptor[] newDescriptorsAsArray = newDescriptors.toArray(new CommonProblemDescriptor[newDescriptors.size()]);
385           getQuickFixActions().put(refEntity, null);
386           if (!newDescriptors.isEmpty()) {
387             problemElements.put(refEntity, newDescriptorsAsArray);
388             for (CommonProblemDescriptor descriptor : newDescriptors) {
389               collectQuickFixes(descriptor.getFixes(), refEntity);
390             }
391           }
392           ignoreProblemElement(refEntity, newDescriptorsAsArray, problem);
393         }
394       }
395     }
396   }
397
398   private void ignoreProblemElement(RefEntity refEntity, CommonProblemDescriptor[] newDescriptors, CommonProblemDescriptor toIgnore){
399     if (newDescriptors != null && newDescriptors.length == 0) {
400       newDescriptors = null;
401     }
402     if (newDescriptors == null) {
403       getProblemElements().remove(refEntity);
404     } else {
405       getProblemElements().put(refEntity, newDescriptors);
406     }
407     CommonProblemDescriptor[] oldIgnored = getIgnoredElements().getOrDefault(refEntity, CommonProblemDescriptor.EMPTY_ARRAY);
408     CommonProblemDescriptor[] update = new CommonProblemDescriptor[oldIgnored.length + 1];
409     System.arraycopy(oldIgnored, 0, update, 0, oldIgnored.length);
410     update[update.length - 1] = toIgnore;
411     getIgnoredElements().put(refEntity, update);
412   }
413
414   @Override
415   public void ignoreCurrentElementProblem(RefEntity refEntity, CommonProblemDescriptor descriptor) {
416     CommonProblemDescriptor[] descriptors = getIgnoredElements().get(refEntity);
417     if (descriptors == null) {
418       descriptors = new CommonProblemDescriptor[0];
419     }
420     getIgnoredElements().put(refEntity, ArrayUtil.append(descriptors, descriptor));
421   }
422
423   private static boolean isIgnoreProblem(QuickFix[] problemFixes, Set<QuickFix> fixes, int idx){
424     if (problemFixes == null || fixes == null) {
425       return true;
426     }
427     if (problemFixes.length <= idx){
428       return true;
429     }
430     for (QuickFix fix : problemFixes) {
431       if (fix != problemFixes[idx] && !fixes.contains(fix)){
432         return false;
433       }
434     }
435     return true;
436   }
437
438   @Override
439   public void cleanup() {
440     synchronized (lock) {
441       myProblemElements.clear();
442       myProblemToElements.clear();
443       myQuickFixActions.clear();
444       myIgnoredElements.clear();
445       myContents.clear();
446       myModulesProblems.clear();
447     }
448
449     isDisposed = true;
450   }
451
452   @Override
453   public void finalCleanup() {
454     cleanup();
455   }
456
457   @Override
458   @Nullable
459   public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) {
460     final CommonProblemDescriptor[] problems = getProblemElements().get(refEntity);
461     if (problems == null) return null;
462
463     if (!refEntity.isValid()) {
464       ignoreElement(refEntity);
465       return null;
466     }
467
468     return problems;
469   }
470
471   @NotNull
472   @Override
473   public HTMLComposerImpl getComposer() {
474     if (myComposer == null) {
475       myComposer = new DescriptorComposer(this);
476     }
477     return myComposer;
478   }
479
480   @Override
481   public void exportResults(@NotNull final Element parentNode,
482                             @NotNull RefEntity refEntity,
483                             @NotNull Predicate<CommonProblemDescriptor> isDescriptorExcluded) {
484     synchronized (lock) {
485       if (getProblemElements().containsKey(refEntity)) {
486         CommonProblemDescriptor[] descriptions = getDescriptions(refEntity);
487         if (descriptions != null) {
488           exportResults(descriptions, refEntity, parentNode, isDescriptorExcluded);
489         }
490       }
491     }
492   }
493
494   private void exportResults(@NotNull final CommonProblemDescriptor[] descriptors,
495                              @NotNull RefEntity refEntity,
496                              @NotNull Element parentNode,
497                              @NotNull Predicate<CommonProblemDescriptor> isDescriptorExcluded) {
498     for (CommonProblemDescriptor descriptor : descriptors) {
499       if (isDescriptorExcluded.test(descriptor)) continue;
500       @NonNls final String template = descriptor.getDescriptionTemplate();
501       int line = descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getLineNumber() : -1;
502       final PsiElement psiElement = descriptor instanceof ProblemDescriptor ? ((ProblemDescriptor)descriptor).getPsiElement() : null;
503       @NonNls String problemText = StringUtil.replace(StringUtil.replace(template, "#ref", psiElement != null ? ProblemDescriptorUtil
504         .extractHighlightedText(descriptor, psiElement) : ""), " #loc ", " ");
505
506       Element element = refEntity.getRefManager().export(refEntity, parentNode, line);
507       if (element == null) return;
508       @NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag"));
509       problemClassElement.addContent(myToolWrapper.getDisplayName());
510
511       final HighlightSeverity severity;
512       if (refEntity instanceof RefElement){
513         final RefElement refElement = (RefElement)refEntity;
514         severity = getSeverity(refElement);
515       }
516       else {
517         final InspectionProfile profile = InspectionProjectProfileManager.getInstance(getContext().getProject()).getCurrentProfile();
518         final HighlightDisplayLevel level = profile.getErrorLevel(HighlightDisplayKey.find(myToolWrapper.getShortName()), psiElement);
519         severity = level.getSeverity();
520       }
521
522       if (severity != null) {
523         ProblemHighlightType problemHighlightType = descriptor instanceof ProblemDescriptor
524                                                     ? ((ProblemDescriptor)descriptor).getHighlightType()
525                                                     : ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
526         final String attributeKey = getTextAttributeKey(getRefManager().getProject(), severity, problemHighlightType);
527         problemClassElement.setAttribute("severity", severity.myName);
528         problemClassElement.setAttribute("attribute_key", attributeKey);
529       }
530
531       element.addContent(problemClassElement);
532       if (myToolWrapper instanceof GlobalInspectionToolWrapper) {
533         final GlobalInspectionTool globalInspectionTool = ((GlobalInspectionToolWrapper)myToolWrapper).getTool();
534         final QuickFix[] fixes = descriptor.getFixes();
535         if (fixes != null) {
536           @NonNls Element hintsElement = new Element("hints");
537           for (QuickFix fix : fixes) {
538             final String hint = globalInspectionTool.getHint(fix);
539             if (hint != null) {
540               @NonNls Element hintElement = new Element("hint");
541               hintElement.setAttribute("value", hint);
542               hintsElement.addContent(hintElement);
543             }
544           }
545           element.addContent(hintsElement);
546         }
547       }
548       try {
549         Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
550         descriptionElement.addContent(problemText);
551         element.addContent(descriptionElement);
552       }
553       catch (IllegalDataException e) {
554         //noinspection HardCodedStringLiteral,UseOfSystemOutOrSystemErr
555         System.out.println("Cannot save results for " + refEntity.getName() + ", inspection which caused problem: " + myToolWrapper.getShortName());
556       }
557     }
558   }
559
560   @Override
561   public boolean isGraphNeeded() {
562     return false;
563   }
564
565   @Override
566   public boolean hasReportedProblems() {
567     return !getProblemElements().isEmpty();
568   }
569
570   @Override
571   public void updateContent() {
572     myContents.clear();
573     myModulesProblems.clear();
574     final Set<RefEntity> elements = getProblemElements().keySet();
575     for (RefEntity element : elements) {
576       if (getContext().getUIOptions().FILTER_RESOLVED_ITEMS && getIgnoredElements().containsKey(element)) continue;
577       if (element instanceof RefModule) {
578         myModulesProblems.add((RefModule)element);
579       }
580       else {
581         String groupName = element instanceof RefElement ? element.getRefManager().getGroupName((RefElement)element) : element.getQualifiedName() ;
582         Set<RefEntity> content = myContents.get(groupName);
583         if (content == null) {
584           content = new HashSet<RefEntity>();
585           myContents.put(groupName, content);
586         }
587         content.add(element);
588       }
589     }
590   }
591
592   @NotNull
593   @Override
594   public Map<String, Set<RefEntity>> getContent() {
595     return myContents;
596   }
597
598   @NotNull
599   @Override
600   public Set<RefModule> getModuleProblems() {
601     return myModulesProblems;
602   }
603
604   @Override
605   @Nullable
606   public QuickFixAction[] getQuickFixes(@NotNull final RefEntity[] refElements, CommonProblemDescriptor[] allowedDescriptors) {
607     return extractActiveFixes(refElements, getProblemElements(), allowedDescriptors);
608   }
609
610   @Override
611   @Nullable
612   public QuickFixAction[] extractActiveFixes(@NotNull RefEntity[] refElements,
613                                              @NotNull Map<RefEntity, CommonProblemDescriptor[]> descriptorMap,
614                                              @Nullable CommonProblemDescriptor[] allowedDescriptors) {
615     final Set<CommonProblemDescriptor> allowedDescriptorSet = allowedDescriptors == null ? null : ContainerUtil.newHashSet(allowedDescriptors);
616     Map<Class, QuickFixAction> result = new com.intellij.util.containers.HashMap<>();
617     boolean isFirst = true;
618     for (RefEntity refElement : refElements) {
619       final CommonProblemDescriptor[] descriptors = descriptorMap.get(refElement);
620       if (descriptors == null) continue;
621       for (CommonProblemDescriptor d : descriptors) {
622         if (allowedDescriptorSet != null && !allowedDescriptorSet.contains(d)) {
623           continue;
624         }
625         QuickFix[] fixes = d.getFixes();
626         if (fixes != null) {
627           if (isFirst) {
628             for (QuickFix fix : fixes) {
629               if (fix == null) continue;
630               final Class klass = getFixClass(fix);
631               LocalQuickFixWrapper quickFixWrapper = new LocalQuickFixWrapper(fix, myToolWrapper);
632               result.put(klass, quickFixWrapper);
633             }
634             isFirst = false;
635           }
636           else {
637             for (Class clazz : new ArrayList<>(result.keySet())) {
638               boolean isFound = false;
639               for (QuickFix fix : fixes) {
640                 if (fix == null) continue;
641                 final Class klass = getFixClass(fix);
642                 if (clazz.equals(klass)) {
643                   isFound = true;
644                   final QuickFixAction quickFixAction = result.get(clazz);
645                   try {
646                     String familyName = fix.getFamilyName();
647                     ((LocalQuickFixWrapper)quickFixAction).setText(familyName);
648                   }
649                   catch (AbstractMethodError e) {
650                     //for plugin compatibility
651                     ((LocalQuickFixWrapper)quickFixAction).setText("Name is not available");
652                   }
653                   break;
654                 }
655               }
656               if (!isFound) {
657                 result.remove(clazz);
658                 if (result.isEmpty()) {
659                   return QuickFixAction.EMPTY;
660                 }
661               }
662             }
663           }
664         }
665       }
666     }
667     return result.values().isEmpty() ? null : result.values().toArray(new QuickFixAction[result.size()]);
668   }
669
670   private static Class getFixClass(QuickFix fix) {
671     return fix instanceof ActionClassHolder ? ((ActionClassHolder)fix).getActionClass() : fix.getClass();
672   }
673
674   @Override
675   public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) {
676     return getProblemToElements().get(descriptor);
677   }
678
679   @Override
680   public void ignoreProblem(@NotNull CommonProblemDescriptor descriptor, @NotNull QuickFix fix) {
681     RefEntity refElement = getProblemToElements().get(descriptor);
682     if (refElement != null) {
683       final QuickFix[] fixes = descriptor.getFixes();
684       for (int i = 0; i < fixes.length; i++) {
685         if (fixes[i] == fix){
686           ignoreProblem(refElement, descriptor, i);
687           return;
688         }
689       }
690     }
691   }
692
693
694   @Override
695   public boolean isElementIgnored(final RefEntity element) {
696     return getIgnoredElements().containsKey(element);
697   }
698
699   @Override
700   public boolean isProblemResolved(RefEntity refEntity, CommonProblemDescriptor descriptor) {
701     if (descriptor == null) return true;
702     CommonProblemDescriptor[] descriptors = getIgnoredElements().get(refEntity);
703     return descriptors != null && ArrayUtil.contains(descriptor, descriptors);
704   }
705
706   @Override
707   @NotNull
708   public FileStatus getProblemStatus(@NotNull final CommonProblemDescriptor descriptor) {
709     return FileStatus.NOT_CHANGED;
710   }
711
712   @NotNull
713   @Override
714   public FileStatus getElementStatus(final RefEntity element) {
715     return FileStatus.NOT_CHANGED;
716   }
717
718   @NotNull
719   @Override
720   public Set<RefEntity> getIgnoredRefElements() {
721     return getIgnoredElements().keySet();
722   }
723
724   @Override
725   @NotNull
726   public Map<RefEntity, CommonProblemDescriptor[]> getProblemElements() {
727     return myProblemElements;
728   }
729
730   @NotNull
731   private Map<CommonProblemDescriptor, RefEntity> getProblemToElements() {
732     return myProblemToElements;
733   }
734
735   @NotNull
736   private Map<RefEntity, Set<QuickFix>> getQuickFixActions() {
737     return myQuickFixActions;
738   }
739
740   @NotNull
741   @Override
742   public Map<RefEntity, CommonProblemDescriptor[]> getIgnoredElements() {
743     return myIgnoredElements;
744   }
745
746   @NotNull
747   @Override
748   public InspectionNode createToolNode(@NotNull GlobalInspectionContextImpl globalInspectionContext, @NotNull InspectionNode node,
749                                        @NotNull InspectionRVContentProvider provider,
750                                        @NotNull InspectionTreeNode parentNode,
751                                        boolean showStructure,
752                                        boolean groupBySeverity) {
753     return node;
754   }
755
756
757   @Override
758   @Nullable
759   public IntentionAction findQuickFixes(@NotNull final CommonProblemDescriptor problemDescriptor, final String hint) {
760     InspectionProfileEntry tool = getToolWrapper().getTool();
761     if (!(tool instanceof GlobalInspectionTool)) return null;
762     final QuickFix fix = ((GlobalInspectionTool)tool).getQuickFix(hint);
763     if (fix == null) {
764       return null;
765     }
766     if (problemDescriptor instanceof ProblemDescriptor) {
767       final ProblemDescriptor descriptor = new ProblemDescriptorImpl(((ProblemDescriptor)problemDescriptor).getStartElement(),
768                                                                      ((ProblemDescriptor)problemDescriptor).getEndElement(),
769                                                                      problemDescriptor.getDescriptionTemplate(),
770                                                                      new LocalQuickFix[]{(LocalQuickFix)fix},
771                                                                      ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false, null, false);
772       return QuickFixWrapper.wrap(descriptor, 0);
773     }
774     return new IntentionAction() {
775       @Override
776       @NotNull
777       public String getText() {
778         return fix.getName();
779       }
780
781       @Override
782       @NotNull
783       public String getFamilyName() {
784         return fix.getFamilyName();
785       }
786
787       @Override
788       public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
789         return true;
790       }
791
792       @Override
793       public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
794         fix.applyFix(project, problemDescriptor); //todo check type consistency
795       }
796
797       @Override
798       public boolean startInWriteAction() {
799         return true;
800       }
801     };
802   }
803
804   public synchronized static void setOutputPath(final String output) {
805     ourOutputPath = output;
806   }
807
808   private synchronized static boolean isOutputPathSet() {
809     return ourOutputPath != null;
810   }
811 }