8356080: Address post-integration comments for Stable Values
Reviewed-by: liach
This commit is contained in:
parent
fa419489d3
commit
066477de80
@ -384,8 +384,8 @@ import java.util.function.Supplier;
|
|||||||
* <p>
|
* <p>
|
||||||
* The method {@link #orElseSet(Supplier)} guarantees that the provided
|
* The method {@link #orElseSet(Supplier)} guarantees that the provided
|
||||||
* {@linkplain Supplier} is invoked successfully at most once, even under race.
|
* {@linkplain Supplier} is invoked successfully at most once, even under race.
|
||||||
* Invocations of {@link #setOrThrow(Object)} form a total order of zero or more
|
* Invocations of {@link #orElseSet(Supplier)} form a total order of zero or
|
||||||
* exceptional invocations followed by zero (if the contents were already set) or one
|
* more exceptional invocations followed by zero (if the contents were already set) or one
|
||||||
* successful invocation. Since stable functions and stable collections are built on top
|
* successful invocation. Since stable functions and stable collections are built on top
|
||||||
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
|
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
|
||||||
* too are thread safe and guarantee at-most-once-per-input invocation.
|
* too are thread safe and guarantee at-most-once-per-input invocation.
|
||||||
@ -444,8 +444,6 @@ import java.util.function.Supplier;
|
|||||||
public sealed interface StableValue<T>
|
public sealed interface StableValue<T>
|
||||||
permits StableValueImpl {
|
permits StableValueImpl {
|
||||||
|
|
||||||
// Principal methods
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tries to set the contents of this StableValue to the provided {@code contents}.
|
* Tries to set the contents of this StableValue to the provided {@code contents}.
|
||||||
* The contents of this StableValue can only be set once, implying this method only
|
* The contents of this StableValue can only be set once, implying this method only
|
||||||
@ -509,8 +507,6 @@ public sealed interface StableValue<T>
|
|||||||
*/
|
*/
|
||||||
T orElseSet(Supplier<? extends T> supplier);
|
T orElseSet(Supplier<? extends T> supplier);
|
||||||
|
|
||||||
// Convenience methods
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the contents of this StableValue to the provided {@code contents}, or, if
|
* Sets the contents of this StableValue to the provided {@code contents}, or, if
|
||||||
* already set, throws {@code IllegalStateException}.
|
* already set, throws {@code IllegalStateException}.
|
||||||
@ -519,6 +515,9 @@ public sealed interface StableValue<T>
|
|||||||
*
|
*
|
||||||
* @param contents to set
|
* @param contents to set
|
||||||
* @throws IllegalStateException if the contents was already set
|
* @throws IllegalStateException if the contents was already set
|
||||||
|
* @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)}
|
||||||
|
* recursively attempts to set this stable value by calling this method
|
||||||
|
* directly or indirectly.
|
||||||
*/
|
*/
|
||||||
void setOrThrow(T contents);
|
void setOrThrow(T contents);
|
||||||
|
|
||||||
@ -573,8 +572,8 @@ public sealed interface StableValue<T>
|
|||||||
* at most once even in a multi-threaded environment. Competing threads invoking the
|
* at most once even in a multi-threaded environment. Competing threads invoking the
|
||||||
* returned supplier's {@linkplain Supplier#get() get()} method when a value is
|
* returned supplier's {@linkplain Supplier#get() get()} method when a value is
|
||||||
* already under computation will block until a value is computed or an exception is
|
* already under computation will block until a value is computed or an exception is
|
||||||
* thrown by the computing thread. The computing threads will then observe the newly
|
* thrown by the computing thread. The competing threads will then observe the newly
|
||||||
* computed value (if any) and will then never execute.
|
* computed value (if any) and will then never execute the {@code underlying} supplier.
|
||||||
* <p>
|
* <p>
|
||||||
* If the provided {@code underlying} supplier throws an exception, it is rethrown
|
* If the provided {@code underlying} supplier throws an exception, it is rethrown
|
||||||
* to the initial caller and no contents is recorded.
|
* to the initial caller and no contents is recorded.
|
||||||
@ -614,9 +613,9 @@ public sealed interface StableValue<T>
|
|||||||
* function for the same input, an {@linkplain IllegalStateException} will
|
* function for the same input, an {@linkplain IllegalStateException} will
|
||||||
* be thrown.
|
* be thrown.
|
||||||
*
|
*
|
||||||
* @param size the size of the allowed inputs in the continuous
|
* @param size the upper bound of the range {@code [0, size)} indicating
|
||||||
* interval {@code [0, size)}
|
* the allowed inputs
|
||||||
* @param underlying IntFunction used to compute cached values
|
* @param underlying {@code IntFunction} used to compute cached values
|
||||||
* @param <R> the type of results delivered by the returned IntFunction
|
* @param <R> the type of results delivered by the returned IntFunction
|
||||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||||
*/
|
*/
|
||||||
@ -684,7 +683,7 @@ public sealed interface StableValue<T>
|
|||||||
* If invoking the provided {@code mapper} function throws an exception, it
|
* If invoking the provided {@code mapper} function throws an exception, it
|
||||||
* is rethrown to the initial caller and no value for the element is recorded.
|
* is rethrown to the initial caller and no value for the element is recorded.
|
||||||
* <p>
|
* <p>
|
||||||
* Any direct {@link List#subList(int, int) subList} or {@link List#reversed()} views
|
* Any {@link List#subList(int, int) subList} or {@link List#reversed()} views
|
||||||
* of the returned list are also stable.
|
* of the returned list are also stable.
|
||||||
* <p>
|
* <p>
|
||||||
* The returned list and its {@link List#subList(int, int) subList} or
|
* The returned list and its {@link List#subList(int, int) subList} or
|
||||||
@ -727,8 +726,8 @@ public sealed interface StableValue<T>
|
|||||||
* is rethrown to the initial caller and no value associated with the provided key
|
* is rethrown to the initial caller and no value associated with the provided key
|
||||||
* is recorded.
|
* is recorded.
|
||||||
* <p>
|
* <p>
|
||||||
* Any direct {@link Map#values()} or {@link Map#entrySet()} views
|
* Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are
|
||||||
* of the returned map are also stable.
|
* also stable.
|
||||||
* <p>
|
* <p>
|
||||||
* The returned map is unmodifiable and does not implement the
|
* The returned map is unmodifiable and does not implement the
|
||||||
* {@linkplain Collection##optional-operations optional operations} in the
|
* {@linkplain Collection##optional-operations optional operations} in the
|
||||||
|
@ -47,7 +47,6 @@ import jdk.internal.lang.stable.StableUtil;
|
|||||||
import jdk.internal.lang.stable.StableValueImpl;
|
import jdk.internal.lang.stable.StableValueImpl;
|
||||||
import jdk.internal.misc.CDS;
|
import jdk.internal.misc.CDS;
|
||||||
import jdk.internal.util.ArraysSupport;
|
import jdk.internal.util.ArraysSupport;
|
||||||
import jdk.internal.util.NullableKeyValueHolder;
|
|
||||||
import jdk.internal.vm.annotation.ForceInline;
|
import jdk.internal.vm.annotation.ForceInline;
|
||||||
import jdk.internal.vm.annotation.Stable;
|
import jdk.internal.vm.annotation.Stable;
|
||||||
|
|
||||||
@ -137,9 +136,11 @@ class ImmutableCollections {
|
|||||||
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
|
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
|
||||||
}
|
}
|
||||||
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
|
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
|
||||||
return ImmutableCollections.stableList(size, mapper);
|
// A stable list is not Serializable, so we cannot return `List.of()` if `size == 0`
|
||||||
|
return new StableList<>(size, mapper);
|
||||||
}
|
}
|
||||||
public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
||||||
|
// A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
|
||||||
return new StableMap<>(keys, mapper);
|
return new StableMap<>(keys, mapper);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -264,11 +265,6 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
|
|
||||||
// A lazy list is not Serializable so, we cannot return `List.of()` if size == 0
|
|
||||||
return new StableList<>(size, mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------- List Implementations ----------
|
// ---------- List Implementations ----------
|
||||||
|
|
||||||
@jdk.internal.ValueBased
|
@jdk.internal.ValueBased
|
||||||
@ -454,17 +450,17 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static final class SubList<E> extends AbstractImmutableList<E>
|
static sealed class SubList<E> extends AbstractImmutableList<E>
|
||||||
implements RandomAccess {
|
implements RandomAccess {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final AbstractImmutableList<E> root;
|
final AbstractImmutableList<E> root;
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final int offset;
|
final int offset;
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final int size;
|
final int size;
|
||||||
|
|
||||||
private SubList(AbstractImmutableList<E> root, int offset, int size) {
|
private SubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||||
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
|
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
|
||||||
@ -517,9 +513,8 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowNulls() {
|
boolean allowNulls() {
|
||||||
return root instanceof ListN<?> listN && listN.allowNulls
|
return root instanceof ListN<?> listN && listN.allowNulls;
|
||||||
|| root instanceof StableList<E>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -572,14 +567,6 @@ class ImmutableCollections {
|
|||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
if (root instanceof StableList<E> stableList) {
|
|
||||||
return StableUtil.renderElements(root, "StableList", stableList.delegates, offset, size);
|
|
||||||
} else {
|
|
||||||
return super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@jdk.internal.ValueBased
|
@jdk.internal.ValueBased
|
||||||
@ -797,8 +784,15 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface HasStableDelegates<E> {
|
||||||
|
StableValueImpl<E>[] delegates();
|
||||||
|
}
|
||||||
|
|
||||||
@jdk.internal.ValueBased
|
@jdk.internal.ValueBased
|
||||||
static final class StableList<E> extends AbstractImmutableList<E> {
|
static final class StableList<E>
|
||||||
|
extends AbstractImmutableList<E>
|
||||||
|
implements HasStableDelegates<E> {
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final IntFunction<? extends E> mapper;
|
private final IntFunction<? extends E> mapper;
|
||||||
@ -879,11 +873,69 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public List<E> subList(int fromIndex, int toIndex) {
|
||||||
return StableUtil.renderElements(this, "StableList", delegates);
|
subListRangeCheck(fromIndex, toIndex, size());
|
||||||
|
return StableSubList.fromStableList(this, fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class StableReverseOrderListView<E> extends ReverseOrderListView.Rand<E> {
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StableUtil.renderElements(this, "StableCollection", delegates);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StableValueImpl<E>[] delegates() {
|
||||||
|
return delegates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StableSubList<E> extends SubList<E>
|
||||||
|
implements HasStableDelegates<E> {
|
||||||
|
|
||||||
|
private StableSubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||||
|
super(root, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<E> reversed() {
|
||||||
|
return new StableReverseOrderListView<>(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<E> subList(int fromIndex, int toIndex) {
|
||||||
|
subListRangeCheck(fromIndex, toIndex, size());
|
||||||
|
return StableSubList.fromStableSubList(this, fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return StableUtil.renderElements(this, "StableCollection", delegates());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean allowNulls() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StableValueImpl<E>[] delegates() {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final var rootDelegates = ((HasStableDelegates<E>) root).delegates();
|
||||||
|
return Arrays.copyOfRange(rootDelegates, offset, offset + size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <E> SubList<E> fromStableList(StableList<E> list, int fromIndex, int toIndex) {
|
||||||
|
return new StableSubList<>(list, fromIndex, toIndex - fromIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <E> SubList<E> fromStableSubList(StableSubList<E> parent, int fromIndex, int toIndex) {
|
||||||
|
return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class StableReverseOrderListView<E>
|
||||||
|
extends ReverseOrderListView.Rand<E>
|
||||||
|
implements HasStableDelegates<E> {
|
||||||
|
|
||||||
private StableReverseOrderListView(List<E> base) {
|
private StableReverseOrderListView(List<E> base) {
|
||||||
super(base, false);
|
super(base, false);
|
||||||
@ -892,17 +944,23 @@ class ImmutableCollections {
|
|||||||
// This method does not evaluate the elements
|
// This method does not evaluate the elements
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StableValueImpl<E>[] delegates = ((StableList<E>)base).delegates;
|
return StableUtil.renderElements(this, "StableCollection", delegates());
|
||||||
final StableValueImpl<E>[] reversed = ArraysSupport.reverse(
|
|
||||||
Arrays.copyOf(delegates, delegates.length));
|
|
||||||
return StableUtil.renderElements(base, "Collection", reversed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<E> reversed() {
|
public List<E> subList(int fromIndex, int toIndex) {
|
||||||
return base;
|
final int size = base.size();
|
||||||
|
subListRangeCheck(fromIndex, toIndex, size);
|
||||||
|
return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StableValueImpl<E>[] delegates() {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
final var baseDelegates = ((HasStableDelegates<E>) base).delegates();
|
||||||
|
return ArraysSupport.reverse(
|
||||||
|
Arrays.copyOf(baseDelegates, baseDelegates.length));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1560,7 +1618,7 @@ class ImmutableCollections {
|
|||||||
|
|
||||||
@Override public boolean containsKey(Object o) { return delegate.containsKey(o); }
|
@Override public boolean containsKey(Object o) { return delegate.containsKey(o); }
|
||||||
@Override public int size() { return delegate.size(); }
|
@Override public int size() { return delegate.size(); }
|
||||||
@Override public Set<Map.Entry<K, V>> entrySet() { return new StableMapEntrySet(); }
|
@Override public Set<Map.Entry<K, V>> entrySet() { return StableMapEntrySet.of(this); }
|
||||||
|
|
||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
@ -1582,32 +1640,49 @@ class ImmutableCollections {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@jdk.internal.ValueBased
|
@jdk.internal.ValueBased
|
||||||
final class StableMapEntrySet extends AbstractImmutableSet<Map.Entry<K, V>> {
|
static final class StableMapEntrySet<K, V> extends AbstractImmutableSet<Map.Entry<K, V>> {
|
||||||
|
|
||||||
|
// Use a separate field for the outer class in order to facilitate
|
||||||
|
// a @Stable annotation.
|
||||||
|
@Stable
|
||||||
|
private final StableMap<K, V> outer;
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
|
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
|
||||||
|
|
||||||
StableMapEntrySet() {
|
private StableMapEntrySet(StableMap<K, V> outer) {
|
||||||
this.delegateEntrySet = delegate.entrySet();
|
this.outer = outer;
|
||||||
|
this.delegateEntrySet = outer.delegate.entrySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Iterator<Map.Entry<K, V>> iterator() { return new LazyMapIterator(); }
|
@Override public Iterator<Map.Entry<K, V>> iterator() { return LazyMapIterator.of(this); }
|
||||||
@Override public int size() { return delegateEntrySet.size(); }
|
@Override public int size() { return delegateEntrySet.size(); }
|
||||||
@Override public int hashCode() { return StableMap.this.hashCode(); }
|
@Override public int hashCode() { return outer.hashCode(); }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return StableUtil.renderMappings(this, "StableSet", delegateEntrySet, false);
|
return StableUtil.renderMappings(this, "StableCollection", delegateEntrySet, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For @ValueBased
|
||||||
|
private static <K, V> StableMapEntrySet<K, V> of(StableMap<K, V> outer) {
|
||||||
|
return new StableMapEntrySet<>(outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@jdk.internal.ValueBased
|
@jdk.internal.ValueBased
|
||||||
final class LazyMapIterator implements Iterator<Map.Entry<K, V>> {
|
static final class LazyMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
|
||||||
|
|
||||||
|
// Use a separate field for the outer class in order to facilitate
|
||||||
|
// a @Stable annotation.
|
||||||
|
@Stable
|
||||||
|
private final StableMapEntrySet<K, V> outer;
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
|
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
|
||||||
|
|
||||||
LazyMapIterator() {
|
private LazyMapIterator(StableMapEntrySet<K, V> outer) {
|
||||||
this.delegateIterator = delegateEntrySet.iterator();
|
this.outer = outer;
|
||||||
|
this.delegateIterator = outer.delegateEntrySet.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public boolean hasNext() { return delegateIterator.hasNext(); }
|
@Override public boolean hasNext() { return delegateIterator.hasNext(); }
|
||||||
@ -1616,8 +1691,8 @@ class ImmutableCollections {
|
|||||||
public Entry<K, V> next() {
|
public Entry<K, V> next() {
|
||||||
final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next();
|
final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next();
|
||||||
final K k = inner.getKey();
|
final K k = inner.getKey();
|
||||||
return new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() {
|
return new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||||
@Override public V get() { return mapper.apply(k); }}));
|
@Override public V get() { return outer.outer.mapper.apply(k); }});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1627,25 +1702,60 @@ class ImmutableCollections {
|
|||||||
@Override
|
@Override
|
||||||
public void accept(Entry<K, StableValueImpl<V>> inner) {
|
public void accept(Entry<K, StableValueImpl<V>> inner) {
|
||||||
final K k = inner.getKey();
|
final K k = inner.getKey();
|
||||||
action.accept(new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() {
|
action.accept(new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||||
@Override public V get() { return mapper.apply(k); }})));
|
@Override public V get() { return outer.outer.mapper.apply(k); }}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
delegateIterator.forEachRemaining(innerAction);
|
delegateIterator.forEachRemaining(innerAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For @ValueBased
|
||||||
|
private static <K, V> LazyMapIterator<K, V> of(StableMapEntrySet<K, V> outer) {
|
||||||
|
return new LazyMapIterator<>(outer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record StableEntry<K, V>(K getKey, // trick
|
||||||
|
StableValueImpl<V> stableValue,
|
||||||
|
Supplier<? extends V> supplier) implements Map.Entry<K, V> {
|
||||||
|
|
||||||
|
@Override public V setValue(V value) { throw uoe(); }
|
||||||
|
@Override public V getValue() { return stableValue.orElseSet(supplier); }
|
||||||
|
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
|
||||||
|
@Override public String toString() { return getKey() + "=" + stableValue.toString(); }
|
||||||
|
@Override public boolean equals(Object o) {
|
||||||
|
return o instanceof Map.Entry<?, ?> e
|
||||||
|
&& Objects.equals(getKey(), e.getKey())
|
||||||
|
// Invoke `getValue()` as late as possible to avoid evaluation
|
||||||
|
&& Objects.equals(getValue(), e.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<V> values() {
|
public Collection<V> values() {
|
||||||
return new StableMapValues();
|
return StableMapValues.of(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
final class StableMapValues extends AbstractImmutableCollection<V> {
|
@jdk.internal.ValueBased
|
||||||
@Override public Iterator<V> iterator() { return new ValueIterator(); }
|
static final class StableMapValues<V> extends AbstractImmutableCollection<V> {
|
||||||
@Override public int size() { return StableMap.this.size(); }
|
|
||||||
@Override public boolean isEmpty() { return StableMap.this.isEmpty();}
|
// Use a separate field for the outer class in order to facilitate
|
||||||
@Override public boolean contains(Object v) { return StableMap.this.containsValue(v); }
|
// a @Stable annotation.
|
||||||
|
@Stable
|
||||||
|
private final StableMap<?, V> outer;
|
||||||
|
|
||||||
|
private StableMapValues(StableMap<?, V> outer) {
|
||||||
|
this.outer = outer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public Iterator<V> iterator() { return outer.new ValueIterator(); }
|
||||||
|
@Override public int size() { return outer.size(); }
|
||||||
|
@Override public boolean isEmpty() { return outer.isEmpty();}
|
||||||
|
@Override public boolean contains(Object v) { return outer.containsValue(v); }
|
||||||
|
|
||||||
private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
|
private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
|
||||||
@Override
|
@Override
|
||||||
@ -1656,9 +1766,15 @@ class ImmutableCollections {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StableValueImpl<?>[] values = delegate.values().toArray(GENERATOR);
|
final StableValueImpl<?>[] values = outer.delegate.values().toArray(GENERATOR);
|
||||||
return StableUtil.renderElements(StableMap.this, "StableMap", values);
|
return StableUtil.renderElements(this, "StableCollection", values);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For @ValueBased
|
||||||
|
private static <V> StableMapValues<V> of(StableMap<?, V> outer) {
|
||||||
|
return new StableMapValues<>(outer);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -33,14 +33,17 @@ import java.util.function.UnaryOperator;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
import jdk.internal.util.ArraysSupport;
|
import jdk.internal.util.ArraysSupport;
|
||||||
|
import jdk.internal.vm.annotation.Stable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides a reverse-ordered view of a List. Not serializable.
|
* Provides a reverse-ordered view of a List. Not serializable.
|
||||||
*/
|
*/
|
||||||
class ReverseOrderListView<E> implements List<E> {
|
class ReverseOrderListView<E> implements List<E> {
|
||||||
|
|
||||||
|
@Stable
|
||||||
final List<E> base;
|
final List<E> base;
|
||||||
final boolean modifiable;
|
@Stable
|
||||||
|
final Boolean modifiable;
|
||||||
|
|
||||||
public static <T> List<T> of(List<T> list, boolean modifiable) {
|
public static <T> List<T> of(List<T> list, boolean modifiable) {
|
||||||
if (list instanceof RandomAccess) {
|
if (list instanceof RandomAccess) {
|
||||||
|
@ -46,7 +46,10 @@ import java.util.function.Supplier;
|
|||||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||||
* rely on reflection, MethodHandles, Streams etc.
|
* rely on reflection, MethodHandles, Streams etc.
|
||||||
*
|
*
|
||||||
|
* @param enumType the class type of the Enum
|
||||||
* @param firstOrdinal the lowest ordinal used
|
* @param firstOrdinal the lowest ordinal used
|
||||||
|
* @param member an int predicate that can be used to test if an enum is a member
|
||||||
|
* of the valid inputs (as there might be "holes")
|
||||||
* @param delegates a delegate array of inputs to StableValue mappings
|
* @param delegates a delegate array of inputs to StableValue mappings
|
||||||
* @param original the original Function
|
* @param original the original Function
|
||||||
* @param <E> the type of the input to the function
|
* @param <E> the type of the input to the function
|
||||||
@ -64,10 +67,8 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
|||||||
throw new IllegalArgumentException("Input not allowed: " + value);
|
throw new IllegalArgumentException("Input not allowed: " + value);
|
||||||
}
|
}
|
||||||
final int index = value.ordinal() - firstOrdinal;
|
final int index = value.ordinal() - firstOrdinal;
|
||||||
final StableValueImpl<R> delegate;
|
|
||||||
// Since we did the member.test above, we know the index is in bounds
|
// Since we did the member.test above, we know the index is in bounds
|
||||||
delegate = delegates[index];
|
return delegates[index].orElseSet(new Supplier<R>() {
|
||||||
return delegate.orElseSet(new Supplier<R>() {
|
|
||||||
@Override public R get() { return original.apply(value); }});
|
@Override public R get() { return original.apply(value); }});
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -84,8 +85,8 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(delegates.length);
|
||||||
final E[] enumElements = enumType.getEnumConstants();
|
final E[] enumElements = enumType.getEnumConstants();
|
||||||
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(enumElements.length);
|
|
||||||
int ordinal = firstOrdinal;
|
int ordinal = firstOrdinal;
|
||||||
for (int i = 0; i < delegates.length; i++, ordinal++) {
|
for (int i = 0; i < delegates.length; i++, ordinal++) {
|
||||||
if (member.test(ordinal)) {
|
if (member.test(ordinal)) {
|
||||||
@ -99,7 +100,7 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
|||||||
public static <T, E extends Enum<E>, R> Function<T, R> of(Set<? extends T> inputs,
|
public static <T, E extends Enum<E>, R> Function<T, R> of(Set<? extends T> inputs,
|
||||||
Function<? super T, ? extends R> original) {
|
Function<? super T, ? extends R> original) {
|
||||||
// The input set is not empty
|
// The input set is not empty
|
||||||
final Class<E> enumType = (Class<E>)inputs.iterator().next().getClass();
|
final Class<E> enumType = ((E) inputs.iterator().next()).getDeclaringClass();
|
||||||
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
|
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
|
||||||
int min = Integer.MAX_VALUE;
|
int min = Integer.MAX_VALUE;
|
||||||
int max = Integer.MIN_VALUE;
|
int max = Integer.MIN_VALUE;
|
||||||
|
@ -32,7 +32,7 @@ import java.util.Set;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
// Note: It would be possible to just use `LazyMap::get` with some additional logic
|
// Note: It would be possible to just use `StableMap::get` with some additional logic
|
||||||
// instead of this class but explicitly providing a class like this provides better
|
// instead of this class but explicitly providing a class like this provides better
|
||||||
// debug capability, exception handling, and may provide better performance.
|
// debug capability, exception handling, and may provide better performance.
|
||||||
/**
|
/**
|
||||||
|
@ -31,9 +31,6 @@ import jdk.internal.vm.annotation.Stable;
|
|||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
// Note: It would be possible to just use `LazyList::get` instead of this
|
|
||||||
// class but explicitly providing a class like this provides better
|
|
||||||
// debug capability, exception handling, and may provide better performance.
|
|
||||||
/**
|
/**
|
||||||
* Implementation of a stable IntFunction.
|
* Implementation of a stable IntFunction.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -58,7 +58,7 @@ public record StableSupplier<T>(StableValueImpl<T> delegate,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final Object t = delegate.wrappedContentAcquire();
|
final Object t = delegate.wrappedContentsAcquire();
|
||||||
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
|
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ public final class StableUtil {
|
|||||||
int length) {
|
int length) {
|
||||||
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
final Object value = delegates[i + offset].wrappedContentAcquire();
|
final Object value = delegates[i + offset].wrappedContentsAcquire();
|
||||||
if (value == self) {
|
if (value == self) {
|
||||||
sj.add("(this " + selfName + ")");
|
sj.add("(this " + selfName + ")");
|
||||||
} else {
|
} else {
|
||||||
@ -63,10 +63,10 @@ public final class StableUtil {
|
|||||||
boolean curly) {
|
boolean curly) {
|
||||||
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
|
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
|
||||||
for (var e : delegates) {
|
for (var e : delegates) {
|
||||||
final Object value = e.getValue().wrappedContentAcquire();
|
final Object value = e.getValue().wrappedContentsAcquire();
|
||||||
final String valueString;
|
final String valueString;
|
||||||
if (value == self) {
|
if (value == self) {
|
||||||
valueString = ("(this ") + selfName + ")";
|
valueString = "(this " + selfName + ")";
|
||||||
} else {
|
} else {
|
||||||
valueString = StableValueImpl.renderWrapped(value);
|
valueString = StableValueImpl.renderWrapped(value);
|
||||||
}
|
}
|
||||||
|
@ -51,10 +51,10 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
|
|
||||||
// Unsafe offsets for direct field access
|
// Unsafe offsets for direct field access
|
||||||
|
|
||||||
private static final long CONTENT_OFFSET =
|
private static final long CONTENTS_OFFSET =
|
||||||
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
|
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
|
||||||
// Used to indicate a holder value is `null` (see field `value` below)
|
|
||||||
// A wrapper method `nullSentinel()` is used for generic type conversion.
|
// Used to indicate a holder value is `null` (see field `contents` below)
|
||||||
private static final Object NULL_SENTINEL = new Object();
|
private static final Object NULL_SENTINEL = new Object();
|
||||||
|
|
||||||
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
|
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
|
||||||
@ -77,13 +77,13 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
public boolean trySet(T contents) {
|
public boolean trySet(T contents) {
|
||||||
if (wrappedContentAcquire() != null) {
|
if (wrappedContentsAcquire() != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Prevent reentry via an orElseSet(supplier)
|
// Prevent reentry via an orElseSet(supplier)
|
||||||
preventReentry();
|
preventReentry();
|
||||||
// Mutual exclusion is required here as `orElseSet` might also
|
// Mutual exclusion is required here as `orElseSet` might also
|
||||||
// attempt to modify the `wrappedValue`
|
// attempt to modify `this.contents`
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
return wrapAndSet(contents);
|
return wrapAndSet(contents);
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
public T orElseThrow() {
|
public T orElseThrow() {
|
||||||
final Object t = wrappedContentAcquire();
|
final Object t = wrappedContentsAcquire();
|
||||||
if (t == null) {
|
if (t == null) {
|
||||||
throw new NoSuchElementException("No contents set");
|
throw new NoSuchElementException("No contents set");
|
||||||
}
|
}
|
||||||
@ -112,21 +112,21 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
public T orElse(T other) {
|
public T orElse(T other) {
|
||||||
final Object t = wrappedContentAcquire();
|
final Object t = wrappedContentsAcquire();
|
||||||
return (t == null) ? other : unwrap(t);
|
return (t == null) ? other : unwrap(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
public boolean isSet() {
|
public boolean isSet() {
|
||||||
return wrappedContentAcquire() != null;
|
return wrappedContentsAcquire() != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ForceInline
|
@ForceInline
|
||||||
@Override
|
@Override
|
||||||
public T orElseSet(Supplier<? extends T> supplier) {
|
public T orElseSet(Supplier<? extends T> supplier) {
|
||||||
Objects.requireNonNull(supplier);
|
Objects.requireNonNull(supplier);
|
||||||
final Object t = wrappedContentAcquire();
|
final Object t = wrappedContentsAcquire();
|
||||||
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
|
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,7 +149,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final Object t = wrappedContentAcquire();
|
final Object t = wrappedContentsAcquire();
|
||||||
return t == this
|
return t == this
|
||||||
? "(this StableValue)"
|
? "(this StableValue)"
|
||||||
: renderWrapped(t);
|
: renderWrapped(t);
|
||||||
@ -158,8 +158,8 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
// Internal methods shared with other internal classes
|
// Internal methods shared with other internal classes
|
||||||
|
|
||||||
@ForceInline
|
@ForceInline
|
||||||
public Object wrappedContentAcquire() {
|
public Object wrappedContentsAcquire() {
|
||||||
return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET);
|
return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String renderWrapped(Object t) {
|
static String renderWrapped(Object t) {
|
||||||
@ -185,11 +185,11 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
|||||||
* @return if the contents was set
|
* @return if the contents was set
|
||||||
*/
|
*/
|
||||||
@ForceInline
|
@ForceInline
|
||||||
private boolean wrapAndSet(Object newValue) {
|
private boolean wrapAndSet(T newValue) {
|
||||||
assert Thread.holdsLock(this);
|
assert Thread.holdsLock(this);
|
||||||
// We know we hold the monitor here so plain semantic is enough
|
// We know we hold the monitor here so plain semantic is enough
|
||||||
if (contents == null) {
|
if (contents == null) {
|
||||||
UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue));
|
UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -52,7 +52,13 @@ final class StableFunctionTest {
|
|||||||
ZERO(0),
|
ZERO(0),
|
||||||
ILLEGAL_BEFORE(-1),
|
ILLEGAL_BEFORE(-1),
|
||||||
// Valid values
|
// Valid values
|
||||||
THIRTEEN(13),
|
THIRTEEN(13) {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
// getEnumConstants will be `null` for this enum as it is overridden
|
||||||
|
return super.toString()+" (Overridden)";
|
||||||
|
}
|
||||||
|
},
|
||||||
ILLEGAL_BETWEEN(-2),
|
ILLEGAL_BETWEEN(-2),
|
||||||
FORTY_TWO(42),
|
FORTY_TWO(42),
|
||||||
// Illegal values (not in the input set)
|
// Illegal values (not in the input set)
|
||||||
@ -196,6 +202,13 @@ final class StableFunctionTest {
|
|||||||
assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName());
|
assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void overriddenEnum() {
|
||||||
|
final var overridden = Value.THIRTEEN;
|
||||||
|
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt);
|
||||||
|
assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden));
|
||||||
|
}
|
||||||
|
|
||||||
private static Stream<Set<Value>> nonEmptySets() {
|
private static Stream<Set<Value>> nonEmptySets() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
Set.of(Value.FORTY_TWO, Value.THIRTEEN),
|
Set.of(Value.FORTY_TWO, Value.THIRTEEN),
|
||||||
|
@ -35,7 +35,6 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||||||
import org.junit.jupiter.params.provider.MethodSource;
|
import org.junit.jupiter.params.provider.MethodSource;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.IdentityHashMap;
|
import java.util.IdentityHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -46,7 +45,9 @@ import java.util.Set;
|
|||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
@ -242,18 +243,8 @@ final class StableListTest {
|
|||||||
assertEquals(eq.toString(), lazy.toString());
|
assertEquals(eq.toString(), lazy.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
void assertUnevaluated(List<Integer> subList) {
|
||||||
void subListToString() {
|
|
||||||
subListToString0(newList());
|
|
||||||
subListToString0(newList().subList(1, SIZE));
|
|
||||||
subListToString0(newList().subList(1, SIZE).subList(0, SIZE - 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
void subListToString0(List<Integer> subList) {
|
|
||||||
assertEquals(asString(".unset", subList), subList.toString());
|
assertEquals(asString(".unset", subList), subList.toString());
|
||||||
|
|
||||||
var first = subList.getFirst();
|
|
||||||
assertEquals(asString(first.toString(), subList), subList.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -272,17 +263,47 @@ final class StableListTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reversedToString() {
|
void sublistReversedToString() {
|
||||||
var reversed = newList().reversed();
|
var actual = StableValue.list(4, IDENTITY);
|
||||||
subListToString0(reversed);
|
var expected = List.of(0, 1, 2, 3);
|
||||||
|
for (UnaryOperation op : List.of(
|
||||||
|
new UnaryOperation("subList", l -> l.subList(1, 3)),
|
||||||
|
new UnaryOperation("reversed", List::reversed))) {
|
||||||
|
actual = op.apply(actual);
|
||||||
|
expected = op.apply(expected);
|
||||||
|
}
|
||||||
|
// Touch one of the elements
|
||||||
|
actual.getLast();
|
||||||
|
|
||||||
|
var actualToString = actual.toString();
|
||||||
|
var expectedToString = expected.toString().replace("2", ".unset");
|
||||||
|
assertEquals(expectedToString, actualToString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This test makes sure successive view operations retains the property
|
||||||
|
// of being a Stable view.
|
||||||
@Test
|
@Test
|
||||||
void subListReversedToString() {
|
void viewsStable() {
|
||||||
var list = newList().subList(1, SIZE - 1).reversed();
|
viewOperations().forEach(op0 -> {
|
||||||
// This combination is not lazy. There has to be a limit somewhere.
|
viewOperations().forEach( op1 -> {
|
||||||
var regularList = newRegularList().subList(1, SIZE - 1).reversed();
|
viewOperations().forEach(op2 -> {
|
||||||
assertEquals(regularList.toString(), list.toString());
|
var list = newList();
|
||||||
|
var view1 = op0.apply(list);
|
||||||
|
var view2 = op1.apply(view1);
|
||||||
|
var view3 = op2.apply(view2);
|
||||||
|
var className3 = className(view3);
|
||||||
|
var transitions = className(list) + ", " +
|
||||||
|
op0 + " -> " + className(view1) + ", " +
|
||||||
|
op1 + " -> " + className(view2) + ", " +
|
||||||
|
op2 + " -> " + className3;
|
||||||
|
assertTrue(className3.contains("Stable"), transitions);
|
||||||
|
assertUnevaluated(list);
|
||||||
|
assertUnevaluated(view1);
|
||||||
|
assertUnevaluated(view2);
|
||||||
|
assertUnevaluated(view3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -294,6 +315,23 @@ final class StableListTest {
|
|||||||
assertEquals("Recursive initialization of a stable value is illegal", x.getMessage());
|
assertEquals("Recursive initialization of a stable value is illegal", x.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void indexOfNullInViews() {
|
||||||
|
final int size = 5;
|
||||||
|
final int middle = 2;
|
||||||
|
viewOperations().forEach(op0 -> {
|
||||||
|
viewOperations().forEach( op1 -> {
|
||||||
|
viewOperations().forEach(op2 -> {
|
||||||
|
var list = StableValue.list(size, x -> x == middle ? null : x);;
|
||||||
|
var view1 = op0.apply(list);
|
||||||
|
var view2 = op1.apply(view1);
|
||||||
|
var view3 = op2.apply(view2);
|
||||||
|
assertEquals(middle, view3.indexOf(null));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Immutability
|
// Immutability
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ -362,6 +400,39 @@ final class StableListTest {
|
|||||||
assertEquals(SIZE, idMap.size());
|
assertEquals(SIZE, idMap.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void childObjectOpsLazy() {
|
||||||
|
viewOperations().forEach(op0 -> {
|
||||||
|
viewOperations().forEach(op1 -> {
|
||||||
|
viewOperations().forEach(op2 -> {
|
||||||
|
childOperations().forEach(co -> {
|
||||||
|
var list = newList();
|
||||||
|
var view1 = op0.apply(list);
|
||||||
|
var view2 = op1.apply(view1);
|
||||||
|
var view3 = op2.apply(view2);
|
||||||
|
var child = co.apply(view3);
|
||||||
|
var childClassName = className(child);
|
||||||
|
var transitions = className(list) + ", " +
|
||||||
|
op0 + " -> " + className(view1) + ", " +
|
||||||
|
op1 + " -> " + className(view2) + ", " +
|
||||||
|
op2 + " -> " + className(view3) + ", " +
|
||||||
|
co + " -> " + childClassName;
|
||||||
|
|
||||||
|
// None of these operations should trigger evaluation
|
||||||
|
var childToString = child.toString();
|
||||||
|
int childHashCode = child.hashCode();
|
||||||
|
boolean childEqualToNewObj = child.equals(new Object());
|
||||||
|
|
||||||
|
assertUnevaluated(list);
|
||||||
|
assertUnevaluated(view1);
|
||||||
|
assertUnevaluated(view2);
|
||||||
|
assertUnevaluated(view3);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Support constructs
|
// Support constructs
|
||||||
|
|
||||||
record Operation(String name,
|
record Operation(String name,
|
||||||
@ -370,6 +441,36 @@ final class StableListTest {
|
|||||||
@Override public String toString() { return name; }
|
@Override public String toString() { return name; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record UnaryOperation(String name,
|
||||||
|
UnaryOperator<List<Integer>> operator) implements UnaryOperator<List<Integer>> {
|
||||||
|
@Override public List<Integer> apply(List<Integer> list) { return operator.apply(list); }
|
||||||
|
@Override public String toString() { return name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
record ListFunction(String name,
|
||||||
|
Function<List<Integer>, Object> function) implements Function<List<Integer>, Object> {
|
||||||
|
@Override public Object apply(List<Integer> list) { return function.apply(list); }
|
||||||
|
@Override public String toString() { return name; }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<UnaryOperation> viewOperations() {
|
||||||
|
return Stream.of(
|
||||||
|
// We need identity to capture all combinations
|
||||||
|
new UnaryOperation("identity", l -> l),
|
||||||
|
new UnaryOperation("reversed", List::reversed),
|
||||||
|
new UnaryOperation("subList", l -> l.subList(0, l.size()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Stream<ListFunction> childOperations() {
|
||||||
|
return Stream.of(
|
||||||
|
// We need identity to capture all combinations
|
||||||
|
new ListFunction("iterator", List::iterator),
|
||||||
|
new ListFunction("listIterator", List::listIterator),
|
||||||
|
new ListFunction("listIterator", List::stream)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
static Stream<Operation> nullAverseOperations() {
|
static Stream<Operation> nullAverseOperations() {
|
||||||
return Stream.of(
|
return Stream.of(
|
||||||
new Operation("forEach", l -> l.forEach(null)),
|
new Operation("forEach", l -> l.forEach(null)),
|
||||||
@ -433,4 +534,7 @@ final class StableListTest {
|
|||||||
.collect(Collectors.joining(", ")) + "]";
|
.collect(Collectors.joining(", ")) + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String className(Object o) {
|
||||||
|
return o.getClass().getName();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,6 +248,37 @@ final class StableMapTest {
|
|||||||
assertEquals(KEYS, encountered);
|
assertEquals(KEYS, encountered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stableEntry() {
|
||||||
|
var map = newMap();
|
||||||
|
var entry = map.entrySet().stream()
|
||||||
|
.filter(e -> e.getKey().equals(KEY))
|
||||||
|
.findAny()
|
||||||
|
.orElseThrow();
|
||||||
|
|
||||||
|
assertEquals(KEY + "=.unset", entry.toString());
|
||||||
|
var otherDifferent = Map.entry(-1, -1);
|
||||||
|
assertNotEquals(entry, otherDifferent);
|
||||||
|
assertEquals(KEY + "=.unset", entry.toString());
|
||||||
|
var otherEqual = Map.entry(KEY, KEY);
|
||||||
|
assertEquals(entry, otherEqual);
|
||||||
|
assertEquals(KEY + "=" + KEY, entry.toString());
|
||||||
|
assertEquals(entry.hashCode(), otherEqual.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void stableForEachEntry() {
|
||||||
|
var map = newMap();
|
||||||
|
// Only touch the key.
|
||||||
|
map.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
|
||||||
|
map.entrySet().iterator()
|
||||||
|
.forEachRemaining(e -> assertTrue(e.toString().contains(".unset")));
|
||||||
|
// Only touch the value.
|
||||||
|
map.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
|
||||||
|
map.entrySet().iterator()
|
||||||
|
.forEachRemaining(e -> assertFalse(e.toString().contains(".unset")));
|
||||||
|
}
|
||||||
|
|
||||||
// Immutability
|
// Immutability
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("unsupportedOperations")
|
@MethodSource("unsupportedOperations")
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.NoSuchElementException;
|
import java.util.NoSuchElementException;
|
||||||
@ -355,12 +356,14 @@ final class StableValueTest {
|
|||||||
|
|
||||||
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
|
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
|
||||||
int noThreads = 10;
|
int noThreads = 10;
|
||||||
CountDownLatch starter = new CountDownLatch(1);
|
CountDownLatch starter = new CountDownLatch(noThreads);
|
||||||
StableValue<Integer> stable = StableValue.of();
|
StableValue<Integer> stable = StableValue.of();
|
||||||
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
|
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
|
||||||
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
|
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
// Ready, set ...
|
// Ready ...
|
||||||
|
starter.countDown();
|
||||||
|
// ... set ...
|
||||||
starter.await();
|
starter.await();
|
||||||
// Here we go!
|
// Here we go!
|
||||||
winners.put(i, winnerPredicate.test(stable, i));
|
winners.put(i, winnerPredicate.test(stable, i));
|
||||||
@ -370,9 +373,6 @@ final class StableValueTest {
|
|||||||
}))
|
}))
|
||||||
.toList();
|
.toList();
|
||||||
threads.forEach(Thread::start);
|
threads.forEach(Thread::start);
|
||||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
|
|
||||||
// Start the race
|
|
||||||
starter.countDown();
|
|
||||||
threads.forEach(StableValueTest::join);
|
threads.forEach(StableValueTest::join);
|
||||||
// There can only be one winner
|
// There can only be one winner
|
||||||
assertEquals(1, winners.values().stream().filter(b -> b).count());
|
assertEquals(1, winners.values().stream().filter(b -> b).count());
|
||||||
|
@ -65,7 +65,8 @@ public class StableMethodHandleBenchmark {
|
|||||||
|
|
||||||
private static final MethodHandle FINAL_MH = identityHandle();
|
private static final MethodHandle FINAL_MH = identityHandle();
|
||||||
private static final StableValue<MethodHandle> STABLE_MH;
|
private static final StableValue<MethodHandle> STABLE_MH;
|
||||||
private static MethodHandle mh = identityHandle();
|
|
||||||
|
private static /* intentionally not final */ MethodHandle mh = identityHandle();
|
||||||
private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle);
|
private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle);
|
||||||
private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
|
private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
|
||||||
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
|
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
|
||||||
@ -112,14 +113,6 @@ public class StableMethodHandleBenchmark {
|
|||||||
return (int) STABLE_MH.orElseThrow().invokeExact(1);
|
return (int) STABLE_MH.orElseThrow().invokeExact(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object cp() {
|
|
||||||
CodeBuilder cob = null;
|
|
||||||
ConstantPoolBuilder cp = ConstantPoolBuilder.of();
|
|
||||||
cob.ldc(cp.constantDynamicEntry(cp.bsmEntry(cp.methodHandleEntry(BSM_CLASS_DATA), List.of()),
|
|
||||||
cp.nameAndTypeEntry(DEFAULT_NAME, CD_MethodHandle)));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static MethodHandle identityHandle() {
|
static MethodHandle identityHandle() {
|
||||||
var lookup = MethodHandles.lookup();
|
var lookup = MethodHandles.lookup();
|
||||||
try {
|
try {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user