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>
|
||||
* The method {@link #orElseSet(Supplier)} guarantees that the provided
|
||||
* {@linkplain Supplier} is invoked successfully at most once, even under race.
|
||||
* Invocations of {@link #setOrThrow(Object)} form a total order of zero or more
|
||||
* exceptional invocations followed by zero (if the contents were already set) or one
|
||||
* Invocations of {@link #orElseSet(Supplier)} form a total order of zero or
|
||||
* 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
|
||||
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
|
||||
* 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>
|
||||
permits StableValueImpl {
|
||||
|
||||
// Principal methods
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -509,8 +507,6 @@ public sealed interface StableValue<T>
|
||||
*/
|
||||
T orElseSet(Supplier<? extends T> supplier);
|
||||
|
||||
// Convenience methods
|
||||
|
||||
/**
|
||||
* Sets the contents of this StableValue to the provided {@code contents}, or, if
|
||||
* already set, throws {@code IllegalStateException}.
|
||||
@ -519,6 +515,9 @@ public sealed interface StableValue<T>
|
||||
*
|
||||
* @param contents to 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);
|
||||
|
||||
@ -573,8 +572,8 @@ public sealed interface StableValue<T>
|
||||
* 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
|
||||
* 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
|
||||
* computed value (if any) and will then never execute.
|
||||
* thrown by the computing thread. The competing threads will then observe the newly
|
||||
* computed value (if any) and will then never execute the {@code underlying} supplier.
|
||||
* <p>
|
||||
* If the provided {@code underlying} supplier throws an exception, it is rethrown
|
||||
* 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
|
||||
* be thrown.
|
||||
*
|
||||
* @param size the size of the allowed inputs in the continuous
|
||||
* interval {@code [0, size)}
|
||||
* @param underlying IntFunction used to compute cached values
|
||||
* @param size the upper bound of the range {@code [0, size)} indicating
|
||||
* the allowed inputs
|
||||
* @param underlying {@code IntFunction} used to compute cached values
|
||||
* @param <R> the type of results delivered by the returned IntFunction
|
||||
* @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
|
||||
* is rethrown to the initial caller and no value for the element is recorded.
|
||||
* <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.
|
||||
* <p>
|
||||
* 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 recorded.
|
||||
* <p>
|
||||
* Any direct {@link Map#values()} or {@link Map#entrySet()} views
|
||||
* of the returned map are also stable.
|
||||
* Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are
|
||||
* also stable.
|
||||
* <p>
|
||||
* The returned map is unmodifiable and does not implement 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.misc.CDS;
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import jdk.internal.util.NullableKeyValueHolder;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
@ -137,9 +136,11 @@ class ImmutableCollections {
|
||||
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
|
||||
}
|
||||
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) {
|
||||
// A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
|
||||
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 ----------
|
||||
|
||||
@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 {
|
||||
|
||||
@Stable
|
||||
private final AbstractImmutableList<E> root;
|
||||
final AbstractImmutableList<E> root;
|
||||
|
||||
@Stable
|
||||
private final int offset;
|
||||
final int offset;
|
||||
|
||||
@Stable
|
||||
private final int size;
|
||||
final int size;
|
||||
|
||||
private SubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
|
||||
@ -517,9 +513,8 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean allowNulls() {
|
||||
return root instanceof ListN<?> listN && listN.allowNulls
|
||||
|| root instanceof StableList<E>;
|
||||
boolean allowNulls() {
|
||||
return root instanceof ListN<?> listN && listN.allowNulls;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -572,14 +567,6 @@ class ImmutableCollections {
|
||||
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
|
||||
@ -797,8 +784,15 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface HasStableDelegates<E> {
|
||||
StableValueImpl<E>[] delegates();
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableList<E> extends AbstractImmutableList<E> {
|
||||
static final class StableList<E>
|
||||
extends AbstractImmutableList<E>
|
||||
implements HasStableDelegates<E> {
|
||||
|
||||
@Stable
|
||||
private final IntFunction<? extends E> mapper;
|
||||
@ -879,11 +873,69 @@ class ImmutableCollections {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableList", delegates);
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
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) {
|
||||
super(base, false);
|
||||
@ -892,17 +944,23 @@ class ImmutableCollections {
|
||||
// This method does not evaluate the elements
|
||||
@Override
|
||||
public String toString() {
|
||||
final StableValueImpl<E>[] delegates = ((StableList<E>)base).delegates;
|
||||
final StableValueImpl<E>[] reversed = ArraysSupport.reverse(
|
||||
Arrays.copyOf(delegates, delegates.length));
|
||||
return StableUtil.renderElements(base, "Collection", reversed);
|
||||
return StableUtil.renderElements(this, "StableCollection", delegates());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> reversed() {
|
||||
return base;
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
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 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
|
||||
@Override
|
||||
@ -1582,32 +1640,49 @@ class ImmutableCollections {
|
||||
}
|
||||
|
||||
@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
|
||||
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
|
||||
|
||||
StableMapEntrySet() {
|
||||
this.delegateEntrySet = delegate.entrySet();
|
||||
private StableMapEntrySet(StableMap<K, V> outer) {
|
||||
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 hashCode() { return StableMap.this.hashCode(); }
|
||||
@Override public int hashCode() { return outer.hashCode(); }
|
||||
|
||||
@Override
|
||||
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
|
||||
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
|
||||
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
|
||||
|
||||
LazyMapIterator() {
|
||||
this.delegateIterator = delegateEntrySet.iterator();
|
||||
private LazyMapIterator(StableMapEntrySet<K, V> outer) {
|
||||
this.outer = outer;
|
||||
this.delegateIterator = outer.delegateEntrySet.iterator();
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() { return delegateIterator.hasNext(); }
|
||||
@ -1616,8 +1691,8 @@ class ImmutableCollections {
|
||||
public Entry<K, V> next() {
|
||||
final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next();
|
||||
final K k = inner.getKey();
|
||||
return new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() {
|
||||
@Override public V get() { return mapper.apply(k); }}));
|
||||
return new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||
@Override public V get() { return outer.outer.mapper.apply(k); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1627,25 +1702,60 @@ class ImmutableCollections {
|
||||
@Override
|
||||
public void accept(Entry<K, StableValueImpl<V>> inner) {
|
||||
final K k = inner.getKey();
|
||||
action.accept(new NullableKeyValueHolder<>(k, inner.getValue().orElseSet(new Supplier<V>() {
|
||||
@Override public V get() { return mapper.apply(k); }})));
|
||||
action.accept(new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||
@Override public V get() { return outer.outer.mapper.apply(k); }}));
|
||||
}
|
||||
};
|
||||
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
|
||||
public Collection<V> values() {
|
||||
return new StableMapValues();
|
||||
return StableMapValues.of(this);
|
||||
}
|
||||
|
||||
final class StableMapValues extends AbstractImmutableCollection<V> {
|
||||
@Override public Iterator<V> iterator() { return new ValueIterator(); }
|
||||
@Override public int size() { return StableMap.this.size(); }
|
||||
@Override public boolean isEmpty() { return StableMap.this.isEmpty();}
|
||||
@Override public boolean contains(Object v) { return StableMap.this.containsValue(v); }
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableMapValues<V> extends AbstractImmutableCollection<V> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// 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<?>[]>() {
|
||||
@Override
|
||||
@ -1656,9 +1766,15 @@ class ImmutableCollections {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StableValueImpl<?>[] values = delegate.values().toArray(GENERATOR);
|
||||
return StableUtil.renderElements(StableMap.this, "StableMap", values);
|
||||
final StableValueImpl<?>[] values = outer.delegate.values().toArray(GENERATOR);
|
||||
return StableUtil.renderElements(this, "StableCollection", values);
|
||||
}
|
||||
|
||||
// For @ValueBased
|
||||
private static <V> StableMapValues<V> of(StableMap<?, V> outer) {
|
||||
return new StableMapValues<>(outer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -33,14 +33,17 @@ import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
/**
|
||||
* Provides a reverse-ordered view of a List. Not serializable.
|
||||
*/
|
||||
class ReverseOrderListView<E> implements List<E> {
|
||||
|
||||
@Stable
|
||||
final List<E> base;
|
||||
final boolean modifiable;
|
||||
@Stable
|
||||
final Boolean modifiable;
|
||||
|
||||
public static <T> List<T> of(List<T> list, boolean modifiable) {
|
||||
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
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param enumType the class type of the Enum
|
||||
* @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 original the original 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);
|
||||
}
|
||||
final int index = value.ordinal() - firstOrdinal;
|
||||
final StableValueImpl<R> delegate;
|
||||
// Since we did the member.test above, we know the index is in bounds
|
||||
delegate = delegates[index];
|
||||
return delegate.orElseSet(new Supplier<R>() {
|
||||
return delegates[index].orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(value); }});
|
||||
|
||||
}
|
||||
@ -84,8 +85,8 @@ public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(delegates.length);
|
||||
final E[] enumElements = enumType.getEnumConstants();
|
||||
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(enumElements.length);
|
||||
int ordinal = firstOrdinal;
|
||||
for (int i = 0; i < delegates.length; i++, 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,
|
||||
Function<? super T, ? extends R> original) {
|
||||
// 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);
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
@ -32,7 +32,7 @@ import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
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
|
||||
// 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.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.
|
||||
* <p>
|
||||
|
@ -58,7 +58,7 @@ public record StableSupplier<T>(StableValueImpl<T> delegate,
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = delegate.wrappedContentAcquire();
|
||||
final Object t = delegate.wrappedContentsAcquire();
|
||||
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ public final class StableUtil {
|
||||
int length) {
|
||||
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Object value = delegates[i + offset].wrappedContentAcquire();
|
||||
final Object value = delegates[i + offset].wrappedContentsAcquire();
|
||||
if (value == self) {
|
||||
sj.add("(this " + selfName + ")");
|
||||
} else {
|
||||
@ -63,10 +63,10 @@ public final class StableUtil {
|
||||
boolean curly) {
|
||||
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
|
||||
for (var e : delegates) {
|
||||
final Object value = e.getValue().wrappedContentAcquire();
|
||||
final Object value = e.getValue().wrappedContentsAcquire();
|
||||
final String valueString;
|
||||
if (value == self) {
|
||||
valueString = ("(this ") + selfName + ")";
|
||||
valueString = "(this " + selfName + ")";
|
||||
} else {
|
||||
valueString = StableValueImpl.renderWrapped(value);
|
||||
}
|
||||
|
@ -51,10 +51,10 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
|
||||
// Unsafe offsets for direct field access
|
||||
|
||||
private static final long CONTENT_OFFSET =
|
||||
private static final long CONTENTS_OFFSET =
|
||||
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();
|
||||
|
||||
// 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
|
||||
@Override
|
||||
public boolean trySet(T contents) {
|
||||
if (wrappedContentAcquire() != null) {
|
||||
if (wrappedContentsAcquire() != null) {
|
||||
return false;
|
||||
}
|
||||
// Prevent reentry via an orElseSet(supplier)
|
||||
preventReentry();
|
||||
// Mutual exclusion is required here as `orElseSet` might also
|
||||
// attempt to modify the `wrappedValue`
|
||||
// attempt to modify `this.contents`
|
||||
synchronized (this) {
|
||||
return wrapAndSet(contents);
|
||||
}
|
||||
@ -102,7 +102,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseThrow() {
|
||||
final Object t = wrappedContentAcquire();
|
||||
final Object t = wrappedContentsAcquire();
|
||||
if (t == null) {
|
||||
throw new NoSuchElementException("No contents set");
|
||||
}
|
||||
@ -112,21 +112,21 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElse(T other) {
|
||||
final Object t = wrappedContentAcquire();
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return (t == null) ? other : unwrap(t);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean isSet() {
|
||||
return wrappedContentAcquire() != null;
|
||||
return wrappedContentsAcquire() != null;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseSet(Supplier<? extends T> supplier) {
|
||||
Objects.requireNonNull(supplier);
|
||||
final Object t = wrappedContentAcquire();
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
|
||||
}
|
||||
|
||||
@ -149,7 +149,7 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = wrappedContentAcquire();
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return t == this
|
||||
? "(this StableValue)"
|
||||
: renderWrapped(t);
|
||||
@ -158,8 +158,8 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
// Internal methods shared with other internal classes
|
||||
|
||||
@ForceInline
|
||||
public Object wrappedContentAcquire() {
|
||||
return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET);
|
||||
public Object wrappedContentsAcquire() {
|
||||
return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET);
|
||||
}
|
||||
|
||||
static String renderWrapped(Object t) {
|
||||
@ -185,11 +185,11 @@ public final class StableValueImpl<T> implements StableValue<T> {
|
||||
* @return if the contents was set
|
||||
*/
|
||||
@ForceInline
|
||||
private boolean wrapAndSet(Object newValue) {
|
||||
private boolean wrapAndSet(T newValue) {
|
||||
assert Thread.holdsLock(this);
|
||||
// We know we hold the monitor here so plain semantic is enough
|
||||
if (contents == null) {
|
||||
UNSAFE.putReferenceRelease(this, CONTENT_OFFSET, wrap(newValue));
|
||||
UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -52,7 +52,13 @@ final class StableFunctionTest {
|
||||
ZERO(0),
|
||||
ILLEGAL_BEFORE(-1),
|
||||
// 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),
|
||||
FORTY_TWO(42),
|
||||
// Illegal values (not in the input set)
|
||||
@ -196,6 +202,13 @@ final class StableFunctionTest {
|
||||
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() {
|
||||
return Stream.of(
|
||||
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 java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
@ -46,7 +45,9 @@ import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
@ -242,18 +243,8 @@ final class StableListTest {
|
||||
assertEquals(eq.toString(), lazy.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void subListToString() {
|
||||
subListToString0(newList());
|
||||
subListToString0(newList().subList(1, SIZE));
|
||||
subListToString0(newList().subList(1, SIZE).subList(0, SIZE - 2));
|
||||
}
|
||||
|
||||
void subListToString0(List<Integer> subList) {
|
||||
void assertUnevaluated(List<Integer> subList) {
|
||||
assertEquals(asString(".unset", subList), subList.toString());
|
||||
|
||||
var first = subList.getFirst();
|
||||
assertEquals(asString(first.toString(), subList), subList.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -272,17 +263,47 @@ final class StableListTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void reversedToString() {
|
||||
var reversed = newList().reversed();
|
||||
subListToString0(reversed);
|
||||
void sublistReversedToString() {
|
||||
var actual = StableValue.list(4, IDENTITY);
|
||||
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
|
||||
void subListReversedToString() {
|
||||
var list = newList().subList(1, SIZE - 1).reversed();
|
||||
// This combination is not lazy. There has to be a limit somewhere.
|
||||
var regularList = newRegularList().subList(1, SIZE - 1).reversed();
|
||||
assertEquals(regularList.toString(), list.toString());
|
||||
void viewsStable() {
|
||||
viewOperations().forEach(op0 -> {
|
||||
viewOperations().forEach( op1 -> {
|
||||
viewOperations().forEach(op2 -> {
|
||||
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
|
||||
@ -294,6 +315,23 @@ final class StableListTest {
|
||||
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
|
||||
|
||||
@ParameterizedTest
|
||||
@ -362,6 +400,39 @@ final class StableListTest {
|
||||
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
|
||||
|
||||
record Operation(String name,
|
||||
@ -370,6 +441,36 @@ final class StableListTest {
|
||||
@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() {
|
||||
return Stream.of(
|
||||
new Operation("forEach", l -> l.forEach(null)),
|
||||
@ -433,4 +534,7 @@ final class StableListTest {
|
||||
.collect(Collectors.joining(", ")) + "]";
|
||||
}
|
||||
|
||||
static String className(Object o) {
|
||||
return o.getClass().getName();
|
||||
}
|
||||
}
|
||||
|
@ -248,6 +248,37 @@ final class StableMapTest {
|
||||
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
|
||||
@ParameterizedTest
|
||||
@MethodSource("unsupportedOperations")
|
||||
|
@ -29,6 +29,7 @@
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
@ -355,12 +356,14 @@ final class StableValueTest {
|
||||
|
||||
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
|
||||
int noThreads = 10;
|
||||
CountDownLatch starter = new CountDownLatch(1);
|
||||
CountDownLatch starter = new CountDownLatch(noThreads);
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
|
||||
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
|
||||
try {
|
||||
// Ready, set ...
|
||||
// Ready ...
|
||||
starter.countDown();
|
||||
// ... set ...
|
||||
starter.await();
|
||||
// Here we go!
|
||||
winners.put(i, winnerPredicate.test(stable, i));
|
||||
@ -370,9 +373,6 @@ final class StableValueTest {
|
||||
}))
|
||||
.toList();
|
||||
threads.forEach(Thread::start);
|
||||
LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1));
|
||||
// Start the race
|
||||
starter.countDown();
|
||||
threads.forEach(StableValueTest::join);
|
||||
// There can only be one winner
|
||||
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 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 AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
|
||||
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
|
||||
@ -112,14 +113,6 @@ public class StableMethodHandleBenchmark {
|
||||
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() {
|
||||
var lookup = MethodHandles.lookup();
|
||||
try {
|
||||
|
Loading…
x
Reference in New Issue
Block a user