evictionListener in CollectionFactory should accept this map as an argument, to make it easier to write self-referential listeners

GitOrigin-RevId: e5257b1c282a95624fdb6cc2d53bff7c46828e22
This commit is contained in:
Alexey Kudravtsev
2024-03-20 15:33:37 +01:00
committed by intellij-monorepo-bot
parent 4458ed6efc
commit 23bbdb8cc7
8 changed files with 50 additions and 29 deletions

View File

@@ -56,7 +56,7 @@ final class HighlightInfoUpdater implements Disposable {
} }
static class ToolHighlights { static class ToolHighlights {
final Map<PsiElement, List<? extends HighlightInfo>> elementHighlights = CollectionFactory.createConcurrentSoftMap(evicted -> removeEvicted(evicted)); final Map<PsiElement, List<? extends HighlightInfo>> elementHighlights = CollectionFactory.createConcurrentSoftMap((m,evicted) -> removeEvicted(evicted));
@NotNull ToolLatencies latencies = new ToolLatencies(0,0,0); @NotNull ToolLatencies latencies = new ToolLatencies(0,0,0);
} }
@@ -117,7 +117,7 @@ final class HighlightInfoUpdater implements Disposable {
private static @NotNull Map<PsiFile, Map<Object, ToolHighlights>> getOrCreateHostMap(@NotNull Document hostDocument) { private static @NotNull Map<PsiFile, Map<Object, ToolHighlights>> getOrCreateHostMap(@NotNull Document hostDocument) {
Map<PsiFile, Map<Object, ToolHighlights>> map = hostDocument.getUserData(VISITED_PSI_ELEMENTS); Map<PsiFile, Map<Object, ToolHighlights>> map = hostDocument.getUserData(VISITED_PSI_ELEMENTS);
if (map == null) { if (map == null) {
map = ((UserDataHolderEx)hostDocument).putUserDataIfAbsent(VISITED_PSI_ELEMENTS, CollectionFactory.createConcurrentSoftMap(oldMap -> removeEvictedFile(oldMap))); map = ((UserDataHolderEx)hostDocument).putUserDataIfAbsent(VISITED_PSI_ELEMENTS, CollectionFactory.createConcurrentSoftMap((m,oldMap) -> removeEvictedFile(oldMap)));
} }
return map; return map;
} }

View File

@@ -865,28 +865,32 @@ public class ContainerUtilCollectionsTest extends Assert {
@Test @Test
public void testKeyEvictionListenerWorks() { public void testKeyEvictionListenerWorks() {
AtomicReference<Object> evicted = new AtomicReference<>(); AtomicReference<Object> evicted = new AtomicReference<>();
Map<Object, Object> map = CollectionFactory.createConcurrentSoftMap(value -> { AtomicReference<Map<Object, Object>> map = new AtomicReference<>();
map.set(CollectionFactory.createConcurrentSoftMap((thisMap,value) -> {
assertTrue(evicted.compareAndSet(null, value)); assertTrue(evicted.compareAndSet(null, value));
}); assertSame(map.get(), thisMap);
}));
Object value = new Object(); Object value = new Object();
map.put(new Object(), value); map.get().put(new Object(), value);
GCUtil.tryGcSoftlyReachableObjects(() -> map.remove("") != null/*to call processQueue()*/ || map.isEmpty()); GCUtil.tryGcSoftlyReachableObjects(() -> map.get().remove("") != null/*to call processQueue()*/ || map.get().isEmpty());
map.remove(""); // to call processQueue() map.get().remove(""); // to call processQueue()
assertSame(value, evicted.get()); assertSame(value, evicted.get());
} }
@Test @Test
public void testValueEvictionListenerWorks() { public void testValueEvictionListenerWorks() {
AtomicReference<Object> evicted = new AtomicReference<>(); AtomicReference<Object> evicted = new AtomicReference<>();
Map<Object, Object> map = CollectionFactory.createConcurrentSoftValueMap(key -> { AtomicReference<Map<Object, Object>> map = new AtomicReference<>();
map.set(CollectionFactory.createConcurrentSoftValueMap((thisMap,key) -> {
assertTrue(evicted.compareAndSet(null, key)); assertTrue(evicted.compareAndSet(null, key));
}); assertSame(map.get(), thisMap);
}));
Object key = new Object(); Object key = new Object();
map.put(key, new Object()); map.get().put(key, new Object());
GCUtil.tryGcSoftlyReachableObjects(() -> map.remove("") != null/*to call processQueue()*/ || map.isEmpty()); GCUtil.tryGcSoftlyReachableObjects(() -> map.get().remove("") != null/*to call processQueue()*/ || map.get().isEmpty());
map.remove(""); // to call processQueue() map.get().remove(""); // to call processQueue()
assertSame(key, evicted.get()); assertSame(key, evicted.get());
} }
} }

