2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInspection.ex;
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;
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;
56 private HashMap<RefEntity, CommonProblemDescriptor[]> myOldProblemElements = null;
57 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.DescriptorProviderInspection");
59 public void addProblemElement(RefEntity refElement, CommonProblemDescriptor... descriptions){
60 addProblemElement(refElement, true, descriptions);
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;
73 problems = ArrayUtil.mergeArrays(problems, descriptions, CommonProblemDescriptor.class);
75 getProblemElements().put(refElement, problems);
76 for (CommonProblemDescriptor description : descriptions) {
77 getProblemToElements().put(description, refElement);
78 collectQuickFixes(description.getFixes(), refElement);
82 writeOutput(descriptions, refElement);
85 else { //just need to collect problems
86 for (CommonProblemDescriptor description : descriptions) {
87 getProblemToElements().put(description, refElement);
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();
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;
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");
109 for (Object o : list) {
110 final Element element = (Element)o;
111 pathMacroManager.collapsePaths(element);
112 JDOMUtil.writeElement(element, writer, "\n");
114 printWriter = new PrintWriter(new BufferedWriter(new FileWriter(file, true)));
115 printWriter.append("\n");
116 printWriter.append(writer.toString());
118 catch (IOException e) {
122 if (printWriter != null) {
128 public Collection<CommonProblemDescriptor> getProblemDescriptors() {
129 return getProblemToElements().keySet();
132 private void collectQuickFixes(final QuickFix[] fixes, final RefEntity refEntity) {
134 Set<QuickFix> localQuickFixes = getQuickFixActions().get(refEntity);
135 if (localQuickFixes == null) {
136 localQuickFixes = new HashSet<QuickFix>();
137 getQuickFixActions().put(refEntity, localQuickFixes);
139 localQuickFixes.addAll(Arrays.asList(fixes));
143 public void ignoreElement(final RefEntity refEntity) {
144 if (refEntity == null) return;
145 getProblemElements().remove(refEntity);
146 getQuickFixActions().remove(refEntity);
149 public void ignoreCurrentElement(RefEntity refEntity) {
150 if (refEntity == null) return;
151 getIgnoredElements().put(refEntity, getProblemElements().get(refEntity));
154 public void amnesty(RefEntity refEntity) {
155 getIgnoredElements().remove(refEntity);
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);
176 ignoreProblemElement(refEntity);
182 private void ignoreProblemElement(RefEntity refEntity){
183 final CommonProblemDescriptor[] problemDescriptors = getProblemElements().remove(refEntity);
184 getIgnoredElements().put(refEntity, problemDescriptors);
187 private static boolean isIgnoreProblem(QuickFix[] problemFixes, Set<QuickFix> fixes, int idx){
188 if (problemFixes == null || fixes == null) {
191 if (problemFixes.length <= idx){
194 for (QuickFix fix : problemFixes) {
195 if (fix != problemFixes[idx] && !fixes.contains(fix)){
202 public void 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[]>();
209 myOldProblemElements.clear();
210 myOldProblemElements.putAll(getIgnoredElements());
211 myOldProblemElements.putAll(getProblemElements());
213 myOldProblemElements = null;
216 myProblemElements = null;
217 myProblemToElements = null;
218 myQuickFixActions = null;
219 myIgnoredElements = null;
221 myModulesProblems = null;
225 public void finalCleanup() {
226 super.finalCleanup();
227 myOldProblemElements = null;
231 public CommonProblemDescriptor[] getDescriptions(RefEntity refEntity) {
232 final CommonProblemDescriptor[] problems = getProblemElements().get(refEntity);
233 if (problems == null) return null;
235 if (!refEntity.isValid()) {
236 ignoreElement(refEntity);
243 public HTMLComposerImpl getComposer() {
244 if (myComposer == null) {
245 myComposer = new DescriptorComposer(this);
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);
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 ", " ");
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);
281 element.addContent(problemClassElement);
282 if (this instanceof GlobalInspectionToolWrapper) {
283 final GlobalInspectionTool globalInspectionTool = ((GlobalInspectionToolWrapper)this).getTool();
284 final QuickFix[] fixes = description.getFixes();
286 @NonNls Element hintsElement = new Element("hints");
287 for (QuickFix fix : fixes) {
288 final String hint = globalInspectionTool.getHint(fix);
290 @NonNls Element hintElement = new Element("hint");
291 hintElement.setAttribute("value", hint);
292 hintsElement.addContent(hintElement);
295 element.addContent(hintsElement);
299 Element descriptionElement = new Element(InspectionsBundle.message("inspection.export.results.description.tag"));
300 descriptionElement.addContent(problemText);
301 element.addContent(descriptionElement);
303 catch (IllegalDataException e) {
304 //noinspection HardCodedStringLiteral,UseOfSystemOutOrSystemErr
305 System.out.println("Cannot save results for " + refEntity.getName() + ", inspection which caused problem: " + getShortName());
310 public boolean isGraphNeeded() {
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) {
322 if (myOldProblemElements != null) {
323 for (RefEntity entity : myOldProblemElements.keySet()) {
324 if (getElementStatus(entity) != FileStatus.NOT_CHANGED) {
331 if (!getProblemElements().isEmpty()) return true;
332 return context != null &&
333 context.getUIOptions().SHOW_DIFF_WITH_PREVIOUS_RUN &&
334 myOldProblemElements != null && !myOldProblemElements.isEmpty();
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);
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);
353 content.add(element);
358 public Map<String, Set<RefEntity>> getContent() {
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;
373 Set<RefEntity> oldContent = oldContents.get(groupName);
374 if (oldContent == null) {
375 oldContent = new HashSet<RefEntity>();
376 oldContents.put(groupName, oldContent);
378 oldContent.add(element);
383 public Set<RefModule> getModuleProblems() {
384 return myModulesProblems;
387 public QuickFixAction[] getQuickFixes(final RefEntity[] refElements) {
388 return extractActiveFixes(refElements, getQuickFixActions());
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){
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));
407 catch (AbstractMethodError e) {
408 //for plugin compatibility
409 ((LocalQuickFixWrapper)quickFixAction).setText(InspectionsBundle.message("inspection.descriptor.provider.apply.fix", ""));
412 LocalQuickFixWrapper quickFixWrapper = new LocalQuickFixWrapper(fix, this);
413 result.put(klass, quickFixWrapper);
418 return result.values().isEmpty() ? null : result.values().toArray(new QuickFixAction[result.size()]);
421 public RefEntity getElement(CommonProblemDescriptor descriptor) {
422 return getProblemToElements().get(descriptor);
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);
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)) {
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));
459 final boolean old = contains(descriptor, allAvailable);
460 final boolean current = contains(descriptor, getProblemToElements().keySet());
461 return calcStatus(old, current);
464 return FileStatus.NOT_CHANGED;
467 private static boolean contains(CommonProblemDescriptor descriptor, Collection<CommonProblemDescriptor> descriptors){
468 PsiElement element = null;
469 if (descriptor instanceof ProblemDescriptor){
470 element = ((ProblemDescriptor)descriptor).getPsiElement();
472 for (CommonProblemDescriptor problemDescriptor : descriptors) {
473 if (problemDescriptor instanceof ProblemDescriptor){
474 if (!Comparing.equal(element, ((ProblemDescriptor)problemDescriptor).getPsiElement())){
478 if (Comparing.strEqual(problemDescriptor.getDescriptionTemplate(), descriptor.getDescriptionTemplate())){
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);
495 return FileStatus.NOT_CHANGED;
498 public Collection<RefEntity> getIgnoredRefElements() {
499 return getIgnoredElements().keySet();
502 public Map<RefEntity, CommonProblemDescriptor[]> getProblemElements() {
503 if (myProblemElements == null) {
504 myProblemElements = Collections.synchronizedMap(new THashMap<RefEntity, CommonProblemDescriptor[]>());
506 return myProblemElements;
510 public HashMap<RefEntity, CommonProblemDescriptor[]> getOldProblemElements() {
511 return myOldProblemElements;
514 private Map<CommonProblemDescriptor, RefEntity> getProblemToElements() {
515 if (myProblemToElements == null) {
516 myProblemToElements = Collections.synchronizedMap(new THashMap<CommonProblemDescriptor, RefEntity>());
518 return myProblemToElements;
521 private Map<RefEntity, Set<QuickFix>> getQuickFixActions() {
522 if (myQuickFixActions == null) {
523 myQuickFixActions = Collections.synchronizedMap(new HashMap<RefEntity, Set<QuickFix>>());
525 return myQuickFixActions;
528 private Map<RefEntity, CommonProblemDescriptor[]> getIgnoredElements() {
529 if (myIgnoredElements == null) {
530 myIgnoredElements = Collections.synchronizedMap(new HashMap<RefEntity, CommonProblemDescriptor[]>());
532 return myIgnoredElements;