From 2113578e9038beb0ff8f68f01f466bb6d1e66a70 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 1 Sep 2023 17:31:24 +0200 Subject: [PATCH] matching meta-languages: make thread safe, memory optimization: do not store Application reference/empty collections in the majority case of a language without matching meta-languages GitOrigin-RevId: e2b2c7735649556ef1becfef26f111477c4d9d71 --- .../src/com/intellij/lang/LanguageUtil.java | 35 ++++++++----------- .../src/com/intellij/lang/MetaLanguage.java | 10 +++++- .../com/intellij/mock/MockApplication.java | 9 +++++ 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/platform/core-api/src/com/intellij/lang/LanguageUtil.java b/platform/core-api/src/com/intellij/lang/LanguageUtil.java index a51bfec2d902..73456c57b718 100644 --- a/platform/core-api/src/com/intellij/lang/LanguageUtil.java +++ b/platform/core-api/src/com/intellij/lang/LanguageUtil.java @@ -1,14 +1,13 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.lang; +import com.intellij.ReviseWhenPortedToJDK; import com.intellij.lexer.Lexer; -import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; @@ -19,7 +18,6 @@ import com.intellij.util.containers.JBIterable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.ref.SoftReference; import java.util.*; import java.util.function.Predicate; @@ -157,14 +155,13 @@ public final class LanguageUtil { return provider.getBaseLanguage(); } - private static final Key,Set>> MATCHING_LANGUAGES = Key.create("language.matching"); + private static final Key> MATCHING_META_LANGUAGES = Key.create("MATCHING_META_LANGUAGES"); + @ReviseWhenPortedToJDK(value = "9", description = "use List.of() instead of deprecated immutableList()") static @NotNull Collection matchingMetaLanguages(@NotNull Language language) { - // A mock application may cause incorrect caching during tests. It does not fire extension point removed events. - // Ensure that we have cached against correct application. - Pair, Set> cached = language.getUserData(MATCHING_LANGUAGES); - if (cached != null && cached.first.get() == ApplicationManager.getApplication()) { - return cached.second; + Collection cached = language.getUserData(MATCHING_META_LANGUAGES); + if (cached != null) { + return cached; } if (!ApplicationManager.getApplication().getExtensionArea().hasExtensionPoint(MetaLanguage.EP_NAME)) { @@ -172,26 +169,25 @@ public final class LanguageUtil { return Collections.emptyList(); } - Set result; + Collection toCache; if (language instanceof MetaLanguage) { - result = Collections.emptySet(); + toCache = Collections.emptySet(); } else { - result = new HashSet<>(); + Set result = new HashSet<>(); MetaLanguage.EP_NAME.forEachExtensionSafe(metaLanguage -> { if (metaLanguage.matchesLanguage(language)) { result.add(metaLanguage); } }); + toCache = result.isEmpty() ? Collections.emptySet() : ContainerUtil.immutableList(result.toArray(new MetaLanguage[0])); } - Pair, Set> toCache = - Pair.create(new SoftReference<>(ApplicationManager.getApplication()), result); - language.putUserData(MATCHING_LANGUAGES, toCache); - return result; + language.putUserData(MATCHING_META_LANGUAGES, toCache); + return toCache; } - static void clearMatchingMetaLanguages(@NotNull Language language) { - language.putUserData(MATCHING_LANGUAGES, null); + static void clearMatchingMetaLanguagesCache(@NotNull Language language) { + language.putUserData(MATCHING_META_LANGUAGES, null); } public static @NotNull JBIterable getBaseLanguages(@NotNull Language language) { @@ -202,7 +198,6 @@ public final class LanguageUtil { final Language language = Language.findLanguageByID(langValueText); if (language != null) return language; - return ContainerUtil.find(Language.getRegisteredLanguages(), - e -> e.getID().equalsIgnoreCase(langValueText)); + return ContainerUtil.find(Language.getRegisteredLanguages(), e -> e.getID().equalsIgnoreCase(langValueText)); } } diff --git a/platform/core-api/src/com/intellij/lang/MetaLanguage.java b/platform/core-api/src/com/intellij/lang/MetaLanguage.java index 22d33cc2f9c8..ce7d0f41c389 100644 --- a/platform/core-api/src/com/intellij/lang/MetaLanguage.java +++ b/platform/core-api/src/com/intellij/lang/MetaLanguage.java @@ -5,6 +5,7 @@ import com.intellij.openapi.extensions.ExtensionPointListener; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.openapi.extensions.PluginDescriptor; import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -25,7 +26,7 @@ public abstract class MetaLanguage extends Language { public void extensionRemoved(@NotNull MetaLanguage metaLanguage, @NotNull PluginDescriptor pluginDescriptor) { if (MetaLanguage.this == metaLanguage) { for (Language matchingLanguage : metaLanguage.getMatchingLanguages()) { - LanguageUtil.clearMatchingMetaLanguages(matchingLanguage); + LanguageUtil.clearMatchingMetaLanguagesCache(matchingLanguage); } metaLanguage.unregisterLanguage(pluginDescriptor); } @@ -48,4 +49,11 @@ public abstract class MetaLanguage extends Language { public @NotNull Collection getMatchingLanguages() { return ContainerUtil.filter(Language.getRegisteredLanguages(), language -> matchesLanguage(language)); } + + @ApiStatus.Internal + public static void clearAllMatchingMetaLanguagesCache() { + for (Language language : Language.getRegisteredLanguages()) { + LanguageUtil.clearMatchingMetaLanguagesCache(language); + } + } } diff --git a/platform/core-impl/src/com/intellij/mock/MockApplication.java b/platform/core-impl/src/com/intellij/mock/MockApplication.java index f79d8b9f5cb9..d23eca1f7b04 100644 --- a/platform/core-impl/src/com/intellij/mock/MockApplication.java +++ b/platform/core-impl/src/com/intellij/mock/MockApplication.java @@ -1,6 +1,7 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.mock; +import com.intellij.lang.MetaLanguage; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.*; import com.intellij.openapi.application.ex.ApplicationEx; @@ -382,4 +383,12 @@ public class MockApplication extends MockComponentManager implements Application @Override public void setSaveAllowed(boolean value) { } + + @Override + public void dispose() { + // A mock application may cause incorrect caching during tests. It does not fire extension point removed events. + // Ensure that we have cached against correct application. + MetaLanguage.clearAllMatchingMetaLanguagesCache(); + super.dispose(); + } }