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