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