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