mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-28 15:24:43 +02:00
8310913: Move ReferencedKeyMap to jdk.internal so it may be shared
Reviewed-by: naoto, rriggs, mchung, liach
This commit is contained in:
parent
86783b9851
commit
6af0af5934
11 changed files with 537 additions and 275 deletions
|
@ -33,7 +33,9 @@ import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.function.Supplier;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -42,7 +44,8 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import jdk.internal.access.SharedSecrets;
|
import jdk.internal.util.ReferencedKeySet;
|
||||||
|
import jdk.internal.util.ReferenceKey;
|
||||||
import jdk.internal.vm.annotation.Stable;
|
import jdk.internal.vm.annotation.Stable;
|
||||||
import sun.invoke.util.BytecodeDescriptor;
|
import sun.invoke.util.BytecodeDescriptor;
|
||||||
import sun.invoke.util.VerifyType;
|
import sun.invoke.util.VerifyType;
|
||||||
|
@ -227,7 +230,13 @@ class MethodType
|
||||||
return new IndexOutOfBoundsException(num.toString());
|
return new IndexOutOfBoundsException(num.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
static final ConcurrentWeakInternSet<MethodType> internTable = new ConcurrentWeakInternSet<>();
|
static final ReferencedKeySet<MethodType> internTable =
|
||||||
|
ReferencedKeySet.create(false, true, new Supplier<>() {
|
||||||
|
@Override
|
||||||
|
public Map<ReferenceKey<MethodType>, ReferenceKey<MethodType>> get() {
|
||||||
|
return new ConcurrentHashMap<>(512);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
static final Class<?>[] NO_PTYPES = {};
|
static final Class<?>[] NO_PTYPES = {};
|
||||||
|
|
||||||
|
@ -405,7 +414,7 @@ class MethodType
|
||||||
mt = new MethodType(rtype, ptypes);
|
mt = new MethodType(rtype, ptypes);
|
||||||
}
|
}
|
||||||
mt.form = MethodTypeForm.findForm(mt);
|
mt.form = MethodTypeForm.findForm(mt);
|
||||||
return internTable.add(mt);
|
return internTable.intern(mt);
|
||||||
}
|
}
|
||||||
private static final @Stable MethodType[] objectOnlyTypes = new MethodType[20];
|
private static final @Stable MethodType[] objectOnlyTypes = new MethodType[20];
|
||||||
|
|
||||||
|
@ -883,10 +892,6 @@ class MethodType
|
||||||
* @param x object to compare
|
* @param x object to compare
|
||||||
* @see Object#equals(Object)
|
* @see Object#equals(Object)
|
||||||
*/
|
*/
|
||||||
// This implementation may also return true if x is a WeakEntry containing
|
|
||||||
// a method type that is equal to this. This is an internal implementation
|
|
||||||
// detail to allow for faster method type lookups.
|
|
||||||
// See ConcurrentWeakInternSet.WeakEntry#equals(Object)
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object x) {
|
public boolean equals(Object x) {
|
||||||
if (this == x) {
|
if (this == x) {
|
||||||
|
@ -895,10 +900,6 @@ class MethodType
|
||||||
if (x instanceof MethodType mt) {
|
if (x instanceof MethodType mt) {
|
||||||
return equals(mt);
|
return equals(mt);
|
||||||
}
|
}
|
||||||
if (x instanceof ConcurrentWeakInternSet.WeakEntry<?> e
|
|
||||||
&& e.get() instanceof MethodType mt) {
|
|
||||||
return equals(mt);
|
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1390,112 +1391,4 @@ s.writeObject(this.parameterArray());
|
||||||
wrapAlt = null;
|
wrapAlt = null;
|
||||||
return mt;
|
return mt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple implementation of weak concurrent intern set.
|
|
||||||
*
|
|
||||||
* @param <T> interned type
|
|
||||||
*/
|
|
||||||
private static class ConcurrentWeakInternSet<T> {
|
|
||||||
|
|
||||||
private final ConcurrentMap<WeakEntry<T>, WeakEntry<T>> map;
|
|
||||||
private final ReferenceQueue<T> stale;
|
|
||||||
|
|
||||||
public ConcurrentWeakInternSet() {
|
|
||||||
this.map = new ConcurrentHashMap<>(512);
|
|
||||||
this.stale = SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the existing interned element.
|
|
||||||
* This method returns null if no element is interned.
|
|
||||||
*
|
|
||||||
* @param elem element to look up
|
|
||||||
* @return the interned element
|
|
||||||
*/
|
|
||||||
public T get(T elem) {
|
|
||||||
if (elem == null) throw new NullPointerException();
|
|
||||||
expungeStaleElements();
|
|
||||||
|
|
||||||
WeakEntry<T> value = map.get(elem);
|
|
||||||
if (value != null) {
|
|
||||||
T res = value.get();
|
|
||||||
if (res != null) {
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interns the element.
|
|
||||||
* Always returns non-null element, matching the one in the intern set.
|
|
||||||
* Under the race against another add(), it can return <i>different</i>
|
|
||||||
* element, if another thread beats us to interning it.
|
|
||||||
*
|
|
||||||
* @param elem element to add
|
|
||||||
* @return element that was actually added
|
|
||||||
*/
|
|
||||||
public T add(T elem) {
|
|
||||||
if (elem == null) throw new NullPointerException();
|
|
||||||
|
|
||||||
// Playing double race here, and so spinloop is required.
|
|
||||||
// First race is with two concurrent updaters.
|
|
||||||
// Second race is with GC purging weak ref under our feet.
|
|
||||||
// Hopefully, we almost always end up with a single pass.
|
|
||||||
T interned;
|
|
||||||
WeakEntry<T> e = new WeakEntry<>(elem, stale);
|
|
||||||
do {
|
|
||||||
expungeStaleElements();
|
|
||||||
WeakEntry<T> exist = map.putIfAbsent(e, e);
|
|
||||||
interned = (exist == null) ? elem : exist.get();
|
|
||||||
} while (interned == null);
|
|
||||||
return interned;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void expungeStaleElements() {
|
|
||||||
Reference<? extends T> reference;
|
|
||||||
while ((reference = stale.poll()) != null) {
|
|
||||||
map.remove(reference);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class WeakEntry<T> extends WeakReference<T> {
|
|
||||||
|
|
||||||
public final int hashcode;
|
|
||||||
|
|
||||||
public WeakEntry(T key, ReferenceQueue<T> queue) {
|
|
||||||
super(key, queue);
|
|
||||||
hashcode = key.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This implementation returns {@code true} if {@code obj} is another
|
|
||||||
* {@code WeakEntry} whose referent is equal to this referent, or
|
|
||||||
* if {@code obj} is equal to the referent of this. This allows
|
|
||||||
* lookups to be made without wrapping in a {@code WeakEntry}.
|
|
||||||
*
|
|
||||||
* @param obj the object to compare
|
|
||||||
* @return true if {@code obj} is equal to this or the referent of this
|
|
||||||
* @see MethodType#equals(Object)
|
|
||||||
* @see Object#equals(Object)
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
Object mine = get();
|
|
||||||
if (obj instanceof WeakEntry<?> we) {
|
|
||||||
Object that = we.get();
|
|
||||||
return (that == null || mine == null) ? (this == obj) : mine.equals(that);
|
|
||||||
}
|
|
||||||
return (mine == null) ? (obj == null) : mine.equals(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return hashcode;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ import java.util.Objects;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import jdk.internal.misc.Unsafe;
|
import jdk.internal.misc.Unsafe;
|
||||||
|
import jdk.internal.util.ReferencedKeyMap;
|
||||||
|
|
||||||
import static java.lang.invoke.MethodType.methodType;
|
import static java.lang.invoke.MethodType.methodType;
|
||||||
|
|
||||||
|
@ -366,7 +367,7 @@ final class Carriers {
|
||||||
* Cache mapping {@link MethodType} to previously defined {@link CarrierElements}.
|
* Cache mapping {@link MethodType} to previously defined {@link CarrierElements}.
|
||||||
*/
|
*/
|
||||||
private static final Map<MethodType, CarrierElements>
|
private static final Map<MethodType, CarrierElements>
|
||||||
methodTypeCache = ReferencedKeyMap.create(ConcurrentHashMap::new);
|
methodTypeCache = ReferencedKeyMap.create(false, ConcurrentHashMap::new);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Permute a raw constructor and component accessor {@link MethodHandle MethodHandles} to
|
* Permute a raw constructor and component accessor {@link MethodHandle MethodHandles} to
|
||||||
|
|
|
@ -54,7 +54,7 @@ public interface JavaLangRefAccess {
|
||||||
/**
|
/**
|
||||||
* Constructs a new NativeReferenceQueue.
|
* Constructs a new NativeReferenceQueue.
|
||||||
*
|
*
|
||||||
* Invoked by MethodType.ConcurrentWeakInternSet
|
* Invoked by jdk.internal.util.ReferencedKeyMap
|
||||||
*/
|
*/
|
||||||
<T> ReferenceQueue<T> newNativeReferenceQueue();
|
<T> ReferenceQueue<T> newNativeReferenceQueue();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,9 @@
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.lang.runtime;
|
package jdk.internal.util;
|
||||||
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.Reference;
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View/wrapper of keys used by the backing {@link ReferencedKeyMap}.
|
* View/wrapper of keys used by the backing {@link ReferencedKeyMap}.
|
||||||
|
@ -39,11 +36,8 @@ import java.util.Objects;
|
||||||
* @param <T> key type
|
* @param <T> key type
|
||||||
*
|
*
|
||||||
* @since 21
|
* @since 21
|
||||||
*
|
|
||||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
|
||||||
* Do not rely on its availability.
|
|
||||||
*/
|
*/
|
||||||
sealed interface ReferenceKey<T> permits StrongReferenceKey, WeakReferenceKey, SoftReferenceKey {
|
public sealed interface ReferenceKey<T> permits StrongReferenceKey, WeakReferenceKey, SoftReferenceKey {
|
||||||
/**
|
/**
|
||||||
* {@return the value of the unwrapped key}
|
* {@return the value of the unwrapped key}
|
||||||
*/
|
*/
|
|
@ -23,7 +23,7 @@
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.lang.runtime;
|
package jdk.internal.util;
|
||||||
|
|
||||||
import java.lang.ref.Reference;
|
import java.lang.ref.Reference;
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
@ -37,9 +37,12 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import jdk.internal.access.SharedSecrets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class provides management of {@link Map maps} where it is desirable to
|
* This class provides management of {@link Map maps} where it is desirable to
|
||||||
* remove entries automatically when the key is garbage collected. This is
|
* remove entries automatically when the key is garbage collected. This is
|
||||||
|
@ -78,11 +81,8 @@ import java.util.stream.Stream;
|
||||||
* @param <V> the type of mapped values
|
* @param <V> the type of mapped values
|
||||||
*
|
*
|
||||||
* @since 21
|
* @since 21
|
||||||
*
|
|
||||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
|
||||||
* Do not rely on its availability.
|
|
||||||
*/
|
*/
|
||||||
final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
public final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
/**
|
/**
|
||||||
* true if {@link SoftReference} keys are to be used,
|
* true if {@link SoftReference} keys are to be used,
|
||||||
* {@link WeakReference} otherwise.
|
* {@link WeakReference} otherwise.
|
||||||
|
@ -95,7 +95,7 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
private final Map<ReferenceKey<K>, V> map;
|
private final Map<ReferenceKey<K>, V> map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ReferenceQueue} for cleaning up {@link WeakReferenceKey EntryKeys}.
|
* {@link ReferenceQueue} for cleaning up entries.
|
||||||
*/
|
*/
|
||||||
private final ReferenceQueue<K> stale;
|
private final ReferenceQueue<K> stale;
|
||||||
|
|
||||||
|
@ -105,11 +105,12 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
* @param isSoft true if {@link SoftReference} keys are to
|
* @param isSoft true if {@link SoftReference} keys are to
|
||||||
* be used, {@link WeakReference} otherwise.
|
* be used, {@link WeakReference} otherwise.
|
||||||
* @param map backing map
|
* @param map backing map
|
||||||
|
* @param stale {@link ReferenceQueue} for cleaning up entries
|
||||||
*/
|
*/
|
||||||
private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map) {
|
private ReferencedKeyMap(boolean isSoft, Map<ReferenceKey<K>, V> map, ReferenceQueue<K> stale) {
|
||||||
this.isSoft = isSoft;
|
this.isSoft = isSoft;
|
||||||
this.map = map;
|
this.map = map;
|
||||||
this.stale = new ReferenceQueue<>();
|
this.stale = stale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -124,15 +125,18 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
* @param <K> the type of keys maintained by the new map
|
* @param <K> the type of keys maintained by the new map
|
||||||
* @param <V> the type of mapped values
|
* @param <V> the type of mapped values
|
||||||
*/
|
*/
|
||||||
static <K, V> ReferencedKeyMap<K, V>
|
public static <K, V> ReferencedKeyMap<K, V>
|
||||||
create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
||||||
return new ReferencedKeyMap<K, V>(isSoft, supplier.get());
|
return create(isSoft, false, supplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new {@link ReferencedKeyMap} map using
|
* Create a new {@link ReferencedKeyMap} map.
|
||||||
* {@link WeakReference} keys.
|
|
||||||
*
|
*
|
||||||
|
* @param isSoft true if {@link SoftReference} keys are to
|
||||||
|
* be used, {@link WeakReference} otherwise.
|
||||||
|
* @param useNativeQueue true if uses NativeReferenceQueue
|
||||||
|
* otherwise use {@link ReferenceQueue}.
|
||||||
* @param supplier {@link Supplier} of the backing map
|
* @param supplier {@link Supplier} of the backing map
|
||||||
*
|
*
|
||||||
* @return a new map with {@link Reference} keys
|
* @return a new map with {@link Reference} keys
|
||||||
|
@ -140,9 +144,12 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
* @param <K> the type of keys maintained by the new map
|
* @param <K> the type of keys maintained by the new map
|
||||||
* @param <V> the type of mapped values
|
* @param <V> the type of mapped values
|
||||||
*/
|
*/
|
||||||
static <K, V> ReferencedKeyMap<K, V>
|
public static <K, V> ReferencedKeyMap<K, V>
|
||||||
create(Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier) {
|
||||||
return new ReferencedKeyMap<K, V>(false, supplier.get());
|
return new ReferencedKeyMap<K, V>(isSoft, supplier.get(),
|
||||||
|
useNativeQueue ? SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue()
|
||||||
|
: new ReferenceQueue<>()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -320,10 +327,9 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
/**
|
/**
|
||||||
* Removes enqueued weak references from map.
|
* Removes enqueued weak references from map.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void removeStaleReferences() {
|
public void removeStaleReferences() {
|
||||||
while (true) {
|
while (true) {
|
||||||
WeakReferenceKey<K> key = (WeakReferenceKey<K>)stale.poll();
|
Object key = stale.poll();
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -331,4 +337,106 @@ final class ReferencedKeyMap<K, V> implements Map<K, V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts an entry where the key and the value are the same. Used for
|
||||||
|
* interning values in a set.
|
||||||
|
*
|
||||||
|
* @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
|
||||||
|
* is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
|
||||||
|
* be thrown.
|
||||||
|
*
|
||||||
|
* @param setMap {@link ReferencedKeyMap} where interning takes place
|
||||||
|
* @param key key to add
|
||||||
|
*
|
||||||
|
* @param <T> type of key
|
||||||
|
*
|
||||||
|
* @return the old key instance if found otherwise the new key instance
|
||||||
|
*
|
||||||
|
* @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
|
||||||
|
*/
|
||||||
|
static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
|
||||||
|
T value = existingKey(setMap, key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return internKey(setMap, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts an entry where the key and the value are the same. Used for
|
||||||
|
* interning values in a set.
|
||||||
|
*
|
||||||
|
* @implNote Requires a {@link ReferencedKeyMap} whose {@code V} type
|
||||||
|
* is a {@code ReferenceKey<K>}. Otherwise, a {@link ClassCastException} will
|
||||||
|
* be thrown.
|
||||||
|
*
|
||||||
|
* @param setMap {@link ReferencedKeyMap} where interning takes place
|
||||||
|
* @param key key to add
|
||||||
|
* @param interner operation to apply to key before adding to map
|
||||||
|
*
|
||||||
|
* @param <T> type of key
|
||||||
|
*
|
||||||
|
* @return the old key instance if found otherwise the new key instance
|
||||||
|
*
|
||||||
|
* @throws ClassCastException if {@code V} is not {@code EntryKey<T>}
|
||||||
|
*
|
||||||
|
* @implNote This version of intern should not be called during phase1
|
||||||
|
* using a lambda. Use an UnaryOperator instance instead.
|
||||||
|
*/
|
||||||
|
static <T> T intern(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key, UnaryOperator<T> interner) {
|
||||||
|
T value = existingKey(setMap, key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
key = interner.apply(key);
|
||||||
|
return internKey(setMap, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the key already exists in the map.
|
||||||
|
*
|
||||||
|
* @param setMap {@link ReferencedKeyMap} where interning takes place
|
||||||
|
* @param key key to test
|
||||||
|
*
|
||||||
|
* @param <T> type of key
|
||||||
|
*
|
||||||
|
* @return key if found otherwise null
|
||||||
|
*/
|
||||||
|
private static <T> T existingKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
|
||||||
|
setMap.removeStaleReferences();
|
||||||
|
ReferenceKey<T> entryKey = setMap.get(setMap.lookupKey(key));
|
||||||
|
return entryKey != null ? entryKey.get() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to add key to map.
|
||||||
|
*
|
||||||
|
* @param setMap {@link ReferencedKeyMap} where interning takes place
|
||||||
|
* @param key key to add
|
||||||
|
*
|
||||||
|
* @param <T> type of key
|
||||||
|
*
|
||||||
|
* @return the old key instance if found otherwise the new key instance
|
||||||
|
*/
|
||||||
|
private static <T> T internKey(ReferencedKeyMap<T, ReferenceKey<T>> setMap, T key) {
|
||||||
|
ReferenceKey<T> entryKey = setMap.entryKey(key);
|
||||||
|
T interned;
|
||||||
|
do {
|
||||||
|
setMap.removeStaleReferences();
|
||||||
|
ReferenceKey<T> existing = setMap.map.putIfAbsent(entryKey, entryKey);
|
||||||
|
if (existing == null) {
|
||||||
|
return key;
|
||||||
|
} else {
|
||||||
|
// If {@code putIfAbsent} returns non-null then was actually a
|
||||||
|
// {@code replace} and older key was used. In that case the new
|
||||||
|
// key was not used and the reference marked stale.
|
||||||
|
interned = existing.get();
|
||||||
|
if (interned != null) {
|
||||||
|
entryKey.unused();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (interned == null);
|
||||||
|
return interned;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation. Oracle designates this
|
||||||
|
* particular file as subject to the "Classpath" exception as provided
|
||||||
|
* by Oracle in the LICENSE file that accompanied this code.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.internal.util;
|
||||||
|
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides management of {@link Set set} where it is desirable to
|
||||||
|
* remove elements automatically when the element is garbage collected. This is
|
||||||
|
* accomplished by using a backing map where the keys and values are either a
|
||||||
|
* {@link WeakReference} or a {@link SoftReference}.
|
||||||
|
* <p>
|
||||||
|
* To create a {@link ReferencedKeySet} the user must provide a {@link Supplier}
|
||||||
|
* of the backing map and whether {@link WeakReference} or
|
||||||
|
* {@link SoftReference} is to be used.
|
||||||
|
* {@snippet :
|
||||||
|
* Set<Long> set;
|
||||||
|
*
|
||||||
|
* set = ReferencedKeySet.create(false, HashMap::new);
|
||||||
|
* set.add(30_000_000L);
|
||||||
|
* set.add(30_000_001L);
|
||||||
|
* set.add(30_000_002L);
|
||||||
|
* set.add(30_000_003L);
|
||||||
|
* set.add(30_000_004L);
|
||||||
|
*
|
||||||
|
* set = ReferencedKeySet.create(true, ConcurrentHashMap::new);
|
||||||
|
* set.add(40_000_000L);
|
||||||
|
* set.add(40_000_001L);
|
||||||
|
* set.add(40_000_002L);
|
||||||
|
* set.add(40_000_003L);
|
||||||
|
* set.add(40_000_004L);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @implNote Care must be given that the backing map does replacement by
|
||||||
|
* replacing the value in the map entry instead of deleting the old entry and
|
||||||
|
* adding a new entry, otherwise replaced entries may end up with a strongly
|
||||||
|
* referenced key. {@link HashMap} and {@link ConcurrentHashMap} are known
|
||||||
|
* to be safe.
|
||||||
|
*
|
||||||
|
* @param <T> the type of elements maintained by this set
|
||||||
|
*/
|
||||||
|
public final class ReferencedKeySet<T> extends AbstractSet<T> {
|
||||||
|
/**
|
||||||
|
* Backing {@link ReferencedKeySet} map.
|
||||||
|
*/
|
||||||
|
final ReferencedKeyMap<T, ReferenceKey<T>> map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Private constructor.
|
||||||
|
*
|
||||||
|
* @param map backing map
|
||||||
|
*/
|
||||||
|
private ReferencedKeySet(ReferencedKeyMap<T, ReferenceKey<T>> map) {
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ReferencedKeySet} elements.
|
||||||
|
*
|
||||||
|
* @param isSoft true if {@link SoftReference} elements are to
|
||||||
|
* be used, {@link WeakReference} otherwise.
|
||||||
|
* @param supplier {@link Supplier} of the backing map
|
||||||
|
*
|
||||||
|
* @return a new set with {@link Reference} elements
|
||||||
|
*
|
||||||
|
* @param <E> the type of elements maintained by this set
|
||||||
|
*/
|
||||||
|
public static <E> ReferencedKeySet<E>
|
||||||
|
create(boolean isSoft, Supplier<Map<ReferenceKey<E>, ReferenceKey<E>>> supplier) {
|
||||||
|
return create(isSoft, false, supplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link ReferencedKeySet} elements.
|
||||||
|
*
|
||||||
|
* @param isSoft true if {@link SoftReference} elements are to
|
||||||
|
* be used, {@link WeakReference} otherwise.
|
||||||
|
* @param useNativeQueue true if uses NativeReferenceQueue
|
||||||
|
* otherwise use {@link ReferenceQueue}.
|
||||||
|
* @param supplier {@link Supplier} of the backing map
|
||||||
|
*
|
||||||
|
* @return a new set with {@link Reference} elements
|
||||||
|
*
|
||||||
|
* @param <E> the type of elements maintained by this set
|
||||||
|
*/
|
||||||
|
public static <E> ReferencedKeySet<E>
|
||||||
|
create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<E>, ReferenceKey<E>>> supplier) {
|
||||||
|
return new ReferencedKeySet<>(ReferencedKeyMap.create(isSoft, useNativeQueue, supplier));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes enqueued weak references from set.
|
||||||
|
*/
|
||||||
|
public void removeStaleReferences() {
|
||||||
|
map.removeStaleReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<T> iterator() {
|
||||||
|
return map.keySet().iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return map.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return map.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return map.containsKey((T)o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T e) {
|
||||||
|
return intern(e) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
return map.remove(o) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
map.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an existing element from the set, returning null if not present or
|
||||||
|
* the old element if previously added.
|
||||||
|
*
|
||||||
|
* @param e element to get
|
||||||
|
*
|
||||||
|
* @return the old element if present, otherwise, null
|
||||||
|
*/
|
||||||
|
public T get(T e) {
|
||||||
|
ReferenceKey<T> key = map.get(e);
|
||||||
|
|
||||||
|
return key == null ? null : key.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intern an element to the set, returning the element if newly added or the
|
||||||
|
* old element if previously added.
|
||||||
|
*
|
||||||
|
* @param e element to add
|
||||||
|
*
|
||||||
|
* @return the old element if present, otherwise, the new element
|
||||||
|
*/
|
||||||
|
public T intern(T e) {
|
||||||
|
return ReferencedKeyMap.intern(map, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intern an element to the set, returning the element if newly added or the
|
||||||
|
* old element if previously added.
|
||||||
|
*
|
||||||
|
* @param e element to add
|
||||||
|
* @param interner operation to apply to key before adding to set
|
||||||
|
*
|
||||||
|
* @return the old element if present, otherwise, the new element
|
||||||
|
*
|
||||||
|
* @implNote This version of intern should not be called during phase1
|
||||||
|
* using a lambda. Use an UnaryOperator instance instead.
|
||||||
|
*/
|
||||||
|
public T intern(T e, UnaryOperator<T> interner) {
|
||||||
|
return ReferencedKeyMap.intern(map, e, interner);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.lang.runtime;
|
package jdk.internal.util;
|
||||||
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.SoftReference;
|
import java.lang.ref.SoftReference;
|
||||||
|
@ -35,9 +35,6 @@ import java.util.Objects;
|
||||||
* @param <T> key type
|
* @param <T> key type
|
||||||
*
|
*
|
||||||
* @since 21
|
* @since 21
|
||||||
*
|
|
||||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
|
||||||
* Do not rely on its availability.
|
|
||||||
*/
|
*/
|
||||||
final class SoftReferenceKey<T> extends SoftReference<T> implements ReferenceKey<T> {
|
final class SoftReferenceKey<T> extends SoftReference<T> implements ReferenceKey<T> {
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +73,8 @@ final class SoftReferenceKey<T> extends SoftReference<T> implements ReferenceKey
|
||||||
if (obj instanceof ReferenceKey<?> key) {
|
if (obj instanceof ReferenceKey<?> key) {
|
||||||
obj = key.get();
|
obj = key.get();
|
||||||
}
|
}
|
||||||
|
// Note: refersTo is insufficient since keys require equivalence.
|
||||||
|
// refersTo would also require a cast to type T.
|
||||||
return Objects.equals(get(), obj);
|
return Objects.equals(get(), obj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.lang.runtime;
|
package jdk.internal.util;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@ -34,9 +34,6 @@ import java.util.Objects;
|
||||||
* @param <T> key type
|
* @param <T> key type
|
||||||
*
|
*
|
||||||
* @since 21
|
* @since 21
|
||||||
*
|
|
||||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
|
||||||
* Do not rely on its availability.
|
|
||||||
*/
|
*/
|
||||||
final class StrongReferenceKey<T> implements ReferenceKey<T> {
|
final class StrongReferenceKey<T> implements ReferenceKey<T> {
|
||||||
T key;
|
T key;
|
|
@ -23,7 +23,7 @@
|
||||||
* questions.
|
* questions.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package java.lang.runtime;
|
package jdk.internal.util;
|
||||||
|
|
||||||
import java.lang.ref.ReferenceQueue;
|
import java.lang.ref.ReferenceQueue;
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
@ -35,9 +35,6 @@ import java.util.Objects;
|
||||||
* @param <T> key type
|
* @param <T> key type
|
||||||
*
|
*
|
||||||
* @since 21
|
* @since 21
|
||||||
*
|
|
||||||
* Warning: This class is part of PreviewFeature.Feature.STRING_TEMPLATES.
|
|
||||||
* Do not rely on its availability.
|
|
||||||
*/
|
*/
|
||||||
final class WeakReferenceKey<T> extends WeakReference<T> implements ReferenceKey<T> {
|
final class WeakReferenceKey<T> extends WeakReference<T> implements ReferenceKey<T> {
|
||||||
/**
|
/**
|
||||||
|
@ -76,6 +73,8 @@ final class WeakReferenceKey<T> extends WeakReference<T> implements ReferenceKey
|
||||||
if (obj instanceof ReferenceKey<?> key) {
|
if (obj instanceof ReferenceKey<?> key) {
|
||||||
obj = key.get();
|
obj = key.get();
|
||||||
}
|
}
|
||||||
|
// Note: refersTo is insufficient since keys require equivalence.
|
||||||
|
// refersTo would also require a cast to type T.
|
||||||
return Objects.equals(get(), obj);
|
return Objects.equals(get(), obj);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
|
||||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
||||||
*
|
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU General Public License version 2 only, as
|
|
||||||
* published by the Free Software Foundation.
|
|
||||||
*
|
|
||||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
||||||
* version 2 for more details (a copy is included in the LICENSE file that
|
|
||||||
* accompanied this code).
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License version
|
|
||||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
||||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
*
|
|
||||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
||||||
* or visit www.oracle.com if you need additional information or have any
|
|
||||||
* questions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @test
|
|
||||||
* @summary Test features provided by the ReferencedKeyMap class.
|
|
||||||
* @modules java.base/java.lang.runtime
|
|
||||||
* @enablePreview
|
|
||||||
* @compile --patch-module java.base=${test.src} ReferencedKeyTest.java
|
|
||||||
* @run main/othervm --patch-module java.base=${test.class.path} java.lang.runtime.ReferencedKeyTest
|
|
||||||
*/
|
|
||||||
|
|
||||||
package java.lang.runtime;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
public class ReferencedKeyTest {
|
|
||||||
static long BASE_KEY = 10_000_000L;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws Throwable {
|
|
||||||
mapTest(false, HashMap::new);
|
|
||||||
mapTest(true, HashMap::new);
|
|
||||||
mapTest(false, ConcurrentHashMap::new);
|
|
||||||
mapTest(true, ConcurrentHashMap::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void assertTrue(boolean test, String message) {
|
|
||||||
if (!test) {
|
|
||||||
throw new RuntimeException(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void mapTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, String>> supplier) {
|
|
||||||
Map<Long, String> map = ReferencedKeyMap.create(isSoft, supplier);
|
|
||||||
populate(map);
|
|
||||||
collect();
|
|
||||||
// assertTrue(map.isEmpty() || isSoft, "Weak not collecting");
|
|
||||||
populate(map);
|
|
||||||
methods(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void methods(Map<Long, String> map) {
|
|
||||||
assertTrue(map.size() == 26, "missing key");
|
|
||||||
assertTrue(map.containsKey(BASE_KEY + 'a' -'a'), "missing key");
|
|
||||||
assertTrue(map.get(BASE_KEY + 'b' -'a').equals("b"), "wrong key");
|
|
||||||
assertTrue(map.containsValue("c"), "missing value");
|
|
||||||
map.remove(BASE_KEY + 'd' -'a');
|
|
||||||
assertTrue(map.get(BASE_KEY + 'd' -'a') == null, "not removed");
|
|
||||||
map.putAll(Map.of(1L, "A", 2L, "B"));
|
|
||||||
assertTrue(map.get(2L).equals("B"), "collection not added");
|
|
||||||
assertTrue(map.keySet().contains(1L), "key missing");
|
|
||||||
assertTrue(map.values().contains("A"), "key missing");
|
|
||||||
assertTrue(map.entrySet().contains(Map.entry(1L, "A")), "key missing");
|
|
||||||
map.putIfAbsent(3L, "C");
|
|
||||||
assertTrue(map.get(3L).equals("C"), "key missing");
|
|
||||||
map.putIfAbsent(2L, "D");
|
|
||||||
assertTrue(map.get(2L).equals("B"), "key replaced");
|
|
||||||
map.remove(3L);
|
|
||||||
assertTrue(map.get(3L) == null, "key not removed");
|
|
||||||
map.replace(2L, "D");
|
|
||||||
assertTrue(map.get(2L).equals("D"), "key not replaced");
|
|
||||||
map.replace(2L, "B", "E");
|
|
||||||
assertTrue(map.get(2L).equals("D"), "key replaced");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void collect() {
|
|
||||||
System.gc();
|
|
||||||
sleep();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sleep() {
|
|
||||||
try {
|
|
||||||
Thread.sleep(100L);
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void populate(Map<Long, String> map) {
|
|
||||||
for (int i = 0; i < 26; i++) {
|
|
||||||
Long key = BASE_KEY + i;
|
|
||||||
String value = String.valueOf((char) ('a' + i));
|
|
||||||
map.put(key, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
175
test/jdk/jdk/internal/util/ReferencedKeyTest.java
Normal file
175
test/jdk/jdk/internal/util/ReferencedKeyTest.java
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
|
||||||
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
|
*
|
||||||
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* version 2 for more details (a copy is included in the LICENSE file that
|
||||||
|
* accompanied this code).
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License version
|
||||||
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||||
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||||
|
* or visit www.oracle.com if you need additional information or have any
|
||||||
|
* questions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8285932 8310913
|
||||||
|
* @summary Test features provided by the ReferencedKeyMap/ReferencedKeySet classes.
|
||||||
|
* @modules java.base/jdk.internal.util
|
||||||
|
* @compile --patch-module java.base=${test.src} ReferencedKeyTest.java
|
||||||
|
* @run main/othervm --patch-module java.base=${test.class.path} jdk.internal.util.ReferencedKeyTest
|
||||||
|
*/
|
||||||
|
|
||||||
|
package jdk.internal.util;
|
||||||
|
|
||||||
|
import java.lang.ref.PhantomReference;
|
||||||
|
import java.lang.ref.Reference;
|
||||||
|
import java.lang.ref.ReferenceQueue;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.BooleanSupplier;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class ReferencedKeyTest {
|
||||||
|
static long BASE_KEY = 10_000_000L;
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
mapTest(false, HashMap::new);
|
||||||
|
mapTest(true, HashMap::new);
|
||||||
|
mapTest(false, ConcurrentHashMap::new);
|
||||||
|
mapTest(true, ConcurrentHashMap::new);
|
||||||
|
|
||||||
|
setTest(false, HashMap::new);
|
||||||
|
setTest(true, HashMap::new);
|
||||||
|
setTest(false, ConcurrentHashMap::new);
|
||||||
|
setTest(true, ConcurrentHashMap::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void assertTrue(boolean test, String message) {
|
||||||
|
if (!test) {
|
||||||
|
throw new RuntimeException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void mapTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, String>> supplier) {
|
||||||
|
Map<Long, String> map = ReferencedKeyMap.create(isSoft, supplier);
|
||||||
|
populate(map);
|
||||||
|
if (!isSoft) {
|
||||||
|
if (!collect(() -> map.isEmpty())) {
|
||||||
|
throw new RuntimeException("WeakReference map not collecting!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populate(map);
|
||||||
|
methods(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setTest(boolean isSoft, Supplier<Map<ReferenceKey<Long>, ReferenceKey<Long>>> supplier) {
|
||||||
|
ReferencedKeySet<Long> set = ReferencedKeySet.create(isSoft, supplier);
|
||||||
|
populate(set);
|
||||||
|
if (!isSoft) {
|
||||||
|
if (!collect(() -> set.isEmpty())) {
|
||||||
|
throw new RuntimeException("WeakReference set not collecting!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
populate(set);
|
||||||
|
methods(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void methods(Map<Long, String> map) {
|
||||||
|
assertTrue(map.size() == 26, "missing key");
|
||||||
|
assertTrue(map.containsKey(BASE_KEY + 'a' -'a'), "missing key");
|
||||||
|
assertTrue(map.get(BASE_KEY + 'b' -'a').equals("b"), "wrong key");
|
||||||
|
assertTrue(map.containsValue("c"), "missing value");
|
||||||
|
map.remove(BASE_KEY + 'd' -'a');
|
||||||
|
assertTrue(map.get(BASE_KEY + 'd' -'a') == null, "not removed");
|
||||||
|
map.putAll(Map.of(1L, "A", 2L, "B"));
|
||||||
|
assertTrue(map.get(2L).equals("B"), "collection not added");
|
||||||
|
assertTrue(map.containsKey(1L), "key missing");
|
||||||
|
assertTrue(map.containsValue("A"), "key missing");
|
||||||
|
assertTrue(map.entrySet().contains(Map.entry(1L, "A")), "key missing");
|
||||||
|
map.putIfAbsent(3L, "C");
|
||||||
|
assertTrue(map.get(3L).equals("C"), "key missing");
|
||||||
|
map.putIfAbsent(2L, "D");
|
||||||
|
assertTrue(map.get(2L).equals("B"), "key replaced");
|
||||||
|
map.remove(3L);
|
||||||
|
assertTrue(map.get(3L) == null, "key not removed");
|
||||||
|
map.replace(2L, "D");
|
||||||
|
assertTrue(map.get(2L).equals("D"), "key not replaced");
|
||||||
|
map.replace(2L, "B", "E");
|
||||||
|
assertTrue(map.get(2L).equals("D"), "key replaced");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void methods(ReferencedKeySet<Long> set) {
|
||||||
|
assertTrue(set.size() == 26, "missing key");
|
||||||
|
assertTrue(set.contains(BASE_KEY + 3), "missing key");
|
||||||
|
set.remove(BASE_KEY + 3);
|
||||||
|
assertTrue(!set.contains(BASE_KEY + 3), "not removed");
|
||||||
|
Long element1 = set.get(BASE_KEY + 2);
|
||||||
|
Long element2 = set.get(BASE_KEY + 3);
|
||||||
|
Long element3 = set.get(BASE_KEY + 4);
|
||||||
|
Long intern1 = set.intern(BASE_KEY + 2);
|
||||||
|
Long intern2 = set.intern(BASE_KEY + 3);
|
||||||
|
Long intern3 = set.intern(BASE_KEY + 4, e -> e);
|
||||||
|
assertTrue(element1 != null, "missing key");
|
||||||
|
assertTrue(element2 == null, "not removed");
|
||||||
|
assertTrue(element1 == intern1, "intern failed"); // must be same object
|
||||||
|
assertTrue(intern2 != null, "intern failed");
|
||||||
|
assertTrue(element3 == intern3, "intern failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrowed from jdk.test.lib.util.ForceGC but couldn't use from java.base/jdk.internal.util
|
||||||
|
static boolean collect(BooleanSupplier booleanSupplier) {
|
||||||
|
ReferenceQueue<Object> queue = new ReferenceQueue<>();
|
||||||
|
Object obj = new Object();
|
||||||
|
PhantomReference<Object> ref = new PhantomReference<>(obj, queue);
|
||||||
|
obj = null;
|
||||||
|
Reference.reachabilityFence(obj);
|
||||||
|
Reference.reachabilityFence(ref);
|
||||||
|
long timeout = 1000L;
|
||||||
|
long quanta = 200L;
|
||||||
|
long retries = timeout / quanta;
|
||||||
|
|
||||||
|
for (; retries >= 0; retries--) {
|
||||||
|
if (booleanSupplier.getAsBoolean()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.gc();
|
||||||
|
|
||||||
|
try {
|
||||||
|
queue.remove(quanta);
|
||||||
|
} catch (InterruptedException ie) {
|
||||||
|
// ignore, the loop will try again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return booleanSupplier.getAsBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populate(Map<Long, String> map) {
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
|
Long key = BASE_KEY + i;
|
||||||
|
String value = String.valueOf((char) ('a' + i));
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void populate(Set<Long> set) {
|
||||||
|
for (int i = 0; i < 26; i++) {
|
||||||
|
Long value = BASE_KEY + i;
|
||||||
|
set.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue