Files
openide/platform/analysis-impl/src/com/intellij/codeInspection/ex/InspectionProfileImpl.java
Max Medvedev c0f11e5298 HighlightDisplayKey: introduce proper getShortName instead of toString
GitOrigin-RevId: 7c6fb37449dbebb6308c3d76f4cbb48bb3cc1392
2024-08-09 20:25:06 +00:00

1136 lines
43 KiB
Java

// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.ex;
import com.intellij.codeHighlighting.HighlightDisplayLevel;
import com.intellij.codeInsight.daemon.HighlightDisplayKey;
import com.intellij.codeInsight.daemon.impl.SeverityRegistrar;
import com.intellij.codeInspection.InspectionEP;
import com.intellij.codeInspection.InspectionProfileEntry;
import com.intellij.codeInspection.options.OptionController;
import com.intellij.concurrency.ConcurrentCollectionFactory;
import com.intellij.configurationStore.SchemeDataHolder;
import com.intellij.lang.annotation.HighlightSeverity;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.colors.TextAttributesKey;
import com.intellij.openapi.options.SchemeState;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectTypeService;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.Strings;
import com.intellij.profile.codeInspection.BaseInspectionProfileManager;
import com.intellij.profile.codeInspection.InspectionProfileManager;
import com.intellij.profile.codeInspection.ProjectBasedInspectionProfileManager;
import com.intellij.project.ProjectKt;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.scope.packageSet.NamedScope;
import com.intellij.psi.search.scope.packageSet.NamedScopesHolder;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.NotNullList;
import com.intellij.util.xmlb.annotations.Attribute;
import com.intellij.util.xmlb.annotations.Tag;
import com.intellij.util.xmlb.annotations.Transient;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.util.*;
import java.util.stream.Collectors;
import static com.intellij.openapi.util.NotNullLazyValue.lazy;
public class InspectionProfileImpl extends NewInspectionProfile {
public static final NotNullLazyValue<InspectionProfileImpl> BASE_PROFILE = lazy(() -> new InspectionProfileImpl(DEFAULT_PROFILE_NAME));
@SuppressWarnings("StaticNonFinalField") @TestOnly
public static boolean INIT_INSPECTIONS;
static final String INSPECTION_TOOL_TAG = "inspection_tool";
static final String CLASS_TAG = "class";
protected static final Logger LOG = Logger.getInstance(InspectionProfileImpl.class);
private static final String VALID_VERSION = "1.0";
private static final String VERSION_TAG = "version";
private static final String USED_LEVELS = "used_levels";
protected final @NotNull InspectionToolsSupplier myToolSupplier;
protected final Map<String, Element> myUninitializedSettings = new TreeMap<>(); // accessed in EDT
@SuppressWarnings("FieldAccessedSynchronizedAndUnsynchronized")
protected Map<String, ToolsImpl> myTools = ConcurrentCollectionFactory.createConcurrentMap(); // `addTool` is called concurrently
protected volatile Set<String> myChangedToolNames;
@Attribute("is_locked")
protected boolean myLockedProfile;
protected final InspectionProfileImpl myBaseProfile;
private final Object myLock = new Object();
private BaseInspectionProfileManager myProfileManager;
private volatile boolean myInitialized;
private boolean myProjectLevel;
private volatile String myToolShortName;
private List<String> myScopeOrder = new ArrayList<>();
private @NlsContexts.DetailedDescription String myDescription;
private @Nullable SchemeState mySchemeState;
private Disposable myDisposable;
private SchemeDataHolder<? super InspectionProfileImpl> myDataHolder;
public InspectionProfileImpl(@NotNull String profileName) {
this(profileName, InspectionToolRegistrar.getInstance(), (BaseInspectionProfileManager)InspectionProfileManager.getInstance(), null, null);
}
public InspectionProfileImpl(@NotNull String profileName,
@NotNull InspectionToolsSupplier toolSupplier,
@NotNull BaseInspectionProfileManager profileManager) {
this(profileName, toolSupplier, profileManager, BASE_PROFILE.get(), null);
}
public InspectionProfileImpl(@NotNull String profileName,
@NotNull InspectionToolsSupplier toolSupplier,
@Nullable InspectionProfileImpl baseProfile) {
this(profileName, toolSupplier, (BaseInspectionProfileManager)InspectionProfileManager.getInstance(), baseProfile, null);
}
public InspectionProfileImpl(@NotNull String profileName,
@NotNull InspectionToolsSupplier toolSupplier,
@NotNull BaseInspectionProfileManager profileManager,
@Nullable SchemeDataHolder<? super InspectionProfileImpl> dataHolder) {
this(profileName, toolSupplier, profileManager, BASE_PROFILE.get(), dataHolder);
}
public InspectionProfileImpl(@NotNull String profileName,
@NotNull InspectionToolsSupplier toolSupplier,
@NotNull BaseInspectionProfileManager profileManager,
@Nullable InspectionProfileImpl baseProfile,
@Nullable SchemeDataHolder<? super InspectionProfileImpl> dataHolder) {
super(profileName);
myProfileManager = profileManager;
myToolSupplier = toolSupplier;
myBaseProfile = baseProfile;
myDataHolder = dataHolder;
if (dataHolder != null) {
mySchemeState = SchemeState.UNCHANGED;
}
}
private @Nullable Project getDefaultProject() {
return myProfileManager instanceof ProjectBasedInspectionProfileManager pm ? pm.getProject() : null;
}
public boolean wasInitialized() {
return myInitialized;
}
public void initInspectionTools() {
initInspectionTools(null);
}
public void initInspectionTools(@Nullable Project project) {
if (!myInitialized && forceInitInspectionTools()) {
if (project == null) project = getDefaultProject();
synchronized (myLock) {
if (!myInitialized) {
initialize(project);
}
}
}
}
@SuppressWarnings("TestOnlyProblems")
protected boolean forceInitInspectionTools() {
return !ApplicationManager.getApplication().isUnitTestMode() || INIT_INSPECTIONS;
}
public final void copyFrom(@NotNull InspectionProfileImpl profile) {
var element = profile.writeScheme();
if ("component".equals(element.getName())) {
element = element.getChild("profile");
}
readExternal(element);
}
@Transient
public final @NotNull BaseInspectionProfileManager getProfileManager() {
return myProfileManager;
}
public final void setProfileManager(@NotNull BaseInspectionProfileManager profileManager) {
myProfileManager = profileManager;
}
@Transient
public final boolean isProjectLevel() {
return myProjectLevel;
}
public final void setProjectLevel(boolean projectLevel) {
myProjectLevel = projectLevel;
}
public static @NotNull InspectionToolWrapper<?, ?> copyToolSettings(@NotNull InspectionToolWrapper<?,?> toolWrapper) {
InspectionToolWrapper<?, ?> inspectionTool = toolWrapper.createCopy();
if (toolWrapper.isInitialized()) {
Element config = new Element("config");
ScopeToolState.tryWriteSettings(toolWrapper.getTool(), config);
ScopeToolState.tryReadSettings(inspectionTool.getTool(), config);
}
return inspectionTool;
}
@Override
public @NotNull HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey inspectionToolKey, PsiElement element) {
Project project = element == null ? null : element.getProject();
ToolsImpl tools = getToolsOrNull(inspectionToolKey.getShortName(), project);
HighlightDisplayLevel level = tools != null ? tools.getLevel(element) : HighlightDisplayLevel.WARNING;
if (!myProfileManager.getSeverityRegistrar().isSeverityValid(level.getSeverity().getName())) {
level = HighlightDisplayLevel.WARNING;
if (tools != null) {
setErrorLevel(inspectionToolKey, level, project);
}
}
return level;
}
public void readExternal(@NotNull Element element) {
mySerializer.readExternal(this, element);
Element highlightElement = element.getChild(USED_LEVELS);
if (highlightElement != null) {
// from old profiles
myProfileManager.getSeverityRegistrar().readExternal(highlightElement);
}
String version = element.getAttributeValue(VERSION_TAG);
if (!VALID_VERSION.equals(version)) {
List<InspectionToolWrapper<?, ?>> tools = getInspectionTools(null);
for (Element toolElement : element.getChildren("inspection_tool")) {
String toolClassName = toolElement.getAttributeValue(CLASS_TAG);
String shortName = convertToShortName(toolClassName, tools);
if (shortName == null) {
continue;
}
toolElement.setAttribute(CLASS_TAG, shortName);
myUninitializedSettings.put(shortName, JDOMUtil.internElement(toolElement));
}
}
else {
for (Element toolElement : element.getChildren(INSPECTION_TOOL_TAG)) {
myUninitializedSettings.put(toolElement.getAttributeValue(CLASS_TAG), JDOMUtil.internElement(toolElement));
}
}
}
private static @Nullable String convertToShortName(@Nullable String displayName, @NotNull List<? extends InspectionToolWrapper<?, ?>> tools) {
if (displayName == null) return null;
for (InspectionToolWrapper<?, ?> tool : tools) {
if (displayName.equals(tool.getDisplayName())) {
return tool.getShortName();
}
}
return null;
}
public @NotNull Set<HighlightSeverity> getUsedSeverities() {
LOG.assertTrue(myInitialized);
Set<HighlightSeverity> result = new HashSet<>();
for (Tools tools : myTools.values()) {
for (ScopeToolState state : tools.getTools()) {
result.add(state.getLevel().getSeverity());
}
}
return result;
}
@Override
public @NotNull Element writeScheme() {
if (myDataHolder != null) {
return myDataHolder.read();
}
Element element = new Element(PROFILE);
writeExternal(element);
if (myProjectLevel) {
element.setAttribute("version", "1.0");
}
if (myProjectLevel && ProjectKt.isDirectoryBased(((ProjectBasedInspectionProfileManager)myProfileManager).getProject())) {
return new Element("component").setAttribute("name", "InspectionProjectProfileManager").addContent(element);
}
mySchemeState = SchemeState.UNCHANGED;
return element;
}
@Override
public @Nullable SchemeState getSchemeState() {
return mySchemeState;
}
public void writeExternal(@NotNull Element element) {
// must be first - compatibility
writeVersion(element);
mySerializer.writeExternal(this, element);
synchronized (myLock) {
if (!myInitialized) {
for (Element el : myUninitializedSettings.values()) {
element.addContent(el.clone());
}
return;
}
}
Set<String> changedToolNames = getChangedToolNames();
if (changedToolNames == null) {
return;
}
List<String> allToolNames = new ArrayList<>(myTools.keySet());
allToolNames.addAll(myUninitializedSettings.keySet());
allToolNames.sort(null);
for (String toolName : allToolNames) {
Element toolElement = myUninitializedSettings.get(toolName);
if (toolElement != null) {
element.addContent(toolElement.clone());
continue;
}
if (!myLockedProfile && !changedToolNames.contains(toolName)) {
markSettingsMerged(toolName, element);
continue;
}
ToolsImpl toolList = myTools.get(toolName);
LOG.assertTrue(toolList != null);
Element inspectionElement = new Element(INSPECTION_TOOL_TAG);
inspectionElement.setAttribute(CLASS_TAG, toolName);
try {
toolList.writeExternal(inspectionElement);
}
catch (WriteExternalException e) {
LOG.error(e);
continue;
}
if (!areSettingsMerged(toolName, inspectionElement)) {
element.addContent(inspectionElement);
}
}
getPathMacroManager().collapsePaths(element);
}
protected static void writeVersion(@NotNull Element element) {
element.setAttribute(VERSION_TAG, VALID_VERSION);
}
private void markSettingsMerged(@NotNull String toolName, @NotNull Element element) {
//add marker if already merged, but the result is now default (-> empty node)
String mergedName = InspectionElementsMergerBase.getMergedMarkerName(toolName);
if (!myUninitializedSettings.containsKey(mergedName)) {
InspectionElementsMergerBase merger = getMerger(toolName);
if (merger != null && merger.markSettingsMerged(myUninitializedSettings)) {
element.addContent(new Element(INSPECTION_TOOL_TAG).setAttribute(CLASS_TAG, mergedName));
}
}
}
private boolean areSettingsMerged(String toolName, Element inspectionElement) {
//skip merged settings as they could be restored from already provided data
InspectionElementsMergerBase merger = getMerger(toolName);
return merger != null && merger.areSettingsMerged(myUninitializedSettings, inspectionElement);
}
private PathMacroManager getPathMacroManager() {
return PathMacroManager.getInstance(
myProfileManager instanceof ProjectBasedInspectionProfileManager pm ? pm.getProject() : ApplicationManager.getApplication());
}
public void collectDependentInspections(@NotNull InspectionToolWrapper<?,?> toolWrapper,
@NotNull Set<? super InspectionToolWrapper<?, ?>> dependentEntries,
Project project) {
String mainToolId = toolWrapper.getMainToolId();
if (mainToolId != null) {
InspectionToolWrapper<?,?> dependentEntryWrapper = getInspectionTool(mainToolId, project);
if (dependentEntryWrapper == null) {
LOG.error("Can't find main tool: '" + mainToolId+"' which was specified in "+toolWrapper);
return;
}
if (!dependentEntries.add(dependentEntryWrapper)) {
collectDependentInspections(dependentEntryWrapper, dependentEntries, project);
}
}
}
@Override
public @Nullable InspectionToolWrapper<?, ?> getInspectionTool(@NotNull String shortName, @Nullable PsiElement element) {
Tools toolList = getToolsOrNull(shortName, element == null ? null : element.getProject());
return toolList == null ? null : toolList.getInspectionTool(element);
}
@Override
public @Nullable InspectionProfileEntry getUnwrappedTool(@NotNull String shortName, @NotNull PsiElement element) {
InspectionToolWrapper<?, ?> tool = getInspectionTool(shortName, element);
return tool == null ? null : tool.getTool();
}
@Override
public <T extends InspectionProfileEntry> T getUnwrappedTool(@NotNull Key<T> shortNameKey, @NotNull PsiElement element) {
@SuppressWarnings("unchecked") T tool = (T)getUnwrappedTool(shortNameKey.toString(), element);
return tool;
}
public void modifyProfile(@NotNull Consumer<? super InspectionProfileModifiableModel> modelConsumer) {
InspectionProfileModifiableModelKt.edit(this, it -> {
modelConsumer.consume(it);
return null;
});
}
@Override
public <T extends InspectionProfileEntry> void modifyToolSettings(@NotNull Key<T> shortNameKey,
@NotNull PsiElement psiElement,
@NotNull Consumer<? super T> toolConsumer) {
modifyProfile(model -> {
@SuppressWarnings("unchecked") T tool = (T)model.getUnwrappedTool(shortNameKey.toString(), psiElement);
toolConsumer.consume(tool);
});
}
/**
* Warning: Usage of this method is discouraged, as if separate tool options are defined for different scopes, it just returns
* the options for the first scope which may lead to unexpected results. Consider using {@link #getInspectionTool(String, PsiElement)} instead.
*
* @param shortName an inspection short name
* @param project a project
* @return an InspectionToolWrapper associated with this tool.
*/
@Override
public @Nullable InspectionToolWrapper<?, ?> getInspectionTool(@NotNull String shortName, @Nullable Project project) {
ToolsImpl tools = getToolsOrNull(shortName, project);
return tools == null ? null : tools.getTool();
}
public InspectionToolWrapper<?,?> getToolById(@NotNull String id, @NotNull PsiElement element) {
initInspectionTools();
for (Tools toolList : myTools.values()) {
InspectionToolWrapper<?,?> tool = toolList.getInspectionTool(element);
if (id.equals(tool.getID())) return tool;
}
return null;
}
public @NotNull List<InspectionToolWrapper<?, ?>> findToolsById(@NotNull String id, @NotNull PsiElement element) {
initInspectionTools();
List<InspectionToolWrapper<?, ?>> result = null;
for (Tools toolList : myTools.values()) {
InspectionToolWrapper<?,?> tool = toolList.getInspectionTool(element);
if (id.equals(tool.getID())) {
if (result == null) {
result = new ArrayList<>();
}
result.add(tool);
}
}
return ContainerUtil.notNullize(result);
}
@Override
public @Nullable String getSingleTool() {
return myToolShortName;
}
public void setSingleTool(@NotNull String toolShortName) {
myToolShortName = toolShortName;
}
@Override
@SuppressWarnings("HardCodedStringLiteral")
public @NotNull String getDisplayName() {
return getName();
}
public void scopesChanged() {
if (!myInitialized) {
return;
}
for (ToolsImpl tools : myTools.values()) {
tools.scopesChanged();
}
myProfileManager.fireProfileChanged(this);
}
@Transient
public boolean isProfileLocked() {
return myLockedProfile;
}
public void lockProfile(boolean isLocked) {
myLockedProfile = isLocked;
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public @NotNull InspectionToolsSupplier getInspectionToolsSupplier() {
return myToolSupplier;
}
@Override
public @NotNull List<InspectionToolWrapper<?, ?>> getInspectionTools(@Nullable PsiElement element) {
initInspectionTools();
List<InspectionToolWrapper<?, ?>> result = new ArrayList<>(myTools.size());
for (Tools toolList : myTools.values()) {
result.add(toolList.getInspectionTool(element));
}
return result;
}
@Override
public @NotNull List<Tools> getAllEnabledInspectionTools(Project project) {
initInspectionTools();
List<Tools> result = new ArrayList<>();
Set<String> projectTypes = ProjectTypeService.getProjectTypeIds(project);
boolean isTests = ApplicationManager.getApplication().isUnitTestMode();
for (ToolsImpl toolList : myTools.values()) {
if (toolList.isEnabled()) {
InspectionToolWrapper<?, ?> toolWrapper = toolList.getTool();
if (!isTests && !toolWrapper.isApplicable(projectTypes)) {
continue;
}
result.add(toolList);
}
}
return result;
}
public void disableToolByDefault(@NotNull Collection<String> toolShortNames, @Nullable Project project) {
for (String toolId : toolShortNames) {
getTools(toolId, project).setDefaultEnabled(false);
}
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public @NotNull ScopeToolState getToolDefaultState(@NotNull String toolShortName, @Nullable Project project) {
return getTools(toolShortName, project).getDefaultState();
}
public void enableToolsByDefault(@NotNull List<String> toolShortNames, Project project) {
for (String shortName : toolShortNames) {
getTools(shortName, project).setDefaultEnabled(true);
}
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
protected @NotNull List<InspectionToolWrapper<?, ?>> createTools(@Nullable Project project) {
return myToolSupplier.createTools();
}
private void initialize(@Nullable Project project) {
SchemeDataHolder<? super InspectionProfileImpl> dataHolder = myDataHolder;
if (dataHolder != null) {
myDataHolder = null;
Element element = dataHolder.read();
if (element.getName().equals("component")) {
element = element.getChild("profile");
}
assert element != null;
readExternal(element);
}
if (myBaseProfile != null) {
myBaseProfile.initInspectionTools();
}
List<InspectionToolWrapper<?, ?>> tools;
try {
tools = createTools(project);
}
catch (ProcessCanceledException ignored) {
return;
}
Map<String, List<String>> dependencies = new HashMap<>();
for (InspectionToolWrapper<?, ?> toolWrapper : tools) {
addTool(project, toolWrapper, dependencies);
if (toolWrapper instanceof LocalInspectionToolWrapper &&
((LocalInspectionToolWrapper)toolWrapper).isDynamicGroup() &&
// some settings were read for the tool, so it must be initialized, otherwise no dynamic tools are expected
toolWrapper.isInitialized()) {
ToolsImpl parent = myTools.get(toolWrapper.getShortName());
if (parent.isEnabled()) {
var children = ((DynamicGroupTool)toolWrapper.getTool()).getChildren();
var childNames = children.stream().map(LocalInspectionToolWrapper::getShortName).collect(Collectors.toSet());
//noinspection SSBasedInspection
if (tools.stream().noneMatch(tool -> childNames.contains(tool.getShortName()))) {
boolean isLocked = myLockedProfile;
myLockedProfile = false;
for (LocalInspectionToolWrapper wrapper : children) {
addTool(project, wrapper, dependencies);
String shortName = wrapper.getShortName();
if (InspectionElementsMerger.getMerger(shortName) == null) {
InspectionElementsMerger.addMerger(shortName, new MyInspectionElementsMerger(shortName, wrapper));
}
}
myLockedProfile = isLocked;
}
}
}
}
// initialize is invoked in synchronized block, so we can publish the new value of myDisposable here
myDisposable = project != null ? Disposer.newDisposable(project, "[" + getClass().getSimpleName() + "] " + getName()) : null;
myToolSupplier.addListener(new InspectionToolsSupplier.Listener() {
@Override
public void toolAdded(@NotNull InspectionToolWrapper<?,?> inspectionTool) {
addTool(project, inspectionTool, null);
myProfileManager.fireProfileChanged(InspectionProfileImpl.this);
}
@Override
public void toolRemoved(@NotNull InspectionToolWrapper<?,?> inspectionTool) {
removeTool(inspectionTool);
myProfileManager.fireProfileChanged(InspectionProfileImpl.this);
}
}, myDisposable);
copyToolsConfigurations(project);
myInitialized = true;
if (dataHolder != null) {
// should be only after set myInitialized
dataHolder.updateDigest(this);
}
}
protected void copyToolsConfigurations(@Nullable Project project) {
}
public final void addTool(@Nullable Project project, @NotNull InspectionToolWrapper<?, ?> toolWrapper, @Nullable Map<? super String, List<String>> dependencies) {
String shortName = toolWrapper.getShortName();
HighlightDisplayKey key = HighlightDisplayKey.find(shortName);
if (key == null) {
InspectionEP extension = toolWrapper.getExtension();
Computable<String> computable = extension == null || extension.displayName == null && extension.key == null
? new Computable.PredefinedValueComputable<>(toolWrapper.getDisplayName())
: extension::getDisplayName;
if (toolWrapper instanceof LocalInspectionToolWrapper) {
key = HighlightDisplayKey.register(shortName, computable, toolWrapper.getID(),
((LocalInspectionToolWrapper)toolWrapper).getAlternativeID());
}
else {
key = HighlightDisplayKey.register(shortName, computable, shortName);
}
}
if (key == null) {
LOG.error(shortName + " ; number of initialized tools: " + myTools.size());
return;
}
HighlightDisplayLevel baseLevel = myBaseProfile != null && myBaseProfile.getToolsOrNull(shortName, project) != null
? myBaseProfile.getErrorLevel(key, project)
: HighlightDisplayLevel.DO_NOT_SHOW;
HighlightDisplayLevel defaultLevel = toolWrapper.getDefaultLevel();
HighlightDisplayLevel level = baseLevel.getSeverity().compareTo(defaultLevel.getSeverity()) > 0 ? baseLevel : defaultLevel;
boolean enabled = myBaseProfile != null && myBaseProfile.getToolsOrNull(shortName, project) != null ? myBaseProfile.isToolEnabled(key) : toolWrapper.isEnabledByDefault();
ToolsImpl toolsList = new ToolsImpl(toolWrapper, level, !myLockedProfile && enabled, enabled);
Element element = myUninitializedSettings.remove(shortName);
try {
if (element != null) {
element = element.clone();
getPathMacroManager().expandPaths(element);
toolsList.readExternal(element, myProfileManager, dependencies);
}
else if (!myUninitializedSettings.containsKey(InspectionElementsMergerBase.getMergedMarkerName(shortName))) {
InspectionElementsMergerBase merger = getMerger(shortName);
Element merged = merger == null ? null : merger.merge(myUninitializedSettings);
if (merged != null) {
getPathMacroManager().expandPaths(merged);
toolsList.readExternal(merged, myProfileManager, dependencies);
}
else if (isProfileLocked()) {
// https://youtrack.jetbrains.com/issue/IDEA-158936
toolsList.setEnabled(false);
if (toolsList.getNonDefaultTools() == null) {
toolsList.getDefaultState().setEnabled(false);
}
}
}
}
catch (InvalidDataException e) {
LOG.error("Can't read settings for " + toolWrapper, e);
}
myTools.put(shortName, toolsList);
}
public void removeTool(@NotNull InspectionToolWrapper<?, ?> inspectionTool) {
String shortName = inspectionTool.getShortName();
myTools.remove(shortName);
}
private static @Nullable InspectionElementsMergerBase getMerger(@NotNull String shortName) {
InspectionElementsMerger merger = InspectionElementsMerger.getMerger(shortName);
if (merger instanceof InspectionElementsMergerBase) {
return (InspectionElementsMergerBase)merger;
}
return merger == null ? null : new InspectionElementsMergerBase() {
@Override
public @NotNull String getMergedToolName() {
return merger.getMergedToolName();
}
@Override
public String @NotNull [] getSourceToolNames() {
return merger.getSourceToolNames();
}
};
}
public List<String> getScopesOrder() {
return myScopeOrder;
}
public void setScopesOrder(List<String> scopeOrder) {
myScopeOrder = scopeOrder;
mySchemeState = SchemeState.POSSIBLY_CHANGED;
for (ToolsImpl tools : myTools.values()) {
tools.changeToolsOrder(myScopeOrder);
}
}
private @NotNull HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey key, @Nullable Project project) {
return getTools(key.getShortName(), project).getLevel();
}
@TestOnly
public @NotNull InspectionProfileModifiableModel getModifiableModel() {
return new InspectionProfileModifiableModel(this);
}
public void cleanup(@Nullable Project project) {
if (!myInitialized) {
return;
}
if (myDisposable != null) {
Disposer.dispose(myDisposable);
}
if (project != null) {
for (ToolsImpl toolList : myTools.values()) {
if (toolList.isEnabled()) {
toolList.cleanupTools(project);
}
}
}
}
public void enableTool(@NotNull String toolShortName, @Nullable Project project) {
setToolEnabled(toolShortName, true, project);
}
public void enableTool(@NotNull String toolShortName, @NotNull NamedScope namedScope, @NotNull Project project) {
getTools(toolShortName, project).enableTool(namedScope, project);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void enableTools(@NotNull List<String> toolShortNames, @NotNull NamedScope namedScope, @NotNull Project project) {
for (String toolShortName : toolShortNames) {
enableTool(toolShortName, namedScope, project);
}
}
public void disableTools(@NotNull List<String> toolShortNames, @NotNull NamedScope namedScope, @NotNull Project project) {
for (String toolShortName : toolShortNames) {
getTools(toolShortName, project).disableTool(namedScope, project);
}
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void setErrorLevel(@NotNull HighlightDisplayKey key, @NotNull HighlightDisplayLevel level, Project project) {
getTools(key.getShortName(), project).setLevel(level);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
@Override
public boolean isToolEnabled(@Nullable HighlightDisplayKey key, @Nullable PsiElement element) {
if (key == null) {
return false;
}
Tools toolState = getToolsOrNull(key.getShortName(), element == null ? null : element.getProject());
return toolState != null && toolState.isEnabled(element);
}
@Override
public @Nullable TextAttributesKey getEditorAttributes(@NotNull String shortName, @Nullable PsiElement element) {
ToolsImpl tools = getToolsOrNull(shortName, element != null ? element.getProject() : null);
return tools != null ? tools.getAttributesKey(element) : null;
}
public void setEditorAttributesKey(@NotNull String shortName, @Nullable String keyName, String scopeName, @Nullable Project project) {
final ToolsImpl tools = getTools(shortName, project);
final var level = tools.getLevel(scopeName, project);
if (keyName == null) {
keyName = SeverityRegistrar.getSeverityRegistrar(project).getHighlightInfoTypeBySeverity(level.getSeverity()).getAttributesKey().getExternalName();
}
String attributes = tools.getDefaultState().getTool().getDefaultEditorAttributes();
tools.setEditorAttributesKey(Objects.equals(attributes, keyName) ? null : keyName, scopeName);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
@Override
public boolean isExecutable(@Nullable Project project) {
initInspectionTools();
for (Tools tools : myTools.values()) {
if (tools.isEnabled()) return true;
}
return false;
}
@Tag
public @NlsContexts.DetailedDescription String getDescription() {
return myDescription;
}
public void setDescription(@NlsContexts.DetailedDescription @Nullable String description) {
myDescription = Strings.nullize(description);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void resetToBase(@NotNull String toolId, NamedScope scope, @NotNull Project project) {
ToolsImpl tools = myBaseProfile.getToolsOrNull(toolId, project);
if (tools == null) return;
InspectionToolWrapper<?, ?> baseDefaultWrapper = tools.getDefaultState().getTool();
ScopeToolState state = myTools.get(toolId).getTools().stream().filter(s -> scope == s.getScope(project)).findFirst().orElseThrow(IllegalStateException::new);
state.setTool(copyToolSettings(baseDefaultWrapper));
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void convert(@NotNull Element element, @NotNull Project project) {
Element scopes = element.getChild("scopes");
if (scopes == null) return;
initInspectionTools();
for (Element scopeElement : scopes.getChildren(SCOPE)) {
String profile = scopeElement.getAttributeValue(PROFILE);
InspectionProfileImpl inspectionProfile = profile == null ? null : myProfileManager.getProfile(profile);
NamedScope scope = null;
if (inspectionProfile != null) {
NamedScopesHolder scopesManager = myProfileManager.getScopesManager();
if (scopesManager != null) {
scope = scopesManager.getScope(scopeElement.getAttributeValue(NAME));
}
}
if (scope == null) {
continue;
}
for (InspectionToolWrapper<?, ?> toolWrapper : inspectionProfile.getInspectionTools(null)) {
HighlightDisplayKey key = HighlightDisplayKey.find(toolWrapper.getShortName());
try {
InspectionToolWrapper<?, ?> toolWrapperCopy = copyToolSettings(toolWrapper);
HighlightDisplayLevel errorLevel = inspectionProfile.getErrorLevel(Objects.requireNonNull(key), null, project);
getTools(toolWrapper.getShortName(), project)
.addTool(scope, toolWrapperCopy, inspectionProfile.isToolEnabled(key), errorLevel);
}
catch (Exception e) {
LOG.error(e);
}
}
}
reduceConvertedScopes();
}
private void reduceConvertedScopes() {
for (ToolsImpl tools : myTools.values()) {
ScopeToolState toolState = tools.getDefaultState();
List<ScopeToolState> nonDefaultTools = tools.getNonDefaultTools();
if (nonDefaultTools != null) {
boolean equal = true;
boolean isEnabled = toolState.isEnabled();
for (ScopeToolState state : nonDefaultTools) {
isEnabled |= state.isEnabled();
if (!state.equalTo(toolState)) {
equal = false;
}
}
tools.setEnabled(isEnabled);
if (equal) {
tools.removeAllScopes();
}
}
}
}
public @NotNull List<ScopeToolState> getAllTools() {
initInspectionTools();
List<ScopeToolState> result = new NotNullList<>();
for (Tools tools : myTools.values()) {
tools.collectTools(result);
}
return result;
}
public @NotNull List<ScopeToolState> getDefaultStates(@SuppressWarnings("unused") @Nullable Project project) {
initInspectionTools();
List<ScopeToolState> result = new ArrayList<>();
for (Tools tools : myTools.values()) {
result.add(tools.getDefaultState());
}
return result;
}
public @NotNull List<ScopeToolState> getNonDefaultTools(@NotNull String shortName, Project project) {
List<ScopeToolState> result = new ArrayList<>();
List<ScopeToolState> nonDefaultTools = getTools(shortName, project).getNonDefaultTools();
if (nonDefaultTools != null) {
result.addAll(nonDefaultTools);
}
return result;
}
public boolean isToolEnabled(@NotNull HighlightDisplayKey key, @Nullable NamedScope namedScope, @NotNull Project project) {
return getTools(key.getShortName(), project).isEnabled(namedScope,project);
}
public void removeScope(@NotNull String toolShortName, @NotNull String scopeName, @NotNull Project project) {
getTools(toolShortName, project).removeScope(scopeName);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void removeScopes(@NotNull List<String> shortNames, @NotNull String scopeName, @NotNull Project project) {
for (String shortName : shortNames) {
removeScope(shortName, scopeName, project);
}
}
/**
* @return null if it has no base profile
*/
private @Nullable Set<String> getChangedToolNames() {
if (myBaseProfile == null) {
return null;
}
if (myChangedToolNames == null) {
synchronized (myLock) {
if (myChangedToolNames == null) {
initInspectionTools(null);
Set<String> names = myTools.keySet();
Set<String> map = new HashSet<>(names.size());
for (String toolId : names) {
Tools toolList1 = myBaseProfile.myTools.get(toolId);
Tools toolList2 = myTools.get(toolId);
if (!Comparing.equal(toolList1, toolList2)) {
map.add(toolId);
}
}
myChangedToolNames = map;
return map;
}
}
}
return myChangedToolNames;
}
public void profileChanged() {
myChangedToolNames = null;
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
@Transient
public @NotNull HighlightDisplayLevel getErrorLevel(@NotNull HighlightDisplayKey key, @Nullable NamedScope scope, @NotNull Project project) {
ToolsImpl tools = getToolsOrNull(key.getShortName(), project);
return tools != null ? tools.getLevel(scope, project) : HighlightDisplayLevel.WARNING;
}
@Transient
public @Nullable TextAttributesKey getEditorAttributesKey(@NotNull HighlightDisplayKey key, @Nullable NamedScope scope, @NotNull Project project) {
ToolsImpl tools = getToolsOrNull(key.getShortName(), project);
return tools != null ? tools.getEditorAttributesKey(scope, project) : null;
}
public ScopeToolState addScope(@NotNull InspectionToolWrapper<?,?> toolWrapper,
@NotNull NamedScope scope,
@NotNull HighlightDisplayLevel level,
boolean enabled,
@Nullable Project project) {
return getTools(toolWrapper.getShortName(), project).prependTool(scope, toolWrapper, enabled, level);
}
public void setErrorLevel(@NotNull HighlightDisplayKey key, @NotNull HighlightDisplayLevel level, @Nullable String scopeName, @NotNull Project project) {
getTools(key.getShortName(), project).setLevel(level, scopeName, project);
mySchemeState = SchemeState.POSSIBLY_CHANGED;
}
public void setErrorLevel(@NotNull List<? extends HighlightDisplayKey> keys, @NotNull HighlightDisplayLevel level, @Nullable String scopeName, @NotNull Project project) {
for (HighlightDisplayKey key : keys) {
setErrorLevel(key, level, scopeName, project);
}
}
public void setEditorAttributesKey(@NotNull List<? extends HighlightDisplayKey> keys, @Nullable TextAttributesKey attributesKey, @Nullable String scopeName, @NotNull Project project) {
for (HighlightDisplayKey key : keys) {
setEditorAttributesKey(key.getShortName(), attributesKey == null ? null : attributesKey.getExternalName(), scopeName, project);
}
}
public @NotNull ToolsImpl getTools(@NotNull String name, @Nullable Project project) {
return Objects.requireNonNull(
getToolsOrNull(name, project),
() -> "Can't find tools for \"" + name + "\" in the profile \"" + getName() + "\"");
}
public @Nullable ToolsImpl getToolsOrNull(@NotNull String name, @Nullable Project project) {
initInspectionTools();
return myTools.get(name);
}
public @NotNull Collection<ToolsImpl> getTools() {
initInspectionTools();
return myTools.values();
}
public void enableAllTools(@NotNull Project project) {
for (InspectionToolWrapper<?,?> entry : getInspectionTools(null)) {
enableTool(entry.getShortName(), project);
}
}
public void disableAllTools(@NotNull Project project) {
for (InspectionToolWrapper<?,?> entry : getInspectionTools(null)) {
setToolEnabled(entry.getShortName(), false, project);
}
}
/** See {@link #setToolEnabled(String, boolean, Project, boolean)} */
public final void setToolEnabled(@NotNull String toolShortName, boolean enabled) {
setToolEnabled(toolShortName, enabled, null, true);
}
/** See {@link #setToolEnabled(String, boolean, Project, boolean)} */
public final void setToolEnabled(@NotNull String toolShortName, boolean enabled, @Nullable Project project) {
setToolEnabled(toolShortName, enabled, project, true);
}
/**
* If you need to enable multiple tools, please use {@link #modifyProfile}.
*/
public final void setToolEnabled(@NotNull String toolShortName, boolean enabled, @Nullable Project project, boolean fireEvents) {
var tool = getTools(toolShortName, project != null ? project : getDefaultProject());
if (enabled && tool.isEnabled() && tool.getDefaultState().isEnabled()) {
return;
}
if (enabled) {
tool.setEnabled(true);
tool.getDefaultState().setEnabled(true);
}
else {
tool.setEnabled(false);
if (tool.getNonDefaultTools() == null) {
tool.getDefaultState().setEnabled(false);
}
}
mySchemeState = SchemeState.POSSIBLY_CHANGED;
if (fireEvents) {
myProfileManager.fireProfileChanged(this);
}
}
@Override
public String toString() {
return getName();
}
@Override
public boolean equals(@Nullable Object other) {
return super.equals(other) && ((InspectionProfileImpl)other).myProfileManager == myProfileManager;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + myProfileManager.hashCode();
}
public static void setToolEnabled(boolean newState,
@NotNull InspectionProfileImpl profile,
@NotNull String shortName,
@NotNull Project project) {
setToolEnabled(newState, profile, shortName, false, project);
}
public static void setToolEnabled(boolean newState,
@NotNull InspectionProfileImpl profile,
@NotNull String shortName,
boolean fireEvents,
@NotNull Project project) {
profile.setToolEnabled(shortName, newState, project, fireEvents);
for (ScopeToolState scopeToolState : profile.getTools(shortName, project).getTools()) {
scopeToolState.setEnabled(newState);
}
}
public @NotNull OptionController controllerFor(@NotNull PsiElement element) {
return OptionController.empty().onPrefixes(toolId -> containerForTool(toolId, element));
}
private @Nullable OptionController containerForTool(@NotNull String toolShortName, @NotNull PsiElement element) {
var model = new InspectionProfileModifiableModel(this);
ToolsImpl toolList = model.getToolsOrNull(toolShortName, element.getProject());
if (toolList == null) return null;
return OptionController.empty()
.onPrefix("options", toolList.getInspectionTool(element).getTool().getOptionController())
.onValueSet((bindId, value) -> {
model.commit();
getProfileManager().fireProfileChanged(this);
});
}
private static final class MyInspectionElementsMerger extends InspectionElementsMergerBase {
private final String myShortName;
private final LocalInspectionToolWrapper myWrapper;
private MyInspectionElementsMerger(@NotNull String shortName, @NotNull LocalInspectionToolWrapper wrapper) {
myShortName = shortName;
myWrapper = wrapper;
}
@Override
public @NotNull String getMergedToolName() {
return myShortName;
}
@Override
public String @NotNull [] getSourceToolNames() {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
@Override
protected boolean areSettingsMerged(@NotNull Map<String, Element> settings, @NotNull Element element) {
// returns true when settings are default, so defaults will not be saved in profile
boolean enabled = myWrapper.isEnabledByDefault();
return Boolean.parseBoolean(element.getAttributeValue("enabled")) == enabled &&
Boolean.parseBoolean(element.getAttributeValue("enabled_by_default")) == enabled &&
myWrapper.getDefaultLevel().toString().equals(element.getAttributeValue("level")) &&
element.getChildren("scope").isEmpty();
}
}
}