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