Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / analysis-api / src / com / intellij / codeInspection / InspectionProfileEntry.java
1 /*
2  * Copyright 2000-2014 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;
17
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.lang.Language;
20 import com.intellij.lang.injection.InjectedLanguageManager;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.project.Project;
23 import com.intellij.openapi.util.InvalidDataException;
24 import com.intellij.openapi.util.WriteExternalException;
25 import com.intellij.openapi.util.text.StringUtil;
26 import com.intellij.psi.FileViewProvider;
27 import com.intellij.psi.PsiElement;
28 import com.intellij.psi.PsiLanguageInjectionHost;
29 import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider;
30 import com.intellij.util.ResourceUtil;
31 import com.intellij.util.ThreeState;
32 import com.intellij.util.containers.ContainerUtil;
33 import com.intellij.util.xmlb.SerializationFilter;
34 import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
35 import com.intellij.util.xmlb.XmlSerializationException;
36 import com.intellij.util.xmlb.XmlSerializer;
37 import gnu.trove.THashSet;
38 import gnu.trove.TObjectHashingStrategy;
39 import org.jdom.Element;
40 import org.jetbrains.annotations.Nls;
41 import org.jetbrains.annotations.NonNls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import javax.swing.*;
46 import java.io.BufferedReader;
47 import java.io.IOException;
48 import java.io.InputStreamReader;
49 import java.net.URL;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.LinkedHashSet;
53 import java.util.Set;
54
55 /**
56  * @author anna
57  * @since 28-Nov-2005
58  */
59 public abstract class InspectionProfileEntry implements BatchSuppressableTool {
60   public static final String GENERAL_GROUP_NAME = InspectionsBundle.message("inspection.general.tools.group.name");
61
62   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.InspectionProfileEntry");
63
64   private static final SkipDefaultValuesSerializationFilters DEFAULT_FILTER = new SkipDefaultValuesSerializationFilters();
65   private static Set<String> ourBlackList = null;
66   private static final Object BLACK_LIST_LOCK = new Object();
67   private Boolean myUseNewSerializer = null;
68
69   @NonNls
70   @Nullable
71   public String getAlternativeID() {
72     return null;
73   }
74
75   @Override
76   public boolean isSuppressedFor(@NotNull PsiElement element) {
77     Set<InspectionSuppressor> suppressors = getSuppressors(element);
78     String toolId = getSuppressId();
79     for (InspectionSuppressor suppressor : suppressors) {
80       if (isSuppressed(toolId, suppressor, element)) {
81         return true;
82       }
83     }
84     return false;
85   }
86
87   @NotNull
88   protected String getSuppressId() {
89     return getShortName();
90   }
91
92   @NotNull
93   @Override
94   public SuppressQuickFix[] getBatchSuppressActions(@Nullable PsiElement element) {
95     if (element == null) {
96       return SuppressQuickFix.EMPTY_ARRAY;
97     }
98     Set<SuppressQuickFix> fixes = new THashSet<SuppressQuickFix>(new TObjectHashingStrategy<SuppressQuickFix>() {
99       @Override
100       public int computeHashCode(SuppressQuickFix object) {
101         int result = object instanceof InjectionAwareSuppressQuickFix
102           ? ((InjectionAwareSuppressQuickFix)object).isShouldBeAppliedToInjectionHost().hashCode()
103           : 0;
104         return 31 * result + object.getName().hashCode();
105       }
106
107       @Override
108       public boolean equals(SuppressQuickFix o1, SuppressQuickFix o2) {
109         if (o1 instanceof InjectionAwareSuppressQuickFix && o2 instanceof InjectionAwareSuppressQuickFix) {
110           if (((InjectionAwareSuppressQuickFix)o1).isShouldBeAppliedToInjectionHost() != ((InjectionAwareSuppressQuickFix)o2).isShouldBeAppliedToInjectionHost()) {
111             return false;
112           }
113         }
114         return o1.getName().equals(o2.getName());
115       }
116     });
117     
118     Set<InspectionSuppressor> suppressors = getSuppressors(element);
119     final PsiLanguageInjectionHost injectionHost = InjectedLanguageManager.getInstance(element.getProject()).getInjectionHost(element);
120     if (injectionHost != null) {
121       Set<InspectionSuppressor> injectionHostSuppressors = getSuppressors(injectionHost);
122       for (InspectionSuppressor suppressor : injectionHostSuppressors) {
123         addAllSuppressActions(fixes, injectionHost, suppressor, ThreeState.YES, getShortName());
124       }
125     }
126
127     for (InspectionSuppressor suppressor : suppressors) {
128       addAllSuppressActions(fixes, element, suppressor, injectionHost != null ? ThreeState.NO : ThreeState.UNSURE, getShortName());
129     }
130     return fixes.toArray(new SuppressQuickFix[fixes.size()]);
131   }
132
133   private static void addAllSuppressActions(Set<SuppressQuickFix> fixes,
134                                             PsiElement element,
135                                             InspectionSuppressor suppressor,
136                                             ThreeState appliedToInjectionHost,
137                                             String toolShortName) {
138     final SuppressQuickFix[] actions = suppressor.getSuppressActions(element, toolShortName);
139     for (SuppressQuickFix action : actions) {
140       if (action instanceof InjectionAwareSuppressQuickFix) {
141         ((InjectionAwareSuppressQuickFix)action).setShouldBeAppliedToInjectionHost(appliedToInjectionHost);
142       }
143       fixes.add(action);
144     }
145   }
146
147   private boolean isSuppressed(@NotNull String toolId,
148                                @NotNull InspectionSuppressor suppressor,
149                                @NotNull PsiElement element) {
150     if (suppressor.isSuppressedFor(element, toolId)) {
151       return true;
152     }
153     final String alternativeId = getAlternativeID();
154     return alternativeId != null && !alternativeId.equals(toolId) && suppressor.isSuppressedFor(element, alternativeId);
155   }
156
157   @NotNull
158   public static Set<InspectionSuppressor> getSuppressors(@NotNull PsiElement element) {
159     FileViewProvider viewProvider = element.getContainingFile().getViewProvider();
160     final InspectionSuppressor elementLanguageSuppressor = LanguageInspectionSuppressors.INSTANCE.forLanguage(element.getLanguage());
161     if (viewProvider instanceof TemplateLanguageFileViewProvider) {
162       Set<InspectionSuppressor> suppressors = new LinkedHashSet<InspectionSuppressor>();
163       ContainerUtil.addIfNotNull(suppressors, LanguageInspectionSuppressors.INSTANCE.forLanguage(viewProvider.getBaseLanguage()));
164       for (Language language : viewProvider.getLanguages()) {
165         ContainerUtil.addIfNotNull(suppressors, LanguageInspectionSuppressors.INSTANCE.forLanguage(language));
166       }
167       ContainerUtil.addIfNotNull(suppressors, elementLanguageSuppressor);
168       return suppressors;
169     }
170     return elementLanguageSuppressor != null
171            ? Collections.singleton(elementLanguageSuppressor)
172            : Collections.<InspectionSuppressor>emptySet();
173   }
174
175   public void cleanup(Project project) {
176
177   }
178
179   interface DefaultNameProvider {
180     @Nullable String getDefaultShortName();
181     @Nullable String getDefaultDisplayName();
182     @Nullable String getDefaultGroupDisplayName();
183   }
184
185   protected volatile DefaultNameProvider myNameProvider = null;
186
187   /**
188    * @see InspectionEP#groupDisplayName
189    * @see InspectionEP#groupKey
190    * @see InspectionEP#groupBundle
191    */
192   @Nls
193   @NotNull
194   public String getGroupDisplayName() {
195     if (myNameProvider != null) {
196       final String name = myNameProvider.getDefaultGroupDisplayName();
197       if (name != null) {
198         return name;
199       }
200     }
201     LOG.error(getClass() + ": group display name should be overridden or configured via XML " + getClass());
202     return "";
203   }
204
205   /**
206    * @see InspectionEP#groupPath
207    */
208   @NotNull
209   public String[] getGroupPath() {
210     String groupDisplayName = getGroupDisplayName();
211     if (groupDisplayName.isEmpty()) {
212       groupDisplayName = GENERAL_GROUP_NAME;
213     }
214     return new String[]{groupDisplayName};
215   }
216
217   /**
218    * @see InspectionEP#displayName
219    * @see InspectionEP#key
220    * @see InspectionEP#bundle
221    */
222   @Nls
223   @NotNull
224   public String getDisplayName() {
225     if (myNameProvider != null) {
226       final String name = myNameProvider.getDefaultDisplayName();
227       if (name != null) {
228         return name;
229       }
230     }
231     LOG.error(getClass() + ": display name should be overridden or configured via XML " + getClass());
232     return "";
233   }
234
235   /**
236    * DO NOT OVERRIDE this method.
237    *
238    * @see InspectionEP#shortName
239    */
240   @NonNls
241   @NotNull
242   public String getShortName() {
243     if (myNameProvider != null) {
244       final String name = myNameProvider.getDefaultShortName();
245       if (name != null) {
246         return name;
247       }
248     }
249     return getShortName(getClass().getSimpleName());
250   }
251
252   @NotNull
253   public static String getShortName(@NotNull String className) {
254     return StringUtil.trimEnd(StringUtil.trimEnd(className, "Inspection"), "InspectionBase");
255   }
256
257   /**
258    * DO NOT OVERRIDE this method.
259    *
260    * @see InspectionEP#level
261    */
262   @NotNull
263   public HighlightDisplayLevel getDefaultLevel() {
264     return HighlightDisplayLevel.WARNING;
265   }
266
267   /**
268    * DO NOT OVERRIDE this method.
269    *
270    * @see InspectionEP#enabledByDefault
271    */
272   public boolean isEnabledByDefault() {
273     return false;
274   }
275
276   /**
277    * This method is called each time UI is shown.
278    *
279    * @return null if no UI options required.
280    */
281   @Nullable
282   public JComponent createOptionsPanel() {
283     return null;
284   }
285
286   /**
287    * Read in settings from XML config.
288    * Default implementation uses XmlSerializer so you may use public fields (like <code>int TOOL_OPTION</code>)
289    * and bean-style getters/setters (like <code>int getToolOption(), void setToolOption(int)</code>) to store your options.
290    *
291    * @param node to read settings from.
292    * @throws InvalidDataException if the loaded data was not valid.
293    */
294   @SuppressWarnings("deprecation")
295   public void readSettings(@NotNull Element node) throws InvalidDataException {
296     if (useNewSerializer()) {
297       try {
298         XmlSerializer.deserializeInto(this, node);
299       }
300       catch (XmlSerializationException e) {
301         throw new InvalidDataException(e);
302       }
303     }
304     else {
305       //noinspection UnnecessaryFullyQualifiedName
306       com.intellij.openapi.util.DefaultJDOMExternalizer.readExternal(this, node);
307     }
308   }
309
310   /**
311    * Store current settings in XML config.
312    * Default implementation uses XmlSerializer so you may use public fields (like <code>int TOOL_OPTION</code>)
313    * and bean-style getters/setters (like <code>int getToolOption(), void setToolOption(int)</code>) to store your options.
314    *
315    * @param node to store settings to.
316    * @throws WriteExternalException if no data should be saved for this component.
317    */
318   @SuppressWarnings("deprecation")
319   public void writeSettings(@NotNull Element node) throws WriteExternalException {
320     if (useNewSerializer()) {
321       XmlSerializer.serializeInto(this, node, getSerializationFilter());
322     }
323     else {
324       //noinspection UnnecessaryFullyQualifiedName
325       com.intellij.openapi.util.DefaultJDOMExternalizer.writeExternal(this, node);
326     }
327   }
328
329   private synchronized boolean useNewSerializer() {
330     if (myUseNewSerializer == null) {
331       myUseNewSerializer = !getBlackList().contains(getClass().getName());
332     }
333     return myUseNewSerializer;
334   }
335
336   private static void loadBlackList() {
337     ourBlackList = ContainerUtil.newHashSet();
338
339     final URL url = InspectionProfileEntry.class.getResource("inspection-black-list.txt");
340     if (url == null) {
341       LOG.error("Resource not found");
342       return;
343     }
344
345     try {
346       final BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
347       try {
348         String line;
349         while ((line = reader.readLine()) != null) {
350           line = line.trim();
351           if (!line.isEmpty()) ourBlackList.add(line);
352         }
353       }
354       finally {
355         reader.close();
356       }
357     }
358     catch (IOException e) {
359       LOG.error("Unable to load resource: " + url, e);
360     }
361   }
362
363   @NotNull
364   public static Collection<String> getBlackList() {
365     synchronized (BLACK_LIST_LOCK) {
366       if (ourBlackList == null) {
367         loadBlackList();
368       }
369       return ourBlackList;
370     }
371   }
372
373   /**
374    * Returns filter used to omit default values on saving inspection settings.
375    * Default implementation uses SkipDefaultValuesSerializationFilters.
376    *
377    * @return serialization filter.
378    */
379   @SuppressWarnings("MethodMayBeStatic")
380   @Nullable
381   protected SerializationFilter getSerializationFilter() {
382     return DEFAULT_FILTER;
383   }
384
385   /**
386    * Initialize inspection with project. Is called on project opened for all profiles as well as on profile creation.
387    *
388    * @param project to be associated with this entry
389    * @deprecated this won't work for inspections configured via {@link InspectionEP}
390    */
391   public void projectOpened(@NotNull Project project) {
392   }
393
394   /**
395    * Cleanup inspection settings corresponding to the project. Is called on project closed for all profiles as well as on profile deletion.
396    *
397    * @param project to be disassociated from this entry
398    * @deprecated this won't work for inspections configured via {@link InspectionEP}
399    */
400   public void projectClosed(@NotNull Project project) {
401   }
402
403   /**
404    * Override this method to return a html inspection description. Otherwise it will be loaded from resources using ID.
405    *
406    * @return hard-code inspection description.
407    */
408   @Nullable
409   public String getStaticDescription() {
410     return null;
411   }
412
413   @Nullable
414   public String getDescriptionFileName() {
415     return null;
416   }
417
418   @Nullable
419   protected URL getDescriptionUrl() {
420     final String fileName = getDescriptionFileName();
421     if (fileName == null) return null;
422     return ResourceUtil.getResource(getDescriptionContextClass(), "/inspectionDescriptions", fileName);
423   }
424
425   @NotNull
426   protected Class<? extends InspectionProfileEntry> getDescriptionContextClass() {
427     return getClass();
428   }
429
430   public boolean isInitialized() {
431     return true;
432   }
433
434   /**
435    * @return short name of tool whose results will be used
436    */
437   @Nullable
438   public String getMainToolId() {
439     return null;
440   }
441
442   @Nullable
443   public String loadDescription() {
444     final String description = getStaticDescription();
445     if (description != null) return description;
446
447     try {
448       URL descriptionUrl = getDescriptionUrl();
449       if (descriptionUrl == null) return null;
450       return ResourceUtil.loadText(descriptionUrl);
451     }
452     catch (IOException ignored) {
453     }
454
455     return null;
456   }
457 }