Skip to content

Commit

Permalink
IJPL-306 we read a lot — reduce GC and use persistent collections
Browse files Browse the repository at this point in the history
known issue — Kotlin/kotlinx.collections.immutable#153 (still the change makes sense, as we do not create intermediate ArrayList)

(cherry picked from commit 821cafa2dffc173d19fd613e7e552f4fc5854add)

IJ-CR-117903

GitOrigin-RevId: 2116580985cd514c97ee0b94c1a53a241bc59c6f
  • Loading branch information
develar authored and intellij-monorepo-bot committed Oct 26, 2023
1 parent 46e1a19 commit 5276832
Showing 1 changed file with 71 additions and 39 deletions.
110 changes: 71 additions & 39 deletions platform/core-api/src/com/intellij/lang/Language.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.tree.IElementType;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.containers.ContainerUtil;
import kotlinx.collections.immutable.PersistentList;
import kotlinx.collections.immutable.PersistentMap;
import kotlinx.collections.immutable.PersistentSet;
import org.jetbrains.annotations.*;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static kotlinx.collections.immutable.ExtensionsKt.toPersistentList;
import static kotlinx.collections.immutable.ExtensionsKt.*;

/**
* A language represents a programming language such as Java,
Expand All @@ -39,15 +42,19 @@
*/
public abstract class Language extends UserDataHolderBase {
public static final Language[] EMPTY_ARRAY = new Language[0];
private static final Map<Class<? extends Language>, Language> registeredLanguages = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, List<Language>> registeredMimeTypes = new ConcurrentHashMap<>();
private static final Map<String, Language> ourRegisteredIDs = new ConcurrentHashMap<>();

private static final Object staticLock = new Object();
private static volatile PersistentMap<Class<? extends Language>, Language> registeredLanguages = persistentHashMapOf();
private static volatile PersistentMap<String, PersistentList<Language>> registeredMimeTypes = persistentHashMapOf();
private static volatile PersistentMap<String, Language> registeredIds = persistentHashMapOf();

private final Language myBaseLanguage;
private final String myID;
private final String[] myMimeTypes;
private final List<Language> myDialects = ContainerUtil.createLockFreeCopyOnWriteList();
private final Set<@NotNull Language> transitiveDialects = ContainerUtil.newConcurrentSet();

private final Object instanceLock = new Object();
private volatile PersistentList<Language> dialects = persistentListOf();
private volatile PersistentSet<@NotNull Language> transitiveDialects = persistentHashSetOf();

public static final Language ANY = new Language("") {
@Override
Expand All @@ -69,6 +76,7 @@ protected Language(@NonNls @NotNull String ID, @NonNls @NotNull String @NotNull
this(null, ID, mimeTypes);
}

@SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
protected Language(@Nullable Language baseLanguage, @NonNls @NotNull String ID, @NonNls @NotNull String @NotNull ... mimeTypes) {
if (baseLanguage instanceof MetaLanguage) {
throw new ImplementationConflictException(
Expand All @@ -83,27 +91,38 @@ protected Language(@Nullable Language baseLanguage, @NonNls @NotNull String ID,
myMimeTypes = mimeTypes.length == 0 ? ArrayUtilRt.EMPTY_STRING_ARRAY : mimeTypes;

Class<? extends Language> langClass = getClass();
Language prev = registeredLanguages.putIfAbsent(langClass, this);
if (prev != null) {
throw new ImplementationConflictException("Language of '" + langClass + "' is already registered: " + prev, null, prev, this);
}
synchronized (staticLock) {
Language existing = registeredLanguages.get(langClass);
if (existing != null) {
throw new ImplementationConflictException("Language of '" + langClass + "' is already registered: " + existing, null, existing, this);
}

prev = ourRegisteredIDs.putIfAbsent(ID, this);
if (prev != null) {
throw new ImplementationConflictException("Language with ID '" + ID + "' is already registered: " + prev.getClass(), null, prev, this);
}
existing = registeredIds.get(ID);
if (existing != null) {
throw new ImplementationConflictException("Language with ID '" + ID + "' is already registered: " + existing.getClass(), null, existing, this);
}

registeredLanguages = registeredLanguages.put(langClass, this);
registeredIds = registeredIds.put(ID, this);

for (String mimeType : mimeTypes) {
if (Strings.isEmpty(mimeType)) {
continue;
}

for (String mimeType : mimeTypes) {
if (Strings.isEmpty(mimeType)) {
continue;
PersistentList<Language> list = registeredMimeTypes.get(mimeType);
registeredMimeTypes = registeredMimeTypes.put(mimeType, list == null ? persistentListOf(this) : list.add(this));
}
registeredMimeTypes.computeIfAbsent(mimeType, __ -> ContainerUtil.createConcurrentList()).add(this);
}

if (baseLanguage != null) {
baseLanguage.myDialects.add(this);
synchronized (baseLanguage.instanceLock) {
baseLanguage.dialects = baseLanguage.dialects.add(this);
}
while (baseLanguage != null) {
baseLanguage.transitiveDialects.add(this);
synchronized (baseLanguage.instanceLock) {
baseLanguage.transitiveDialects = baseLanguage.transitiveDialects.add(this);
}
baseLanguage = baseLanguage.getBaseLanguage();
}
}
Expand All @@ -112,13 +131,13 @@ protected Language(@Nullable Language baseLanguage, @NonNls @NotNull String ID,
/**
* @return collection of all languages registered so far.
*/
public static @NotNull Collection<Language> getRegisteredLanguages() {
return toPersistentList(registeredLanguages.values());
public static @Unmodifiable @NotNull Collection<Language> getRegisteredLanguages() {
return registeredLanguages.values();
}

@ApiStatus.Internal
public static void unregisterAllLanguagesIn(@NotNull ClassLoader classLoader, @NotNull PluginDescriptor pluginDescriptor) {
for (Map.Entry<Class<? extends Language>, Language> e : new ArrayList<>(registeredLanguages.entrySet())) {
for (Map.Entry<Class<? extends Language>, Language> e : registeredLanguages.entrySet()) {
Class<? extends Language> clazz = e.getKey();
Language language = e.getValue();
if (clazz.getClassLoader() == classLoader) {
Expand All @@ -128,6 +147,7 @@ public static void unregisterAllLanguagesIn(@NotNull ClassLoader classLoader, @N
IElementType.unregisterElementTypes(classLoader, pluginDescriptor);
}

@SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
@ApiStatus.Internal
public void unregisterLanguage(@NotNull PluginDescriptor pluginDescriptor) {
IElementType.unregisterElementTypes(this, pluginDescriptor);
Expand All @@ -136,11 +156,15 @@ public void unregisterLanguage(@NotNull PluginDescriptor pluginDescriptor) {
if (referenceProvidersRegistry != null) {
referenceProvidersRegistry.unloadProvidersFor(this);
}
registeredLanguages.remove(getClass());
ourRegisteredIDs.remove(getID());
for (String mimeType : getMimeTypes()) {
registeredMimeTypes.remove(mimeType);

synchronized (staticLock) {
registeredLanguages = registeredLanguages.remove(getClass());
registeredIds = registeredIds.remove(getID());
for (String mimeType : getMimeTypes()) {
registeredMimeTypes.remove(mimeType);
}
}

Language baseLanguage = getBaseLanguage();
if (baseLanguage != null) {
baseLanguage.unregisterDialect(this);
Expand All @@ -149,9 +173,13 @@ public void unregisterLanguage(@NotNull PluginDescriptor pluginDescriptor) {

@ApiStatus.Internal
public void unregisterDialect(@NotNull Language language) {
myDialects.remove(language);
synchronized (instanceLock) {
dialects = dialects.remove(language);
}
for (Language baseLanguage = this; baseLanguage != null; baseLanguage = baseLanguage.getBaseLanguage()) {
baseLanguage.transitiveDialects.remove(language);
synchronized (baseLanguage.instanceLock) {
baseLanguage.transitiveDialects = baseLanguage.transitiveDialects.remove(language);
}
}
}

Expand All @@ -170,7 +198,7 @@ public static <T extends Language> T findInstance(@NotNull Class<T> klass) {
*/
public static @Unmodifiable @NotNull Collection<Language> findInstancesByMimeType(@Nullable String mimeType) {
List<Language> result = mimeType == null ? null : registeredMimeTypes.get(mimeType);
return result == null ? Collections.emptyList() : Collections.unmodifiableCollection(result);
return result == null ? Collections.emptyList() : result;
}

@Override
Expand Down Expand Up @@ -262,11 +290,11 @@ public final boolean isKindOf(@NotNull @NonNls String anotherLanguageId) {
}

public @Unmodifiable @NotNull List<Language> getDialects() {
return Collections.unmodifiableList(myDialects);
return dialects;
}

public static @Nullable Language findLanguageByID(@NonNls String id) {
return id == null ? null : ourRegisteredIDs.get(id);
return id == null ? null : registeredIds.get(id);
}

/** Fake language identifier without registering */
Expand All @@ -288,16 +316,20 @@ protected Language(@NotNull String ID, @SuppressWarnings("UnusedParameters") boo
*/
@ApiStatus.Internal
protected void registerDialect(@NotNull Language dialect) {
myDialects.add(dialect);
synchronized (instanceLock) {
dialects = dialects.add(dialect);
}
for (Language baseLanguage = this; baseLanguage != null; baseLanguage = baseLanguage.getBaseLanguage()) {
baseLanguage.transitiveDialects.add(dialect);
synchronized (baseLanguage.instanceLock) {
baseLanguage.transitiveDialects = baseLanguage.transitiveDialects.add(dialect);
}
}
}

/**
* @return this language dialects and their dialects transitively
*/
public @Unmodifiable @NotNull Collection<@NotNull Language> getTransitiveDialects() {
return Collections.unmodifiableSet(transitiveDialects);
return transitiveDialects;
}
}

0 comments on commit 5276832

Please sign in to comment.