mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8351565: Implement JEP 502: Stable Values (Preview)
Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org> Reviewed-by: vklang, jvernee, alanb, liach
This commit is contained in:
parent
4c695fa8a4
commit
fbc4691bfa
757
src/java.base/share/classes/java/lang/StableValue.java
Normal file
757
src/java.base/share/classes/java/lang/StableValue.java
Normal file
@ -0,0 +1,757 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.lang;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.lang.stable.StableEnumFunction;
|
||||
import jdk.internal.lang.stable.StableFunction;
|
||||
import jdk.internal.lang.stable.StableIntFunction;
|
||||
import jdk.internal.lang.stable.StableSupplier;
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A stable value is a holder of contents that can be set at most once.
|
||||
* <p>
|
||||
* A {@code StableValue<T>} is typically created using the factory method
|
||||
* {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way,
|
||||
* the stable value is <em>unset</em>, which means it holds no <em>contents</em>.
|
||||
* Its contents, of type {@code T}, can be <em>set</em> by calling
|
||||
* {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()},
|
||||
* or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the contents
|
||||
* can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()}
|
||||
* , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}.
|
||||
* <p>
|
||||
* Consider the following example where a stable value field "{@code logger}" is a
|
||||
* shallowly immutable holder of contents of type {@code Logger} and that is initially
|
||||
* created as <em>unset</em>, which means it holds no contents. Later in the example, the
|
||||
* state of the "{@code logger}" field is checked and if it is still <em>unset</em>,
|
||||
* the contents is <em>set</em>:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* // Creates a new unset stable value with no contents
|
||||
* // @link substring="of" target="#of" :
|
||||
* private final StableValue<Logger> logger = StableValue.of();
|
||||
*
|
||||
* private Logger getLogger() {
|
||||
* if (!logger.isSet()) {
|
||||
* logger.trySet(Logger.create(Component.class));
|
||||
* }
|
||||
* return logger.orElseThrow();
|
||||
* }
|
||||
*
|
||||
* public void process() {
|
||||
* getLogger().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* <p>
|
||||
* If {@code getLogger()} is called from several threads, several instances of
|
||||
* {@code Logger} might be created. However, the contents can only be set at most once
|
||||
* meaning the first writer wins.
|
||||
* <p>
|
||||
* In order to guarantee that, even under races, only one instance of {@code Logger} is
|
||||
* ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used
|
||||
* instead, where the contents are lazily computed, and atomically set, via a
|
||||
* {@linkplain Supplier supplier}. In the example below, the supplier is provided in the
|
||||
* form of a lambda expression:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* // Creates a new unset stable value with no contents
|
||||
* // @link substring="of" target="#of" :
|
||||
* private final StableValue<Logger> logger = StableValue.of();
|
||||
*
|
||||
* private Logger getLogger() {
|
||||
* return logger.orElseSet( () -> Logger.create(Component.class) );
|
||||
* }
|
||||
*
|
||||
* public void process() {
|
||||
* getLogger().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* <p>
|
||||
* The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to
|
||||
* retrieve its contents. If the stable value is <em>unset</em>, then {@code orElseSet()}
|
||||
* evaluates the given supplier, and sets the contents to the result; the result is then
|
||||
* returned to the client. In other words, {@code orElseSet()} guarantees that a
|
||||
* stable value's contents is <em>set</em> before it returns.
|
||||
* <p>
|
||||
* Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided,
|
||||
* only at most one is ever evaluated, and that one is only ever evaluated once,
|
||||
* even when {@code logger.orElseSet()} is invoked concurrently. This property is crucial
|
||||
* as evaluation of the supplier may have side effects, for example, the call above to
|
||||
* {@code Logger.create()} may result in storage resources being prepared.
|
||||
*
|
||||
* <h2 id="stable-functions">Stable Functions</h2>
|
||||
* Stable values provide the foundation for higher-level functional abstractions. A
|
||||
* <em>stable supplier</em> is a supplier that computes a value and then caches it into
|
||||
* a backing stable value storage for subsequent use. A stable supplier is created via the
|
||||
* {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by
|
||||
* providing an underlying {@linkplain Supplier} which is invoked when the stable supplier
|
||||
* is first accessed:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* private final Supplier<Logger> logger =
|
||||
* // @link substring="supplier" target="#supplier(Supplier)" :
|
||||
* StableValue.supplier( () -> Logger.getLogger(Component.class) );
|
||||
*
|
||||
* public void process() {
|
||||
* logger.get().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* A stable supplier encapsulates access to its backing stable value storage. This means
|
||||
* that code inside {@code Component} can obtain the logger object directly from the
|
||||
* stable supplier, without having to go through an accessor method like {@code getLogger()}.
|
||||
* <p>
|
||||
* A <em>stable int function</em> is a function that takes an {@code int} parameter and
|
||||
* uses it to compute a result that is then cached by the backing stable value storage
|
||||
* for that parameter value. A stable {@link IntFunction} is created via the
|
||||
* {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()}
|
||||
* factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together
|
||||
* with an underlying {@linkplain IntFunction} which is invoked at most once per input
|
||||
* value. In effect, the stable int function will act like a cache for the underlying
|
||||
* {@linkplain IntFunction}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* final class PowerOf2Util {
|
||||
*
|
||||
* private PowerOf2Util() {}
|
||||
*
|
||||
* private static final int SIZE = 6;
|
||||
* private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
|
||||
* v -> 1 << v;
|
||||
*
|
||||
* private static final IntFunction<Integer> POWER_OF_TWO =
|
||||
* // @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
* StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO);
|
||||
*
|
||||
* public static int powerOfTwo(int a) {
|
||||
* return POWER_OF_TWO.apply(a);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
*
|
||||
*}
|
||||
* The {@code PowerOf2Util.powerOfTwo()} function is a <em>partial function</em> that only
|
||||
* allows a subset {@code [0, 5]} of the underlying function's {@code UNDERLYING_POWER_OF_TWO}
|
||||
* input range.
|
||||
*
|
||||
* <p>
|
||||
* A <em>stable function</em> is a function that takes a parameter (of type {@code T}) and
|
||||
* uses it to compute a result (of type {@code R}) that is then cached by the backing
|
||||
* stable value storage for that parameter value. A stable function is created via the
|
||||
* {@linkplain StableValue#function(Set, Function) StableValue.function()} factory.
|
||||
* Upon creation, the input {@linkplain Set} is specified together with an underlying
|
||||
* {@linkplain Function} which is invoked at most once per input value. In effect, the
|
||||
* stable function will act like a cache for the underlying {@linkplain Function}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* class Log2Util {
|
||||
*
|
||||
* private Log2Util() {}
|
||||
*
|
||||
* private static final Set<Integer> KEYS =
|
||||
* Set.of(1, 2, 4, 8, 16, 32);
|
||||
* private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
|
||||
* i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
*
|
||||
* private static final Function<Integer, Integer> LOG2 =
|
||||
* // @link substring="function" target="#function(Set,Function)" :
|
||||
* StableValue.function(KEYS, UNDERLYING_LOG2);
|
||||
*
|
||||
* public static int log2(int a) {
|
||||
* return LOG2.apply(a);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
|
||||
*}
|
||||
*
|
||||
* The {@code Log2Util.log2()} function is a <em>partial function</em> that only allows
|
||||
* a subset {@code {1, 2, 4, 8, 16, 32}} of the underlying function's
|
||||
* {@code UNDERLYING_LOG2} input range.
|
||||
*
|
||||
* <h2 id="stable-collections">Stable Collections</h2>
|
||||
* Stable values can also be used as backing storage for
|
||||
* {@linkplain Collection##unmodifiable unmodifiable collections}. A <em>stable list</em>
|
||||
* is an unmodifiable list, backed by an array of stable values. The stable list elements
|
||||
* are computed when they are first accessed, using a provided {@linkplain IntFunction}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* final class PowerOf2Util {
|
||||
*
|
||||
* private PowerOf2Util() {}
|
||||
*
|
||||
* private static final int SIZE = 6;
|
||||
* private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
|
||||
* v -> 1 << v;
|
||||
*
|
||||
* private static final List<Integer> POWER_OF_TWO =
|
||||
* // @link substring="list" target="#list(int,IntFunction)" :
|
||||
* StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO);
|
||||
*
|
||||
* public static int powerOfTwo(int a) {
|
||||
* return POWER_OF_TWO.get(a);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
*
|
||||
* }
|
||||
* <p>
|
||||
* Similarly, a <em>stable map</em> is an unmodifiable map whose keys are known at
|
||||
* construction. The stable map values are computed when they are first accessed,
|
||||
* using a provided {@linkplain Function}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* class Log2Util {
|
||||
*
|
||||
* private Log2Util() {}
|
||||
*
|
||||
* private static final Set<Integer> KEYS =
|
||||
* Set.of(1, 2, 4, 8, 16, 32);
|
||||
* private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
|
||||
* i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
*
|
||||
* private static final Map<Integer, INTEGER> LOG2 =
|
||||
* // @link substring="map" target="#map(Set,Function)" :
|
||||
* StableValue.map(CACHED_KEYS, UNDERLYING_LOG2);
|
||||
*
|
||||
* public static int log2(int a) {
|
||||
* return LOG2.get(a);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
|
||||
*
|
||||
*}
|
||||
*
|
||||
* <h2 id="composition">Composing stable values</h2>
|
||||
* A stable value can depend on other stable values, forming a dependency graph
|
||||
* that can be lazily computed but where access to individual elements can still be
|
||||
* performant. In the following example, a single {@code Foo} and a {@code Bar}
|
||||
* instance (that is dependent on the {@code Foo} instance) are lazily created, both of
|
||||
* which are held by stable values:
|
||||
* {@snippet lang = java:
|
||||
* public final class DependencyUtil {
|
||||
*
|
||||
* private DependencyUtil() {}
|
||||
*
|
||||
* public static class Foo {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* public static class Bar {
|
||||
* public Bar(Foo foo) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* private static final Supplier<Foo> FOO = StableValue.supplier(Foo::new);
|
||||
* private static final Supplier<Bar> BAR = StableValue.supplier(() -> new Bar(FOO.get()));
|
||||
*
|
||||
* public static Foo foo() {
|
||||
* return FOO.get();
|
||||
* }
|
||||
*
|
||||
* public static Bar bar() {
|
||||
* return BAR.get();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*}
|
||||
* Calling {@code bar()} will create the {@code Bar} singleton if it is not already
|
||||
* created. Upon such a creation, the dependent {@code Foo} will first be created if
|
||||
* the {@code Foo} does not already exist.
|
||||
* <p>
|
||||
* Another example, which has a more complex dependency graph, is to compute the
|
||||
* Fibonacci sequence lazily:
|
||||
* {@snippet lang = java:
|
||||
* public final class Fibonacci {
|
||||
*
|
||||
* private Fibonacci() {}
|
||||
*
|
||||
* private static final int MAX_SIZE_INT = 46;
|
||||
*
|
||||
* private static final IntFunction<Integer> FIB =
|
||||
* StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib);
|
||||
*
|
||||
* public static int fib(int n) {
|
||||
* return n < 2
|
||||
* ? n
|
||||
* : FIB.apply(n - 1) + FIB.apply(n - 2);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*}
|
||||
* Both {@code FIB} and {@code Fibonacci::fib} recurse into each other. Because the
|
||||
* stable int function {@code FIB} caches intermediate results, the initial
|
||||
* computational complexity is reduced from exponential to linear compared to a
|
||||
* traditional non-caching recursive fibonacci method. Once computed, the VM is free to
|
||||
* constant-fold expressions like {@code Fibonacci.fib(5)}.
|
||||
* <p>
|
||||
* The fibonacci example above is a directed acyclic graph (i.e.,
|
||||
* it has no circular dependencies and is therefore a dependency tree):
|
||||
*{@snippet lang=text :
|
||||
*
|
||||
* ___________fib(5)____________
|
||||
* / \
|
||||
* ____fib(4)____ ____fib(3)____
|
||||
* / \ / \
|
||||
* fib(3) fib(2) fib(2) fib(1)
|
||||
* / \ / \ / \
|
||||
* fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
|
||||
*}
|
||||
*
|
||||
* If there are circular dependencies in a dependency graph, a stable value will
|
||||
* eventually throw an {@linkplain IllegalStateException} upon referencing elements in
|
||||
* a circularity.
|
||||
*
|
||||
* <h2 id="thread-safety">Thread Safety</h2>
|
||||
* The contents of a stable value is guaranteed to be set at most once. If competing
|
||||
* threads are racing to set a stable value, only one update succeeds, while the other
|
||||
* updates are blocked until the stable value is set, whereafter the other updates
|
||||
* observes the stable value is set and leave the stable value unchanged.
|
||||
* <p>
|
||||
* The at-most-once write operation on a stable value that succeeds
|
||||
* (e.g. {@linkplain #trySet(Object) trySet()})
|
||||
* {@linkplain java.util.concurrent##MemoryVisibility <em>happens-before</em>}
|
||||
* any successful read operation (e.g. {@linkplain #orElseThrow()}).
|
||||
* A successful write operation can be either:
|
||||
* <ul>
|
||||
* <li>a {@link #trySet(Object)} that returns {@code true},</li>
|
||||
* <li>a {@link #setOrThrow(Object)} that does not throw, or</li>
|
||||
* <li>an {@link #orElseSet(Supplier)} that successfully runs the supplier</li>
|
||||
* </ul>
|
||||
* A successful read operation can be either:
|
||||
* <ul>
|
||||
* <li>a {@link #orElseThrow()} that does not throw,</li>
|
||||
* <li>a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value</li>
|
||||
* <li>an {@link #orElseSet(Supplier)} that does not {@code throw}, or</li>
|
||||
* <li>an {@link #isSet()} that returns {@code true}</li>
|
||||
* </ul>
|
||||
* <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
|
||||
* 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.
|
||||
*
|
||||
* <h2 id="performance">Performance</h2>
|
||||
* As the contents of a stable value can never change after it has been set, a JVM
|
||||
* implementation may, for a set stable value, elide all future reads of that
|
||||
* stable value, and instead directly use any contents that it has previously observed.
|
||||
* This is true if the reference to the stable value is a constant (e.g. in cases where
|
||||
* the stable value itself is stored in a {@code static final} field). Stable functions
|
||||
* and collections are built on top of StableValue. As such, they might also be eligible
|
||||
* for the same JVM optimizations as for StableValue.
|
||||
*
|
||||
* @implSpec Implementing classes of {@code StableValue} are free to synchronize on
|
||||
* {@code this} and consequently, it should be avoided to
|
||||
* (directly or indirectly) synchronize on a {@code StableValue}. Hence,
|
||||
* synchronizing on {@code this} may lead to deadlock.
|
||||
* <p>
|
||||
* Except for a {@code StableValue}'s contents itself,
|
||||
* an {@linkplain #orElse(Object) orElse(other)} parameter, and
|
||||
* an {@linkplain #equals(Object) equals(obj)} parameter; all
|
||||
* method parameters must be <em>non-null</em> or a {@link NullPointerException}
|
||||
* will be thrown.
|
||||
*
|
||||
* @implNote A {@code StableValue} is mainly intended to be a non-public field in
|
||||
* a class and is usually neither exposed directly via accessors nor passed as
|
||||
* a method parameter.
|
||||
* <p>
|
||||
* Stable functions and collections make reasonable efforts to provide
|
||||
* {@link Object#toString()} operations that do not trigger evaluation
|
||||
* of the internal stable values when called.
|
||||
* Stable collections have {@link Object#equals(Object)} operations that try
|
||||
* to minimize evaluation of the internal stable values when called.
|
||||
* <p>
|
||||
* As objects can be set via stable values but never removed, this can be a
|
||||
* source of unintended memory leaks. A stable value's contents are
|
||||
* {@linkplain java.lang.ref##reachability strongly reachable}.
|
||||
* Be advised that reachable stable values will hold their set contents until
|
||||
* the stable value itself is collected.
|
||||
* <p>
|
||||
* A {@code StableValue} that has a type parameter {@code T} that is an array
|
||||
* type (of arbitrary rank) will only allow the JVM to treat the
|
||||
* <em>array reference</em> as a stable value but <em>not its components</em>.
|
||||
* Instead, a {@linkplain #list(int, IntFunction) a stable list} of arbitrary
|
||||
* depth can be used, which provides stable components. More generally, a
|
||||
* stable value can hold other stable values of arbitrary depth and still
|
||||
* provide transitive constantness.
|
||||
* <p>
|
||||
* Stable values, functions, and collections are not {@link Serializable}.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES)
|
||||
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
|
||||
* returns {@code true} once.
|
||||
* <p>
|
||||
* When this method returns, the contents of this StableValue is always set.
|
||||
*
|
||||
* @return {@code true} if the contents of this StableValue was set to the
|
||||
* provided {@code contents}, {@code false} otherwise
|
||||
* @param contents to 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.
|
||||
*/
|
||||
boolean trySet(T contents);
|
||||
|
||||
/**
|
||||
* {@return the contents if set, otherwise, returns the provided {@code other} value}
|
||||
*
|
||||
* @param other to return if the contents is not set
|
||||
*/
|
||||
T orElse(T other);
|
||||
|
||||
/**
|
||||
* {@return the contents if set, otherwise, throws {@code NoSuchElementException}}
|
||||
*
|
||||
* @throws NoSuchElementException if no contents is set
|
||||
*/
|
||||
T orElseThrow();
|
||||
|
||||
/**
|
||||
* {@return {@code true} if the contents is set, {@code false} otherwise}
|
||||
*/
|
||||
boolean isSet();
|
||||
|
||||
/**
|
||||
* {@return the contents; if unset, first attempts to compute and set the
|
||||
* contents using the provided {@code supplier}}
|
||||
* <p>
|
||||
* The provided {@code supplier} is guaranteed to be invoked at most once if it
|
||||
* completes without throwing an exception. If this method is invoked several times
|
||||
* with different suppliers, only one of them will be invoked provided it completes
|
||||
* without throwing an exception.
|
||||
* <p>
|
||||
* If the supplier throws an (unchecked) exception, the exception is rethrown and no
|
||||
* contents is set. The most common usage is to construct a new object serving
|
||||
* as a lazily computed value or memoized result, as in:
|
||||
*
|
||||
* {@snippet lang=java:
|
||||
* Value v = stable.orElseSet(Value::new);
|
||||
* }
|
||||
* <p>
|
||||
* When this method returns successfully, the contents is always set.
|
||||
* <p>
|
||||
* The provided {@code supplier} will only be invoked once even if invoked from
|
||||
* several threads unless the {@code supplier} throws an exception.
|
||||
*
|
||||
* @param supplier to be used for computing the contents, if not previously set
|
||||
* @throws IllegalStateException if the provided {@code supplier} recursively
|
||||
* attempts to set this stable value.
|
||||
*/
|
||||
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}.
|
||||
* <p>
|
||||
* When this method returns (or throws an exception), the contents is always set.
|
||||
*
|
||||
* @param contents to set
|
||||
* @throws IllegalStateException if the contents was already set
|
||||
*/
|
||||
void setOrThrow(T contents);
|
||||
|
||||
// Object methods
|
||||
|
||||
/**
|
||||
* {@return {@code true} if {@code this == obj}, {@code false} otherwise}
|
||||
*
|
||||
* @param obj to check for equality
|
||||
*/
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* {@return the {@linkplain System#identityHashCode(Object) identity hash code} of
|
||||
* {@code this} object}
|
||||
*/
|
||||
int hashCode();
|
||||
|
||||
// Factories
|
||||
|
||||
/**
|
||||
* {@return a new unset stable value}
|
||||
* <p>
|
||||
* An unset stable value has no contents.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
static <T> StableValue<T> of() {
|
||||
return StableValueImpl.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new pre-set stable value with the provided {@code contents}}
|
||||
*
|
||||
* @param contents to set
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
static <T> StableValue<T> of(T contents) {
|
||||
final StableValue<T> stableValue = StableValue.of();
|
||||
stableValue.trySet(contents);
|
||||
return stableValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable supplier}
|
||||
* <p>
|
||||
* The returned {@linkplain Supplier supplier} is a caching supplier that records
|
||||
* the value of the provided {@code underlying} supplier upon being first accessed via
|
||||
* the returned supplier's {@linkplain Supplier#get() get()} method.
|
||||
* <p>
|
||||
* The provided {@code underlying} supplier is guaranteed to be successfully invoked
|
||||
* 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.
|
||||
* <p>
|
||||
* If the provided {@code underlying} supplier throws an exception, it is rethrown
|
||||
* to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} supplier recursively calls the returned
|
||||
* supplier, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param underlying supplier used to compute a cached value
|
||||
* @param <T> the type of results supplied by the returned supplier
|
||||
*/
|
||||
static <T> Supplier<T> supplier(Supplier<? extends T> underlying) {
|
||||
Objects.requireNonNull(underlying);
|
||||
return StableSupplier.of(underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable {@linkplain IntFunction}}
|
||||
* <p>
|
||||
* The returned function is a caching function that, for each allowed {@code int}
|
||||
* input, records the values of the provided {@code underlying}
|
||||
* function upon being first accessed via the returned function's
|
||||
* {@linkplain IntFunction#apply(int) apply()} method. If the returned function is
|
||||
* invoked with an input that is not in the range {@code [0, size)}, an
|
||||
* {@link IllegalArgumentException} will be thrown.
|
||||
* <p>
|
||||
* The provided {@code underlying} function is guaranteed to be successfully invoked
|
||||
* at most once per allowed input, even in a multi-threaded environment. Competing
|
||||
* threads invoking the returned function's
|
||||
* {@linkplain IntFunction#apply(int) apply()} method when a value is already under
|
||||
* computation will block until a value is computed or an exception is thrown by
|
||||
* the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code underlying} function throws an exception, it is
|
||||
* rethrown to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} function recursively calls the returned
|
||||
* 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 <R> the type of results delivered by the returned IntFunction
|
||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||
*/
|
||||
static <R> IntFunction<R> intFunction(int size,
|
||||
IntFunction<? extends R> underlying) {
|
||||
StableUtil.assertSizeNonNegative(size);
|
||||
Objects.requireNonNull(underlying);
|
||||
return StableIntFunction.of(size, underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable {@linkplain Function}}
|
||||
* <p>
|
||||
* The returned function is a caching function that, for each allowed
|
||||
* input in the given set of {@code inputs}, records the values of the provided
|
||||
* {@code underlying} function upon being first accessed via the returned function's
|
||||
* {@linkplain Function#apply(Object) apply()} method. If the returned function is
|
||||
* invoked with an input that is not in {@code inputs}, an {@link IllegalArgumentException}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The provided {@code underlying} function is guaranteed to be successfully invoked
|
||||
* at most once per allowed input, even in a multi-threaded environment. Competing
|
||||
* threads invoking the returned function's {@linkplain Function#apply(Object) apply()}
|
||||
* method when a value is already under computation will block until a value is
|
||||
* computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code underlying} function throws an exception, it is
|
||||
* rethrown to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} function recursively calls the returned
|
||||
* function for the same input, an {@linkplain IllegalStateException} will
|
||||
* be thrown.
|
||||
*
|
||||
* @param inputs the set of (non-null) allowed input values
|
||||
* @param underlying {@code Function} used to compute cached values
|
||||
* @param <T> the type of the input to the returned Function
|
||||
* @param <R> the type of results delivered by the returned Function
|
||||
* @throws NullPointerException if the provided set of {@code inputs} contains a
|
||||
* {@code null} element.
|
||||
*/
|
||||
static <T, R> Function<T, R> function(Set<? extends T> inputs,
|
||||
Function<? super T, ? extends R> underlying) {
|
||||
Objects.requireNonNull(inputs);
|
||||
// Checking that the Set of inputs does not contain a `null` value is made in the
|
||||
// implementing classes.
|
||||
Objects.requireNonNull(underlying);
|
||||
return inputs instanceof EnumSet<?> && !inputs.isEmpty()
|
||||
? StableEnumFunction.of(inputs, underlying)
|
||||
: StableFunction.of(inputs, underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable list with the provided {@code size}}
|
||||
* <p>
|
||||
* The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list
|
||||
* with the provided {@code size}. The list's elements are computed via the
|
||||
* provided {@code mapper} when they are first accessed
|
||||
* (e.g. via {@linkplain List#get(int) List::get}).
|
||||
* <p>
|
||||
* The provided {@code mapper} function is guaranteed to be successfully invoked
|
||||
* at most once per list index, even in a multi-threaded environment. Competing
|
||||
* threads accessing an element already under computation will block until an element
|
||||
* is computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* 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
|
||||
* of the returned list are also stable.
|
||||
* <p>
|
||||
* The returned list and its {@link List#subList(int, int) subList} or
|
||||
* {@link List#reversed()} views implement the {@link RandomAccess} interface.
|
||||
* <p>
|
||||
* The returned list is unmodifiable and does not implement the
|
||||
* {@linkplain Collection##optional-operation optional operations} in the
|
||||
* {@linkplain List} interface.
|
||||
* <p>
|
||||
* If the provided {@code mapper} recursively calls the returned list for the
|
||||
* same index, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param size the size of the returned list
|
||||
* @param mapper to invoke whenever an element is first accessed
|
||||
* (may return {@code null})
|
||||
* @param <E> the type of elements in the returned list
|
||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||
*/
|
||||
static <E> List<E> list(int size,
|
||||
IntFunction<? extends E> mapper) {
|
||||
StableUtil.assertSizeNonNegative(size);
|
||||
Objects.requireNonNull(mapper);
|
||||
return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable map with the provided {@code keys}}
|
||||
* <p>
|
||||
* The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose
|
||||
* keys are known at construction. The map's values are computed via the provided
|
||||
* {@code mapper} when they are first accessed
|
||||
* (e.g. via {@linkplain Map#get(Object) Map::get}).
|
||||
* <p>
|
||||
* The provided {@code mapper} function is guaranteed to be successfully invoked
|
||||
* at most once per key, even in a multi-threaded environment. Competing
|
||||
* threads accessing a value already under computation will block until an element
|
||||
* is computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code mapper} function throws an exception, it
|
||||
* 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.
|
||||
* <p>
|
||||
* The returned map is unmodifiable and does not implement the
|
||||
* {@linkplain Collection##optional-operations optional operations} in the
|
||||
* {@linkplain Map} interface.
|
||||
* <p>
|
||||
* If the provided {@code mapper} recursively calls the returned map for
|
||||
* the same key, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param keys the (non-null) keys in the returned map
|
||||
* @param mapper to invoke whenever an associated value is first accessed
|
||||
* (may return {@code null})
|
||||
* @param <K> the type of keys maintained by the returned map
|
||||
* @param <V> the type of mapped values in the returned map
|
||||
* @throws NullPointerException if the provided set of {@code inputs} contains a
|
||||
* {@code null} element.
|
||||
*/
|
||||
static <K, V> Map<K, V> map(Set<K> keys,
|
||||
Function<? super K, ? extends V> mapper) {
|
||||
Objects.requireNonNull(keys);
|
||||
// Checking that the Set of keys does not contain a `null` value is made in the
|
||||
// implementing class.
|
||||
Objects.requireNonNull(mapper);
|
||||
return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper);
|
||||
}
|
||||
|
||||
}
|
||||
@ -58,7 +58,7 @@ import java.util.stream.StreamSupport;
|
||||
* constructors) but all of the general-purpose {@code Collection}
|
||||
* implementations in the Java platform libraries comply.
|
||||
*
|
||||
* <p>Certain methods are specified to be
|
||||
* <p><a id="optional-operations"></a>Certain methods are specified to be
|
||||
* <i>optional</i>. If a collection implementation doesn't implement a
|
||||
* particular operation, it should define the corresponding method to throw
|
||||
* {@code UnsupportedOperationException}. Such methods are marked "optional
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -36,11 +36,19 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import jdk.internal.access.JavaUtilCollectionAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -128,6 +136,12 @@ class ImmutableCollections {
|
||||
public <E> List<E> listFromTrustedArrayNullsAllowed(Object[] array) {
|
||||
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
|
||||
}
|
||||
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
|
||||
return ImmutableCollections.stableList(size, mapper);
|
||||
}
|
||||
public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
||||
return new StableMap<>(keys, mapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -250,6 +264,11 @@ 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
|
||||
@ -448,7 +467,7 @@ class ImmutableCollections {
|
||||
private final int size;
|
||||
|
||||
private SubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||
assert root instanceof List12 || root instanceof ListN;
|
||||
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
|
||||
this.root = root;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
@ -499,7 +518,8 @@ class ImmutableCollections {
|
||||
}
|
||||
|
||||
private boolean allowNulls() {
|
||||
return root instanceof ListN && ((ListN<?>)root).allowNulls;
|
||||
return root instanceof ListN<?> listN && listN.allowNulls
|
||||
|| root instanceof StableList<E>;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -551,6 +571,15 @@ 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
|
||||
@ -768,6 +797,116 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableList<E> extends AbstractImmutableList<E> {
|
||||
|
||||
@Stable
|
||||
private final IntFunction<? extends E> mapper;
|
||||
@Stable
|
||||
final StableValueImpl<E>[] delegates;
|
||||
|
||||
StableList(int size, IntFunction<? extends E> mapper) {
|
||||
this.mapper = mapper;
|
||||
this.delegates = StableUtil.array(size);
|
||||
}
|
||||
|
||||
@Override public boolean isEmpty() { return delegates.length == 0;}
|
||||
@Override public int size() { return delegates.length; }
|
||||
@Override public Object[] toArray() { return copyInto(new Object[size()]); }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public E get(int i) {
|
||||
final StableValueImpl<E> delegate;
|
||||
try {
|
||||
delegate = delegates[i];
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
throw new IndexOutOfBoundsException(i);
|
||||
}
|
||||
return delegate.orElseSet(new Supplier<E>() {
|
||||
@Override public E get() { return mapper.apply(i); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
final int size = delegates.length;
|
||||
if (a.length < size) {
|
||||
// Make a new array of a's runtime type, but my contents:
|
||||
T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size);
|
||||
return copyInto(n);
|
||||
}
|
||||
copyInto(a);
|
||||
if (a.length > size) {
|
||||
a[size] = null; // null-terminate
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
final int size = size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
for (int i = size() - 1; i >= 0; i--) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T[] copyInto(Object[] a) {
|
||||
final int len = delegates.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
a[i] = get(i);
|
||||
}
|
||||
return (T[]) a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> reversed() {
|
||||
return new StableReverseOrderListView<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableList", delegates);
|
||||
}
|
||||
|
||||
private static final class StableReverseOrderListView<E> extends ReverseOrderListView.Rand<E> {
|
||||
|
||||
private StableReverseOrderListView(List<E> base) {
|
||||
super(base, false);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> reversed() {
|
||||
return base;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------- Set Implementations ----------
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
@ -1112,7 +1251,7 @@ class ImmutableCollections {
|
||||
// ---------- Map Implementations ----------
|
||||
|
||||
// Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap
|
||||
abstract static class AbstractImmutableMap<K,V> extends AbstractMap<K,V> implements Serializable {
|
||||
abstract static class AbstractImmutableMap<K,V> extends AbstractMap<K,V> {
|
||||
@Override public void clear() { throw uoe(); }
|
||||
@Override public V compute(K key, BiFunction<? super K,? super V,? extends V> rf) { throw uoe(); }
|
||||
@Override public V computeIfAbsent(K key, Function<? super K,? extends V> mf) { throw uoe(); }
|
||||
@ -1143,7 +1282,7 @@ class ImmutableCollections {
|
||||
}
|
||||
|
||||
// Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap
|
||||
static final class Map1<K,V> extends AbstractImmutableMap<K,V> {
|
||||
static final class Map1<K,V> extends AbstractImmutableMap<K,V> implements Serializable {
|
||||
@Stable
|
||||
private final K k0;
|
||||
@Stable
|
||||
@ -1215,7 +1354,7 @@ class ImmutableCollections {
|
||||
* @param <V> the value type
|
||||
*/
|
||||
// Not a jdk.internal.ValueBased class; disqualified by fields in superclass AbstractMap
|
||||
static final class MapN<K,V> extends AbstractImmutableMap<K,V> {
|
||||
static final class MapN<K,V> extends AbstractImmutableMap<K,V> implements Serializable {
|
||||
|
||||
@Stable
|
||||
final Object[] table; // pairs of key, value
|
||||
@ -1405,6 +1544,130 @@ class ImmutableCollections {
|
||||
return new CollSer(CollSer.IMM_MAP, array);
|
||||
}
|
||||
}
|
||||
|
||||
static final class StableMap<K, V>
|
||||
extends AbstractImmutableMap<K, V> {
|
||||
|
||||
@Stable
|
||||
private final Function<? super K, ? extends V> mapper;
|
||||
@Stable
|
||||
private final Map<K, StableValueImpl<V>> delegate;
|
||||
|
||||
StableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
||||
this.mapper = mapper;
|
||||
this.delegate = StableUtil.map(keys);
|
||||
}
|
||||
|
||||
@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(); }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
return getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
final StableValueImpl<V> stable = delegate.get(key);
|
||||
if (stable == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final K k = (K) key;
|
||||
return stable.orElseSet(new Supplier<V>() {
|
||||
@Override public V get() { return mapper.apply(k); }});
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
final class StableMapEntrySet extends AbstractImmutableSet<Map.Entry<K, V>> {
|
||||
|
||||
@Stable
|
||||
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
|
||||
|
||||
StableMapEntrySet() {
|
||||
this.delegateEntrySet = delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override public Iterator<Map.Entry<K, V>> iterator() { return new LazyMapIterator(); }
|
||||
@Override public int size() { return delegateEntrySet.size(); }
|
||||
@Override public int hashCode() { return StableMap.this.hashCode(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableSet", delegateEntrySet, false);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
final class LazyMapIterator implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
@Stable
|
||||
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
|
||||
|
||||
LazyMapIterator() {
|
||||
this.delegateIterator = delegateEntrySet.iterator();
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() { return delegateIterator.hasNext(); }
|
||||
|
||||
@Override
|
||||
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); }}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super Map.Entry<K, V>> action) {
|
||||
final Consumer<? super Map.Entry<K, StableValueImpl<V>>> innerAction =
|
||||
new Consumer<>() {
|
||||
@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); }})));
|
||||
}
|
||||
};
|
||||
delegateIterator.forEachRemaining(innerAction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return new StableMapValues();
|
||||
}
|
||||
|
||||
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); }
|
||||
|
||||
private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
|
||||
@Override
|
||||
public StableValueImpl<?>[] apply(int len) {
|
||||
return new StableValueImpl<?>[len];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StableValueImpl<?>[] values = delegate.values().toArray(GENERATOR);
|
||||
return StableUtil.renderElements(StableMap.this, "StableMap", values);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------- Serialization Proxy ----------
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -26,8 +26,14 @@
|
||||
package jdk.internal.access;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public interface JavaUtilCollectionAccess {
|
||||
<E> List<E> listFromTrustedArray(Object[] array);
|
||||
<E> List<E> listFromTrustedArrayNullsAllowed(Object[] array);
|
||||
<E> List<E> stableList(int size, IntFunction<? extends E> mapper);
|
||||
<K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper);
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -80,6 +80,8 @@ public @interface PreviewFeature {
|
||||
MODULE_IMPORTS,
|
||||
@JEP(number=478, title="Key Derivation Function API", status="Preview")
|
||||
KEY_DERIVATION,
|
||||
@JEP(number = 502, title = "Stable Values", status = "Preview")
|
||||
STABLE_VALUES,
|
||||
LANGUAGE_MODEL,
|
||||
/**
|
||||
* A key for testing.
|
||||
|
||||
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.util.ImmutableBitSetPredicate;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Optimized implementation of a stable Function with enums as keys.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param firstOrdinal the lowest ordinal used
|
||||
* @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
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
||||
int firstOrdinal,
|
||||
IntPredicate member,
|
||||
@Stable StableValueImpl<R>[] delegates,
|
||||
Function<? super E, ? extends R> original) implements Function<E, R> {
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(E value) {
|
||||
if (!member.test(value.ordinal())) { // Implicit null-check of value
|
||||
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>() {
|
||||
@Override public R get() { return original.apply(value); }});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
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)) {
|
||||
entries.add(new AbstractMap.SimpleImmutableEntry<>(enumElements[ordinal], delegates[i]));
|
||||
}
|
||||
}
|
||||
return StableUtil.renderMappings(this, "StableFunction", entries, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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 BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
for (T t : inputs) {
|
||||
final int ordinal = ((E) t).ordinal();
|
||||
min = Math.min(min, ordinal);
|
||||
max = Math.max(max, ordinal);
|
||||
bitSet.set(ordinal);
|
||||
}
|
||||
final int size = max - min + 1;
|
||||
final IntPredicate member = ImmutableBitSetPredicate.of(bitSet);
|
||||
return (Function<T, R>) new StableEnumFunction<E, R>(enumType, min, member, StableUtil.array(size), (Function<E, R>) original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.util.Map;
|
||||
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
|
||||
// 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 Function.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param values a delegate map of inputs to StableValue mappings
|
||||
* @param original the original Function
|
||||
* @param <T> the type of the input to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
public record StableFunction<T, R>(Map<? extends T, StableValueImpl<R>> values,
|
||||
Function<? super T, ? extends R> original) implements Function<T, R> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(T value) {
|
||||
final StableValueImpl<R> stable = values.get(value);
|
||||
if (stable == null) {
|
||||
throw new IllegalArgumentException("Input not allowed: " + value);
|
||||
}
|
||||
return stable.orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(value); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableFunction", values.entrySet(), true);
|
||||
}
|
||||
|
||||
public static <T, R> StableFunction<T, R> of(Set<? extends T> inputs,
|
||||
Function<? super T, ? extends R> original) {
|
||||
return new StableFunction<>(StableUtil.map(inputs), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
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>
|
||||
* For performance reasons (~10%), we are not delegating to a StableList but are using
|
||||
* the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <R> the return type
|
||||
*/
|
||||
public record StableIntFunction<R>(@Stable StableValueImpl<R>[] delegates,
|
||||
IntFunction<? extends R> original) implements IntFunction<R> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(int index) {
|
||||
final StableValueImpl<R> delegate;
|
||||
try {
|
||||
delegate = delegates[index];
|
||||
} catch (ArrayIndexOutOfBoundsException ioob) {
|
||||
throw new IllegalArgumentException("Input not allowed: " + index, ioob);
|
||||
}
|
||||
return delegate.orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(index); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableIntFunction", delegates);
|
||||
}
|
||||
|
||||
public static <R> StableIntFunction<R> of(int size, IntFunction<? extends R> original) {
|
||||
return new StableIntFunction<>(StableUtil.array(size), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Implementation of a stable supplier.
|
||||
* <p>
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <T> the return type
|
||||
*/
|
||||
public record StableSupplier<T>(StableValueImpl<T> delegate,
|
||||
Supplier<? extends T> original) implements Supplier<T> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T get() {
|
||||
return delegate.orElseSet(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = delegate.wrappedContentAcquire();
|
||||
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
|
||||
}
|
||||
|
||||
public static <T> StableSupplier<T> of(Supplier<? extends T> original) {
|
||||
return new StableSupplier<>(StableValueImpl.of(), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public final class StableUtil {
|
||||
|
||||
private StableUtil() {}
|
||||
|
||||
public static <R> String renderElements(Object self,
|
||||
String selfName,
|
||||
StableValueImpl<?>[] delegates) {
|
||||
return renderElements(self, selfName, delegates, 0, delegates.length);
|
||||
}
|
||||
|
||||
public static <R> String renderElements(Object self,
|
||||
String selfName,
|
||||
StableValueImpl<?>[] delegates,
|
||||
int offset,
|
||||
int length) {
|
||||
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Object value = delegates[i + offset].wrappedContentAcquire();
|
||||
if (value == self) {
|
||||
sj.add("(this " + selfName + ")");
|
||||
} else {
|
||||
sj.add(StableValueImpl.renderWrapped(value));
|
||||
}
|
||||
}
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
public static <K, V> String renderMappings(Object self,
|
||||
String selfName,
|
||||
Iterable<Map.Entry<K, StableValueImpl<V>>> delegates,
|
||||
boolean curly) {
|
||||
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
|
||||
for (var e : delegates) {
|
||||
final Object value = e.getValue().wrappedContentAcquire();
|
||||
final String valueString;
|
||||
if (value == self) {
|
||||
valueString = ("(this ") + selfName + ")";
|
||||
} else {
|
||||
valueString = StableValueImpl.renderWrapped(value);
|
||||
}
|
||||
sj.add(e.getKey() + "=" + valueString);
|
||||
}
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
public static <T> StableValueImpl<T>[] array(int size) {
|
||||
assertSizeNonNegative(size);
|
||||
@SuppressWarnings("unchecked")
|
||||
final var stableValues = (StableValueImpl<T>[]) new StableValueImpl<?>[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
stableValues[i] = StableValueImpl.of();
|
||||
}
|
||||
return stableValues;
|
||||
}
|
||||
|
||||
public static <K, T> Map<K, StableValueImpl<T>> map(Set<K> keys) {
|
||||
Objects.requireNonNull(keys);
|
||||
@SuppressWarnings("unchecked")
|
||||
final var entries = (Map.Entry<K, StableValueImpl<T>>[]) new Map.Entry<?, ?>[keys.size()];
|
||||
int i = 0;
|
||||
for (K key : keys) {
|
||||
entries[i++] = Map.entry(key, StableValueImpl.of());
|
||||
}
|
||||
return Map.ofEntries(entries);
|
||||
}
|
||||
|
||||
public static void assertSizeNonNegative(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("size can not be negative: " + size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.vm.annotation.DontInline;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The implementation of StableValue.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
public final class StableValueImpl<T> implements StableValue<T> {
|
||||
|
||||
static final String UNSET_LABEL = ".unset";
|
||||
|
||||
// Unsafe allows StableValue to be used early in the boot sequence
|
||||
static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
// Unsafe offsets for direct field access
|
||||
|
||||
private static final long CONTENT_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.
|
||||
private static final Object NULL_SENTINEL = new Object();
|
||||
|
||||
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
|
||||
// memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`).
|
||||
//
|
||||
// This field is used directly and reflectively via Unsafe using explicit memory semantics.
|
||||
//
|
||||
// | Value | Meaning |
|
||||
// | -------------- | ------------ |
|
||||
// | null | Unset |
|
||||
// | NULL_SENTINEL | Set(null) |
|
||||
// | other | Set(other) |
|
||||
//
|
||||
@Stable
|
||||
private Object contents;
|
||||
|
||||
// Only allow creation via the factory `StableValueImpl::newInstance`
|
||||
private StableValueImpl() {}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean trySet(T contents) {
|
||||
if (wrappedContentAcquire() != null) {
|
||||
return false;
|
||||
}
|
||||
// Prevent reentry via an orElseSet(supplier)
|
||||
preventReentry();
|
||||
// Mutual exclusion is required here as `orElseSet` might also
|
||||
// attempt to modify the `wrappedValue`
|
||||
synchronized (this) {
|
||||
return wrapAndSet(contents);
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public void setOrThrow(T contents) {
|
||||
if (!trySet(contents)) {
|
||||
// Neither the set contents nor the provided contents is revealed in the
|
||||
// exception message as it might be sensitive.
|
||||
throw new IllegalStateException("The contents is already set");
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseThrow() {
|
||||
final Object t = wrappedContentAcquire();
|
||||
if (t == null) {
|
||||
throw new NoSuchElementException("No contents set");
|
||||
}
|
||||
return unwrap(t);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElse(T other) {
|
||||
final Object t = wrappedContentAcquire();
|
||||
return (t == null) ? other : unwrap(t);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean isSet() {
|
||||
return wrappedContentAcquire() != null;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseSet(Supplier<? extends T> supplier) {
|
||||
Objects.requireNonNull(supplier);
|
||||
final Object t = wrappedContentAcquire();
|
||||
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
|
||||
}
|
||||
|
||||
@DontInline
|
||||
private T orElseSetSlowPath(Supplier<? extends T> supplier) {
|
||||
preventReentry();
|
||||
synchronized (this) {
|
||||
final Object t = contents; // Plain semantics suffice here
|
||||
if (t == null) {
|
||||
final T newValue = supplier.get();
|
||||
// The mutex is not reentrant so we know newValue should be returned
|
||||
wrapAndSet(newValue);
|
||||
return newValue;
|
||||
}
|
||||
return unwrap(t);
|
||||
}
|
||||
}
|
||||
|
||||
// The methods equals() and hashCode() should be based on identity (defaults from Object)
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = wrappedContentAcquire();
|
||||
return t == this
|
||||
? "(this StableValue)"
|
||||
: renderWrapped(t);
|
||||
}
|
||||
|
||||
// Internal methods shared with other internal classes
|
||||
|
||||
@ForceInline
|
||||
public Object wrappedContentAcquire() {
|
||||
return UNSAFE.getReferenceAcquire(this, CONTENT_OFFSET);
|
||||
}
|
||||
|
||||
static String renderWrapped(Object t) {
|
||||
return (t == null) ? UNSET_LABEL : Objects.toString(unwrap(t));
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
// This method is not annotated with @ForceInline as it is always called
|
||||
// in a slow path.
|
||||
private void preventReentry() {
|
||||
if (Thread.holdsLock(this)) {
|
||||
throw new IllegalStateException("Recursive initialization of a stable value is illegal");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the provided {@code newValue} and tries to set the contents.
|
||||
* <p>
|
||||
* This method ensures the {@link Stable} field is written to at most once.
|
||||
*
|
||||
* @param newValue to wrap and set
|
||||
* @return if the contents was set
|
||||
*/
|
||||
@ForceInline
|
||||
private boolean wrapAndSet(Object 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));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Wraps `null` values into a sentinel value
|
||||
@ForceInline
|
||||
private static Object wrap(Object t) {
|
||||
return (t == null) ? NULL_SENTINEL : t;
|
||||
}
|
||||
|
||||
// Unwraps null sentinel values into `null`
|
||||
@SuppressWarnings("unchecked")
|
||||
@ForceInline
|
||||
private static <T> T unwrap(Object t) {
|
||||
return t != NULL_SENTINEL ? (T) t : null;
|
||||
}
|
||||
|
||||
// Factory
|
||||
|
||||
public static <T> StableValueImpl<T> of() {
|
||||
return new StableValueImpl<>();
|
||||
}
|
||||
|
||||
}
|
||||
237
test/jdk/java/lang/StableValue/StableFunctionTest.java
Normal file
237
test/jdk/java/lang/StableValue/StableFunctionTest.java
Normal file
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableFunction methods
|
||||
* @enablePreview
|
||||
* @run junit StableFunctionTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableFunctionTest {
|
||||
|
||||
enum Value {
|
||||
// Zero is here so that we have enums with ordinals before the first one
|
||||
// actually used in input sets (i.e. ZERO is not in the input set)
|
||||
ZERO(0),
|
||||
ILLEGAL_BEFORE(-1),
|
||||
// Valid values
|
||||
THIRTEEN(13),
|
||||
ILLEGAL_BETWEEN(-2),
|
||||
FORTY_TWO(42),
|
||||
// Illegal values (not in the input set)
|
||||
ILLEGAL_AFTER(-3);
|
||||
|
||||
final int intValue;
|
||||
|
||||
Value(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
int asInt() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Function<Value, Integer> MAPPER = Value::asInt;
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void factoryInvariants(Set<Value> inputs) {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(null, MAPPER));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(inputs, null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void basic(Set<Value> inputs) {
|
||||
basic(inputs, MAPPER);
|
||||
toStringTest(inputs, MAPPER);
|
||||
basic(inputs, _ -> null);
|
||||
toStringTest(inputs, _ -> null);
|
||||
}
|
||||
|
||||
void basic(Set<Value> inputs, Function<Value, Integer> mapper) {
|
||||
StableTestUtil.CountingFunction<Value, Integer> cif = new StableTestUtil.CountingFunction<>(mapper);
|
||||
var cached = StableValue.function(inputs, cif);
|
||||
assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE));
|
||||
assertEquals("Input not allowed: ILLEGAL_BEFORE", x0.getMessage());
|
||||
var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN));
|
||||
assertEquals("Input not allowed: ILLEGAL_BETWEEN", x1.getMessage());
|
||||
var x2 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_AFTER));
|
||||
assertEquals("Input not allowed: ILLEGAL_AFTER", x2.getMessage());
|
||||
}
|
||||
|
||||
void toStringTest(Set<Value> inputs, Function<Value, Integer> mapper) {
|
||||
var cached = StableValue.function(inputs, mapper);
|
||||
cached.apply(Value.FORTY_TWO);
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.startsWith("{"));
|
||||
// Key order is unspecified
|
||||
assertTrue(toString.contains(Value.THIRTEEN + "=.unset"));
|
||||
assertTrue(toString.contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO)));
|
||||
assertTrue(toString.endsWith("}"));
|
||||
// One between the values
|
||||
assertEquals(1L, toString.chars().filter(ch -> ch == ',').count());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("emptySets")
|
||||
void empty(Set<Value> inputs) {
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, Value::asInt);
|
||||
Function<Value, Integer> f1 = StableValue.function(inputs, Value::asInt);
|
||||
assertEquals("{}", f0.toString());
|
||||
assertThrows(NullPointerException.class, () -> f0.apply(null));
|
||||
assertNotEquals(f0, f1);
|
||||
assertNotEquals(null, f0);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void exception(Set<Value> inputs) {
|
||||
StableTestUtil.CountingFunction<Value, Integer> cif = new StableTestUtil.CountingFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.function(inputs, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(2, cif.cnt());
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.startsWith("{"));
|
||||
// Key order is unspecified
|
||||
assertTrue(toString.contains(Value.THIRTEEN + "=.unset"));
|
||||
assertTrue(toString.contains(Value.FORTY_TWO + "=.unset"));
|
||||
assertTrue(toString.endsWith("}"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void circular(Set<Value> inputs) {
|
||||
final AtomicReference<Function<?, ?>> ref = new AtomicReference<>();
|
||||
Function<Value, Function<?, ?>> cached = StableValue.function(inputs, _ -> ref.get());
|
||||
ref.set(cached);
|
||||
cached.apply(Value.FORTY_TWO);
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.contains("FORTY_TWO=(this StableFunction)"), toString);
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
assertDoesNotThrow((() -> cached.equals(cached)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void equality(Set<Value> inputs) {
|
||||
Function<Value, Integer> mapper = Value::asInt;
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, mapper);
|
||||
Function<Value, Integer> f1 = StableValue.function(inputs, mapper);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void hashCodeStable(Set<Value> inputs) {
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, Value::asInt);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
if (!inputs.isEmpty()) {
|
||||
f0.apply(Value.FORTY_TWO);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullKeys() {
|
||||
Set<Value> inputs = new HashSet<>();
|
||||
inputs.add(Value.FORTY_TWO);
|
||||
inputs.add(null);
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(inputs, MAPPER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesOptimizedVersion() {
|
||||
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt);
|
||||
assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName());
|
||||
Function<Value, Integer> emptyFunction = StableValue.function(Set.of(), Value::asInt);
|
||||
assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName());
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> nonEmptySets() {
|
||||
return Stream.of(
|
||||
Set.of(Value.FORTY_TWO, Value.THIRTEEN),
|
||||
linkedHashSet(Value.THIRTEEN, Value.FORTY_TWO),
|
||||
treeSet(Value.FORTY_TWO, Value.THIRTEEN),
|
||||
EnumSet.of(Value.FORTY_TWO, Value.THIRTEEN)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> emptySets() {
|
||||
return Stream.of(
|
||||
Set.of(),
|
||||
linkedHashSet(),
|
||||
treeSet(),
|
||||
EnumSet.noneOf(Value.class)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> allSets() {
|
||||
return Stream.concat(
|
||||
nonEmptySets(),
|
||||
emptySets()
|
||||
);
|
||||
}
|
||||
|
||||
static Set<Value> treeSet(Value... values) {
|
||||
return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values);
|
||||
}
|
||||
|
||||
static Set<Value> linkedHashSet(Value... values) {
|
||||
return populate(new LinkedHashSet<>(), values);
|
||||
}
|
||||
|
||||
static Set<Value> populate(Set<Value> set, Value... values) {
|
||||
set.addAll(Arrays.asList(values));
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
109
test/jdk/java/lang/StableValue/StableIntFunctionTest.java
Normal file
109
test/jdk/java/lang/StableValue/StableIntFunctionTest.java
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableIntFunction methods
|
||||
* @enablePreview
|
||||
* @run junit StableIntFunctionTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableIntFunctionTest {
|
||||
|
||||
private static final int SIZE = 2;
|
||||
private static final IntFunction<Integer> MAPPER = i -> i;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(IllegalArgumentException.class, () -> StableValue.intFunction(-1, MAPPER));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.intFunction(SIZE, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void basic() {
|
||||
basic(MAPPER);
|
||||
basic(i -> null);
|
||||
}
|
||||
|
||||
void basic(IntFunction<Integer> mapper) {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(mapper);
|
||||
var cached = StableValue.intFunction(SIZE, cif);
|
||||
assertEquals("[.unset, .unset]", cached.toString());
|
||||
assertEquals(mapper.apply(1), cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals(mapper.apply(1), cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals("[.unset, " + mapper.apply(1) + "]", cached.toString());
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE));
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exception() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.intFunction(SIZE, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(1));
|
||||
assertEquals(2, cif.cnt());
|
||||
assertEquals("[.unset, .unset]", cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void circular() {
|
||||
final AtomicReference<IntFunction<?>> ref = new AtomicReference<>();
|
||||
IntFunction<IntFunction<?>> cached = StableValue.intFunction(SIZE, _ -> ref.get());
|
||||
ref.set(cached);
|
||||
cached.apply(0);
|
||||
String toString = cached.toString();
|
||||
assertEquals("[(this StableIntFunction), .unset]", toString);
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
assertDoesNotThrow((() -> cached.equals(cached)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equality() {
|
||||
IntFunction<Integer> f0 = StableValue.intFunction(8, MAPPER);
|
||||
IntFunction<Integer> f1 = StableValue.intFunction(8, MAPPER);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeStable() {
|
||||
IntFunction<Integer> f0 = StableValue.intFunction(8, MAPPER);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
f0.apply(4);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
436
test/jdk/java/lang/StableValue/StableListTest.java
Normal file
436
test/jdk/java/lang/StableValue/StableListTest.java
Normal file
@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableList methods
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @enablePreview
|
||||
* @run junit StableListTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
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;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.RandomAccess;
|
||||
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.IntFunction;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableListTest {
|
||||
|
||||
private static final int ZERO = 0;
|
||||
private static final int INDEX = 7;
|
||||
private static final int SIZE = 31;
|
||||
private static final IntFunction<Integer> IDENTITY = i -> i;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.list(SIZE, null));
|
||||
assertThrows(IllegalArgumentException.class, () -> StableValue.list(-1, IDENTITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
assertFalse(newList().isEmpty());
|
||||
assertTrue(newEmptyList().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
assertEquals(SIZE, newList().size());
|
||||
assertEquals(ZERO, newEmptyList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(IDENTITY);
|
||||
var lazy = StableValue.list(SIZE, cif);
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(i + 1, cif.cnt());
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(i + 1, cif.cnt());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getException() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = StableValue.list(SIZE, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
|
||||
assertEquals(2, cif.cnt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArray() {
|
||||
assertArrayEquals(new Object[ZERO], newEmptyList().toArray());
|
||||
assertArrayEquals(newRegularList().toArray(), newList().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArrayWithArrayLarger() {
|
||||
Integer[] actual = new Integer[SIZE];
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
actual[INDEX] = 100 + i;
|
||||
}
|
||||
var list = StableValue.list(INDEX, IDENTITY);
|
||||
assertSame(actual, list.toArray(actual));
|
||||
Integer[] expected = IntStream.range(0, SIZE)
|
||||
.mapToObj(i -> i < INDEX ? i : null)
|
||||
.toArray(Integer[]::new);
|
||||
assertArrayEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArrayWithArraySmaller() {
|
||||
Integer[] arr = new Integer[INDEX];
|
||||
Integer[] actual = newList().toArray(arr);
|
||||
assertNotSame(arr, actual);
|
||||
Integer[] expected = newRegularList().toArray(new Integer[0]);
|
||||
assertArrayEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toArrayWithGenerator() {
|
||||
Integer[] expected = newRegularList().toArray(Integer[]::new);
|
||||
Integer[] actual = newList().toArray(Integer[]::new);
|
||||
assertArrayEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstIndex() {
|
||||
var lazy = newList();
|
||||
for (int i = INDEX; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.indexOf(i));
|
||||
}
|
||||
assertEquals(-1, lazy.indexOf(SIZE + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lastIndex() {
|
||||
var lazy = newList();
|
||||
for (int i = INDEX; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.lastIndexOf(i));
|
||||
}
|
||||
assertEquals(-1, lazy.lastIndexOf(SIZE + 1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringTest() {
|
||||
assertEquals("[]", newEmptyList().toString());
|
||||
var list = StableValue.list(2, IDENTITY);
|
||||
assertEquals("[.unset, .unset]", list.toString());
|
||||
list.get(0);
|
||||
assertEquals("[0, .unset]", list.toString());
|
||||
list.get(1);
|
||||
assertEquals("[0, 1]", list.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeTest() {
|
||||
assertEquals(List.of().hashCode(), newEmptyList().hashCode());
|
||||
assertEquals(newRegularList().hashCode(), newList().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsTest() {
|
||||
assertTrue(newEmptyList().equals(List.of()));
|
||||
assertTrue(List.of().equals(newEmptyList()));
|
||||
assertTrue(newList().equals(newRegularList()));
|
||||
assertTrue(newRegularList().equals(newList()));
|
||||
assertFalse(newList().equals("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsPartialEvaluationTest() {
|
||||
var list = StableValue.list(2, IDENTITY);
|
||||
assertFalse(list.equals(List.of(0)));
|
||||
assertEquals("[0, .unset]", list.toString());
|
||||
assertTrue(list.equals(List.of(0, 1)));
|
||||
assertEquals("[0, 1]", list.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorTotal() {
|
||||
var iterator = newList().iterator();
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
assertTrue(iterator.hasNext());
|
||||
assertTrue(iterator.hasNext());
|
||||
assertEquals(i, iterator.next());
|
||||
}
|
||||
assertFalse(iterator.hasNext());
|
||||
assertThrows(NoSuchElementException.class, iterator::next);
|
||||
AtomicInteger cnt = new AtomicInteger();
|
||||
iterator.forEachRemaining(_ -> cnt.incrementAndGet());
|
||||
assertEquals(0, cnt.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorPartial() {
|
||||
var iterator = newList().iterator();
|
||||
for (int i = 0; i < INDEX; i++) {
|
||||
assertTrue(iterator.hasNext());
|
||||
assertTrue(iterator.hasNext());
|
||||
assertEquals(i, iterator.next());
|
||||
}
|
||||
assertTrue(iterator.hasNext());
|
||||
AtomicInteger cnt = new AtomicInteger();
|
||||
iterator.forEachRemaining(_ -> cnt.incrementAndGet());
|
||||
assertEquals(SIZE - INDEX, cnt.get());
|
||||
assertFalse(iterator.hasNext());
|
||||
assertThrows(NoSuchElementException.class, iterator::next);
|
||||
}
|
||||
|
||||
@Test
|
||||
void subList() {
|
||||
var lazy = newList();
|
||||
var lazySubList = lazy.subList(1, SIZE);
|
||||
assertInstanceOf(RandomAccess.class, lazySubList);
|
||||
var regularList = newRegularList();
|
||||
var regularSubList = regularList.subList(1, SIZE);
|
||||
assertEquals(regularSubList, lazySubList);
|
||||
}
|
||||
|
||||
@Test
|
||||
void subList2() {
|
||||
var lazy = newList();
|
||||
var lazySubList = lazy.subList(1, SIZE);
|
||||
lazySubList.get(0);
|
||||
var eq = newList();
|
||||
eq.get(1);
|
||||
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) {
|
||||
assertEquals(asString(".unset", subList), subList.toString());
|
||||
|
||||
var first = subList.getFirst();
|
||||
assertEquals(asString(first.toString(), subList), subList.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reversed() {
|
||||
var reversed = newList().reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversed);
|
||||
assertEquals(SIZE - 1, reversed.getFirst());
|
||||
assertEquals(0, reversed.getLast());
|
||||
|
||||
var reversed2 = reversed.reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversed2);
|
||||
assertEquals(0, reversed2.getFirst());
|
||||
assertEquals(SIZE - 1, reversed2.getLast());
|
||||
// Make sure we get back a non-reversed implementation
|
||||
assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reversedToString() {
|
||||
var reversed = newList().reversed();
|
||||
subListToString0(reversed);
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@Test
|
||||
void recursiveCall() {
|
||||
AtomicReference<IntFunction<Integer>> ref = new AtomicReference<>();
|
||||
var lazy = StableValue.list(SIZE, i -> ref.get().apply(i));
|
||||
ref.set(lazy::get);
|
||||
var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX));
|
||||
assertEquals("Recursive initialization of a stable value is illegal", x.getMessage());
|
||||
}
|
||||
|
||||
// Immutability
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("unsupportedOperations")
|
||||
void unsupported(Operation operation) {
|
||||
assertThrowsForOperation(UnsupportedOperationException.class, operation);
|
||||
}
|
||||
|
||||
// Method parameter invariant checking
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nullAverseOperations")
|
||||
void nullAverse(Operation operation) {
|
||||
assertThrowsForOperation(NullPointerException.class, operation);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("outOfBoundsOperations")
|
||||
void outOfBounds(Operation operation) {
|
||||
assertThrowsForOperation(IndexOutOfBoundsException.class, operation);
|
||||
}
|
||||
|
||||
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
|
||||
var lazy = newList();
|
||||
assertThrows(expectedType, () -> operation.accept(lazy));
|
||||
var sub = lazy.subList(1, SIZE / 2);
|
||||
assertThrows(expectedType, () -> operation.accept(sub));
|
||||
var subSub = sub.subList(1, sub.size() / 2);
|
||||
assertThrows(expectedType, () -> operation.accept(subSub));
|
||||
}
|
||||
|
||||
// Implementing interfaces
|
||||
|
||||
@Test
|
||||
void serializable() {
|
||||
serializable(newList());
|
||||
serializable(newEmptyList());
|
||||
}
|
||||
|
||||
void serializable(List<Integer> list) {
|
||||
assertFalse(list instanceof Serializable);
|
||||
if (list.size()>INDEX) {
|
||||
assertFalse(newList().subList(1, INDEX) instanceof Serializable);
|
||||
}
|
||||
assertFalse(list.iterator() instanceof Serializable);
|
||||
assertFalse(list.reversed() instanceof Serializable);
|
||||
assertFalse(list.spliterator() instanceof Serializable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void randomAccess() {
|
||||
assertInstanceOf(RandomAccess.class, newList());
|
||||
assertInstanceOf(RandomAccess.class, newEmptyList());
|
||||
assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX));
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinct() {
|
||||
StableValueImpl<Integer>[] array = StableUtil.array(SIZE);
|
||||
assertEquals(SIZE, array.length);
|
||||
// Check, every StableValue is distinct
|
||||
Map<StableValue<Integer>, Boolean> idMap = new IdentityHashMap<>();
|
||||
for (var e: array) {
|
||||
idMap.put(e, true);
|
||||
}
|
||||
assertEquals(SIZE, idMap.size());
|
||||
}
|
||||
|
||||
// Support constructs
|
||||
|
||||
record Operation(String name,
|
||||
Consumer<List<Integer>> consumer) implements Consumer<List<Integer>> {
|
||||
@Override public void accept(List<Integer> list) { consumer.accept(list); }
|
||||
@Override public String toString() { return name; }
|
||||
}
|
||||
|
||||
static Stream<Operation> nullAverseOperations() {
|
||||
return Stream.of(
|
||||
new Operation("forEach", l -> l.forEach(null)),
|
||||
new Operation("containsAll", l -> l.containsAll(null)),
|
||||
new Operation("toArray", l -> l.toArray((Integer[]) null)),
|
||||
new Operation("toArray", l -> l.toArray((IntFunction<Integer[]>) null))
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Operation> outOfBoundsOperations() {
|
||||
return Stream.of(
|
||||
new Operation("get(-1)", l -> l.get(-1)),
|
||||
new Operation("get(size)", l -> l.get(l.size())),
|
||||
new Operation("sublist(-1,)", l -> l.subList(-1, INDEX)),
|
||||
new Operation("sublist(,size)", l -> l.subList(0, l.size() + 1)),
|
||||
new Operation("listIter(-1)", l -> l.listIterator(-1)),
|
||||
new Operation("listIter(size)", l -> l.listIterator(l.size() + 1))
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Operation> unsupportedOperations() {
|
||||
final Set<Integer> SET = Set.of(0, 1);
|
||||
return Stream.of(
|
||||
new Operation("add(0)", l -> l.add(0)),
|
||||
new Operation("add(0, 1)", l -> l.add(0, 1)),
|
||||
new Operation("addAll(col)", l -> l.addAll(SET)),
|
||||
new Operation("addAll(1, coll)", l -> l.addAll(1, SET)),
|
||||
new Operation("addFirst(0)", l -> l.addFirst(0)),
|
||||
new Operation("addLast(0)", l -> l.addLast(0)),
|
||||
new Operation("clear", List::clear),
|
||||
new Operation("remove(Obj)", l -> l.remove((Object)1)),
|
||||
new Operation("remove(1)", l -> l.remove(1)),
|
||||
new Operation("removeAll", l -> l.removeAll(SET)),
|
||||
new Operation("removeFirst", List::removeFirst),
|
||||
new Operation("removeLast", List::removeLast),
|
||||
new Operation("removeIf", l -> l.removeIf(i -> i % 2 == 0)),
|
||||
new Operation("replaceAll", l -> l.replaceAll(i -> i + 1)),
|
||||
new Operation("sort", l -> l.sort(Comparator.naturalOrder())),
|
||||
new Operation("iterator().remove", l -> l.iterator().remove()),
|
||||
new Operation("listIter().remove", l -> l.listIterator().remove()),
|
||||
new Operation("listIter().add", l -> l.listIterator().add(1)),
|
||||
new Operation("listIter().set", l -> l.listIterator().set(1))
|
||||
);
|
||||
}
|
||||
|
||||
static List<Integer> newList() {
|
||||
return StableValue.list(SIZE, IDENTITY);
|
||||
}
|
||||
|
||||
static List<Integer> newEmptyList() {
|
||||
return StableValue.list(ZERO, IDENTITY);
|
||||
}
|
||||
|
||||
static List<Integer> newRegularList() {
|
||||
return IntStream.range(0, SIZE).boxed().toList();
|
||||
}
|
||||
|
||||
static String asString(String first, List<Integer> list) {
|
||||
return "[" + first + ", " + Stream.generate(() -> ".unset")
|
||||
.limit(list.size() - 1)
|
||||
.collect(Collectors.joining(", ")) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
357
test/jdk/java/lang/StableValue/StableMapTest.java
Normal file
357
test/jdk/java/lang/StableValue/StableMapTest.java
Normal file
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableMap methods
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @enablePreview
|
||||
* @run junit StableMapTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableMapTest {
|
||||
|
||||
private static final int NOT_PRESENT = 147;
|
||||
private static final int KEY = 7;
|
||||
private static final Set<Integer> KEYS = Set.of(0, KEY, 13);
|
||||
private static final Set<Integer> EMPTY = Set.of();
|
||||
private static final Function<Integer, Integer> IDENTITY = Function.identity();
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(KEYS, null));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(null, IDENTITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
assertFalse(newMap().isEmpty());
|
||||
assertTrue(newEmptyMap().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
assertEquals(KEYS.size(), newMap().size());
|
||||
assertEquals(EMPTY.size(), newEmptyMap().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
StableTestUtil.CountingFunction<Integer, Integer> cf = new StableTestUtil.CountingFunction<>(IDENTITY);
|
||||
var lazy = StableValue.map(KEYS, cf);
|
||||
int cnt = 1;
|
||||
for (int i : KEYS) {
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(cnt, cf.cnt());
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(cnt++, cf.cnt());
|
||||
}
|
||||
assertNull(lazy.get(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getException() {
|
||||
StableTestUtil.CountingFunction<Integer, Integer> cf = new StableTestUtil.CountingFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = StableValue.map(KEYS, cf);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(1, cf.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(2, cf.cnt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsKey() {
|
||||
var lazy = newMap();
|
||||
for (int i : KEYS) {
|
||||
assertTrue(lazy.containsKey(i));
|
||||
}
|
||||
assertFalse(lazy.containsKey(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsValue() {
|
||||
var lazy = newMap();
|
||||
for (int i : KEYS) {
|
||||
assertTrue(lazy.containsValue(i));
|
||||
}
|
||||
assertFalse(lazy.containsValue(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forEach() {
|
||||
var lazy = newMap();
|
||||
Set<Map.Entry<Integer, Integer>> expected = KEYS.stream()
|
||||
.map(i -> new AbstractMap.SimpleImmutableEntry<>(i , i))
|
||||
.collect(Collectors.toSet());
|
||||
Set<Map.Entry<Integer, Integer>> actual = new HashSet<>();
|
||||
lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v)));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringTest() {
|
||||
assertEquals("{}", newEmptyMap().toString());
|
||||
var map = StableValue.map(Set.of(KEY), IDENTITY);
|
||||
assertEquals("{" + KEY + "=.unset}", map.toString());
|
||||
map.get(KEY);
|
||||
assertEquals("{" + KEY + "=" + KEY + "}", map.toString());
|
||||
String actual = newMap().toString();
|
||||
assertTrue(actual.startsWith("{"));
|
||||
for (int key : KEYS) {
|
||||
assertTrue(actual.contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(actual.endsWith("}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeTest() {
|
||||
assertEquals(Map.of().hashCode(), newEmptyMap().hashCode());
|
||||
assertEquals(newRegularMap().hashCode(), newMap().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsTest() {
|
||||
assertTrue(newEmptyMap().equals(Map.of()));
|
||||
assertTrue(Map.of().equals(newEmptyMap()));
|
||||
assertTrue(newMap().equals(newRegularMap()));
|
||||
assertTrue(newRegularMap().equals(newMap()));
|
||||
assertFalse(newMap().equals("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySet() {
|
||||
var regular = newRegularMap().entrySet();
|
||||
var actual = newMap().entrySet();
|
||||
assertTrue(regular.equals(actual));
|
||||
assertTrue(actual.equals(regular));
|
||||
assertTrue(regular.equals(actual));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySetToString() {
|
||||
var map = newMap();
|
||||
var entrySet = map.entrySet();
|
||||
var toString = entrySet.toString();
|
||||
for (var key : KEYS) {
|
||||
assertTrue(toString.contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(toString.startsWith("["));
|
||||
assertTrue(toString.endsWith("]"));
|
||||
|
||||
map.get(KEY);
|
||||
for (var key : KEYS) {
|
||||
if (key.equals(KEY)) {
|
||||
continue;
|
||||
}
|
||||
assertTrue(entrySet.toString().contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(entrySet.toString().contains(KEY + "=" + KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void values() {
|
||||
var map = newMap();
|
||||
var values = map.values();
|
||||
// Look at one of the elements
|
||||
var val = values.stream().iterator().next();
|
||||
var toString = map.toString();
|
||||
for (var key : KEYS) {
|
||||
if (key.equals(val)) {
|
||||
assertTrue(toString.contains(key + "=" + key));
|
||||
} else {
|
||||
assertTrue(toString.contains(key + "=.unset"));
|
||||
}
|
||||
}
|
||||
|
||||
// Mod ops
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.remove(KEY));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.add(KEY));
|
||||
assertThrows(UnsupportedOperationException.class, values::clear);
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.addAll(Set.of(1)));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.removeIf(i -> true));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.retainAll(Set.of(KEY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesToString() {
|
||||
var map = newMap();
|
||||
var values = map.values();
|
||||
assertEquals("[.unset, .unset, .unset]", values.toString());
|
||||
map.get(KEY);
|
||||
var afterGet = values.toString();
|
||||
assertTrue(afterGet.contains(Integer.toString(KEY)), afterGet);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorNext() {
|
||||
Set<Integer> encountered = new HashSet<>();
|
||||
var iterator = newMap().entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var entry = iterator.next();
|
||||
assertEquals(entry.getKey(), entry.getValue());
|
||||
encountered.add(entry.getValue());
|
||||
}
|
||||
assertEquals(KEYS, encountered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorForEachRemaining() {
|
||||
Set<Integer> encountered = new HashSet<>();
|
||||
var iterator = newMap().entrySet().iterator();
|
||||
var entry = iterator.next();
|
||||
assertEquals(entry.getKey(), entry.getValue());
|
||||
encountered.add(entry.getValue());
|
||||
iterator.forEachRemaining(e -> {
|
||||
assertEquals(e.getKey(), e.getValue());
|
||||
encountered.add(e.getValue());
|
||||
});
|
||||
assertEquals(KEYS, encountered);
|
||||
}
|
||||
|
||||
// Immutability
|
||||
@ParameterizedTest
|
||||
@MethodSource("unsupportedOperations")
|
||||
void unsupported(Operation operation) {
|
||||
assertThrowsForOperation(UnsupportedOperationException.class, operation);
|
||||
}
|
||||
|
||||
// Method parameter invariant checking
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nullAverseOperations")
|
||||
void nullAverse(Operation operation) {
|
||||
assertThrowsForOperation(NullPointerException.class, operation);
|
||||
}
|
||||
|
||||
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
|
||||
var lazy = newMap();
|
||||
assertThrows(expectedType, () -> operation.accept(lazy));
|
||||
}
|
||||
|
||||
// Implementing interfaces
|
||||
|
||||
@Test
|
||||
void serializable() {
|
||||
serializable(newMap());
|
||||
serializable(newEmptyMap());
|
||||
}
|
||||
|
||||
void serializable(Map<Integer, Integer> map) {
|
||||
assertFalse(map instanceof Serializable);
|
||||
assertFalse(map.entrySet() instanceof Serializable);
|
||||
assertFalse(map.keySet() instanceof Serializable);
|
||||
assertFalse(map.values() instanceof Serializable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinct() {
|
||||
Map<Integer, StableValueImpl<Integer>> map = StableUtil.map(Set.of(1, 2, 3));
|
||||
assertEquals(3, map.size());
|
||||
// Check, every StableValue is distinct
|
||||
Map<StableValue<Integer>, Boolean> idMap = new IdentityHashMap<>();
|
||||
map.forEach((k, v) -> idMap.put(v, true));
|
||||
assertEquals(3, idMap.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullResult() {
|
||||
var map = StableValue.map(Set.of(0), _ -> null);
|
||||
assertNull(map.getOrDefault(0, 1));;
|
||||
assertTrue(map.containsKey(0));
|
||||
assertNull(map.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullKeys() {
|
||||
Set<Integer> inputs = new HashSet<>();
|
||||
inputs.add(0);
|
||||
inputs.add(null);
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY));
|
||||
}
|
||||
|
||||
// Support constructs
|
||||
|
||||
record Operation(String name,
|
||||
Consumer<Map<Integer, Integer>> consumer) implements Consumer<Map<Integer, Integer>> {
|
||||
@java.lang.Override
|
||||
public void accept(Map<Integer, Integer> map) { consumer.accept(map); }
|
||||
@java.lang.Override
|
||||
public String toString() { return name; }
|
||||
}
|
||||
|
||||
static Stream<Operation> nullAverseOperations() {
|
||||
return Stream.of(
|
||||
new Operation("forEach", m -> m.forEach(null))
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Operation> unsupportedOperations() {
|
||||
return Stream.of(
|
||||
new Operation("clear", Map::clear),
|
||||
new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)),
|
||||
new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)),
|
||||
new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)),
|
||||
new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)),
|
||||
new Operation("put", m -> m.put(0, 0)),
|
||||
new Operation("putAll", m -> m.putAll(Map.of())),
|
||||
new Operation("remove1", m -> m.remove(KEY)),
|
||||
new Operation("remove2", m -> m.remove(KEY, KEY)),
|
||||
new Operation("replace2", m -> m.replace(KEY, 1)),
|
||||
new Operation("replace3", m -> m.replace(KEY, KEY, 1)),
|
||||
new Operation("replaceAll", m -> m.replaceAll((a, _) -> a))
|
||||
);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newMap() {
|
||||
return StableValue.map(KEYS, IDENTITY);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newEmptyMap() {
|
||||
return StableValue.map(EMPTY, IDENTITY);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newRegularMap() {
|
||||
return KEYS.stream().collect(Collectors.toMap(IDENTITY, IDENTITY));
|
||||
}
|
||||
|
||||
}
|
||||
104
test/jdk/java/lang/StableValue/StableSupplierTest.java
Normal file
104
test/jdk/java/lang/StableValue/StableSupplierTest.java
Normal file
@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableSupplier methods
|
||||
* @enablePreview
|
||||
* @run junit StableSupplierTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableSupplierTest {
|
||||
|
||||
private static final Supplier<Integer> SUPPLIER = () -> 42;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.supplier(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void basic() {
|
||||
basic(SUPPLIER);
|
||||
basic(() -> null);
|
||||
}
|
||||
|
||||
void basic(Supplier<Integer> supplier) {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(supplier);
|
||||
var cached = StableValue.supplier(cs);
|
||||
assertEquals(".unset", cached.toString());
|
||||
assertEquals(supplier.get(), cached.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(supplier.get(), cached.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(Objects.toString(supplier.get()), cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exception() {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.supplier(cs);
|
||||
assertThrows(UnsupportedOperationException.class, cached::get);
|
||||
assertEquals(1, cs.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, cached::get);
|
||||
assertEquals(2, cs.cnt());
|
||||
assertEquals(".unset", cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void circular() {
|
||||
final AtomicReference<Supplier<?>> ref = new AtomicReference<>();
|
||||
Supplier<Supplier<?>> cached = StableValue.supplier(ref::get);
|
||||
ref.set(cached);
|
||||
cached.get();
|
||||
String toString = cached.toString();
|
||||
assertTrue(toString.startsWith("(this StableSupplier)"));
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equality() {
|
||||
Supplier<Integer> f0 = StableValue.supplier(SUPPLIER);
|
||||
Supplier<Integer> f1 = StableValue.supplier(SUPPLIER);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeStable() {
|
||||
Supplier<Integer> f0 = StableValue.supplier(SUPPLIER);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
f0.get();
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
120
test/jdk/java/lang/StableValue/StableTestUtil.java
Normal file
120
test/jdk/java/lang/StableValue/StableTestUtil.java
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class StableTestUtil {
|
||||
|
||||
private StableTestUtil() {}
|
||||
|
||||
public static final class CountingSupplier<T>
|
||||
extends AbstractCounting<Supplier<T>>
|
||||
implements Supplier<T> {
|
||||
|
||||
public CountingSupplier(Supplier<T> delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
incrementCounter();
|
||||
return delegate.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class CountingIntFunction<R>
|
||||
extends AbstractCounting<IntFunction<R>>
|
||||
implements IntFunction<R> {
|
||||
|
||||
public CountingIntFunction(IntFunction<R> delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(int value) {
|
||||
incrementCounter();
|
||||
return delegate.apply(value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class CountingFunction<T, R>
|
||||
extends AbstractCounting<Function<T, R>>
|
||||
implements Function<T, R> {
|
||||
|
||||
public CountingFunction(Function<T, R> delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(T t) {
|
||||
incrementCounter();
|
||||
return delegate.apply(t);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static final class CountingBiFunction<T, U, R>
|
||||
extends AbstractCounting<BiFunction<T, U, R>>
|
||||
implements BiFunction<T, U, R> {
|
||||
|
||||
public CountingBiFunction(BiFunction<T, U, R> delegate) {
|
||||
super(delegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public R apply(T t, U u) {
|
||||
incrementCounter();
|
||||
return delegate.apply(t, u);
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class AbstractCounting<D> {
|
||||
|
||||
private final AtomicInteger cnt = new AtomicInteger();
|
||||
protected final D delegate;
|
||||
|
||||
protected AbstractCounting(D delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
protected final void incrementCounter() {
|
||||
cnt.incrementAndGet();
|
||||
}
|
||||
|
||||
public final int cnt() {
|
||||
return cnt.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String toString() {
|
||||
return cnt.toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
43
test/jdk/java/lang/StableValue/StableValueFactoriesTest.java
Normal file
43
test/jdk/java/lang/StableValue/StableValueFactoriesTest.java
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableValue factory implementations
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @enablePreview
|
||||
* @run junit StableValueFactoriesTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableValueFactoriesTest {
|
||||
|
||||
@Test
|
||||
void array() {
|
||||
assertThrows(IllegalArgumentException.class, () -> StableUtil.array(-1));
|
||||
}
|
||||
|
||||
}
|
||||
389
test/jdk/java/lang/StableValue/StableValueTest.java
Normal file
389
test/jdk/java/lang/StableValue/StableValueTest.java
Normal file
@ -0,0 +1,389 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableValue implementations
|
||||
* @enablePreview
|
||||
* @run junit StableValueTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
final class StableValueTest {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 13;
|
||||
|
||||
@Test
|
||||
void trySet() {
|
||||
trySet(VALUE);
|
||||
trySet(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preSet() {
|
||||
StableValue<Integer> stable = StableValue.of(VALUE);
|
||||
assertTrue(stable.isSet());
|
||||
assertEquals(VALUE, stable.orElseThrow());
|
||||
assertEquals(VALUE, stable.orElse(VALUE2));
|
||||
assertEquals(VALUE, stable.orElseSet(() -> VALUE2));
|
||||
assertFalse(stable.trySet(VALUE2));
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
|
||||
assertEquals(
|
||||
"The contents is already set",
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
void trySet(Integer initial) {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(initial));
|
||||
assertFalse(stable.trySet(null));
|
||||
assertFalse(stable.trySet(VALUE));
|
||||
assertFalse(stable.trySet(VALUE2));
|
||||
assertEquals(initial, stable.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setOrThrowValue() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
stable.setOrThrow(VALUE);
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
|
||||
assertEquals("The contents is already set", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setOrThrowNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
stable.setOrThrow(null);
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null));
|
||||
assertEquals("The contents is already set", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElse() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertEquals(VALUE, stable.orElse(VALUE));
|
||||
assertNull(stable.orElse(null));
|
||||
stable.trySet(VALUE);
|
||||
assertEquals(VALUE, stable.orElse(VALUE2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrow() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
var e = assertThrows(NoSuchElementException.class, stable::orElseThrow);
|
||||
assertEquals("No contents set", e.getMessage());
|
||||
stable.trySet(VALUE);
|
||||
assertEquals(VALUE, stable.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isSet() {
|
||||
isSet(VALUE);
|
||||
isSet(null);
|
||||
}
|
||||
|
||||
void isSet(Integer initial) {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertFalse(stable.isSet());
|
||||
stable.trySet(initial);
|
||||
assertTrue(stable.isSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrElseSetSupplier() {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(() -> VALUE);
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertThrows(NullPointerException.class, () -> stable.orElseSet(null));
|
||||
assertEquals(VALUE, stable.orElseSet(cs));
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(VALUE, stable.orElseSet(cs));
|
||||
assertEquals(1, cs.cnt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode() {
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
// Should be Object::hashCode
|
||||
assertEquals(System.identityHashCode(stableValue), stableValue.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
StableValue<Integer> s0 = StableValue.of();
|
||||
assertNotEquals(null, s0);
|
||||
StableValue<Integer> s1 = StableValue.of();
|
||||
assertNotEquals(s0, s1); // Identity based
|
||||
s0.setOrThrow(42);
|
||||
s1.setOrThrow(42);
|
||||
assertNotEquals(s0, s1);
|
||||
assertNotEquals("a", s0);
|
||||
StableValue<Integer> null0 = StableValue.of();
|
||||
StableValue<Integer> null1 = StableValue.of();
|
||||
null0.setOrThrow(null);
|
||||
null1.setOrThrow(null);
|
||||
assertNotEquals(null0, null1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringUnset() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertEquals(".unset", stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(null));
|
||||
assertEquals("null", stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringNonNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(VALUE));
|
||||
assertEquals(Objects.toString(VALUE), stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringCircular() {
|
||||
StableValue<StableValue<?>> stable = StableValue.of();
|
||||
stable.trySet(stable);
|
||||
String toString = assertDoesNotThrow(stable::toString);
|
||||
assertEquals("(this StableValue)", toString);
|
||||
assertDoesNotThrow(stable::hashCode);
|
||||
assertDoesNotThrow((() -> stable.equals(stable)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void recursiveCall() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
AtomicReference<StableValue<Integer>> ref = new AtomicReference<>(stable);
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
stable.orElseSet(() -> {
|
||||
ref.get().trySet(1);
|
||||
return 1;
|
||||
})
|
||||
);
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
stable.orElseSet(() -> {
|
||||
ref.get().orElseSet(() -> 1);
|
||||
return 1;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void intFunctionExample() {
|
||||
final class SqrtUtil {
|
||||
|
||||
private SqrtUtil() {}
|
||||
|
||||
private static final int CACHED_SIZE = 10;
|
||||
|
||||
private static final IntFunction<Double> SQRT =
|
||||
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt);
|
||||
|
||||
public static double sqrt(int a) {
|
||||
return SQRT.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime
|
||||
|
||||
assertEquals(3, sqrt9);
|
||||
assertThrows(IllegalArgumentException.class, () -> SqrtUtil.sqrt(16));
|
||||
}
|
||||
|
||||
@Test
|
||||
void intFunctionExample2() {
|
||||
final class PowerOf2Util {
|
||||
|
||||
private PowerOf2Util() {}
|
||||
|
||||
private static final int SIZE = 6;
|
||||
private static final IntFunction<Integer> ORIGINAL_POWER_OF_TWO =
|
||||
v -> 1 << v;
|
||||
|
||||
private static final IntFunction<Integer> POWER_OF_TWO =
|
||||
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO);
|
||||
|
||||
public static int powerOfTwo(int a) {
|
||||
return POWER_OF_TWO.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
|
||||
assertEquals(16, pwr4);
|
||||
assertEquals(1, PowerOf2Util.powerOfTwo(0));
|
||||
assertEquals(8, PowerOf2Util.powerOfTwo(3));
|
||||
assertEquals(32, PowerOf2Util.powerOfTwo(5));
|
||||
assertThrows(IllegalArgumentException.class, () -> PowerOf2Util.powerOfTwo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionExample() {
|
||||
|
||||
class Log2Util {
|
||||
|
||||
private Log2Util() {}
|
||||
|
||||
private static final Set<Integer> CACHED_KEYS =
|
||||
Set.of(1, 2, 4, 8, 16, 32);
|
||||
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
|
||||
i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
|
||||
private static final Function<Integer, Integer> LOG2_CACHED =
|
||||
// @link substring="function" target="#function(Set,Function)" :
|
||||
StableValue.function(CACHED_KEYS, LOG2_ORIGINAL);
|
||||
|
||||
public static double log2(int a) {
|
||||
if (CACHED_KEYS.contains(a)) {
|
||||
return LOG2_CACHED.apply(a);
|
||||
} else {
|
||||
return LOG2_ORIGINAL.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double log16 = Log2Util.log2(16); // May eventually constant fold to 4.0 at runtime
|
||||
double log256 = Log2Util.log2(256); // Will not constant fold
|
||||
|
||||
assertEquals(4, log16);
|
||||
assertEquals(8, log256);
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionExample2() {
|
||||
|
||||
class Log2Util {
|
||||
|
||||
private Log2Util() {}
|
||||
|
||||
private static final Set<Integer> KEYS =
|
||||
Set.of(1, 2, 4, 8);
|
||||
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
|
||||
i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
|
||||
private static final Function<Integer, Integer> LOG2 =
|
||||
// @link substring="function" target="#function(Set,Function)" :
|
||||
StableValue.function(KEYS, LOG2_ORIGINAL);
|
||||
|
||||
public static double log2(int a) {
|
||||
return LOG2.apply(a);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double log16 = Log2Util.log2(8); // May eventually constant fold to 3.0 at runtime
|
||||
|
||||
assertEquals(3, log16);
|
||||
assertThrows(IllegalArgumentException.class, () -> Log2Util.log2(3));
|
||||
}
|
||||
|
||||
private static final BiPredicate<StableValue<Integer>, Integer> TRY_SET = StableValue::trySet;
|
||||
private static final BiPredicate<StableValue<Integer>, Integer> SET_OR_THROW = (s, i) -> {
|
||||
try {
|
||||
s.setOrThrow(i);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
void raceTrySet() {
|
||||
race(TRY_SET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void raceSetOrThrow() {
|
||||
race(SET_OR_THROW);
|
||||
}
|
||||
|
||||
@Test
|
||||
void raceMixed() {
|
||||
race((s, i) -> switch (i % 2) {
|
||||
case 0 -> TRY_SET.test(s, i);
|
||||
case 1 -> SET_OR_THROW.test(s, i);
|
||||
default -> fail("should not reach here");
|
||||
});
|
||||
}
|
||||
|
||||
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
|
||||
int noThreads = 10;
|
||||
CountDownLatch starter = new CountDownLatch(1);
|
||||
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 ...
|
||||
starter.await();
|
||||
// Here we go!
|
||||
winners.put(i, winnerPredicate.test(stable, i));
|
||||
} catch (Throwable t) {
|
||||
fail(t);
|
||||
}
|
||||
}))
|
||||
.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());
|
||||
}
|
||||
|
||||
private static void join(Thread thread) {
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,179 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for making sure StableValue publishes values safely
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @enablePreview
|
||||
* @run junit StableValuesSafePublicationTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableValuesSafePublicationTest {
|
||||
|
||||
private static final int SIZE = 100_000;
|
||||
private static final int THREADS = Runtime.getRuntime().availableProcessors();
|
||||
private static final StableValue<Holder>[] STABLES = stables();
|
||||
|
||||
static StableValue<Holder>[] stables() {
|
||||
@SuppressWarnings("unchecked")
|
||||
StableValue<Holder>[] stables = (StableValue<Holder>[]) new StableValue[SIZE];
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
stables[i] = StableValue.of();
|
||||
}
|
||||
return stables;
|
||||
}
|
||||
|
||||
static final class Holder {
|
||||
// These are non-final fields but should be seen
|
||||
// fully initialized thanks to the HB properties of StableValue.
|
||||
int a, b, c, d, e;
|
||||
|
||||
Holder() {
|
||||
a = b = c = d = e = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static final class Consumer implements Runnable {
|
||||
|
||||
final int[] observations = new int[SIZE];
|
||||
final StableValue<Holder>[] stables = STABLES;
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (; i < SIZE; i++) {
|
||||
StableValue<Holder> s = stables[i];
|
||||
Holder h;
|
||||
// Wait until the StableValue has a holder value
|
||||
while ((h = s.orElse(null)) == null) {}
|
||||
int a = h.a;
|
||||
int b = h.b;
|
||||
int c = h.c;
|
||||
int d = h.d;
|
||||
int e = h.e;
|
||||
observations[i] = a + (b << 1) + (c << 2) + (c << 3) + (d << 4) + (e << 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final class Producer implements Runnable {
|
||||
|
||||
final StableValue<Holder>[] stables = STABLES;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StableValue<Holder> s;
|
||||
long deadlineNs = System.nanoTime();
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
s = stables[i];
|
||||
s.trySet(new Holder());
|
||||
deadlineNs += 1000;
|
||||
while (System.nanoTime() < deadlineNs) {
|
||||
Thread.onSpinWait();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void main() {
|
||||
List<Consumer> consumers = IntStream.range(0, THREADS)
|
||||
.mapToObj(_ -> new Consumer())
|
||||
.toList();
|
||||
|
||||
List<Thread> consumersThreads = IntStream.range(0, THREADS)
|
||||
.mapToObj(i -> Thread.ofPlatform()
|
||||
.name("Consumer Thread " + i)
|
||||
.start(consumers.get(i)))
|
||||
.toList();
|
||||
|
||||
Producer producer = new Producer();
|
||||
|
||||
Thread producerThread = Thread.ofPlatform()
|
||||
.name("Producer Thread")
|
||||
.start(producer);
|
||||
|
||||
join(consumers, producerThread);
|
||||
join(consumers, consumersThreads.toArray(Thread[]::new));
|
||||
|
||||
int[] histogram = new int[64];
|
||||
for (Consumer consumer : consumers) {
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
histogram[consumer.observations[i]]++;
|
||||
}
|
||||
}
|
||||
|
||||
// unless a = 1, ..., e = 1, zero observations should be seen
|
||||
for (int i = 0; i < 63; i++) {
|
||||
assertEquals(0, histogram[i]);
|
||||
}
|
||||
// a = 1, ..., e = 1 : index 2^5-1 = 63
|
||||
// All observations should end up in this bucket
|
||||
assertEquals(THREADS * SIZE, histogram[63]);
|
||||
}
|
||||
|
||||
static void join(List<Consumer> consumers, Thread... threads) {
|
||||
try {
|
||||
for (Thread t:threads) {
|
||||
long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(1);
|
||||
while (t.isAlive()) {
|
||||
t.join(TimeUnit.SECONDS.toMillis(10));
|
||||
if (t.isAlive()) {
|
||||
String stack = Arrays.stream(t.getStackTrace())
|
||||
.map(Objects::toString)
|
||||
.collect(Collectors.joining(System.lineSeparator()));
|
||||
System.err.println(t + ": " + stack);
|
||||
for (int i = 0; i < consumers.size(); i++) {
|
||||
System.err.println("Consumer " + i + ": " + consumers.get(i).i);
|
||||
}
|
||||
}
|
||||
if (System.nanoTime() > deadline) {
|
||||
long nonNulls = CompletableFuture.supplyAsync(() ->
|
||||
Stream.of(STABLES)
|
||||
.map(s -> s.orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.count(), Executors.newSingleThreadExecutor()).join();
|
||||
fail("Giving up! Set stables seen by a new thread: " + nonNulls);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
fail(ie);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
124
test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java
Normal file
124
test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java
Normal file
@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for TrustedFieldType implementations
|
||||
* @modules jdk.unsupported/sun.misc
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @enablePreview
|
||||
* @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest
|
||||
* @run junit/othervm -Dopens=false TrustedFieldTypeTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InaccessibleObjectException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class TrustedFieldTypeTest {
|
||||
|
||||
@Test
|
||||
void varHandle() throws NoSuchFieldException, IllegalAccessException {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
StableValue<Integer> originalValue = StableValue.of();
|
||||
@SuppressWarnings("unchecked")
|
||||
StableValue<Integer>[] originalArrayValue = new StableValue[10];
|
||||
|
||||
final class Holder {
|
||||
private final StableValue<Integer> value = originalValue;
|
||||
}
|
||||
final class ArrayHolder {
|
||||
private final StableValue<Integer>[] array = originalArrayValue;
|
||||
}
|
||||
|
||||
|
||||
VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class);
|
||||
Holder holder = new Holder();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
valueVarHandle.set(holder, StableValue.of())
|
||||
);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
valueVarHandle.compareAndSet(holder, originalValue, StableValue.of())
|
||||
);
|
||||
|
||||
VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class);
|
||||
ArrayHolder arrayHolder = new ArrayHolder();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
arrayVarHandle.set(arrayHolder, new StableValue[1])
|
||||
);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new StableValue[1])
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateStableValueContentVia_j_i_m_Unsafe() {
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
stableValue.trySet(42);
|
||||
jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe();
|
||||
|
||||
long offset = unsafe.objectFieldOffset(stableValue.getClass(), "contents");
|
||||
assertTrue(offset > 0);
|
||||
|
||||
// Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe
|
||||
Object oldData = unsafe.getAndSetReference(stableValue, offset, 13);
|
||||
assertEquals(42, oldData);
|
||||
assertEquals(13, stableValue.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException {
|
||||
|
||||
if (Boolean.getBoolean("opens")) {
|
||||
// Unfortunately, add-opens allows direct access to the `value` field
|
||||
Field field = StableValueImpl.class.getDeclaredField("contents");
|
||||
field.setAccessible(true);
|
||||
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
stableValue.trySet(42);
|
||||
|
||||
Object oldData = field.get(stableValue);
|
||||
assertEquals(42, oldData);
|
||||
|
||||
field.set(stableValue, 13);
|
||||
assertEquals(13, stableValue.orElseThrow());
|
||||
} else {
|
||||
Field field = StableValueImpl.class.getDeclaredField("contents");
|
||||
assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -30,6 +30,7 @@
|
||||
* @summary Run many tests on many Collection and Map implementations
|
||||
* @author Martin Buchholz
|
||||
* @modules java.base/java.util:open
|
||||
* @enablePreview
|
||||
* @run main MOAT
|
||||
* @key randomness
|
||||
*/
|
||||
@ -219,10 +220,15 @@ public class MOAT {
|
||||
// Immutable List
|
||||
testEmptyList(List.of());
|
||||
testEmptyList(List.of().subList(0,0));
|
||||
testEmptyList(StableValue.list(0, i -> i));
|
||||
testEmptyList(StableValue.list(3, i -> i).subList(0, 0));
|
||||
testListMutatorsAlwaysThrow(List.of());
|
||||
testListMutatorsAlwaysThrow(List.<Integer>of().subList(0,0));
|
||||
testListMutatorsAlwaysThrow(StableValue.list(0, i -> i));
|
||||
testEmptyListMutatorsAlwaysThrow(List.of());
|
||||
testEmptyListMutatorsAlwaysThrow(List.<Integer>of().subList(0,0));
|
||||
testEmptyListMutatorsAlwaysThrow(StableValue.list(0, i -> i));
|
||||
testEmptyListMutatorsAlwaysThrow(StableValue.list(3, i -> i).subList(0, 0));
|
||||
for (List<Integer> list : Arrays.asList(
|
||||
List.<Integer>of(),
|
||||
List.of(1),
|
||||
@ -244,7 +250,10 @@ public class MOAT {
|
||||
Stream.of((Integer)null).toList(),
|
||||
Stream.of(1, null).toList(),
|
||||
Stream.of(1, null, 3).toList(),
|
||||
Stream.of(1, null, 3, 4).toList())) {
|
||||
Stream.of(1, null, 3, 4).toList(),
|
||||
StableValue.list(0, i -> i),
|
||||
StableValue.list(3, i -> i),
|
||||
StableValue.list(10, i -> i))) {
|
||||
testCollection(list);
|
||||
testImmutableList(list);
|
||||
testListMutatorsAlwaysThrow(list);
|
||||
@ -356,6 +365,9 @@ public class MOAT {
|
||||
testEmptyMap(Map.of());
|
||||
testMapMutatorsAlwaysThrow(Map.of());
|
||||
testEmptyMapMutatorsAlwaysThrow(Map.of());
|
||||
testEmptyMap(StableValue.map(Set.of(), k -> k));
|
||||
testMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k));
|
||||
testEmptyMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k));
|
||||
for (Map<Integer,Integer> map : Arrays.asList(
|
||||
Map.<Integer,Integer>of(),
|
||||
Map.of(1, 101),
|
||||
@ -368,7 +380,10 @@ public class MOAT {
|
||||
Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808),
|
||||
Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909),
|
||||
Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909, 10, 1010),
|
||||
Map.ofEntries(ea))) {
|
||||
Map.ofEntries(ea),
|
||||
StableValue.map(Set.<Integer>of(), k -> k),
|
||||
StableValue.map(Set.of(1), k -> k),
|
||||
StableValue.map(Set.of(1, 2, 3), k -> k))) {
|
||||
testMap(map);
|
||||
testImmutableMap(map);
|
||||
testMapMutatorsAlwaysThrow(map);
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(100)
|
||||
public class StableFunctionBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final Set<Integer> SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet());
|
||||
|
||||
private static final Map<Integer, Integer> MAP = StableValue.map(SET, Function.identity());
|
||||
private static final Function<Integer, Integer> FUNCTION = StableValue.function(SET, Function.identity());
|
||||
|
||||
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
|
||||
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
|
||||
|
||||
@Benchmark
|
||||
public int map() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += map.get(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int function() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += function.apply(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSMap() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += MAP.get(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticIntFunction() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += FUNCTION.apply(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
public class StableFunctionSingleBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final Set<Integer> SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet());
|
||||
|
||||
private static final Map<Integer, Integer> MAP = StableValue.map(SET, Function.identity());
|
||||
private static final Function<Integer, Integer> FUNCTION = StableValue.function(SET, Function.identity());
|
||||
|
||||
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
|
||||
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
|
||||
|
||||
@Benchmark
|
||||
public int map() {
|
||||
return map.get(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int function() {
|
||||
return function.apply(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSMap() {
|
||||
return MAP.get(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticIntFunction() {
|
||||
return FUNCTION.apply(1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(100)
|
||||
public class StableIntFunctionBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final IntFunction<Integer> IDENTITY = i -> i;
|
||||
|
||||
private static final List<Integer> LIST = StableValue.list(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY);
|
||||
|
||||
private final List<Integer> list = StableValue.list(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
|
||||
|
||||
@Benchmark
|
||||
public int list() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += list.get(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int intFunction() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += intFunction.apply(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticList() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += LIST.get(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticIntFunction() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += INT_FUNCTION.apply(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
public class StableIntFunctionSingleBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final IntFunction<Integer> IDENTITY = i -> i;
|
||||
|
||||
private static final List<Integer> STABLE = StableValue.list(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY);
|
||||
|
||||
private final List<Integer> stable = StableValue.list(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
|
||||
|
||||
@Benchmark
|
||||
public int list() {
|
||||
return stable.get(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int intFunction() {
|
||||
return intFunction.apply(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticList() {
|
||||
return STABLE.get(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticIntFunction() {
|
||||
return INT_FUNCTION.apply(1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.bench.java.lang.stable.StableValueBenchmark.Dcl;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
public class StableMethodHandleBenchmark {
|
||||
|
||||
private static final MethodHandle FINAL_MH = identityHandle();
|
||||
private static final StableValue<MethodHandle> STABLE_MH;
|
||||
private static 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<>();
|
||||
private static final Map<String, MethodHandle> STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle());
|
||||
|
||||
static {
|
||||
STABLE_MH = StableValue.of();
|
||||
STABLE_MH.setOrThrow(identityHandle());
|
||||
MAP.put("identityHandle", identityHandle());
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int atomic() throws Throwable {
|
||||
return (int) ATOMIC_REFERENCE.get().invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int dcl() throws Throwable {
|
||||
return (int) DCL.get().invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int finalMh() throws Throwable {
|
||||
return (int) FINAL_MH.invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int map() throws Throwable {
|
||||
return (int) MAP.get("identityHandle").invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int nonFinalMh() throws Throwable {
|
||||
return (int) mh.invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int stableMap() throws Throwable {
|
||||
return (int) STABLE_MAP.get("identityHandle").invokeExact(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int stableMh() throws Throwable {
|
||||
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 {
|
||||
return lookup.findStatic(StableMethodHandleBenchmark.class, "identity", MethodType.methodType(int.class, int.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static int identity(int value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(2)
|
||||
public class StableSupplierBenchmark {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 23;
|
||||
|
||||
private static final StableValue<Integer> STABLE = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> STABLE2 = init(StableValue.of(), VALUE2);
|
||||
private static final Supplier<Integer> SUPPLIER = StableValue.supplier(() -> VALUE);
|
||||
private static final Supplier<Integer> SUPPLIER2 = StableValue.supplier(() -> VALUE);
|
||||
|
||||
private final StableValue<Integer> stable = init(StableValue.of(), VALUE);
|
||||
private final StableValue<Integer> stable2 = init(StableValue.of(), VALUE2);
|
||||
private final Supplier<Integer> supplier = StableValue.supplier(() -> VALUE);
|
||||
private final Supplier<Integer> supplier2 = StableValue.supplier(() -> VALUE2);
|
||||
|
||||
@Benchmark
|
||||
public int stable() {
|
||||
return stable.orElseThrow() + stable2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int supplier() {
|
||||
return supplier.get() + supplier2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticStable() {
|
||||
return STABLE.orElseThrow() + STABLE2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSupplier() {
|
||||
return SUPPLIER.get() + SUPPLIER2.get();
|
||||
}
|
||||
|
||||
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
|
||||
m.trySet(value);
|
||||
return m;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@State(Scope.Benchmark) // Share the same state instance (for contention)
|
||||
@Warmup(iterations = 5, time = 1)
|
||||
@Measurement(iterations = 5, time = 2)
|
||||
@Fork(value = 2, jvmArgsAppend = {
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(2)
|
||||
public class StableValueBenchmark {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 23;
|
||||
|
||||
private static final StableValue<Integer> STABLE = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> STABLE2 = init(StableValue.of(), VALUE2);
|
||||
private static final StableValue<Integer> DCL = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> DCL2 = init(StableValue.of(), VALUE2);
|
||||
private static final AtomicReference<Integer> ATOMIC = new AtomicReference<>(VALUE);
|
||||
private static final AtomicReference<Integer> ATOMIC2 = new AtomicReference<>(VALUE2);
|
||||
private static final Holder HOLDER = new Holder(VALUE);
|
||||
private static final Holder HOLDER2 = new Holder(VALUE2);
|
||||
private static final RecordHolder RECORD_HOLDER = new RecordHolder(VALUE);
|
||||
private static final RecordHolder RECORD_HOLDER2 = new RecordHolder(VALUE2);
|
||||
|
||||
private final StableValue<Integer> stable = init(StableValue.of(), VALUE);
|
||||
private final StableValue<Integer> stable2 = init(StableValue.of(), VALUE2);
|
||||
private final StableValue<Integer> stableNull = StableValue.of();
|
||||
private final StableValue<Integer> stableNull2 = StableValue.of();
|
||||
private final Supplier<Integer> dcl = new Dcl<>(() -> VALUE);
|
||||
private final Supplier<Integer> dcl2 = new Dcl<>(() -> VALUE2);
|
||||
private final AtomicReference<Integer> atomic = new AtomicReference<>(VALUE);
|
||||
private final AtomicReference<Integer> atomic2 = new AtomicReference<>(VALUE2);
|
||||
private final Supplier<Integer> supplier = () -> VALUE;
|
||||
private final Supplier<Integer> supplier2 = () -> VALUE2;
|
||||
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
stableNull.trySet(null);
|
||||
stableNull2.trySet(null);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int atomic() {
|
||||
return atomic.get() + atomic2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int dcl() {
|
||||
return dcl.get() + dcl2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int stable() {
|
||||
return stable.orElseThrow() + stable2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int stableNull() {
|
||||
return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2);
|
||||
}
|
||||
|
||||
// Reference case
|
||||
@Benchmark
|
||||
public int refSupplier() {
|
||||
return supplier.get() + supplier2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticAtomic() {
|
||||
return ATOMIC.get() + ATOMIC2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticDcl() {
|
||||
return DCL.orElseThrow() + DCL2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticHolder() {
|
||||
return HOLDER.get() + HOLDER2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticRecordHolder() {
|
||||
return RECORD_HOLDER.get() + RECORD_HOLDER2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticStable() {
|
||||
return STABLE.orElseThrow() + STABLE2.orElseThrow();
|
||||
}
|
||||
|
||||
|
||||
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
|
||||
m.trySet(value);
|
||||
return m;
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
private final StableValue<Integer> delegate = StableValue.of();
|
||||
|
||||
Holder(int value) {
|
||||
delegate.setOrThrow(value);
|
||||
}
|
||||
|
||||
int get() {
|
||||
return delegate.orElseThrow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record RecordHolder(StableValue<Integer> delegate) {
|
||||
|
||||
RecordHolder(int value) {
|
||||
this(StableValue.of());
|
||||
delegate.setOrThrow(value);
|
||||
}
|
||||
|
||||
int get() {
|
||||
return delegate.orElseThrow();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Handles null values
|
||||
public static class Dcl<V> implements Supplier<V> {
|
||||
|
||||
private final Supplier<V> supplier;
|
||||
|
||||
private volatile V value;
|
||||
private boolean bound;
|
||||
|
||||
public Dcl(Supplier<V> supplier) {
|
||||
this.supplier = supplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get() {
|
||||
V v = value;
|
||||
if (v == null) {
|
||||
if (!bound) {
|
||||
synchronized (this) {
|
||||
v = value;
|
||||
if (v == null) {
|
||||
if (!bound) {
|
||||
value = v = supplier.get();
|
||||
bound = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.MemoryLayout;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.foreign.ValueLayout;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
@Warmup(iterations = 5, time = 5, timeUnit = SECONDS)
|
||||
@Measurement(iterations = 5, time = 5, timeUnit = SECONDS)
|
||||
@Fork(value = 1, jvmArgs = { "--enable-preview" })
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(NANOSECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
public class VarHandleHolderBenchmark {
|
||||
|
||||
private static final MemoryLayout LAYOUT = MemoryLayout.structLayout(
|
||||
ValueLayout.JAVA_INT.withName("x"),
|
||||
ValueLayout.JAVA_INT.withName("y")
|
||||
);
|
||||
|
||||
private static final long SIZEOF = LAYOUT.byteSize();
|
||||
private static final long OFFSET_X = LAYOUT.byteOffset(MemoryLayout.PathElement.groupElement("x"));
|
||||
private static final long OFFSET_Y = LAYOUT.byteOffset(groupElement("y"));
|
||||
|
||||
static final class MyVarHandleLookup implements Function<String, VarHandle> {
|
||||
@Override
|
||||
public VarHandle apply(String name) {
|
||||
return LAYOUT.arrayElementVarHandle(groupElement(name)).withInvokeExactBehavior();
|
||||
}
|
||||
}
|
||||
|
||||
private static final Function<String, VarHandle> VAR_HANDLE_FUNCTION = new MyVarHandleLookup();
|
||||
|
||||
private static final VarHandle VH_X = VAR_HANDLE_FUNCTION.apply("x");
|
||||
private static final VarHandle VH_Y = VAR_HANDLE_FUNCTION.apply("y");
|
||||
|
||||
private static final Supplier<VarHandle> SV_X = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("x"));
|
||||
private static final Supplier<VarHandle> SV_Y = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("y"));
|
||||
|
||||
private static final Map<String, VarHandle> U_MAP = Map.of(
|
||||
"x", VH_X,
|
||||
"y", VH_Y);
|
||||
|
||||
private static final Map<String, VarHandle> U_MAP_ELEMENT = Map.of(
|
||||
"x", LAYOUT.varHandle(groupElement("x")),
|
||||
"y", LAYOUT.varHandle(groupElement("y")));
|
||||
|
||||
private static final Map<String, VarHandle> S_MAP = StableValue.map(
|
||||
Set.of("x", "y"),
|
||||
VAR_HANDLE_FUNCTION);
|
||||
|
||||
private static final Function<String, VarHandle> S_FUN = StableValue.function(
|
||||
Set.of("x", "y"),
|
||||
VAR_HANDLE_FUNCTION);
|
||||
|
||||
private static final MemorySegment confined;
|
||||
static {
|
||||
var array = new int[512 * (int) SIZEOF / (int) ValueLayout.JAVA_INT.byteSize()];
|
||||
var heap = MemorySegment.ofArray(array);
|
||||
for(var i = 0; i < 512; i++) {
|
||||
heap.set(ValueLayout.JAVA_INT, i * SIZEOF + OFFSET_X, i);
|
||||
heap.set(ValueLayout.JAVA_INT, i * SIZEOF + OFFSET_Y, i);
|
||||
}
|
||||
confined = Arena.ofConfined().allocate(LAYOUT, 512);
|
||||
confined.copyFrom(heap);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedVarHandleLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) VH_X.get(confined, 0L, (long) i);
|
||||
var y = (int) VH_Y.get(confined, 0L, (long) i);
|
||||
sum += x /*+y*/;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedStableValueLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) SV_X.get().get(confined, 0L, (long) i);
|
||||
var y = (int) SV_Y.get().get(confined, 0L, (long) i);
|
||||
sum += x + y;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedStableMapLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) S_MAP.get("x").get(confined, 0L, (long) i);
|
||||
var y = (int) S_MAP.get("y").get(confined, 0L, (long) i);
|
||||
sum += x + y;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedStableMapElementLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) U_MAP_ELEMENT.get("x").get(confined, i * 8L);
|
||||
var y = (int) U_MAP_ELEMENT.get("y").get(confined, i * 8L);
|
||||
sum += x + y;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedUnmodifiableMapLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) U_MAP.get("x").get(confined, 0L, (long) i);
|
||||
var y = (int) U_MAP.get("y").get(confined, 0L, (long) i);
|
||||
sum += x + y;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int confinedStableFunctionLoop() {
|
||||
var sum = 0;
|
||||
for (var i = 0; i < 512; i++) {
|
||||
var x = (int) S_FUN.apply("x").get(confined, 0L, (long) i);
|
||||
var y = (int) S_FUN.apply("y").get(confined, 0L, (long) i);
|
||||
sum += x + y;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user