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