inspections: disable whole tool if tool is disabled for all scopes and disabled for...
[idea/community.git] / platform / analysis-impl / src / com / intellij / codeInspection / ex / ToolsImpl.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
17 /*
18  * User: anna
19  * Date: 15-Apr-2009
20  */
21 package com.intellij.codeInspection.ex;
22
23 import com.intellij.codeHighlighting.HighlightDisplayLevel;
24 import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
25 import com.intellij.codeInspection.InspectionProfile;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.util.Comparing;
28 import com.intellij.openapi.util.InvalidDataException;
29 import com.intellij.openapi.util.WriteExternalException;
30 import com.intellij.packageDependencies.DependencyValidationManager;
31 import com.intellij.profile.ProfileEx;
32 import com.intellij.profile.ProfileManager;
33 import com.intellij.profile.codeInspection.SeverityProvider;
34 import com.intellij.psi.PsiElement;
35 import com.intellij.psi.PsiFile;
36 import com.intellij.psi.search.scope.packageSet.*;
37 import com.intellij.psi.util.PsiUtilCore;
38 import org.jdom.Element;
39 import org.jetbrains.annotations.NonNls;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 import org.jetbrains.annotations.TestOnly;
43
44 import java.util.ArrayList;
45 import java.util.Collections;
46 import java.util.List;
47 import java.util.Map;
48
49 public class ToolsImpl implements Tools {
50   @NonNls static final String ENABLED_BY_DEFAULT_ATTRIBUTE = "enabled_by_default";
51   @NonNls static final String ENABLED_ATTRIBUTE = "enabled";
52   @NonNls static final String LEVEL_ATTRIBUTE = "level";
53
54   private final String myShortName;
55   private final ScopeToolState myDefaultState;
56   private List<ScopeToolState> myTools;
57   private boolean myEnabled;
58
59   public ToolsImpl(@NotNull InspectionToolWrapper toolWrapper, @NotNull HighlightDisplayLevel level, boolean enabled, boolean enabledByDefault) {
60     myShortName = toolWrapper.getShortName();
61     myDefaultState = new ScopeToolState(CustomScopesProviderEx.getAllScope(), toolWrapper, enabledByDefault, level);
62     myTools = null;
63     myEnabled = enabled;
64   }
65
66   @TestOnly
67   public ToolsImpl(@NotNull InspectionToolWrapper toolWrapper, @NotNull HighlightDisplayLevel level, boolean enabled) {
68     this(toolWrapper, level, enabled, enabled);
69   }
70
71   @NotNull
72   public ScopeToolState addTool(@NotNull NamedScope scope, @NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level) {
73     return insertTool(scope, toolWrapper, enabled, level, myTools != null ? myTools.size() : 0);
74   }
75
76   @NotNull
77   public ScopeToolState prependTool(@NotNull NamedScope scope, @NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level) {
78     return insertTool(scope, toolWrapper, enabled, level, 0);
79   }
80
81   public ScopeToolState addTool(@NotNull String scopeName, @NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level) {
82     return insertTool(new ScopeToolState(scopeName, toolWrapper, enabled, level), myTools != null ? myTools.size() : 0);
83   }
84
85   @NotNull
86   private ScopeToolState insertTool(@NotNull NamedScope scope, @NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level, int idx) {
87     return insertTool(new ScopeToolState(scope, toolWrapper, enabled, level), idx);
88   }
89
90   @NotNull
91   private ScopeToolState insertTool(@NotNull final ScopeToolState scopeToolState, final int idx) {
92     if (myTools == null) {
93       myTools = new ArrayList<>();
94       if (scopeToolState.isEnabled()) {
95         setEnabled(true);
96       }
97     }
98     myTools.add(idx, scopeToolState);
99     return scopeToolState;
100   }
101
102   @NotNull
103   @Override
104   public InspectionToolWrapper getInspectionTool(@Nullable PsiElement element) {
105     if (myTools != null) {
106       final PsiFile containingFile = element == null ? null : element.getContainingFile();
107       final Project project = containingFile == null ? null : containingFile.getProject();
108       for (ScopeToolState state : myTools) {
109         if (element == null) {
110           return state.getTool();
111         }
112         NamedScope scope = state.getScope(project);
113         if (scope != null) {
114           final PackageSet packageSet = scope.getValue();
115           if (packageSet != null) {
116             if (containingFile != null && packageSet.contains(containingFile, DependencyValidationManager.getInstance(project))) {
117               return state.getTool();
118             }
119           }
120         }
121       }
122
123     }
124     return myDefaultState.getTool();
125   }
126
127   @NotNull
128   @Override
129   public String getShortName() {
130     return myShortName;
131   }
132
133   public void cleanupTools(@NotNull Project project) {
134     for (ScopeToolState state : getTools()) {
135       state.getTool().cleanup(project);
136     }
137   }
138
139   public void writeExternal(@NotNull Element inspectionElement) throws WriteExternalException {
140     if (myTools != null) {
141       for (ScopeToolState state : myTools) {
142         final Element scopeElement = new Element("scope");
143         scopeElement.setAttribute("name", state.getScopeName());
144         scopeElement.setAttribute(LEVEL_ATTRIBUTE, state.getLevel().getName());
145         scopeElement.setAttribute(ENABLED_ATTRIBUTE, Boolean.toString(state.isEnabled()));
146         InspectionToolWrapper toolWrapper = state.getTool();
147         if (toolWrapper.isInitialized()) {
148           toolWrapper.getTool().writeSettings(scopeElement);
149         }
150         inspectionElement.addContent(scopeElement);
151       }
152     }
153     inspectionElement.setAttribute(ENABLED_ATTRIBUTE, Boolean.toString(isEnabled()));
154     inspectionElement.setAttribute(LEVEL_ATTRIBUTE, getLevel().getName());
155     inspectionElement.setAttribute(ENABLED_BY_DEFAULT_ATTRIBUTE, Boolean.toString(myDefaultState.isEnabled()));
156     InspectionToolWrapper toolWrapper = myDefaultState.getTool();
157     if (toolWrapper.isInitialized()) {
158       toolWrapper.getTool().writeSettings(inspectionElement);
159     }
160   }
161
162   void readExternal(@NotNull Element toolElement, @NotNull InspectionProfile profile, Map<String, List<String>> dependencies) throws InvalidDataException {
163     final String levelName = toolElement.getAttributeValue(LEVEL_ATTRIBUTE);
164     final ProfileManager profileManager = profile.getProfileManager();
165     final SeverityRegistrar registrar = ((SeverityProvider)profileManager).getOwnSeverityRegistrar();
166     HighlightDisplayLevel level = levelName != null ? HighlightDisplayLevel.find(registrar.getSeverity(levelName)) : null;
167     if (level == null) {
168       level = HighlightDisplayLevel.WARNING;
169     }
170     myDefaultState.setLevel(level);
171     final String enabled = toolElement.getAttributeValue(ENABLED_ATTRIBUTE);
172     final boolean isEnabled = enabled != null && Boolean.parseBoolean(enabled);
173
174     final String enabledTool = toolElement.getAttributeValue(ENABLED_BY_DEFAULT_ATTRIBUTE);
175     myDefaultState.setEnabled(enabledTool != null ? Boolean.parseBoolean(enabledTool) : isEnabled);
176     final InspectionToolWrapper toolWrapper = myDefaultState.getTool();
177
178     final List<Element> scopeElements = toolElement.getChildren(ProfileEx.SCOPE);
179     final List<String> scopeNames = new ArrayList<>();
180     for (Element scopeElement : scopeElements) {
181       final String scopeName = scopeElement.getAttributeValue(ProfileEx.NAME);
182       if (scopeName == null) {
183         continue;
184       }
185       final NamedScopesHolder scopesHolder = profileManager.getScopesManager();
186       NamedScope namedScope = null;
187       if (scopesHolder != null) {
188         namedScope = scopesHolder.getScope(scopeName);
189       }
190       final String errorLevel = scopeElement.getAttributeValue(LEVEL_ATTRIBUTE);
191       final String enabledInScope = scopeElement.getAttributeValue(ENABLED_ATTRIBUTE);
192       final InspectionToolWrapper copyToolWrapper = toolWrapper.createCopy();
193     // check if unknown children exists
194       if (scopeElement.getAttributes().size() > 3 || !scopeElement.getChildren().isEmpty()) {
195         copyToolWrapper.getTool().readSettings(scopeElement);
196       }
197       HighlightDisplayLevel scopeLevel = errorLevel != null ?
198                                          HighlightDisplayLevel.find(registrar.getSeverity(errorLevel)) : null;
199       if (scopeLevel == null) {
200         scopeLevel = level;
201       }
202       if (namedScope != null) {
203         addTool(namedScope, copyToolWrapper, enabledInScope != null && Boolean.parseBoolean(enabledInScope), scopeLevel);
204       }
205       else {
206         addTool(scopeName, copyToolWrapper, enabledInScope != null && Boolean.parseBoolean(enabledInScope), scopeLevel);
207       }
208
209       scopeNames.add(scopeName);
210     }
211
212     for (int i = 0; i < scopeNames.size(); i++) {
213       String scopeName = scopeNames.get(i);
214       List<String> order = dependencies.get(scopeName);
215       if (order == null) {
216         order = new ArrayList<>();
217         dependencies.put(scopeName, order);
218       }
219       for (int j = i + 1; j < scopeNames.size(); j++) {
220         order.add(scopeNames.get(j));
221       }
222     }
223
224     // check if unknown children exists
225     if (toolElement.getAttributes().size() > 4 || toolElement.getChildren().size() > scopeElements.size()) {
226       toolWrapper.getTool().readSettings(toolElement);
227     }
228
229     myEnabled = isEnabled;
230   }
231
232   @NotNull
233   @Override
234   public InspectionToolWrapper getTool() {
235     if (myTools == null) return myDefaultState.getTool();
236     return myTools.iterator().next().getTool();
237   }
238
239   @Override
240   @NotNull
241   public List<ScopeToolState> getTools() {
242     if (myTools == null) {
243       return Collections.singletonList(myDefaultState);
244     }
245
246     List<ScopeToolState> result = new ArrayList<>(myTools);
247     result.add(myDefaultState);
248     return result;
249   }
250
251   @Override
252   public void collectTools(@NotNull List<ScopeToolState> result) {
253     if (myTools != null) {
254       result.addAll(myTools);
255     }
256     result.add(myDefaultState);
257   }
258
259   @Override
260   @NotNull
261   public ScopeToolState getDefaultState() {
262     return myDefaultState;
263   }
264
265   public void setDefaultEnabled(boolean isEnabled) {
266     getDefaultState().setEnabled(isEnabled);
267     if (isEnabled) {
268       setEnabled(true);
269     }
270     else {
271       disableWholeToolIfCan();
272     }
273   }
274
275   public void removeScope(int scopeIdx) {
276     if (myTools != null && scopeIdx >= 0 && myTools.size() > scopeIdx) {
277       myTools.remove(scopeIdx);
278       checkToolsIsEmpty();
279     }
280   }
281
282   public void removeScope(@NotNull final String scopeName) {
283     if (myTools != null) {
284       for (ScopeToolState tool : myTools) {
285         if (scopeName.equals(tool.getScopeName())) {
286           myTools.remove(tool);
287           break;
288         }
289       }
290       checkToolsIsEmpty();
291     }
292   }
293
294   private void checkToolsIsEmpty() {
295     if (myTools.isEmpty()) {
296       myTools = null;
297       setEnabled(myDefaultState.isEnabled());
298     }
299   }
300
301   public void removeAllScopes() {
302     myTools = null;
303   }
304
305   public void setScope(int idx, NamedScope namedScope) {
306     if (myTools != null && myTools.size() > idx && idx >= 0) {
307       final ScopeToolState scopeToolState = myTools.get(idx);
308       InspectionToolWrapper toolWrapper = scopeToolState.getTool();
309       myTools.remove(idx);
310       myTools.add(idx, new ScopeToolState(namedScope, toolWrapper, scopeToolState.isEnabled(), scopeToolState.getLevel()));
311     }
312   }
313
314   public void moveScope(int idx, int dir) {
315     if (myTools != null && idx >= 0 && idx < myTools.size() && idx + dir >= 0 && idx + dir < myTools.size()) {
316       final ScopeToolState state = myTools.get(idx);
317       myTools.set(idx, myTools.get(idx + dir));
318       myTools.set(idx + dir, state);
319     }
320   }
321
322   public boolean isEnabled(NamedScope namedScope, Project project) {
323     if (!myEnabled) return false;
324     if (namedScope != null && myTools != null) {
325       for (ScopeToolState state : myTools) {
326         if (Comparing.equal(namedScope, state.getScope(project))) return state.isEnabled();
327       }
328     }
329     return myDefaultState.isEnabled();
330   }
331
332   public HighlightDisplayLevel getLevel(PsiElement element) {
333     if (myTools == null || element == null) return myDefaultState.getLevel();
334     final Project project = element.getProject();
335     final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
336     for (ScopeToolState state : myTools) {
337       final NamedScope scope = state.getScope(project);
338       final PackageSet set = scope != null ? scope.getValue() : null;
339       if (set != null && set.contains(element.getContainingFile(), manager)) {
340         return state.getLevel();
341       }
342     }
343     return myDefaultState.getLevel();
344   }
345
346   public HighlightDisplayLevel getLevel() {
347     return myDefaultState.getLevel();
348   }
349
350   @Override
351   public boolean isEnabled() {
352     return myEnabled;
353   }
354
355
356   @Override
357   public boolean isEnabled(PsiElement element) {
358     if (!myEnabled) return false;
359     if (myTools == null || element == null) return myDefaultState.isEnabled();
360     final Project project = element.getProject();
361     final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
362     for (ScopeToolState state : myTools) {
363       final NamedScope scope = state.getScope(project);
364       if (scope != null) {
365         final PackageSet set = scope.getValue();
366         if (set != null && set.contains(element.getContainingFile(), manager)) {
367           return state.isEnabled();
368         }
369       }
370     }
371     return myDefaultState.isEnabled();
372   }
373
374   @Nullable
375   @Override
376   public InspectionToolWrapper getEnabledTool(@Nullable PsiElement element, boolean includeDoNotShow) {
377     if (!myEnabled) return null;
378     if (myTools != null && element != null) {
379       final Project project = element.getProject();
380       final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
381       for (ScopeToolState state : myTools) {
382         final NamedScope scope = state.getScope(project);
383         if (scope != null) {
384           final PackageSet set = scope.getValue();
385           if (set != null && set.contains(element.getContainingFile(), manager)) {
386             return state.isEnabled() && (includeDoNotShow || !HighlightDisplayLevel.DO_NOT_SHOW.equals(state.getLevel())) ? state.getTool() : null;
387           }
388         }
389       }
390     }
391     return myDefaultState.isEnabled() && (includeDoNotShow || !HighlightDisplayLevel.DO_NOT_SHOW.equals(myDefaultState.getLevel())) ? myDefaultState.getTool() : null;
392   }
393
394   @Nullable
395   @Override
396   public InspectionToolWrapper getEnabledTool(@Nullable PsiElement element) {
397     return getEnabledTool(element, true);
398   }
399
400   public void setEnabled(boolean enabled) {
401     myEnabled = enabled;
402   }
403
404   public void enableTool(NamedScope namedScope, Project project) {
405     if (myTools != null) {
406       for (ScopeToolState state : myTools) {
407         if (Comparing.equal(state.getScope(project), namedScope)) {
408           state.setEnabled(true);
409         }
410       }
411     }
412     setEnabled(true);
413   }
414
415   public void disableTool(NamedScope namedScope, Project project) {
416     if (myTools != null) {
417       for (ScopeToolState state : myTools) {
418         if (Comparing.equal(state.getScope(project), namedScope)) {
419           state.setEnabled(false);
420         }
421       }
422       disableWholeToolIfCan();
423     }
424   }
425
426   public void disableTool(@NotNull PsiElement element) {
427     final Project project = element.getProject();
428     final DependencyValidationManager validationManager = DependencyValidationManager.getInstance(project);
429     if (myTools != null) {
430       for (ScopeToolState state : myTools) {
431         final NamedScope scope = state.getScope(project);
432         if (scope != null) {
433           final PackageSet packageSet = scope.getValue();
434           if (packageSet != null) {
435             final PsiFile file = element.getContainingFile();
436             if (file != null) {
437               if (packageSet.contains(file, validationManager)) {
438                 state.setEnabled(false);
439                 return;
440               }
441             }
442             else {
443               if (packageSet instanceof PackageSetBase &&
444                   ((PackageSetBase)packageSet).contains(PsiUtilCore.getVirtualFile(element), project, validationManager)) {
445                 state.setEnabled(false);
446                 return;
447               }
448             }
449           }
450         }
451       }
452       myDefaultState.setEnabled(false);
453     }
454     else {
455       myDefaultState.setEnabled(false);
456       setEnabled(false);
457     }
458   }
459
460   @NotNull
461   public HighlightDisplayLevel getLevel(final NamedScope scope, Project project) {
462     if (myTools != null && scope != null){
463       for (ScopeToolState state : myTools) {
464         if (Comparing.equal(state.getScope(project), scope)) {
465           return state.getLevel();
466         }
467       }
468     }
469     return myDefaultState.getLevel();
470   }
471
472    @Override
473    public boolean equals(Object o) {
474      ToolsImpl tools = (ToolsImpl)o;
475       if (myEnabled != tools.myEnabled) return false;
476       if (getTools().size() != tools.getTools().size()) return false;
477       for (int i = 0; i < getTools().size(); i++) {
478         final ScopeToolState state = getTools().get(i);
479         final ScopeToolState toolState = tools.getTools().get(i);
480         if (!state.equalTo(toolState)){
481           return false;
482         }
483       }
484       return true;
485
486   }
487
488   public void setLevel(@NotNull HighlightDisplayLevel level, @Nullable String scopeName, Project project) {
489     if (scopeName == null) {
490       myDefaultState.setLevel(level);
491     } else {
492       if (myTools == null) {
493         return;
494       }
495       ScopeToolState scopeToolState = null;
496       int index = -1;
497       for (int i = 0; i < myTools.size(); i++) {
498         ScopeToolState tool = myTools.get(i);
499         if (scopeName.equals(tool.getScopeName())) {
500           scopeToolState = tool;
501           myTools.remove(tool);
502           index = i;
503           break;
504         }
505       }
506       if (index < 0) {
507         throw new IllegalStateException("Scope " + scopeName + " not found");
508       }
509       final InspectionToolWrapper toolWrapper = scopeToolState.getTool();
510       final NamedScope scope = scopeToolState.getScope(project);
511       if (scope != null) {
512         myTools.add(index, new ScopeToolState(scope, toolWrapper, scopeToolState.isEnabled(), level));
513       }
514       else {
515         myTools.add(index, new ScopeToolState(scopeToolState.getScopeName(), toolWrapper, scopeToolState.isEnabled(), level));
516       }
517     }
518   }
519
520   public void setDefaultState(@NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level) {
521     myDefaultState.setTool(toolWrapper);
522     myDefaultState.setLevel(level);
523     myDefaultState.setEnabled(enabled);
524   }
525
526   public void setLevel(@NotNull HighlightDisplayLevel level) {
527     myDefaultState.setLevel(level);
528   }
529
530   @Nullable
531   public List<ScopeToolState> getNonDefaultTools() {
532     return myTools;
533   }
534
535   private void disableWholeToolIfCan() {
536     if (myDefaultState.isEnabled()) {
537       return;
538     }
539     if (myTools != null) {
540       for (ScopeToolState tool : myTools) {
541         if (tool.isEnabled()) {
542           return;
543         }
544       }
545     }
546     setEnabled(false);
547   }
548 }