optimization: cache inspections with inconsistent shortName <-> EP.shortName to avoid wasting time enumerating all of them on each highlighting restart

(and move away `new LocalInspectionWrapper()` calls from class initializer to avoid fatal ClassDefNotFoundException when is throws)

GitOrigin-RevId: 7e2117288d8f475e5aae7bac830684c67858d080
This commit is contained in:
Alexey Kudravtsev
2024-09-05 21:04:12 +02:00
committed by intellij-monorepo-bot
parent 130a555cb5
commit aa2d1edc99
3 changed files with 82 additions and 44 deletions

View File

@@ -15,8 +15,12 @@ import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.extensions.PluginDescriptor
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.ConcurrencyUtil
import com.intellij.util.SmartList
import com.intellij.util.containers.CollectionFactory
import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentMap
private val LOG = logger<InspectionToolRegistrar>()
private val EP_NAME = ExtensionPointName<InspectionToolProvider>("com.intellij.inspectionToolProvider")
@@ -49,10 +53,12 @@ class InspectionToolRegistrar : InspectionToolsSupplier() {
}
}
private fun <T : InspectionEP> registerInspections(factories: MutableMap<Any, MutableList<InspectionFactory>>,
app: Application,
shortNames: MutableMap<String, InspectionEP>,
extensionPointName: ExtensionPointName<T>) {
private fun <T : InspectionEP> registerInspections(
factories: MutableMap<Any, MutableList<InspectionFactory>>,
app: Application,
shortNames: MutableMap<String, InspectionEP>,
extensionPointName: ExtensionPointName<T>,
) {
val isInternal = app.isInternal
for (extension in extensionPointName.extensionList) {
registerInspection(extension, shortNames, isInternal, factories)
@@ -94,6 +100,7 @@ class InspectionToolRegistrar : InspectionToolsSupplier() {
for (listener in listeners) {
listener.toolAdded(inspectionToolWrapper)
}
inconsistentInspectionNameCache.clear()
}
private fun fireToolRemoved(factory: InspectionFactory) {
@@ -101,6 +108,7 @@ class InspectionToolRegistrar : InspectionToolsSupplier() {
for (listener in listeners) {
listener.toolRemoved(inspectionToolWrapper)
}
inconsistentInspectionNameCache.remove(inspectionToolWrapper.myTool?.javaClass ?: Int::class.java)
}
override fun createTools(): List<InspectionToolWrapper<*, *>> {
@@ -117,12 +125,37 @@ class InspectionToolRegistrar : InspectionToolsSupplier() {
}
return tools
}
/**
* cache of inspection tool class -> inspection EP for which LocalInspectionEP.LOCAL_INSPECTION.getByKey does not work,
* because sometimes tool.getShortName() is inconsistent with `shortName="xxx"` in plugin.xml. For example: CheckDtdReferencesInspection
*
* @see com.intellij.codeInspection.ex.LocalInspectionToolWrapper.findInspectionEP
*/
private val inconsistentInspectionNameCache: ConcurrentMap<Class<*>, LocalInspectionEP> = ConcurrentHashMap()
private val NULL_EP: LocalInspectionEP = LocalInspectionEP() // means find was unsuccessful, null result is cached
@ApiStatus.Internal
fun findInspectionEP(tool: LocalInspectionTool): LocalInspectionEP? {
val byKey = LocalInspectionEP.LOCAL_INSPECTION.getByKey(tool.shortName, LocalInspectionToolWrapper::class.java) { it.getShortName() }
if (byKey != null) {
return byKey
}
val toolClass: Class<*> = tool.javaClass
var ep = inconsistentInspectionNameCache[toolClass]
if (ep == null) {
ep = LocalInspectionEP.LOCAL_INSPECTION.findFirstSafe { toolClass.name == it.implementationClass }
ep = ConcurrencyUtil.cacheOrGet(inconsistentInspectionNameCache, toolClass, ep?:NULL_EP)
}
return if (ep === NULL_EP) null else ep
}
}
private fun <T : InspectionEP> registerInspection(inspection: T,
shortNames: MutableMap<String, InspectionEP>,
isInternal: Boolean,
factories: MutableMap<Any, MutableList<InspectionFactory>>): (InspectionFactory)? {
private fun registerInspection(
inspection: InspectionEP,
shortNames: MutableMap<String, InspectionEP>,
isInternal: Boolean,
factories: MutableMap<Any, MutableList<InspectionFactory>>,
): (InspectionFactory)? {
checkForDuplicateShortName(inspection, shortNames)
if (!isInternal && inspection.isInternal) {
return null
@@ -135,10 +168,12 @@ private fun <T : InspectionEP> registerInspection(inspection: T,
return factory
}
private fun registerToolProvider(provider: InspectionToolProvider,
pluginDescriptor: PluginDescriptor,
keyToFactories: MutableMap<Any, MutableList<InspectionFactory>>,
added: MutableList<InspectionFactory>?) {
private fun registerToolProvider(
provider: InspectionToolProvider,
pluginDescriptor: PluginDescriptor,
keyToFactories: MutableMap<Any, MutableList<InspectionFactory>>,
added: MutableList<InspectionFactory>?,
) {
val factories = keyToFactories.computeIfAbsent(provider) { ArrayList() }
for (aClass in provider.inspectionClasses) {
val supplier = {

View File

@@ -11,7 +11,7 @@ import org.jetbrains.annotations.Nullable;
public class LocalInspectionToolWrapper extends InspectionToolWrapper<LocalInspectionTool, LocalInspectionEP> {
/** This should be used in tests primarily */
public LocalInspectionToolWrapper(@NotNull LocalInspectionTool tool) {
super(tool, findInspectionEP(tool));
super(tool, InspectionToolRegistrar.getInstance().findInspectionEP(tool));
}
public LocalInspectionToolWrapper(@NotNull LocalInspectionEP ep) {
@@ -82,14 +82,4 @@ public class LocalInspectionToolWrapper extends InspectionToolWrapper<LocalInspe
}
return toolWrapper;
}
private static LocalInspectionEP findInspectionEP(@NotNull LocalInspectionTool tool) {
LocalInspectionEP byKey = LocalInspectionEP.LOCAL_INSPECTION.getByKey(tool.getShortName(), LocalInspectionToolWrapper.class, InspectionEP::getShortName);
if (byKey != null) {
return byKey;
}
// sometimes tool.getShortName() is inconsistent with `shortName="xxx"` in plugin.xml. For example: CheckDtdReferencesInspection
// revert to brute force search among all extensions in this case
return LocalInspectionEP.LOCAL_INSPECTION.findFirstSafe(ep -> tool.getClass().getName().equals(ep.implementationClass));
}
}

View File

@@ -34,36 +34,49 @@ import java.util.function.Function;
* Thread-safe.
*/
public class SpellCheckingEditorCustomization extends SimpleEditorCustomization {
private static final Map<String, LocalInspectionToolWrapper> SPELL_CHECK_TOOLS = new HashMap<>();
private static final boolean READY = init();
private static volatile Map<String, LocalInspectionToolWrapper> SPELL_CHECK_TOOLS;
private static volatile boolean READY;
SpellCheckingEditorCustomization(boolean enabled) {
super(enabled);
}
@SuppressWarnings("unchecked")
private static boolean init() {
// It's assumed that default spell checking inspection settings are just fine for processing all types of data.
// Please perform corresponding settings tuning if that assumption is broken in the future.
Class<LocalInspectionTool>[] inspectionClasses = (Class<LocalInspectionTool>[])new Class<?>[]{SpellCheckingInspection.class};
for (Class<LocalInspectionTool> inspectionClass : inspectionClasses) {
try {
LocalInspectionTool tool = inspectionClass.newInstance();
SPELL_CHECK_TOOLS.put(tool.getShortName(), new LocalInspectionToolWrapper(tool));
}
catch (Throwable e) {
return false;
@NotNull
private static Map<String, LocalInspectionToolWrapper> getSpellCheckTools() {
Map<String, LocalInspectionToolWrapper> tools = SPELL_CHECK_TOOLS;
if (tools == null) {
tools = new HashMap<>();
// It's assumed that default spell checking inspection settings are just fine for processing all types of data.
// Please perform corresponding settings tuning if that assumption is broken in the future.
Class<LocalInspectionTool>[] inspectionClasses = (Class<LocalInspectionTool>[])new Class<?>[]{SpellCheckingInspection.class};
for (Class<LocalInspectionTool> inspectionClass : inspectionClasses) {
try {
LocalInspectionTool tool = inspectionClass.newInstance();
tools.put(tool.getShortName(), new LocalInspectionToolWrapper(tool));
}
catch (Throwable e) {
READY = false;
return Map.of();
}
}
SPELL_CHECK_TOOLS = tools;
READY = true;
}
return true;
return tools;
}
private static boolean isReady() {
if (SPELL_CHECK_TOOLS == null) {
getSpellCheckTools();
}
return READY;
}
@Override
public void customize(@NotNull EditorEx editor) {
boolean apply = isEnabled();
if (!READY) {
if (!isReady()) {
return;
}
@@ -106,8 +119,8 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization
return strategy instanceof MyInspectionProfileStrategy && !((MyInspectionProfileStrategy)strategy).myUseSpellCheck;
}
public static Set<String> getSpellCheckingToolNames() {
return Collections.unmodifiableSet(SPELL_CHECK_TOOLS.keySet());
static Set<String> getSpellCheckingToolNames() {
return Collections.unmodifiableSet(getSpellCheckTools().keySet());
}
private static class MyInspectionProfileStrategy implements Function<InspectionProfile, InspectionProfileWrapper> {
@@ -117,7 +130,7 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization
@Override
public @NotNull InspectionProfileWrapper apply(@NotNull InspectionProfile profile) {
if (!READY) {
if (!isReady()) {
return new InspectionProfileWrapper((InspectionProfileImpl)profile);
}
MyInspectionProfileWrapper wrapper = myWrappers.get(profile);
@@ -141,7 +154,7 @@ public class SpellCheckingEditorCustomization extends SimpleEditorCustomization
@Override
public boolean isToolEnabled(HighlightDisplayKey key, PsiElement element) {
return (key != null && SPELL_CHECK_TOOLS.containsKey(key.getShortName()) ? myUseSpellCheck : super.isToolEnabled(key, element));
return (key != null && getSpellCheckTools().containsKey(key.getShortName()) ? myUseSpellCheck : super.isToolEnabled(key, element));
}
}
}