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