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