View File

@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
// ContainerUtil requires trove in classpath // ContainerUtil requires trove in classpath
@@ -39,15 +40,21 @@ public final class CollectionFactory {
return new ConcurrentSoftValueHashMap<>(null); return new ConcurrentSoftValueHashMap<>(null);
} }
/**
* Create {@link ConcurrentMap} with hard-referenced keys and weak-referenced values.
* When the value get garbage-collected, the {@code evictionListener} is (eventually) invoked with this map and the corresponding key
*/
@Contract(value = "_ -> new", pure = true) @Contract(value = "_ -> new", pure = true)
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentWeakValueMap( public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentWeakValueMap(@NotNull BiConsumer<? super ConcurrentMap<K,V>, ? super K> evictionListener) {
@NotNull Consumer<? super K> evictionListener) {
return new ConcurrentWeakValueHashMap<>(evictionListener); return new ConcurrentWeakValueHashMap<>(evictionListener);
} }
/**
* Create {@link ConcurrentMap} with hard-referenced keys and soft-referenced values.
* When the value get garbage-collected, the {@code evictionListener} is (eventually) invoked with this map and the corresponding key
*/
@Contract(value = "_ -> new", pure = true) @Contract(value = "_ -> new", pure = true)
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftValueMap( public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftValueMap(@NotNull BiConsumer<? super ConcurrentMap<K,V>, ? super K> evictionListener) {
@NotNull Consumer<? super K> evictionListener) {
return new ConcurrentSoftValueHashMap<>(evictionListener); return new ConcurrentSoftValueHashMap<>(evictionListener);
} }
@@ -363,8 +370,13 @@ public final class CollectionFactory {
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap() { public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap() {
return new ConcurrentSoftHashMap<>(null); return new ConcurrentSoftHashMap<>(null);
} }
/**
* Create {@link ConcurrentMap} with soft-referenced keys and hard-referenced values.
* When the key get garbage-collected, the {@code evictionListener} is (eventually) invoked with this map and the corresponding value
*/
@Contract(value = "_ -> new", pure = true) @Contract(value = "_ -> new", pure = true)
public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap(@NotNull Consumer<? super V> evictionListener) { public static @NotNull <K, V> ConcurrentMap<@NotNull K, @NotNull V> createConcurrentSoftMap(@NotNull BiConsumer<? super ConcurrentMap<K,V>, ? super V> evictionListener) {
return new ConcurrentSoftHashMap<>(evictionListener); return new ConcurrentSoftHashMap<>(evictionListener);
} }

View File

@@ -8,7 +8,7 @@ import java.lang.ref.ReferenceQueue;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer; import java.util.function.BiConsumer;
/** /**
* Base class for concurrent (soft/weak) key:K -> strong value:V map * Base class for concurrent (soft/weak) key:K -> strong value:V map
@@ -23,9 +23,9 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
static final float LOAD_FACTOR = 0.75f; static final float LOAD_FACTOR = 0.75f;
static final int DEFAULT_CAPACITY = 16; static final int DEFAULT_CAPACITY = 16;
static final int DEFAULT_CONCURRENCY_LEVEL = Math.min(Runtime.getRuntime().availableProcessors(), 4); static final int DEFAULT_CONCURRENCY_LEVEL = Math.min(Runtime.getRuntime().availableProcessors(), 4);
private final Consumer<? super V> myEvictionListener; private final BiConsumer<? super ConcurrentMap<K, V>, ? super V> myEvictionListener;
ConcurrentRefHashMap(@Nullable Consumer<? super V> evictionListener) { ConcurrentRefHashMap(@Nullable BiConsumer<? super ConcurrentMap<K,V>, ? super V> evictionListener) {
myHashingStrategy = this; myHashingStrategy = this;
myMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY, LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); myMap = new ConcurrentHashMap<>(DEFAULT_CAPACITY, LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
myEvictionListener = evictionListener; myEvictionListener = evictionListener;
@@ -66,7 +66,7 @@ abstract class ConcurrentRefHashMap<K, V> extends AbstractMap<K, V> implements C
while ((wk = (KeyReference<K>)myReferenceQueue.poll()) != null) { while ((wk = (KeyReference<K>)myReferenceQueue.poll()) != null) {
V v = myMap.remove(wk); V v = myMap.remove(wk);
if (myEvictionListener != null) { if (myEvictionListener != null) {
myEvictionListener.accept(v); myEvictionListener.accept(this, v);
} }
processed = true; processed = true;
} }

View File

@@ -10,7 +10,7 @@ import java.lang.ref.ReferenceQueue;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer; import java.util.function.BiConsumer;
/** /**
* Base class for concurrent strong key:K -> (soft/weak) value:V map * Base class for concurrent strong key:K -> (soft/weak) value:V map
@@ -20,10 +20,10 @@ import java.util.function.Consumer;
abstract class ConcurrentRefValueHashMap<K, V> implements ConcurrentMap<K, V> { abstract class ConcurrentRefValueHashMap<K, V> implements ConcurrentMap<K, V> {
private final ConcurrentMap<K, ValueReference<K, V>> myMap = new ConcurrentHashMap<>(); private final ConcurrentMap<K, ValueReference<K, V>> myMap = new ConcurrentHashMap<>();
private final Consumer<? super K> myEvictionListener; private final BiConsumer<? super ConcurrentMap<K, V>, ? super K> myEvictionListener;
protected final ReferenceQueue<V> myQueue = new ReferenceQueue<>(); protected final ReferenceQueue<V> myQueue = new ReferenceQueue<>();
ConcurrentRefValueHashMap(@Nullable Consumer<? super K> evictionListener) { ConcurrentRefValueHashMap(@Nullable BiConsumer<? super ConcurrentMap<K,V>, ? super K> evictionListener) {
myEvictionListener = evictionListener; myEvictionListener = evictionListener;
} }
@@ -44,7 +44,7 @@ abstract class ConcurrentRefValueHashMap<K, V> implements ConcurrentMap<K, V> {
if (ref == null) break; if (ref == null) break;
K key = ref.getKey(); K key = ref.getKey();
if (myMap.remove(key, ref) && myEvictionListener != null) { if (myMap.remove(key, ref) && myEvictionListener != null) {
myEvictionListener.accept(key); myEvictionListener.accept(this, key);
} }
processed = true; processed = true;
} }

View File

@@ -6,7 +6,8 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.function.Consumer; import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
/** /**
* Concurrent soft key:K -> strong value:V map * Concurrent soft key:K -> strong value:V map
@@ -15,7 +16,7 @@ import java.util.function.Consumer;
* Use {@link ContainerUtil#createConcurrentSoftMap()} to create this * Use {@link ContainerUtil#createConcurrentSoftMap()} to create this
*/ */
final class ConcurrentSoftHashMap<K, V> extends ConcurrentRefHashMap<K, V> { final class ConcurrentSoftHashMap<K, V> extends ConcurrentRefHashMap<K, V> {
ConcurrentSoftHashMap(@Nullable Consumer<? super V> evictionListener) { ConcurrentSoftHashMap(@Nullable BiConsumer<? super ConcurrentMap<K,V>, ? super V> evictionListener) {
super(evictionListener); super(evictionListener);
} }

View File

@@ -6,6 +6,8 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference; import java.lang.ref.SoftReference;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@@ -16,7 +18,7 @@ import java.util.function.Consumer;
*/ */
final class ConcurrentSoftValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> { final class ConcurrentSoftValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> {
ConcurrentSoftValueHashMap(@Nullable Consumer<? super K> evictionListener) { ConcurrentSoftValueHashMap(@Nullable BiConsumer<? super ConcurrentMap<K,V>, ? super K> evictionListener) {
super(evictionListener); super(evictionListener);
} }

View File

@@ -7,6 +7,8 @@ import org.jetbrains.annotations.Nullable;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
/** /**
@@ -17,7 +19,7 @@ import java.util.function.Consumer;
*/ */
final class ConcurrentWeakValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> { final class ConcurrentWeakValueHashMap<K,V> extends ConcurrentRefValueHashMap<K,V> {
ConcurrentWeakValueHashMap(@Nullable Consumer<? super K> evictionListener) { ConcurrentWeakValueHashMap(@Nullable BiConsumer<? super ConcurrentMap<K,V>, ? super K> evictionListener) {
super(evictionListener); super(evictionListener);
} }