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