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