Merge remote-tracking branch 'origin/master' into develar/is
[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   @NotNull
134   public List<InspectionToolWrapper> getAllTools() {
135     List<InspectionToolWrapper> result = new ArrayList<>();
136     for (ScopeToolState state : getTools()) {
137       result.add(state.getTool());
138     }
139     return result;
140   }
141
142   public void writeExternal(@NotNull Element inspectionElement) throws WriteExternalException {
143     if (myTools != null) {
144       for (ScopeToolState state : myTools) {
145         final Element scopeElement = new Element("scope");
146         scopeElement.setAttribute("name", state.getScopeName());
147         scopeElement.setAttribute(LEVEL_ATTRIBUTE, state.getLevel().getName());
148         scopeElement.setAttribute(ENABLED_ATTRIBUTE, Boolean.toString(state.isEnabled()));
149         InspectionToolWrapper toolWrapper = state.getTool();
150         if (toolWrapper.isInitialized()) {
151           toolWrapper.getTool().writeSettings(scopeElement);
152         }
153         inspectionElement.addContent(scopeElement);
154       }
155     }
156     inspectionElement.setAttribute(ENABLED_ATTRIBUTE, Boolean.toString(isEnabled()));
157     inspectionElement.setAttribute(LEVEL_ATTRIBUTE, getLevel().getName());
158     inspectionElement.setAttribute(ENABLED_BY_DEFAULT_ATTRIBUTE, Boolean.toString(myDefaultState.isEnabled()));
159     InspectionToolWrapper toolWrapper = myDefaultState.getTool();
160     if (toolWrapper.isInitialized()) {
161       toolWrapper.getTool().writeSettings(inspectionElement);
162     }
163   }
164
165   void readExternal(@NotNull Element toolElement, @NotNull InspectionProfile profile, Map<String, List<String>> dependencies) throws InvalidDataException {
166     final String levelName = toolElement.getAttributeValue(LEVEL_ATTRIBUTE);
167     final ProfileManager profileManager = profile.getProfileManager();
168     final SeverityRegistrar registrar = ((SeverityProvider)profileManager).getOwnSeverityRegistrar();
169     HighlightDisplayLevel level = levelName != null ? HighlightDisplayLevel.find(registrar.getSeverity(levelName)) : null;
170     if (level == null) {
171       level = HighlightDisplayLevel.WARNING;
172     }
173     myDefaultState.setLevel(level);
174     final String enabled = toolElement.getAttributeValue(ENABLED_ATTRIBUTE);
175     final boolean isEnabled = enabled != null && Boolean.parseBoolean(enabled);
176
177     final String enabledTool = toolElement.getAttributeValue(ENABLED_BY_DEFAULT_ATTRIBUTE);
178     myDefaultState.setEnabled(enabledTool != null ? Boolean.parseBoolean(enabledTool) : isEnabled);
179     final InspectionToolWrapper toolWrapper = myDefaultState.getTool();
180
181     final List<Element> scopeElements = toolElement.getChildren(ProfileEx.SCOPE);
182     final List<String> scopeNames = new ArrayList<>();
183     for (Element scopeElement : scopeElements) {
184       final String scopeName = scopeElement.getAttributeValue(ProfileEx.NAME);
185       if (scopeName == null) {
186         continue;
187       }
188       final NamedScopesHolder scopesHolder = profileManager.getScopesManager();
189       NamedScope namedScope = null;
190       if (scopesHolder != null) {
191         namedScope = scopesHolder.getScope(scopeName);
192       }
193       final String errorLevel = scopeElement.getAttributeValue(LEVEL_ATTRIBUTE);
194       final String enabledInScope = scopeElement.getAttributeValue(ENABLED_ATTRIBUTE);
195       final InspectionToolWrapper copyToolWrapper = toolWrapper.createCopy();
196     // check if unknown children exists
197       if (scopeElement.getAttributes().size() > 3 || !scopeElement.getChildren().isEmpty()) {
198         copyToolWrapper.getTool().readSettings(scopeElement);
199       }
200       HighlightDisplayLevel scopeLevel = errorLevel != null ?
201                                          HighlightDisplayLevel.find(registrar.getSeverity(errorLevel)) : null;
202       if (scopeLevel == null) {
203         scopeLevel = level;
204       }
205       if (namedScope != null) {
206         addTool(namedScope, copyToolWrapper, enabledInScope != null && Boolean.parseBoolean(enabledInScope), scopeLevel);
207       }
208       else {
209         addTool(scopeName, copyToolWrapper, enabledInScope != null && Boolean.parseBoolean(enabledInScope), scopeLevel);
210       }
211
212       scopeNames.add(scopeName);
213     }
214
215     for (int i = 0; i < scopeNames.size(); i++) {
216       String scopeName = scopeNames.get(i);
217       List<String> order = dependencies.get(scopeName);
218       if (order == null) {
219         order = new ArrayList<>();
220         dependencies.put(scopeName, order);
221       }
222       for (int j = i + 1; j < scopeNames.size(); j++) {
223         order.add(scopeNames.get(j));
224       }
225     }
226
227     // check if unknown children exists
228     if (toolElement.getAttributes().size() > 4 || toolElement.getChildren().size() > scopeElements.size()) {
229       toolWrapper.getTool().readSettings(toolElement);
230     }
231
232     myEnabled = isEnabled;
233   }
234
235   @NotNull
236   @Override
237   public InspectionToolWrapper getTool() {
238     if (myTools == null) return myDefaultState.getTool();
239     return myTools.iterator().next().getTool();
240   }
241
242   @Override
243   @NotNull
244   public List<ScopeToolState> getTools() {
245     if (myTools == null) {
246       return Collections.singletonList(myDefaultState);
247     }
248
249     List<ScopeToolState> result = new ArrayList<>(myTools);
250     result.add(myDefaultState);
251     return result;
252   }
253
254   @Override
255   public void collectTools(@NotNull List<ScopeToolState> result) {
256     if (myTools != null) {
257       result.addAll(myTools);
258     }
259     result.add(myDefaultState);
260   }
261
262   @Override
263   @NotNull
264   public ScopeToolState getDefaultState() {
265     return myDefaultState;
266   }
267
268   public void removeScope(int scopeIdx) {
269     if (myTools != null && scopeIdx >= 0 && myTools.size() > scopeIdx) {
270       myTools.remove(scopeIdx);
271       checkToolsIsEmpty();
272     }
273   }
274
275   public void removeScope(@NotNull final String scopeName) {
276     if (myTools != null) {
277       for (ScopeToolState tool : myTools) {
278         if (scopeName.equals(tool.getScopeName())) {
279           myTools.remove(tool);
280           break;
281         }
282       }
283       checkToolsIsEmpty();
284     }
285   }
286
287   private void checkToolsIsEmpty() {
288     if (myTools.isEmpty()) {
289       myTools = null;
290       setEnabled(myDefaultState.isEnabled());
291     }
292   }
293
294   public void removeAllScopes() {
295     myTools = null;
296   }
297
298   public void setScope(int idx, NamedScope namedScope) {
299     if (myTools != null && myTools.size() > idx && idx >= 0) {
300       final ScopeToolState scopeToolState = myTools.get(idx);
301       InspectionToolWrapper toolWrapper = scopeToolState.getTool();
302       myTools.remove(idx);
303       myTools.add(idx, new ScopeToolState(namedScope, toolWrapper, scopeToolState.isEnabled(), scopeToolState.getLevel()));
304     }
305   }
306
307   public void moveScope(int idx, int dir) {
308     if (myTools != null && idx >= 0 && idx < myTools.size() && idx + dir >= 0 && idx + dir < myTools.size()) {
309       final ScopeToolState state = myTools.get(idx);
310       myTools.set(idx, myTools.get(idx + dir));
311       myTools.set(idx + dir, state);
312     }
313   }
314
315   public boolean isEnabled(NamedScope namedScope, Project project) {
316     if (!myEnabled) return false;
317     if (namedScope != null && myTools != null) {
318       for (ScopeToolState state : myTools) {
319         if (Comparing.equal(namedScope, state.getScope(project))) return state.isEnabled();
320       }
321     }
322     return myDefaultState.isEnabled();
323   }
324
325   public HighlightDisplayLevel getLevel(PsiElement element) {
326     if (myTools == null || element == null) return myDefaultState.getLevel();
327     final Project project = element.getProject();
328     final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
329     for (ScopeToolState state : myTools) {
330       final NamedScope scope = state.getScope(project);
331       final PackageSet set = scope != null ? scope.getValue() : null;
332       if (set != null && set.contains(element.getContainingFile(), manager)) {
333         return state.getLevel();
334       }
335     }
336     return myDefaultState.getLevel();
337   }
338
339   public HighlightDisplayLevel getLevel() {
340     return myDefaultState.getLevel();
341   }
342
343   @Override
344   public boolean isEnabled() {
345     return myEnabled;
346   }
347
348
349   @Override
350   public boolean isEnabled(PsiElement element) {
351     if (!myEnabled) return false;
352     if (myTools == null || element == null) return myDefaultState.isEnabled();
353     final Project project = element.getProject();
354     final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
355     for (ScopeToolState state : myTools) {
356       final NamedScope scope = state.getScope(project);
357       if (scope != null) {
358         final PackageSet set = scope.getValue();
359         if (set != null && set.contains(element.getContainingFile(), manager)) {
360           return state.isEnabled();
361         }
362       }
363     }
364     return myDefaultState.isEnabled();
365   }
366
367   @Nullable
368   @Override
369   public InspectionToolWrapper getEnabledTool(@Nullable PsiElement element, boolean includeDoNotShow) {
370     if (!myEnabled) return null;
371     if (myTools != null && element != null) {
372       final Project project = element.getProject();
373       final DependencyValidationManager manager = DependencyValidationManager.getInstance(project);
374       for (ScopeToolState state : myTools) {
375         final NamedScope scope = state.getScope(project);
376         if (scope != null) {
377           final PackageSet set = scope.getValue();
378           if (set != null && set.contains(element.getContainingFile(), manager)) {
379             return state.isEnabled() && (includeDoNotShow || !HighlightDisplayLevel.DO_NOT_SHOW.equals(state.getLevel())) ? state.getTool() : null;
380           }
381         }
382       }
383     }
384     return myDefaultState.isEnabled() && (includeDoNotShow || !HighlightDisplayLevel.DO_NOT_SHOW.equals(myDefaultState.getLevel())) ? myDefaultState.getTool() : null;
385   }
386
387   @Nullable
388   @Override
389   public InspectionToolWrapper getEnabledTool(@Nullable PsiElement element) {
390     return getEnabledTool(element, true);
391   }
392
393   public void setEnabled(boolean enabled) {
394     myEnabled = enabled;
395   }
396
397   public void enableTool(NamedScope namedScope, Project project) {
398     if (myTools != null) {
399       for (ScopeToolState state : myTools) {
400         if (Comparing.equal(state.getScope(project), namedScope)) {
401           state.setEnabled(true);
402         }
403       }
404     }
405     setEnabled(true);
406   }
407
408   public void disableTool(NamedScope namedScope, Project project) {
409     if (myTools != null) {
410       for (ScopeToolState state : myTools) {
411         if (Comparing.equal(state.getScope(project), namedScope)) {
412           state.setEnabled(false);
413         }
414       }
415     }
416   }
417
418   public void disableTool(@NotNull PsiElement element) {
419     final Project project = element.getProject();
420     final DependencyValidationManager validationManager = DependencyValidationManager.getInstance(project);
421     if (myTools != null) {
422       for (ScopeToolState state : myTools) {
423         final NamedScope scope = state.getScope(project);
424         if (scope != null) {
425           final PackageSet packageSet = scope.getValue();
426           if (packageSet != null) {
427             final PsiFile file = element.getContainingFile();
428             if (file != null) {
429               if (packageSet.contains(file, validationManager)) {
430                 state.setEnabled(false);
431                 return;
432               }
433             }
434             else {
435               if (packageSet instanceof PackageSetBase &&
436                   ((PackageSetBase)packageSet).contains(PsiUtilCore.getVirtualFile(element), project, validationManager)) {
437                 state.setEnabled(false);
438                 return;
439               }
440             }
441           }
442         }
443       }
444       myDefaultState.setEnabled(false);
445     }
446     else {
447       myDefaultState.setEnabled(false);
448       setEnabled(false);
449     }
450   }
451
452   @NotNull
453   public HighlightDisplayLevel getLevel(final NamedScope scope, Project project) {
454     if (myTools != null && scope != null){
455       for (ScopeToolState state : myTools) {
456         if (Comparing.equal(state.getScope(project), scope)) {
457           return state.getLevel();
458         }
459       }
460     }
461     return myDefaultState.getLevel();
462   }
463
464    @Override
465    public boolean equals(Object o) {
466      ToolsImpl tools = (ToolsImpl)o;
467       if (myEnabled != tools.myEnabled) return false;
468       if (getTools().size() != tools.getTools().size()) return false;
469       for (int i = 0; i < getTools().size(); i++) {
470         final ScopeToolState state = getTools().get(i);
471         final ScopeToolState toolState = tools.getTools().get(i);
472         if (!state.equalTo(toolState)){
473           return false;
474         }
475       }
476       return true;
477
478   }
479
480   public void setLevel(@NotNull HighlightDisplayLevel level, @Nullable String scopeName, Project project) {
481     if (scopeName == null) {
482       myDefaultState.setLevel(level);
483     } else {
484       if (myTools == null) {
485         return;
486       }
487       ScopeToolState scopeToolState = null;
488       int index = -1;
489       for (int i = 0; i < myTools.size(); i++) {
490         ScopeToolState tool = myTools.get(i);
491         if (scopeName.equals(tool.getScopeName())) {
492           scopeToolState = tool;
493           myTools.remove(tool);
494           index = i;
495           break;
496         }
497       }
498       if (index < 0) {
499         throw new IllegalStateException("Scope " + scopeName + " not found");
500       }
501       final InspectionToolWrapper toolWrapper = scopeToolState.getTool();
502       final NamedScope scope = scopeToolState.getScope(project);
503       if (scope != null) {
504         myTools.add(index, new ScopeToolState(scope, toolWrapper, scopeToolState.isEnabled(), level));
505       }
506       else {
507         myTools.add(index, new ScopeToolState(scopeToolState.getScopeName(), toolWrapper, scopeToolState.isEnabled(), level));
508       }
509     }
510   }
511
512   public void setDefaultState(@NotNull InspectionToolWrapper toolWrapper, boolean enabled, @NotNull HighlightDisplayLevel level) {
513     myDefaultState.setTool(toolWrapper);
514     myDefaultState.setLevel(level);
515     myDefaultState.setEnabled(enabled);
516   }
517
518   public void setLevel(@NotNull HighlightDisplayLevel level) {
519     myDefaultState.setLevel(level);
520   }
521
522   @Nullable
523   public List<ScopeToolState> getNonDefaultTools() {
524     return myTools;
525   }
526 }