Don't use regexp matching for such a simple case.
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInspection / ex / DescriptorProviderInspection.java
1 /*
2  * Copyright 2000-2009 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
17 package com.intellij.codeInspection.ex;
18
19 import com.intellij.codeInspection.*;
20 import com.intellij.codeInspection.reference.RefElement;
21 import com.intellij.codeInspection.reference.RefEntity;
22 import com.intellij.codeInspection.reference.RefModule;
23 import com.intellij.codeInspection.reference.RefVisitor;
24 import com.intellij.lang.annotation.HighlightSeverity;
25 import com.intellij.openapi.components.PathMacroManager;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.util.Comparing;
28 import com.intellij.openapi.util.JDOMUtil;
29 import com.intellij.openapi.util.text.StringUtil;
30 import com.intellij.openapi.vcs.FileStatus;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.util.ArrayUtil;
33 import com.intellij.util.containers.HashMap;
34 import com.intellij.util.containers.HashSet;
35 import gnu.trove.THashMap;
36 import org.jdom.Element;
37 import org.jdom.IllegalDataException;
38 import org.jetbrains.annotations.NonNls;
39 import org.jetbrains.annotations.Nullable;
40
41 import java.io.*;
42 import java.util.*;
43
44 /**
45  * @author max
46  */
47 public abstract class DescriptorProviderInspection extends InspectionTool implements ProblemDescriptionsProcessor {
48   private Map<RefEntity, CommonProblemDescriptor[]> myProblemElements;
49   private HashMap<String, Set<RefEntity>> myContents = null;
50   private HashSet<RefModule> myModulesProblems = null;
51   private Map<CommonProblemDescriptor, RefEntity> myProblemToElements;
52   private DescriptorComposer myComposer;
53   private Map<RefEntity, Set<QuickFix>> myQuickFixActions;
54   private Map<RefEntity, CommonProblemDescriptor[]> myIgnoredElements;
55
56   private HashMap<RefEntity, CommonProblemDescriptor[]> myOldProblemElements = null;
57   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.DescriptorProviderInspection");
58
59   public void addProblemElement(RefEntity refElement, CommonProblemDescriptor... descriptions){
60     addProblemElement(refElement, true, descriptions);
61   }
62
63   protected void addProblemElement(RefEntity refElement, boolean filterSuppressed, CommonProblemDescriptor... descriptions) {
64     if (refElement == null) return;
65     if (descriptions == null || descriptions.length == 0) return;
66     if (filterSuppressed) {
67       if (ourOutputPath == null || !(this instanceof LocalInspectionToolWrapper)) {
68         CommonProblemDescriptor[] problems = getProblemElements().get(refElement);
69         if (problems == null) {
70           problems = descriptions;
71         }
72         else {
73           problems = ArrayUtil.mergeArrays(problems, descriptions, CommonProblemDescriptor.class);
74         }
75         getProblemElements().put(refElement, problems);
76         for (CommonProblemDescriptor description : descriptions) {
77           getProblemToElements().put(description, refElement);
78           collectQuickFixes(description.getFixes(), refElement);
79         }
80       }
81       else {
82         writeOutput(descriptions, refElement);
83       }
84     }
85     else { //just need to collect problems
86       for (CommonProblemDescriptor description : descriptions) {
87         getProblemToElements().put(description, refElement);
88       }
89     }
90   }
91
92   private void writeOutput(final CommonProblemDescriptor[] descriptions, final RefEntity refElement) {
93     final Element parentNode = new Element(InspectionsBundle.message("inspection.problems"));
94     exportResults(descriptions, refElement, parentNode);
95     final List list = parentNode.getChildren();
96
97     @NonNls final String ext = ".xml";
98     final String fileName = ourOutputPath + File.separator + getShortName() + ext;
99     final PathMacroManager pathMacroManager = PathMacroManager.getInstance(getContext().getProject());
100     PrintWriter printWriter = null;
101     try {
102       new File(ourOutputPath).mkdirs();
103       final File file = new File(fileName);
104       final CharArrayWriter writer = new CharArrayWriter();
105       if (!file.exists()) {
106         writer.append("<").append(InspectionsBundle.message("inspection.problems")).append(" is_local_tool=\"")
107           .append(Boolean.toString(this instanceof LocalInspectionToolWrapper)).append("\">\n");
108       }
109       for (Object o : list) {
110         final Element element = (Element)o;
111         pathMacroManager.collapsePaths(element);
112         JDOMUtil.writeElement(element, writer, "\n");
113       }
114       printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, true)));
115       printWriter.append("\n");
116       printWriter.append(writer.toString());
117     }
118     catch (IOException e) {
119       LOG.error(e);
120     }
121     finally {
122       if (printWriter != null) {
123         printWriter.close();
124       }
125     }
126   }
127
128   public Collection<CommonProblemDescriptor> getProblemDescriptors() {
129     return getProblemToElements().keySet();
130   }
131
132   private void collectQuickFixes(final QuickFix[] fixes, final RefEntity refEntity) {
133     if (fixes != null) {
134       Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
135       if (localQuickFixes == null) {
136         localQuickFixes = new HashSet<QuickFix>();
137         getQuickFixActions().put(refEntity, localQuickFixes);
138       }
139       localQuickFixes.addAll(Arrays.asList(fixes));
140     }
141   }
142
143   public void ignoreElement(final RefEntity refEntity) {
144     if (refEntity == null) return;
145     getProblemElements().remove(refEntity);
146     getQuickFixActions().remove(refEntity);
147   }
148
149   public void ignoreCurrentElement(RefEntity refEntity) {
150     if (refEntity == null) return;
151     getIgnoredElements().put(refEntity, getProblemElements().get(refEntity));
152   }
153
154   public void amnesty(RefEntity refEntity) {
155     getIgnoredElements().remove(refEntity);
156   }
157
158   public void ignoreProblem(RefEntity refEntity, CommonProblemDescriptor problem, int idx) {
159     if (refEntity == null) return;
160     final Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
161     final QuickFix[] fixes = problem.getFixes();
162     if (isIgnoreProblem(fixes, localQuickFixes, idx)){
163       getProblemToElements().remove(problem);
164       CommonProblemDescriptor[] descriptors = getProblemElements().get(refEntity);
165       if (descriptors != null) {
166         ArrayList<CommonProblemDescriptor> newDescriptors = new ArrayList<CommonProblemDescriptor>(Arrays.asList(descriptors));
167         newDescriptors.remove(problem);
168         getQuickFixActions().put(refEntity, null);
169         if (!newDescriptors.isEmpty()) {
170           getProblemElements().put(refEntity, newDescriptors.toArray(new CommonProblemDescriptor[newDescriptors.size()]));
171           for (CommonProblemDescriptor descriptor : newDescriptors) {
172             collectQuickFixes(descriptor.getFixes(), refEntity);
173           }
174         }
175         else {
176           ignoreProblemElement(refEntity);
177         }
178       }
179     }
180   }
181
182   private void ignoreProblemElement(RefEntity refEntity){
183     final CommonProblemDescriptor[] problemDescriptors = getProblemElements().remove(refEntity);
184     getIgnoredElements().put(refEntity, problemDescriptors);
185   }
186
187   private static boolean isIgnoreProblem(QuickFix[] problemFixes, Set<QuickFix> fixes, int idx){
188     if (problemFixes == null || fixes == null) {
189       return true;
190     }
191     if (problemFixes.length <= idx){
192       return true;
193     }
194     for (QuickFix fix : problemFixes) {
195       if (fix != problemFixes[idx] && !fixes.contains(fix)){
196         return false;
197       }
198     }
199     return true;
200   }
201
202   public void cleanup() {
203     super.cleanup();
204     final GlobalInspectionContextImpl context = getContext();
205     if (context != null && context.getUIOptions().SHOW_DIFF_WITH_PREVIOUS_RUN){
206       if (myOldProblemElements == null) {
207         myOldProblemElements = new HashMap<RefEntity, CommonProblemDescriptor[]>();
208       }
209       myOldProblemElements.clear();
210       myOldProblemElements.putAll(getIgnoredElements());
211       myOldProblemElements.putAll(getProblemElements());
212     } else {
213       myOldProblemElements = null;
214     }
215
216     myProblemElements = null;
217     myProblemToElements = null;
218     myQuickFixActions = null;
219     myIgnoredElements = null;
220     myContents = null;
221     myModulesProblems = null;
222   }
223
224
225   public void finalCleanup() {
226     super.finalCleanup();
227     myOldProblemElements = null;
228   }
229
230   @Nullable
231   public CommonProblemDescriptor[] getDescriptions(RefEntity refEntity) {
232     final CommonProblemDescriptor[] problems = getProblemElements().get(refEntity);
233     if (problems == null) return null;
234
235     if (!refEntity.isValid()) {
236       ignoreElement(refEntity);
237       return null;
238     }
239
240     return problems;
241   }
242
243   public HTMLComposerImpl getComposer() {
244     if (myComposer == null) {
245       myComposer = new DescriptorComposer(this);
246     }
247     return myComposer;
248   }
249
250   public void exportResults(final Element parentNode) {
251     getRefManager().iterate(new RefVisitor() {
252       @Override public void visitElement(final RefEntity refEntity) {
253         if (getProblemElements().containsKey(refEntity)) {
254           CommonProblemDescriptor[] descriptions = getDescriptions(refEntity);
255           exportResults(descriptions, refEntity, parentNode);
256         }
257       }
258     });
259   }
260
261   private void exportResults(final CommonProblemDescriptor[] descriptions, final RefEntity refEntity, final Element parentNode) {
262     for (CommonProblemDescriptor description : descriptions) {
263       @NonNls final String template = description.getDescriptionTemplate();
264       int line = description instanceof ProblemDescriptor ? ((ProblemDescriptor)description).getLineNumber() : -1;
265       final String text = description instanceof ProblemDescriptor ? ((ProblemDescriptor)description).getPsiElement().getText() : "";
266       @NonNls String problemText = StringUtil.replace(StringUtil.replace(template, "#ref", StringUtil.quoteReplacement(text)), " #loc ", " ");
267
268       Element element = refEntity.getRefManager().export(refEntity, parentNode, line);
269       @NonNls Element problemClassElement = new Element(InspectionsBundle.message("inspection.export.results.problem.element.tag"));
270       problemClassElement.addContent(getDisplayName());
271       if (refEntity instanceof RefElement){
272         final RefElement refElement = (RefElement)refEntity;
273         final HighlightSeverity severity = getCurrentSeverity(refElement);
274         ProblemHighlightType problemHighlightType = description instanceof ProblemDescriptor
275                                                     ? ((ProblemDescriptor)description).getHighlightType()
276                                                     : ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
277         final String attributeKey = getTextAttributeKey(refElement.getElement().getProject(), severity, problemHighlightType);
278         problemClassElement.setAttribute("severity", severity.myName);
279         problemClassElement.setAttribute("attribute_key", attributeKey);
280       }
281       element.addContent(problemClassElement);
282       if (this instanceof GlobalInspectionToolWrapper) {
283         final GlobalInspectionTool globalInspectionTool = ((GlobalInspectionToolWrapper)this).getTool();
284         final QuickFix[] fixes = description.getFixes();
285         if (fixes != null) {
286           @NonNls Element hintsElement = new Element("hints");
287           for (QuickFix fix : fixes) {
288             final String hint = globalInspectionTool.getHint(fix);
289             if (hint != null) {
290               @NonNls Element hintElement = new Element("hint");
291               hintElement.setAttribute("value", hint);
292               hintsElement.addContent(hintElement);
293             }
294           }
295           element.addContent(hintsElement);
296         }
297       }
298       try {
299         Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
300         descriptionElement.addContent(problemText);
301         element.addContent(descriptionElement);
302       }
303       catch (IllegalDataException e) {
304         //noinspection HardCodedStringLiteral,UseOfSystemOutOrSystemErr
305         System.out.println("Cannot save results for " + refEntity.getName() + ", inspection which caused problem: " + getShortName());
306       }
307     }
308   }
309
310   public boolean isGraphNeeded() {
311     return false;
312   }
313
314   public boolean hasReportedProblems() {
315     final GlobalInspectionContextImpl context = getContext();
316     if (context != null && context.getUIOptions().SHOW_ONLY_DIFF) {
317       for (CommonProblemDescriptor descriptor : getProblemToElements().keySet()) {
318         if (getProblemStatus(descriptor) != FileStatus.NOT_CHANGED) {
319           return true;
320         }
321       }
322       if (myOldProblemElements != null) {
323         for (RefEntity entity : myOldProblemElements.keySet()) {
324           if (getElementStatus(entity) != FileStatus.NOT_CHANGED) {
325             return true;
326           }
327         }
328       }
329       return false;
330     }
331     if (!getProblemElements().isEmpty()) return true;
332     return context != null &&
333            context.getUIOptions().SHOW_DIFF_WITH_PREVIOUS_RUN &&
334            myOldProblemElements != null && !myOldProblemElements.isEmpty();
335   }
336
337   public void updateContent() {
338     myContents = new HashMap<String, Set<RefEntity>>();
339     myModulesProblems = new HashSet<RefModule>();
340     final Set<RefEntity> elements = getProblemElements().keySet();
341     for (RefEntity element : elements) {
342       if (getContext().getUIOptions().FILTER_RESOLVED_ITEMS && getIgnoredElements().containsKey(element)) continue;
343       if (element instanceof RefModule) {
344         myModulesProblems.add((RefModule)element);
345       }
346       else {
347         String groupName = element instanceof RefElement ? element.getRefManager().getGroupName((RefElement)element) : null;
348         Set<RefEntity> content = myContents.get(groupName);
349         if (content == null) {
350           content = new HashSet<RefEntity>();
351           myContents.put(groupName, content);
352         }
353         content.add(element);
354       }
355     }
356   }
357
358   public Map<String, Set<RefEntity>> getContent() {
359     return myContents;
360   }
361
362   public Map<String, Set<RefEntity>> getOldContent() {
363     if (myOldProblemElements == null) return null;
364     final HashMap<String, Set<RefEntity>> oldContents = new HashMap<String, Set<RefEntity>>();
365     final Set<RefEntity> elements = myOldProblemElements.keySet();
366     for (RefEntity element : elements) {
367       String groupName = element instanceof RefElement ? element.getRefManager().getGroupName((RefElement)element) : element.getName();
368       final Set<RefEntity> collection = myContents.get(groupName);
369       if (collection != null) {
370         final Set<RefEntity> currentElements = new HashSet<RefEntity>(collection);
371         if (contains(element, currentElements)) continue;
372       }
373       Set<RefEntity> oldContent = oldContents.get(groupName);
374       if (oldContent == null) {
375         oldContent = new HashSet<RefEntity>();
376         oldContents.put(groupName, oldContent);
377       }
378       oldContent.add(element);
379     }
380     return oldContents;
381   }
382
383   public Set<RefModule> getModuleProblems() {
384     return myModulesProblems;
385   }
386
387   public QuickFixAction[] getQuickFixes(final RefEntity[] refElements) {
388     return extractActiveFixes(refElements, getQuickFixActions());    
389   }
390
391   public QuickFixAction[] extractActiveFixes(final RefEntity[] refElements, final Map<RefEntity, Set<QuickFix>> actions) {
392     if (refElements == null) return null;
393     Map<Class, QuickFixAction> result = new java.util.HashMap<Class, QuickFixAction>();
394     for (RefEntity refElement : refElements) {
395       final Set<QuickFix> localQuickFixes = actions.get(refElement);
396       if (localQuickFixes != null){
397         for (QuickFix fix : localQuickFixes) {
398           if (fix == null) continue;
399           final Class klass = fix.getClass();
400           final QuickFixAction quickFixAction = result.get(klass);
401           if (quickFixAction != null){
402             try {
403               String familyName = fix.getFamilyName();
404               familyName = familyName != null && familyName.length() > 0 ? "\'" + familyName + "\'" : familyName;
405               ((LocalQuickFixWrapper)quickFixAction).setText(InspectionsBundle.message("inspection.descriptor.provider.apply.fix", familyName));
406             }
407             catch (AbstractMethodError e) {
408               //for plugin compatibility
409               ((LocalQuickFixWrapper)quickFixAction).setText(InspectionsBundle.message("inspection.descriptor.provider.apply.fix", ""));
410             }
411           } else {
412             LocalQuickFixWrapper quickFixWrapper = new LocalQuickFixWrapper(fix, this);
413             result.put(klass, quickFixWrapper);
414           }
415         }
416       }
417     }
418     return result.values().isEmpty() ? null : result.values().toArray(new QuickFixAction[result.size()]);
419   }
420
421   public RefEntity getElement(CommonProblemDescriptor descriptor) {
422     return getProblemToElements().get(descriptor);
423   }
424
425   public void ignoreProblem(final CommonProblemDescriptor descriptor, final QuickFix fix) {
426     RefEntity refElement = getProblemToElements().get(descriptor);
427     if (refElement != null) {
428       final QuickFix[] fixes = descriptor.getFixes();
429       for (int i = 0; i < fixes.length; i++) {
430         if (fixes[i] == fix){
431           ignoreProblem(refElement, descriptor, i);
432           return;
433         }
434       }
435     }
436   }
437
438
439   public boolean isElementIgnored(final RefEntity element) {
440     if (getIgnoredElements() == null) return false;
441     for (RefEntity entity : getIgnoredElements().keySet()) {
442       if (Comparing.equal(entity, element)) {
443         return true;
444       }
445     }
446     return false;
447   }
448
449   public FileStatus getProblemStatus(final CommonProblemDescriptor descriptor) {
450     final GlobalInspectionContextImpl context = getContext();
451     if (context != null && context.getUIOptions().SHOW_DIFF_WITH_PREVIOUS_RUN){
452       if (myOldProblemElements != null){
453         final Set<CommonProblemDescriptor> allAvailable = new HashSet<CommonProblemDescriptor>();
454         for (CommonProblemDescriptor[] descriptors : myOldProblemElements.values()) {
455           if (descriptors != null) {
456             allAvailable.addAll(Arrays.asList(descriptors));
457           }
458         }
459         final boolean old = contains(descriptor, allAvailable);
460         final boolean current = contains(descriptor, getProblemToElements().keySet());
461         return calcStatus(old, current);
462       }
463     }
464     return FileStatus.NOT_CHANGED;
465   }
466
467   private static boolean contains(CommonProblemDescriptor descriptor, Collection<CommonProblemDescriptor> descriptors){
468     PsiElement element = null;
469     if (descriptor instanceof ProblemDescriptor){
470       element = ((ProblemDescriptor)descriptor).getPsiElement();
471     }
472     for (CommonProblemDescriptor problemDescriptor : descriptors) {
473       if (problemDescriptor instanceof ProblemDescriptor){
474         if (!Comparing.equal(element, ((ProblemDescriptor)problemDescriptor).getPsiElement())){
475           continue;
476         }
477       }
478       if (Comparing.strEqual(problemDescriptor.getDescriptionTemplate(), descriptor.getDescriptionTemplate())){
479         return true;
480       }
481     }
482     return false;
483   }
484
485
486   public FileStatus getElementStatus(final RefEntity element) {
487     final GlobalInspectionContextImpl context = getContext();
488     if (context != null && context.getUIOptions().SHOW_DIFF_WITH_PREVIOUS_RUN){
489       if (myOldProblemElements != null){
490         final boolean old = contains(element, myOldProblemElements.keySet());
491         final boolean current = contains(element, getProblemElements().keySet());
492         return calcStatus(old, current);
493       }
494     }
495     return FileStatus.NOT_CHANGED;
496   }
497
498   public Collection<RefEntity> getIgnoredRefElements() {
499     return getIgnoredElements().keySet();
500   }
501
502   public Map<RefEntity, CommonProblemDescriptor[]> getProblemElements() {
503     if (myProblemElements == null) {
504       myProblemElements = Collections.synchronizedMap(new THashMap<RefEntity, CommonProblemDescriptor[]>());
505     }
506     return myProblemElements;
507   }
508
509   @Nullable
510   public HashMap<RefEntity, CommonProblemDescriptor[]> getOldProblemElements() {
511     return myOldProblemElements;
512   }
513
514   private Map<CommonProblemDescriptor, RefEntity> getProblemToElements() {
515     if (myProblemToElements == null) {
516       myProblemToElements = Collections.synchronizedMap(new THashMap<CommonProblemDescriptor, RefEntity>());
517     }
518     return myProblemToElements;
519   }
520
521   private Map<RefEntity, Set<QuickFix>> getQuickFixActions() {
522     if (myQuickFixActions == null) {
523       myQuickFixActions = Collections.synchronizedMap(new HashMap<RefEntity, Set<QuickFix>>());
524     }
525     return myQuickFixActions;
526   }
527
528   private Map<RefEntity, CommonProblemDescriptor[]> getIgnoredElements() {
529     if (myIgnoredElements == null) {
530       myIgnoredElements = Collections.synchronizedMap(new HashMap<RefEntity, CommonProblemDescriptor[]>());
531     }
532     return myIgnoredElements;
533   }
534 }