Merge branch 'db/javac-ast'
[idea/community.git] / platform / analysis-impl / src / com / intellij / codeInspection / ex / InspectionProfileImpl.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.ex;
17
18 import com.intellij.codeHighlighting.HighlightDisplayLevel;
19 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
20 import com.intellij.codeInspection.InspectionEP;
21 import com.intellij.codeInspection.InspectionProfile;
22 import com.intellij.codeInspection.InspectionProfileEntry;
23 import com.intellij.codeInspection.ModifiableModel;
24 import com.intellij.configurationStore.SchemeDataHolder;
25 import com.intellij.configurationStore.SerializableScheme;
26 import com.intellij.lang.annotation.HighlightSeverity;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.options.ExternalizableScheme;
30 import com.intellij.openapi.progress.ProcessCanceledException;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.util.*;
33 import com.intellij.profile.ProfileEx;
34 import com.intellij.profile.ProfileManager;
35 import com.intellij.profile.codeInspection.InspectionProfileManager;
36 import com.intellij.profile.codeInspection.ProjectInspectionProfileManager;
37 import com.intellij.profile.codeInspection.ProjectInspectionProfileManagerKt;
38 import com.intellij.profile.codeInspection.SeverityProvider;
39 import com.intellij.project.ProjectKt;
40 import com.intellij.psi.PsiElement;
41 import com.intellij.psi.search.scope.packageSet.NamedScope;
42 import com.intellij.util.ArrayUtil;
43 import com.intellij.util.Consumer;
44 import com.intellij.util.containers.ContainerUtil;
45 import com.intellij.util.graph.CachingSemiGraph;
46 import com.intellij.util.graph.DFSTBuilder;
47 import com.intellij.util.graph.GraphGenerator;
48 import com.intellij.util.xmlb.annotations.Attribute;
49 import com.intellij.util.xmlb.annotations.Tag;
50 import com.intellij.util.xmlb.annotations.Transient;
51 import gnu.trove.THashMap;
52 import gnu.trove.THashSet;
53 import org.jdom.Element;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57 import org.jetbrains.annotations.TestOnly;
58
59 import java.util.*;
60
61 /**
62  * @author max
63  */
64 public class InspectionProfileImpl extends ProfileEx implements ModifiableModel, InspectionProfile, ExternalizableScheme,
65                                                                 SerializableScheme {
66   @NonNls static final String INSPECTION_TOOL_TAG = "inspection_tool";
67   @NonNls static final String CLASS_TAG = "class";
68   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.InspectionProfileImpl");
69   @NonNls private static final String VALID_VERSION = "1.0";
70   @NonNls private static final String VERSION_TAG = "version";
71   @NonNls private static final String USED_LEVELS = "used_levels";
72   public static final String DEFAULT_PROFILE_NAME = "Default";
73   @TestOnly
74   public static boolean INIT_INSPECTIONS = false;
75   private final InspectionToolRegistrar myRegistrar;
76   @NotNull
77   private final Map<String, Element> myUninitializedSettings = new TreeMap<>();
78   protected InspectionProfileImpl mySource;
79   private Map<String, ToolsImpl> myTools = new THashMap<>();
80   private volatile Set<String> myChangedToolNames;
81   @Attribute("is_locked")
82   private boolean myLockedProfile;
83   private final InspectionProfileImpl myBaseProfile;
84   private volatile String myToolShortName = null;
85   private String[] myScopesOrder;
86   private String myDescription;
87   private boolean myModified;
88   private volatile boolean myInitialized;
89
90   private final Object myLock = new Object();
91
92   private SchemeDataHolder<? super InspectionProfileImpl> myDataHolder;
93
94   InspectionProfileImpl(@NotNull InspectionProfileImpl inspectionProfile) {
95     this(inspectionProfile.getName(), inspectionProfile.myRegistrar, inspectionProfile.getProfileManager(), inspectionProfile.myBaseProfile, null);
96     myUninitializedSettings.putAll(inspectionProfile.myUninitializedSettings);
97
98     setProjectLevel(inspectionProfile.isProjectLevel());
99     myLockedProfile = inspectionProfile.myLockedProfile;
100     mySource = inspectionProfile;
101     copyFrom(inspectionProfile);
102   }
103
104   public InspectionProfileImpl(@NotNull String profileName,
105                                @NotNull InspectionToolRegistrar registrar,
106                                @NotNull ProfileManager profileManager) {
107     this(profileName, registrar, profileManager, getDefaultProfile(), null);
108   }
109
110   public InspectionProfileImpl(@NotNull @NonNls String profileName) {
111     this(profileName, InspectionToolRegistrar.getInstance(), InspectionProfileManager.getInstance(), null, null);
112   }
113
114   public InspectionProfileImpl(@NotNull String profileName,
115                                @NotNull InspectionToolRegistrar registrar,
116                                @NotNull ProfileManager profileManager,
117                                @Nullable InspectionProfileImpl baseProfile,
118                                @Nullable SchemeDataHolder<? super InspectionProfileImpl> dataHolder) {
119     super(profileName);
120
121     myRegistrar = registrar;
122     myBaseProfile = baseProfile;
123     myDataHolder = dataHolder;
124     myProfileManager = profileManager;
125   }
126
127   public InspectionProfileImpl(@NotNull String profileName,
128                                @NotNull InspectionToolRegistrar registrar,
129                                @NotNull ProfileManager profileManager,
130                                @Nullable SchemeDataHolder<? super InspectionProfileImpl> dataHolder) {
131     this(profileName, registrar, profileManager, getDefaultProfile(), dataHolder);
132   }
133
134   @NotNull
135   public static InspectionProfileImpl createSimple(@NotNull String name,
136                                                    @NotNull Project project,
137                                                    @NotNull List<InspectionToolWrapper> toolWrappers) {
138     InspectionProfileImpl profile = new InspectionProfileImpl(name, new InspectionToolRegistrar() {
139       @NotNull
140       @Override
141       public List<InspectionToolWrapper> createTools() {
142         return toolWrappers;
143       }
144     }, InspectionProfileManager.getInstance());
145     for (InspectionToolWrapper toolWrapper : toolWrappers) {
146       profile.enableTool(toolWrapper.getShortName(), project);
147     }
148     return profile;
149   }
150
151   private static boolean toolSettingsAreEqual(@NotNull String toolName, @NotNull InspectionProfileImpl profile1, @NotNull InspectionProfileImpl profile2) {
152     final Tools toolList1 = profile1.myTools.get(toolName);
153     final Tools toolList2 = profile2.myTools.get(toolName);
154     return Comparing.equal(toolList1, toolList2);
155   }
156
157   @NotNull
158   private static InspectionToolWrapper copyToolSettings(@NotNull InspectionToolWrapper toolWrapper) {
159     final InspectionToolWrapper inspectionTool = toolWrapper.createCopy();
160     if (toolWrapper.isInitialized()) {
161       Element config = new Element("config");
162       toolWrapper.getTool().writeSettings(config);
163       inspectionTool.getTool().readSettings(config);
164     }
165     return inspectionTool;
166   }
167
168   @NotNull
169   public static InspectionProfileImpl getDefaultProfile() {
170     return InspectionProfileImplHolder.DEFAULT_PROFILE;
171   }
172
173   @Override
174   public void setModified(final boolean modified) {
175     myModified = modified;
176   }
177
178   @Override
179   public InspectionProfile getParentProfile() {
180     return mySource;
181   }
182
183   @Override
184   @SuppressWarnings({"SimplifiableIfStatement"})
185   public boolean isChanged() {
186     if (mySource != null && mySource.myLockedProfile != myLockedProfile) return true;
187     return myModified;
188   }
189
190   @Override
191   public boolean isProperSetting(@NotNull String toolId) {
192     if (myBaseProfile != null) {
193       final Tools tools = myBaseProfile.getTools(toolId, null);
194       final Tools currentTools = myTools.get(toolId);
195       return !Comparing.equal(tools, currentTools);
196     }
197     return false;
198   }
199
200   @Override
201   public void resetToBase(@Nullable Project project) {
202     initInspectionTools(project);
203
204     copyToolsConfigurations(myBaseProfile, project);
205     myChangedToolNames = null;
206   }
207
208   @Override
209   public void resetToEmpty(Project project) {
210     initInspectionTools(project);
211     final InspectionToolWrapper[] profileEntries = getInspectionTools(null);
212     for (InspectionToolWrapper toolWrapper : profileEntries) {
213       disableTool(toolWrapper.getShortName(), project);
214     }
215   }
216
217   @Override
218   public HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey inspectionToolKey, PsiElement element) {
219     Project project = element == null ? null : element.getProject();
220     final ToolsImpl tools = getTools(inspectionToolKey.toString(), project);
221     HighlightDisplayLevel level = tools != null ? tools.getLevel(element) : HighlightDisplayLevel.WARNING;
222     if (!((SeverityProvider)getProfileManager()).getOwnSeverityRegistrar().isSeverityValid(level.getSeverity().getName())) {
223       level = HighlightDisplayLevel.WARNING;
224       setErrorLevel(inspectionToolKey, level, project);
225     }
226     return level;
227   }
228
229   @Override
230   public void readExternal(@NotNull Element element) {
231     super.readExternal(element);
232
233     final Element highlightElement = element.getChild(USED_LEVELS);
234     if (highlightElement != null) {
235       // from old profiles
236       ((SeverityProvider)getProfileManager()).getOwnSeverityRegistrar().readExternal(highlightElement);
237     }
238
239     String version = element.getAttributeValue(VERSION_TAG);
240     if (version == null || !version.equals(VALID_VERSION)) {
241       InspectionToolWrapper[] tools = getInspectionTools(null);
242       for (Element toolElement : element.getChildren("inspection_tool")) {
243         String toolClassName = toolElement.getAttributeValue(CLASS_TAG);
244         String shortName = convertToShortName(toolClassName, tools);
245         if (shortName == null) {
246           continue;
247         }
248         toolElement.setAttribute(CLASS_TAG, shortName);
249         myUninitializedSettings.put(shortName, toolElement.clone());
250       }
251     }
252     else {
253       for (Element toolElement : element.getChildren(INSPECTION_TOOL_TAG)) {
254         myUninitializedSettings.put(toolElement.getAttributeValue(CLASS_TAG), toolElement.clone());
255       }
256     }
257   }
258
259   @Nullable
260   private static String convertToShortName(@Nullable String displayName, InspectionToolWrapper[] tools) {
261     if (displayName == null) return null;
262     for (InspectionToolWrapper tool : tools) {
263       if (displayName.equals(tool.getDisplayName())) {
264         return tool.getShortName();
265       }
266     }
267     return null;
268   }
269
270   @NotNull
271   public Set<HighlightSeverity> getUsedSeverities() {
272     LOG.assertTrue(myInitialized);
273     Set<HighlightSeverity> result = new THashSet<>();
274     for (Tools tools : myTools.values()) {
275       for (ScopeToolState state : tools.getTools()) {
276         result.add(state.getLevel().getSeverity());
277       }
278     }
279     return result;
280   }
281
282   @NotNull
283   public Element writeScheme() {
284     if (myDataHolder != null) {
285       return myDataHolder.read();
286     }
287
288     Element element = new Element("profile");
289     Element result = isProjectLevel() ? element.setAttribute("version", "1.0") : element.setAttribute("profile_name", getName());
290     serializeInto(result, false);
291
292     if (isProjectLevel() && ProjectKt.isDirectoryBased(((ProjectInspectionProfileManager)myProfileManager).getProject())) {
293       return new Element("component").setAttribute("name", "InspectionProjectProfileManager").addContent(result);
294     }
295     return result;
296   }
297
298   @Override
299   public void serializeInto(@NotNull Element element, boolean preserveCompatibility) {
300     // must be first - compatibility
301     element.setAttribute(VERSION_TAG, VALID_VERSION);
302
303     super.serializeInto(element, preserveCompatibility);
304
305     synchronized (myLock) {
306       if (!myInitialized) {
307         for (Element el : myUninitializedSettings.values()) {
308           element.addContent(el.clone());
309         }
310         return;
311       }
312     }
313
314     Set<String> changedToolNames = getChangedToolNames();
315     if (changedToolNames == null) {
316       return;
317     }
318
319     List<String> allToolNames = new ArrayList<>(myTools.keySet());
320     allToolNames.addAll(myUninitializedSettings.keySet());
321     allToolNames.sort(null);
322     for (String toolName : allToolNames) {
323       Element toolElement = myUninitializedSettings.get(toolName);
324       if (toolElement != null) {
325         element.addContent(toolElement.clone());
326         continue;
327       }
328
329       if (!myLockedProfile && !changedToolNames.contains(toolName)) {
330         markSettingsMerged(toolName, element);
331         continue;
332       }
333
334       ToolsImpl toolList = myTools.get(toolName);
335       LOG.assertTrue(toolList != null);
336       Element inspectionElement = new Element(INSPECTION_TOOL_TAG);
337       inspectionElement.setAttribute(CLASS_TAG, toolName);
338       try {
339         toolList.writeExternal(inspectionElement);
340       }
341       catch (WriteExternalException e) {
342         LOG.error(e);
343         continue;
344       }
345
346       if (!areSettingsMerged(toolName, inspectionElement)) {
347         element.addContent(inspectionElement);
348       }
349     }
350   }
351
352   private void markSettingsMerged(@NotNull String toolName, @NotNull Element element) {
353     //add marker if already merged but result is now default (-> empty node)
354     String mergedName = InspectionElementsMergerBase.getMergedMarkerName(toolName);
355     if (!myUninitializedSettings.containsKey(mergedName)) {
356       InspectionElementsMergerBase merger = getMerger(toolName);
357       if (merger != null && merger.markSettingsMerged(myUninitializedSettings)) {
358         element.addContent(new Element(INSPECTION_TOOL_TAG).setAttribute(CLASS_TAG, mergedName));
359       }
360     }
361   }
362
363   private boolean areSettingsMerged(String toolName, Element inspectionElement) {
364     //skip merged settings as they could be restored from already provided data
365     final InspectionElementsMergerBase merger = getMerger(toolName);
366     return merger != null && merger.areSettingsMerged(myUninitializedSettings, inspectionElement);
367   }
368
369   public void collectDependentInspections(@NotNull InspectionToolWrapper toolWrapper,
370                                           @NotNull Set<InspectionToolWrapper> dependentEntries,
371                                           Project project) {
372     String mainToolId = toolWrapper.getMainToolId();
373
374     if (mainToolId != null) {
375       InspectionToolWrapper dependentEntryWrapper = getInspectionTool(mainToolId, project);
376
377       if (dependentEntryWrapper == null) {
378         LOG.error("Can't find main tool: '" + mainToolId+"' which was specified in "+toolWrapper);
379         return;
380       }
381       if (!dependentEntries.add(dependentEntryWrapper)) {
382         collectDependentInspections(dependentEntryWrapper, dependentEntries, project);
383       }
384     }
385   }
386
387   @Override
388   @Nullable
389   public InspectionToolWrapper getInspectionTool(@NotNull String shortName, @NotNull PsiElement element) {
390     final Tools toolList = getTools(shortName, element.getProject());
391     return toolList == null ? null : toolList.getInspectionTool(element);
392   }
393
394   @Nullable
395   @Override
396   public InspectionProfileEntry getUnwrappedTool(@NotNull String shortName, @NotNull PsiElement element) {
397     InspectionToolWrapper tool = getInspectionTool(shortName, element);
398     return tool == null ? null : tool.getTool();
399   }
400
401   @Override
402   public <T extends InspectionProfileEntry> T getUnwrappedTool(@NotNull Key<T> shortNameKey, @NotNull PsiElement element) {
403     //noinspection unchecked
404     return (T) getUnwrappedTool(shortNameKey.toString(), element);
405   }
406
407   @Override
408   public void modifyProfile(@NotNull Consumer<ModifiableModel> modelConsumer) {
409     ModifiableModel model = getModifiableModel();
410     modelConsumer.consume(model);
411     model.commit();
412   }
413
414   @Override
415   public <T extends InspectionProfileEntry> void modifyToolSettings(@NotNull final Key<T> shortNameKey,
416                                                                     @NotNull final PsiElement psiElement,
417                                                                     @NotNull final Consumer<T> toolConsumer) {
418     modifyProfile(model -> {
419       InspectionProfileEntry tool = model.getUnwrappedTool(shortNameKey.toString(), psiElement);
420       //noinspection unchecked
421       toolConsumer.consume((T) tool);
422     });
423   }
424
425   @Override
426   @Nullable
427   public InspectionToolWrapper getInspectionTool(@NotNull String shortName, Project project) {
428     final ToolsImpl tools = getTools(shortName, project);
429     return tools != null? tools.getTool() : null;
430   }
431
432   public InspectionToolWrapper getToolById(@NotNull String id, @NotNull PsiElement element) {
433     initInspectionTools(element.getProject());
434     for (Tools toolList : myTools.values()) {
435       final InspectionToolWrapper tool = toolList.getInspectionTool(element);
436       if (id.equals(tool.getID())) return tool;
437     }
438     return null;
439   }
440
441   @Nullable
442   public List<InspectionToolWrapper> findToolsById(@NotNull String id, @NotNull PsiElement element) {
443     List<InspectionToolWrapper> result = null;
444     initInspectionTools(element.getProject());
445     for (Tools toolList : myTools.values()) {
446       final InspectionToolWrapper tool = toolList.getInspectionTool(element);
447       if (id.equals(tool.getID())) {
448         if (result == null) {
449           result = new ArrayList<>();
450         }
451         result.add(tool);
452       }
453     }
454     return result;
455   }
456
457   @Override
458   public void save() {
459     InspectionProfileManager.getInstance().fireProfileChanged(this);
460   }
461
462   @Nullable
463   @Override
464   public String getSingleTool() {
465     return myToolShortName;
466   }
467
468   @Override
469   public void setSingleTool(@NotNull final String toolShortName) {
470     myToolShortName = toolShortName;
471   }
472
473   @Override
474   @NotNull
475   public String getDisplayName() {
476     return getName();
477   }
478
479   @Override
480   public void scopesChanged() {
481     for (ScopeToolState toolState : getAllTools(null)) {
482       toolState.scopesChanged();
483     }
484     InspectionProfileManager.getInstance().fireProfileChanged(this);
485   }
486
487   @Override
488   @Transient
489   public boolean isProfileLocked() {
490     return myLockedProfile;
491   }
492
493   @Override
494   public void lockProfile(boolean isLocked) {
495     myLockedProfile = isLocked;
496   }
497
498   @Override
499   @NotNull
500   public InspectionToolWrapper[] getInspectionTools(@Nullable PsiElement element) {
501     initInspectionTools(element == null ? null : element.getProject());
502     List<InspectionToolWrapper> result = new ArrayList<>();
503     for (Tools toolList : myTools.values()) {
504       result.add(toolList.getInspectionTool(element));
505     }
506     return result.toArray(new InspectionToolWrapper[result.size()]);
507   }
508
509   @Override
510   @NotNull
511   public List<Tools> getAllEnabledInspectionTools(Project project) {
512     initInspectionTools(project);
513     List<Tools> result = new ArrayList<>();
514     for (final ToolsImpl toolList : myTools.values()) {
515       if (toolList.isEnabled()) {
516         result.add(toolList);
517       }
518     }
519     return result;
520   }
521
522   @Override
523   public void disableTool(@NotNull String toolId, @NotNull PsiElement element) {
524     getTools(toolId, element.getProject()).disableTool(element);
525   }
526
527   public void disableToolByDefault(@NotNull Collection<String> toolIds, @Nullable Project project) {
528     for (String toolId : toolIds) {
529       getTools(toolId, project).setDefaultEnabled(false);
530     }
531   }
532
533   @NotNull
534   public ScopeToolState getToolDefaultState(@NotNull String toolId, @Nullable Project project) {
535     return getTools(toolId, project).getDefaultState();
536   }
537
538   public void enableToolsByDefault(@NotNull List<String> toolIds, Project project) {
539     for (final String toolId : toolIds) {
540       getTools(toolId, project).setDefaultEnabled(true);
541     }
542   }
543
544   public boolean wasInitialized() {
545     return myInitialized;
546   }
547
548   public void initInspectionTools(@Nullable Project project) {
549     //noinspection TestOnlyProblems
550     if (myInitialized || (ApplicationManager.getApplication().isUnitTestMode() && !INIT_INSPECTIONS)) {
551       return;
552     }
553
554     synchronized (myLock) {
555       if (!myInitialized) {
556         initialize(project);
557       }
558     }
559   }
560
561   private void initialize(@Nullable Project project) {
562     SchemeDataHolder<? super InspectionProfileImpl> dataHolder = myDataHolder;
563     if (dataHolder != null) {
564       myDataHolder = null;
565       Element element = dataHolder.read();
566       if (element.getName().equals("component")) {
567         element = element.getChild("profile");
568       }
569       assert element != null;
570       readExternal(element);
571     }
572
573     if (myBaseProfile != null) {
574       myBaseProfile.initInspectionTools(project);
575     }
576
577     final List<InspectionToolWrapper> tools;
578     try {
579       if (mySource == null) {
580         tools = myRegistrar.createTools();
581       }
582       else {
583         tools = ContainerUtil.map(mySource.getDefaultStates(project), ScopeToolState::getTool);
584       }
585     }
586     catch (ProcessCanceledException ignored) {
587       return;
588     }
589
590     final Map<String, List<String>> dependencies = new THashMap<>();
591     for (InspectionToolWrapper toolWrapper : tools) {
592       addTool(project, toolWrapper, dependencies);
593     }
594
595     DFSTBuilder<String> builder = new DFSTBuilder<>(GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<String>() {
596       @Override
597       public Collection<String> getNodes() {
598         return dependencies.keySet();
599       }
600
601       @Override
602       public Iterator<String> getIn(String n) {
603         return dependencies.get(n).iterator();
604       }
605     })));
606     if (builder.isAcyclic()) {
607       myScopesOrder = ArrayUtil.toStringArray(builder.getSortedNodes());
608     }
609
610     if (mySource != null) {
611       copyToolsConfigurations(mySource, project);
612     }
613
614     myInitialized = true;
615     if (dataHolder != null) {
616       // should be only after set myInitialized
617       dataHolder.updateDigest(this);
618     }
619   }
620
621   public void addTool(@Nullable Project project, @NotNull InspectionToolWrapper toolWrapper, @NotNull Map<String, List<String>> dependencies) {
622     final String shortName = toolWrapper.getShortName();
623     HighlightDisplayKey key = HighlightDisplayKey.find(shortName);
624     if (key == null) {
625       final InspectionEP extension = toolWrapper.getExtension();
626       Computable<String> computable = extension == null ? new Computable.PredefinedValueComputable<>(toolWrapper.getDisplayName()) : extension::getDisplayName;
627       if (toolWrapper instanceof LocalInspectionToolWrapper) {
628         key = HighlightDisplayKey.register(shortName, computable, toolWrapper.getID(),
629                                            ((LocalInspectionToolWrapper)toolWrapper).getAlternativeID());
630       }
631       else {
632         key = HighlightDisplayKey.register(shortName, computable);
633       }
634     }
635
636     if (key == null) {
637       LOG.error(shortName + " ; number of initialized tools: " + myTools.size());
638       return;
639     }
640
641     HighlightDisplayLevel baseLevel = myBaseProfile != null && myBaseProfile.getTools(shortName, project) != null
642                                    ? myBaseProfile.getErrorLevel(key, project)
643                                    : HighlightDisplayLevel.DO_NOT_SHOW;
644     HighlightDisplayLevel defaultLevel = toolWrapper.getDefaultLevel();
645     HighlightDisplayLevel level = baseLevel.getSeverity().compareTo(defaultLevel.getSeverity()) > 0 ? baseLevel : defaultLevel;
646     boolean enabled = myBaseProfile != null ? myBaseProfile.isToolEnabled(key) : toolWrapper.isEnabledByDefault();
647     final ToolsImpl toolsList = new ToolsImpl(toolWrapper, level, !myLockedProfile && enabled, enabled);
648     final Element element = myUninitializedSettings.remove(shortName);
649     try {
650       if (element != null) {
651         toolsList.readExternal(element, this, dependencies);
652       }
653       else if (!myUninitializedSettings.containsKey(InspectionElementsMergerBase.getMergedMarkerName(shortName))) {
654         final InspectionElementsMergerBase merger = getMerger(shortName);
655         Element merged = merger == null ? null : merger.merge(myUninitializedSettings);
656         if (merged != null) {
657           toolsList.readExternal(merged, this, dependencies);
658         }
659         else if (isProfileLocked()) {
660           // https://youtrack.jetbrains.com/issue/IDEA-158936
661           toolsList.setEnabled(false);
662           if (toolsList.getNonDefaultTools() == null) {
663             toolsList.getDefaultState().setEnabled(false);
664           }
665         }
666       }
667     }
668     catch (InvalidDataException e) {
669       LOG.error("Can't read settings for " + toolWrapper, e);
670     }
671     myTools.put(shortName, toolsList);
672   }
673
674   @Nullable
675   private static InspectionElementsMergerBase getMerger(String shortName) {
676     final InspectionElementsMerger merger = InspectionElementsMerger.getMerger(shortName);
677     if (merger instanceof InspectionElementsMergerBase) {
678       return (InspectionElementsMergerBase)merger;
679     }
680     return merger != null ? new InspectionElementsMergerBase() {
681       @Override
682       public String getMergedToolName() {
683         return merger.getMergedToolName();
684       }
685
686       @Override
687       public String[] getSourceToolNames() {
688         return merger.getSourceToolNames();
689       }
690     } : null;
691   }
692
693   @Nullable
694   @Transient
695   public String[] getScopesOrder() {
696     return myScopesOrder;
697   }
698
699   public void setScopesOrder(String[] scopesOrder) {
700     myScopesOrder = scopesOrder;
701   }
702
703   private HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey key, @Nullable Project project) {
704     final ToolsImpl tools = getTools(key.toString(), project);
705     LOG.assertTrue(tools != null, "profile name: " + myName +  " base profile: " + (myBaseProfile != null ? myBaseProfile.getName() : "-") + " key: " + key);
706     return tools.getLevel();
707   }
708
709   @Override
710   @NotNull
711   public InspectionProfileImpl getModifiableModel() {
712     return new InspectionProfileImpl(this);
713   }
714
715   private void copyToolsConfigurations(@NotNull InspectionProfileImpl profile, @Nullable Project project) {
716     try {
717       for (ToolsImpl toolList : profile.myTools.values()) {
718         final ToolsImpl tools = myTools.get(toolList.getShortName());
719         final ScopeToolState defaultState = toolList.getDefaultState();
720         tools.setDefaultState(copyToolSettings(defaultState.getTool()), defaultState.isEnabled(), defaultState.getLevel());
721         tools.removeAllScopes();
722         final List<ScopeToolState> nonDefaultToolStates = toolList.getNonDefaultTools();
723         if (nonDefaultToolStates != null) {
724           for (ScopeToolState state : nonDefaultToolStates) {
725             final InspectionToolWrapper toolWrapper = copyToolSettings(state.getTool());
726             final NamedScope scope = state.getScope(project);
727             if (scope != null) {
728               tools.addTool(scope, toolWrapper, state.isEnabled(), state.getLevel());
729             }
730             else {
731               tools.addTool(state.getScopeName(), toolWrapper, state.isEnabled(), state.getLevel());
732             }
733           }
734         }
735         tools.setEnabled(toolList.isEnabled());
736       }
737     }
738     catch (WriteExternalException e) {
739       LOG.error(e);
740     }
741     catch (InvalidDataException e) {
742       LOG.error(e);
743     }
744   }
745
746   @Override
747   public void cleanup(@NotNull Project project) {
748     if (!myInitialized) {
749       return;
750     }
751
752     for (ToolsImpl toolList : myTools.values()) {
753       if (toolList.isEnabled()) {
754         toolList.cleanupTools(project);
755       }
756     }
757   }
758
759   public void enableTool(@NotNull String toolId, Project project) {
760     final ToolsImpl tools = getTools(toolId, project);
761     tools.setEnabled(true);
762     if (tools.getNonDefaultTools() == null) {
763       tools.getDefaultState().setEnabled(true);
764     }
765   }
766
767   @Override
768   public void enableTool(@NotNull String inspectionTool, NamedScope namedScope, Project project) {
769     getTools(inspectionTool, project).enableTool(namedScope, project);
770   }
771
772   public void enableTools(@NotNull List<String> inspectionTools, NamedScope namedScope, Project project) {
773     for (String inspectionTool : inspectionTools) {
774       enableTool(inspectionTool, namedScope, project);
775     }
776   }
777
778   public void disableTools(@NotNull List<String> inspectionTools, NamedScope namedScope, @NotNull Project project) {
779     for (String inspectionTool : inspectionTools) {
780       getTools(inspectionTool, project).disableTool(namedScope, project);
781     }
782   }
783
784   @Override
785   public void disableTool(@NotNull String inspectionTool, @Nullable Project project) {
786     ToolsImpl tools = getTools(inspectionTool, project);
787     tools.setEnabled(false);
788     if (tools.getNonDefaultTools() == null) {
789       tools.getDefaultState().setEnabled(false);
790     }
791   }
792
793   @Override
794   public void setErrorLevel(@NotNull HighlightDisplayKey key, @NotNull HighlightDisplayLevel level, Project project) {
795     getTools(key.toString(), project).setLevel(level);
796   }
797
798   @Override
799   public boolean isToolEnabled(@Nullable HighlightDisplayKey key, PsiElement element) {
800     if (key == null) {
801       return false;
802     }
803     final Tools toolState = getTools(key.toString(), element == null ? null : element.getProject());
804     return toolState != null && toolState.isEnabled(element);
805   }
806
807   @Override
808   public boolean isToolEnabled(@Nullable HighlightDisplayKey key) {
809     return isToolEnabled(key, null);
810   }
811
812   @Override
813   public boolean isExecutable(Project project) {
814     initInspectionTools(project);
815     for (Tools tools : myTools.values()) {
816       if (tools.isEnabled()) return true;
817     }
818     return false;
819   }
820
821   //invoke when isChanged() == true
822   @Override
823   public void commit() {
824     LOG.assertTrue(mySource != null);
825     mySource.commit(this);
826     getProfileManager().updateProfile(mySource);
827     mySource = null;
828   }
829
830   private void commit(@NotNull InspectionProfileImpl model) {
831     setName(model.getName());
832     setDescription(model.getDescription());
833     setProjectLevel(model.isProjectLevel());
834     myLockedProfile = model.myLockedProfile;
835     myChangedToolNames = model.myChangedToolNames;
836     myTools = model.myTools;
837     myProfileManager = model.getProfileManager();
838
839     InspectionProfileManager.getInstance().fireProfileChanged(model);
840   }
841
842   @Tag
843   public String getDescription() {
844     return myDescription;
845   }
846
847   public void setDescription(String description) {
848     myDescription = description;
849   }
850
851   public void convert(@NotNull Element element, @NotNull Project project) {
852     final Element scopes = element.getChild("scopes");
853     if (scopes == null) {
854       return;
855     }
856
857     initInspectionTools(project);
858
859     for (Element scopeElement : scopes.getChildren(SCOPE)) {
860       final String profile = scopeElement.getAttributeValue(ProjectInspectionProfileManagerKt.PROFILE);
861       if (profile != null) {
862         final InspectionProfileImpl inspectionProfile = (InspectionProfileImpl)getProfileManager().getProfile(profile);
863         if (inspectionProfile != null) {
864           final NamedScope scope = getProfileManager().getScopesManager().getScope(scopeElement.getAttributeValue(NAME));
865           if (scope != null) {
866             for (InspectionToolWrapper toolWrapper : inspectionProfile.getInspectionTools(null)) {
867               final HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
868               try {
869                 InspectionToolWrapper toolWrapperCopy = copyToolSettings(toolWrapper);
870                 HighlightDisplayLevel errorLevel = inspectionProfile.getErrorLevel(key, null, project);
871                 getTools(toolWrapper.getShortName(), project).addTool(scope, toolWrapperCopy, inspectionProfile.isToolEnabled(key), errorLevel);
872               }
873               catch (Exception e) {
874                 LOG.error(e);
875               }
876             }
877           }
878         }
879       }
880     }
881     reduceConvertedScopes();
882   }
883
884   private void reduceConvertedScopes() {
885     for (ToolsImpl tools : myTools.values()) {
886       final ScopeToolState toolState = tools.getDefaultState();
887       final List<ScopeToolState> nonDefaultTools = tools.getNonDefaultTools();
888       if (nonDefaultTools != null) {
889         boolean equal = true;
890         boolean isEnabled = toolState.isEnabled();
891         for (ScopeToolState state : nonDefaultTools) {
892           isEnabled |= state.isEnabled();
893           if (!state.equalTo(toolState)) {
894             equal = false;
895           }
896         }
897         tools.setEnabled(isEnabled);
898         if (equal) {
899           tools.removeAllScopes();
900         }
901       }
902     }
903   }
904
905   @NotNull
906   public List<ScopeToolState> getAllTools(@Nullable Project project) {
907     initInspectionTools(project);
908
909     List<ScopeToolState> result = new ArrayList<>();
910     for (Tools tools : myTools.values()) {
911       tools.collectTools(result);
912     }
913     return result;
914   }
915
916   @NotNull
917   public List<ScopeToolState> getDefaultStates(@Nullable Project project) {
918     initInspectionTools(project);
919     List<ScopeToolState> result = new ArrayList<>();
920     for (Tools tools : myTools.values()) {
921       result.add(tools.getDefaultState());
922     }
923     return result;
924   }
925
926   @NotNull
927   public List<ScopeToolState> getNonDefaultTools(@NotNull String shortName, Project project) {
928     final List<ScopeToolState> result = new ArrayList<>();
929     final List<ScopeToolState> nonDefaultTools = getTools(shortName, project).getNonDefaultTools();
930     if (nonDefaultTools != null) {
931       result.addAll(nonDefaultTools);
932     }
933     return result;
934   }
935
936   public boolean isToolEnabled(@NotNull HighlightDisplayKey key, NamedScope namedScope, Project project) {
937     return getTools(key.toString(), project).isEnabled(namedScope,project);
938   }
939
940   public void removeScope(@NotNull String toolId, @NotNull String scopeName, Project project) {
941     getTools(toolId, project).removeScope(scopeName);
942   }
943
944   public void removeScopes(@NotNull List<String> toolIds, @NotNull String scopeName, Project project) {
945     for (final String toolId : toolIds) {
946       removeScope(toolId, scopeName, project);
947     }
948   }
949
950   /**
951    * @return null if it has no base profile
952    */
953   @Nullable
954   private Set<String> getChangedToolNames() {
955     if (myBaseProfile == null) return null;
956     if (myChangedToolNames == null) {
957       synchronized (myLock) {
958         if (myChangedToolNames == null) {
959           initInspectionTools(null);
960           Set<String> names = myTools.keySet();
961           Set<String> map = new THashSet<>(names.size());
962           for (String toolId : names) {
963             if (!toolSettingsAreEqual(toolId, myBaseProfile, this)) {
964               map.add(toolId);
965             }
966           }
967           myChangedToolNames = map;
968           return map;
969         }
970       }
971     }
972     return myChangedToolNames;
973   }
974
975   public void profileChanged() {
976     myChangedToolNames = null;
977   }
978
979   @NotNull
980   @Transient
981   public HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey key, NamedScope scope, Project project) {
982     final ToolsImpl tools = getTools(key.toString(), project);
983     return tools != null ? tools.getLevel(scope, project) : HighlightDisplayLevel.WARNING;
984   }
985
986   public ScopeToolState addScope(@NotNull InspectionToolWrapper toolWrapper,
987                                  NamedScope scope,
988                                  @NotNull HighlightDisplayLevel level,
989                                  boolean enabled,
990                                  Project project) {
991     return getTools(toolWrapper.getShortName(), project).prependTool(scope, toolWrapper, enabled, level);
992   }
993
994   public void setErrorLevel(@NotNull HighlightDisplayKey key, @NotNull HighlightDisplayLevel level, String scopeName, Project project) {
995     getTools(key.toString(), project).setLevel(level, scopeName, project);
996   }
997
998   public void setErrorLevel(@NotNull List<HighlightDisplayKey> keys, @NotNull HighlightDisplayLevel level, String scopeName, Project project) {
999     for (HighlightDisplayKey key : keys) {
1000       setErrorLevel(key, level, scopeName, project);
1001     }
1002   }
1003
1004   public ToolsImpl getTools(@NotNull String toolId, @Nullable Project project) {
1005     initInspectionTools(project);
1006     return myTools.get(toolId);
1007   }
1008
1009   public void enableAllTools(Project project) {
1010     for (InspectionToolWrapper entry : getInspectionTools(null)) {
1011       enableTool(entry.getShortName(), project);
1012     }
1013   }
1014
1015   @Override
1016   @NotNull
1017   public String toString() {
1018     return mySource == null ? getName() : getName() + " (copy)";
1019   }
1020
1021   @Override
1022   public boolean equals(Object o) {
1023     return super.equals(o) && ((InspectionProfileImpl)o).getProfileManager() == getProfileManager();
1024   }
1025
1026   private static class InspectionProfileImplHolder {
1027     private static final InspectionProfileImpl DEFAULT_PROFILE = new InspectionProfileImpl(DEFAULT_PROFILE_NAME);
1028   }
1029 }