8366178: Implement JEP 526: Lazy Constants (Second Preview)

8371882: Improve documentation for JEP 526: Lazy Constants

Reviewed-by: jvernee, mcimadamore
This commit is contained in:
Per Minborg 2025-11-18 12:20:23 +00:00
parent df5b105bbb
commit f946449997
52 changed files with 2784 additions and 3536 deletions

View File

@ -0,0 +1,307 @@
/*
* 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.javac.PreviewFeature;
import jdk.internal.lang.LazyConstantImpl;
import java.io.Serializable;
import java.util.*;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.Supplier;
/**
* A lazy constant is a holder of contents that can be set at most once.
* <p>
* A lazy constant is created using the factory method
* {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code <computing function>})}.
* When created, the lazy constant is <em>not initialized</em>, meaning it has no contents.
* The lazy constant (of type {@code T}) can then be <em>initialized</em>
* (and its contents retrieved) by calling {@linkplain #get() get()}. The first time
* {@linkplain #get() get()} is called, the underlying <em>computing function</em>
* (provided at construction) will be invoked and the result will be used to initialize
* the constant. Once a lazy constant is initialized, its contents can <em>never change</em>
* and will be retrieved over and over again upon subsequent {@linkplain #get() get()}
* invocations.
* <p>
* Consider the following example where a lazy constant field "{@code logger}" holds
* an object of type {@code Logger}:
*
* {@snippet lang = java:
* public class Component {
*
* // Creates a new uninitialized lazy constant
* private final LazyConstant<Logger> logger =
* // @link substring="of" target="#of" :
* LazyConstant.of( () -> Logger.create(Component.class) );
*
* public void process() {
* logger.get().info("Process started");
* // ...
* }
* }
*}
* <p>
* Initially, the lazy constant is <em>not initialized</em>. When {@code logger.get()}
* is first invoked, it evaluates the computing function and initializes the constant to
* the result; the result is then returned to the client. Hence, {@linkplain #get() get()}
* guarantees that the constant is <em>initialized</em> before it returns, barring
* any exceptions.
* <p>
* Furthermore, {@linkplain #get() get()} guarantees that, out of several threads trying to
* invoke the computing function simultaneously, {@linkplain ##thread-safety only one is
* ever selected} for computation. This property is crucial as evaluation of the computing
* function may have side effects, for example, the call above to {@code Logger.create()}
* may result in storage resources being prepared.
*
* <h2 id="exception-handling">Exception handling</h2>
* If the computing function returns {@code null}, a {@linkplain NullPointerException}
* is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who
* want to use a nullable constant can wrap the value into an {@linkplain Optional} holder.
* <p>
* If the computing function recursively invokes itself (directly or indirectly via
* the lazy constant), an {@linkplain IllegalStateException} is thrown, and the lazy
* constant is not initialized.
*
* <h2 id="composition">Composing lazy constants</h2>
* A lazy constant can depend on other lazy constants, 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 lazy constants:
*
* {@snippet lang = java:
* public final class DependencyUtil {
*
* private DependencyUtil() {}
*
* public static class Foo {
* // ...
* }
*
* public static class Bar {
* public Bar(Foo foo) {
* // ...
* }
* }
*
* private static final LazyConstant<Foo> FOO = LazyConstant.of( Foo::new );
* private static final LazyConstant<Bar> BAR = LazyConstant.of( () -> new Bar(FOO.get()) );
*
* public static Foo foo() {
* return FOO.get();
* }
*
* public static Bar bar() {
* return BAR.get();
* }
*
* }
*}
* Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already
* created. Upon such a creation, a dependent {@code Foo} will first be created if
* the {@code Foo} does not already exist.
*
* <h2 id="thread-safety">Thread Safety</h2>
* A lazy constant is guaranteed to be initialized atomically and at most once. If
* competing threads are racing to initialize a lazy constant, only one updating thread
* runs the computing function (which runs on the caller's thread and is hereafter denoted
* <em>the computing thread</em>), while the other threads are blocked until the constant
* is initialized, after which the other threads observe the lazy constant is initialized
* and leave the constant unchanged and will never invoke any computation.
* <p>
* The invocation of the computing function and the resulting initialization of
* the constant {@linkplain java.util.concurrent##MemoryVisibility <em>happens-before</em>}
* the initialized constant's content is read. Hence, the initialized constant's content,
* including any {@code final} fields of any newly created objects, is safely published.
* <p>
* Thread interruption does not cancel the initialization of a lazy constant. In other
* words, if the computing thread is interrupted, {@code LazyConstant::get} doesn't clear
* the interrupted threads status, nor does it throw an {@linkplain InterruptedException}.
* <p>
* If the computing function blocks indefinitely, other threads operating on this
* lazy constant may block indefinitely; no timeouts or cancellations are provided.
*
* <h2 id="performance">Performance</h2>
* The contents of a lazy constant can never change after the lazy constant has been
* initialized. Therefore, a JVM implementation may, for an initialized lazy constant,
* elide all future reads of that lazy constant's contents and instead use the contents
* that has been previously observed. We call this optimization <em>constant folding</em>.
* This is only possible if there is a direct reference from a {@code static final} field
* to a lazy constant or if there is a chain from a {@code static final} field -- via one
* or more <em>trusted fields</em> (i.e., {@code static final} fields,
* {@linkplain Record record} fields, or final instance fields in hidden classes) --
* to a lazy constant.
*
* <h2 id="miscellaneous">Miscellaneous</h2>
* Except for {@linkplain Object#equals(Object) equals(obj)} and
* {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters
* must be <em>non-null</em>, or a {@link NullPointerException} will be thrown.
*
* @apiNote Once a lazy constant is initialized, its contents cannot ever be removed.
* This can be a source of an unintended memory leak. More specifically,
* a lazy constant {@linkplain java.lang.ref##reachability strongly references}
* it contents. Hence, the contents of a lazy constant will be reachable as long
* as the lazy constant itself is reachable.
* <p>
* While it's possible to store an array inside a lazy constant, doing so will
* not result in improved access performance of the array elements. Instead, a
* {@linkplain List#ofLazy(int, IntFunction) lazy list} of arbitrary depth can
* be used, which provides constant components.
* <p>
* The {@code LazyConstant} type is not {@link Serializable}.
* <p>
* Use in static initializers may interact with class initialization order;
* cyclic initialization may result in initialization errors as described
* in section {@jls 12.4} of <cite>The Java Language Specification</cite>.
*
* @implNote
* A lazy constant is free to synchronize on itself. Hence, care must be
* taken when directly or indirectly synchronizing on a lazy constant.
* A lazy constant is unmodifiable but its contents may or may not be
* immutable (e.g., it may hold an {@linkplain ArrayList}).
*
* @param <T> type of the constant
*
* @since 26
*
* @see Optional
* @see Supplier
* @see List#ofLazy(int, IntFunction)
* @see Map#ofLazy(Set, Function)
* @jls 12.4 Initialization of Classes and Interfaces
* @jls 17.4.5 Happens-before Order
*/
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
public sealed interface LazyConstant<T>
extends Supplier<T>
permits LazyConstantImpl {
/**
* {@return the contents of this lazy constant if initialized, otherwise,
* returns {@code other}}
* <p>
* This method never triggers initialization of this lazy constant and will observe
* initialization by other threads atomically (i.e., it returns the contents
* if and only if the initialization has already completed).
*
* @param other value to return if the content is not initialized
* (can be {@code null})
*/
T orElse(T other);
/**
* {@return the contents of this initialized constant. If not initialized, first
* computes and initializes this constant using the computing function}
* <p>
* After this method returns successfully, the constant is guaranteed to be
* initialized.
* <p>
* If the computing function throws, the throwable is relayed to the caller and
* the lazy constant remains uninitialized; a subsequent call to get() may then
* attempt the computation again.
*/
T get();
/**
* {@return {@code true} if the constant is initialized, {@code false} otherwise}
* <p>
* This method never triggers initialization of this lazy constant and will observe
* changes in the initialization state made by other threads atomically.
*/
boolean isInitialized();
// Object methods
/**
* {@return if this lazy constant is the same as the provided {@code obj}}
* <p>
* In other words, equals compares the identity of this lazy constant and {@code obj}
* to determine equality. Hence, two lazy constants with the same contents are
* <em>not</em> equal.
* <p>
* This method never triggers initialization of this lazy constant.
*/
@Override
boolean equals(Object obj);
/**
* {@return the {@linkplain System#identityHashCode(Object) identity hash code} for
* this lazy constant}
*
* This method never triggers initialization of this lazy constant.
*/
@Override
int hashCode();
/**
* {@return a string suitable for debugging}
* <p>
* This method never triggers initialization of this lazy constant and will observe
* initialization by other threads atomically (i.e., it observes the
* contents if and only if the initialization has already completed).
* <p>
* If this lazy constant is initialized, an implementation-dependent string
* containing the {@linkplain Object#toString()} of the
* contents will be returned; otherwise, an implementation-dependent string is
* returned that indicates this lazy constant is not yet initialized.
*/
@Override
String toString();
// Factory
/**
* {@return a lazy constant whose contents is to be computed later via the provided
* {@code computingFunction}}
* <p>
* The returned lazy constant strongly references the provided
* {@code computingFunction} at least until initialization completes successfully.
* <p>
* If the provided computing function is already an instance of
* {@code LazyConstant}, the method is free to return the provided computing function
* directly.
*
* @implNote after initialization completes successfully, the computing function is
* no longer strongly referenced and becomes eligible for
* garbage collection.
*
* @param computingFunction in the form of a {@linkplain Supplier} to be used
* to initialize the constant
* @param <T> type of the constant
*
*/
@SuppressWarnings("unchecked")
static <T> LazyConstant<T> of(Supplier<? extends T> computingFunction) {
Objects.requireNonNull(computingFunction);
if (computingFunction instanceof LazyConstant<? extends T> lc) {
return (LazyConstant<T>) lc;
}
return LazyConstantImpl.ofLazy(computingFunction);
}
}

View File

@ -1,756 +0,0 @@
/*
* 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 #orElseSet(Supplier)} form a total order of zero or
* more exceptional invocations followed by zero (if the contents were already set) or one
* successful invocation. Since stable functions and stable collections are built on top
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
* too are thread safe and guarantee at-most-once-per-input invocation.
*
* <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 {
/**
* 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);
/**
* 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
* @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)}
* recursively attempts to set this stable value by calling this method
* directly or indirectly.
*/
void setOrThrow(T contents);
// 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 competing threads will then observe the newly
* computed value (if any) and will then never execute the {@code underlying} supplier.
* <p>
* If the provided {@code underlying} supplier throws an exception, it is rethrown
* to the initial caller and no contents is recorded.
* <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 upper bound of the range {@code [0, size)} indicating
* the allowed inputs
* @param underlying {@code IntFunction} used to compute cached values
* @param <R> the type of results delivered by the returned IntFunction
* @throws IllegalArgumentException if the provided {@code size} is negative.
*/
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 {@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 {@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);
}
}

View File

@ -25,7 +25,6 @@
package java.nio.charset;
import jdk.internal.misc.ThreadTracker;
import jdk.internal.misc.VM;
import jdk.internal.util.StaticProperty;
import jdk.internal.vm.annotation.Stable;
@ -41,7 +40,6 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.SortedMap;
@ -426,7 +424,7 @@ public abstract class Charset
}
/* The extended set of charsets */
private static final Supplier<List<CharsetProvider>> EXTENDED_PROVIDERS = StableValue.supplier(
private static final LazyConstant<List<CharsetProvider>> EXTENDED_PROVIDERS = LazyConstant.of(
new Supplier<>() { public List<CharsetProvider> get() { return extendedProviders0(); }});
private static List<CharsetProvider> extendedProviders0() {
@ -617,7 +615,7 @@ public abstract class Charset
return Collections.unmodifiableSortedMap(m);
}
private static final Supplier<Charset> defaultCharset = StableValue.supplier(
private static final LazyConstant<Charset> defaultCharset = LazyConstant.of(
new Supplier<>() { public Charset get() { return defaultCharset0(); }});
private static Charset defaultCharset0() {
@ -658,7 +656,7 @@ public abstract class Charset
@Stable
private final String[] aliases;
@Stable
private final Supplier<Set<String>> aliasSet = StableValue.supplier(
private final LazyConstant<Set<String>> aliasSet = LazyConstant.of(
new Supplier<>() { public Set<String> get() { return Set.of(aliases); }});
/**

View File

@ -142,8 +142,8 @@ public final class Currency implements Serializable {
// class data: instance map
private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
private static final Supplier<HashSet<Currency>> available =
StableValue.supplier(Currency::computeAllCurrencies);
private static final LazyConstant<HashSet<Currency>> available =
LazyConstant.of(Currency::computeAllCurrencies);
// Class data: currency data obtained from currency.data file.
// Purpose:

View File

@ -36,18 +36,12 @@ 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.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
/**
@ -135,14 +129,6 @@ class ImmutableCollections {
public <E> List<E> listFromTrustedArrayNullsAllowed(Object[] array) {
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
}
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
// A stable list is not Serializable, so we cannot return `List.of()` if `size == 0`
return new StableList<>(size, mapper);
}
public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
// A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
return new StableMap<>(keys, mapper);
}
});
}
}
@ -450,7 +436,7 @@ class ImmutableCollections {
}
}
static sealed class SubList<E> extends AbstractImmutableList<E>
static final class SubList<E> extends AbstractImmutableList<E>
implements RandomAccess {
@Stable
@ -462,8 +448,10 @@ class ImmutableCollections {
@Stable
final int size;
private SubList(AbstractImmutableList<E> root, int offset, int size) {
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
SubList(AbstractImmutableList<E> root, int offset, int size) {
assert root instanceof List12
|| root instanceof ListN
|| root instanceof LazyCollections.LazyList;
this.root = root;
this.offset = offset;
this.size = size;
@ -795,187 +783,6 @@ class ImmutableCollections {
}
}
@FunctionalInterface
interface HasStableDelegates<E> {
StableValueImpl<E>[] delegates();
}
@jdk.internal.ValueBased
static final class StableList<E>
extends AbstractImmutableList<E>
implements HasStableDelegates<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 List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size());
return StableSubList.fromStableList(this, fromIndex, toIndex);
}
@Override
public String toString() {
return StableUtil.renderElements(this, "StableCollection", delegates);
}
@Override
public StableValueImpl<E>[] delegates() {
return delegates;
}
private static final class StableSubList<E> extends SubList<E>
implements HasStableDelegates<E> {
private StableSubList(AbstractImmutableList<E> root, int offset, int size) {
super(root, offset, size);
}
@Override
public List<E> reversed() {
return new StableReverseOrderListView<>(this);
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
subListRangeCheck(fromIndex, toIndex, size());
return StableSubList.fromStableSubList(this, fromIndex, toIndex);
}
@Override
public String toString() {
return StableUtil.renderElements(this, "StableCollection", delegates());
}
@Override
boolean allowNulls() {
return true;
}
@Override
public StableValueImpl<E>[] delegates() {
@SuppressWarnings("unchecked")
final var rootDelegates = ((HasStableDelegates<E>) root).delegates();
return Arrays.copyOfRange(rootDelegates, offset, offset + size);
}
static <E> SubList<E> fromStableList(StableList<E> list, int fromIndex, int toIndex) {
return new StableSubList<>(list, fromIndex, toIndex - fromIndex);
}
static <E> SubList<E> fromStableSubList(StableSubList<E> parent, int fromIndex, int toIndex) {
return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex);
}
}
private static final class StableReverseOrderListView<E>
extends ReverseOrderListView.Rand<E>
implements HasStableDelegates<E> {
private StableReverseOrderListView(List<E> base) {
super(base, false);
}
// This method does not evaluate the elements
@Override
public String toString() {
return StableUtil.renderElements(this, "StableCollection", delegates());
}
@Override
public List<E> subList(int fromIndex, int toIndex) {
final int size = base.size();
subListRangeCheck(fromIndex, toIndex, size);
return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex));
}
@Override
public StableValueImpl<E>[] delegates() {
@SuppressWarnings("unchecked")
final var baseDelegates = ((HasStableDelegates<E>) base).delegates();
return ArraysSupport.reverse(
Arrays.copyOf(baseDelegates, baseDelegates.length));
}
}
}
// ---------- Set Implementations ----------
@jdk.internal.ValueBased
@ -1614,187 +1421,6 @@ class ImmutableCollections {
}
}
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 StableMapEntrySet.of(this); }
@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
static final class StableMapEntrySet<K, V> extends AbstractImmutableSet<Map.Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final StableMap<K, V> outer;
@Stable
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
private StableMapEntrySet(StableMap<K, V> outer) {
this.outer = outer;
this.delegateEntrySet = outer.delegate.entrySet();
}
@Override public Iterator<Map.Entry<K, V>> iterator() { return LazyMapIterator.of(this); }
@Override public int size() { return delegateEntrySet.size(); }
@Override public int hashCode() { return outer.hashCode(); }
@Override
public String toString() {
return StableUtil.renderMappings(this, "StableCollection", delegateEntrySet, false);
}
// For @ValueBased
private static <K, V> StableMapEntrySet<K, V> of(StableMap<K, V> outer) {
return new StableMapEntrySet<>(outer);
}
@jdk.internal.ValueBased
static final class LazyMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final StableMapEntrySet<K, V> outer;
@Stable
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
private LazyMapIterator(StableMapEntrySet<K, V> outer) {
this.outer = outer;
this.delegateIterator = outer.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 StableEntry<>(k, inner.getValue(), new Supplier<V>() {
@Override public V get() { return outer.outer.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 StableEntry<>(k, inner.getValue(), new Supplier<V>() {
@Override public V get() { return outer.outer.mapper.apply(k); }}));
}
};
delegateIterator.forEachRemaining(innerAction);
}
// For @ValueBased
private static <K, V> LazyMapIterator<K, V> of(StableMapEntrySet<K, V> outer) {
return new LazyMapIterator<>(outer);
}
}
}
private record StableEntry<K, V>(K getKey, // trick
StableValueImpl<V> stableValue,
Supplier<? extends V> supplier) implements Map.Entry<K, V> {
@Override public V setValue(V value) { throw uoe(); }
@Override public V getValue() { return stableValue.orElseSet(supplier); }
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
@Override public String toString() { return getKey() + "=" + stableValue.toString(); }
@Override public boolean equals(Object o) {
return o instanceof Map.Entry<?, ?> e
&& Objects.equals(getKey(), e.getKey())
// Invoke `getValue()` as late as possible to avoid evaluation
&& Objects.equals(getValue(), e.getValue());
}
private int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); }
}
@Override
public Collection<V> values() {
return StableMapValues.of(this);
}
@jdk.internal.ValueBased
static final class StableMapValues<V> extends AbstractImmutableCollection<V> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final StableMap<?, V> outer;
private StableMapValues(StableMap<?, V> outer) {
this.outer = outer;
}
@Override public Iterator<V> iterator() { return outer.new ValueIterator(); }
@Override public int size() { return outer.size(); }
@Override public boolean isEmpty() { return outer.isEmpty();}
@Override public boolean contains(Object v) { return outer.containsValue(v); }
private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
@Override
public StableValueImpl<?>[] apply(int len) {
return new StableValueImpl<?>[len];
}
};
@Override
public String toString() {
final StableValueImpl<?>[] values = outer.delegate.values().toArray(GENERATOR);
return StableUtil.renderElements(this, "StableCollection", values);
}
// For @ValueBased
private static <V> StableMapValues<V> of(StableMap<?, V> outer) {
return new StableMapValues<>(outer);
}
}
@Override
public String toString() {
return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true);
}
}
}
// ---------- Serialization Proxy ----------

View File

@ -0,0 +1,584 @@
/*
* 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.util;
import jdk.internal.misc.Unsafe;
import jdk.internal.util.ImmutableBitSetPredicate;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.lang.LazyConstant;
import java.lang.reflect.Array;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.IntPredicate;
import java.util.function.Supplier;
/**
* Container class for lazy collections implementations. Not part of the public API.
*/
@AOTSafeClassInitializer
final class LazyCollections {
/**
* No instances.
*/
private LazyCollections() { }
// Unsafe allows LazyCollection classes to be used early in the boot sequence
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
@jdk.internal.ValueBased
static final class LazyList<E>
extends ImmutableCollections.AbstractImmutableList<E> {
@Stable
private final E[] elements;
// Keeping track of `size` separately reduces bytecode size compared to
// using `elements.length`.
@Stable
private final int size;
@Stable
final FunctionHolder<IntFunction<? extends E>> functionHolder;
@Stable
private final Mutexes mutexes;
private LazyList(int size, IntFunction<? extends E> computingFunction) {
this.elements = newGenericArray(size);
this.size = size;
this.functionHolder = new FunctionHolder<>(computingFunction, size);
this.mutexes = new Mutexes(size);
super();
}
@Override public boolean isEmpty() { return size == 0; }
@Override public int size() { return size; }
@Override public Object[] toArray() { return copyInto(new Object[size]); }
@ForceInline
@Override
public E get(int i) {
final E e = contentsAcquire(offsetFor(Objects.checkIndex(i, size)));
return (e != null) ? e : getSlowPath(i);
}
private E getSlowPath(int i) {
return orElseComputeSlowPath(elements, i, mutexes, i, functionHolder);
}
@Override
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
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) {
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) {
for (int i = 0; i < size; i++) {
a[i] = get(i);
}
return (T[]) a;
}
@SuppressWarnings("unchecked")
@ForceInline
private E contentsAcquire(long offset) {
return (E) UNSAFE.getReferenceAcquire(elements, offset);
}
}
static final class LazyEnumMap<K extends Enum<K>, V>
extends AbstractLazyMap<K, V> {
@Stable
private final Class<K> enumType;
@Stable
// We are using a wrapper class here to be able to use a min value of zero that
// is also stable.
private final Integer min;
@Stable
private final IntPredicate member;
public LazyEnumMap(Set<K> set,
Class<K> enumType,
int min,
int backingSize,
IntPredicate member,
Function<? super K, ? extends V> computingFunction) {
this.enumType = enumType;
this.min = min;
this.member = member;
super(set, set.size(), backingSize, computingFunction);
}
@Override
@ForceInline
public boolean containsKey(Object o) {
return enumType.isAssignableFrom(o.getClass())
&& member.test(((Enum<?>) o).ordinal());
}
@ForceInline
@Override
public V getOrDefault(Object key, V defaultValue) {
if (enumType.isAssignableFrom(key.getClass())) {
final int ordinal = ((Enum<?>) key).ordinal();
if (member.test(ordinal)) {
@SuppressWarnings("unchecked")
final K k = (K) key;
return orElseCompute(k, indexForAsInt(k));
}
}
return defaultValue;
}
@Override
Integer indexFor(K key) {
return indexForAsInt(key);
}
private int indexForAsInt(K key) {
return key.ordinal() - min;
}
}
static final class LazyMap<K, V>
extends AbstractLazyMap<K, V> {
// Use an unmodifiable map with known entries that are @Stable. Lookups through this map can be folded because
// it is created using Map.ofEntrie. This allows us to avoid creating a separate hashing function.
@Stable
private final Map<K, Integer> indexMapper;
public LazyMap(Set<K> keys, Function<? super K, ? extends V> computingFunction) {
@SuppressWarnings("unchecked")
final Entry<K, Integer>[] entries = (Entry<K, Integer>[]) new Entry<?, ?>[keys.size()];
int i = 0;
for (K k : keys) {
entries[i] = Map.entry(k, i++);
}
this.indexMapper = Map.ofEntries(entries);
super(keys, i, i, computingFunction);
}
@ForceInline
@Override
public V getOrDefault(Object key, V defaultValue) {
final Integer index = indexMapper.get(key);
if (index != null) {
@SuppressWarnings("unchecked")
final K k = (K) key;
return orElseCompute(k, index);
}
return defaultValue;
}
@Override public boolean containsKey(Object o) { return indexMapper.containsKey(o); }
@Override
Integer indexFor(K key) {
return indexMapper.get(key);
}
}
static sealed abstract class AbstractLazyMap<K, V>
extends ImmutableCollections.AbstractImmutableMap<K, V> {
// This field shadows AbstractMap.keySet which is not @Stable.
@Stable
Set<K> keySet;
// This field shadows AbstractMap.values which is of another type
@Stable
final V[] values;
@Stable
Mutexes mutexes;
@Stable
private final int size;
@Stable
final FunctionHolder<Function<? super K, ? extends V>> functionHolder;
@Stable
private final Set<Entry<K, V>> entrySet;
private AbstractLazyMap(Set<K> keySet,
int size,
int backingSize,
Function<? super K, ? extends V> computingFunction) {
this.size = size;
this.functionHolder = new FunctionHolder<>(computingFunction, size);
this.values = newGenericArray(backingSize);
this.mutexes = new Mutexes(backingSize);
super();
this.keySet = keySet;
this.entrySet = LazyMapEntrySet.of(this);
}
// Abstract methods
@Override public abstract boolean containsKey(Object o);
abstract Integer indexFor(K key);
// Public methods
@Override public final int size() { return size; }
@Override public final boolean isEmpty() { return size == 0; }
@Override public final Set<Entry<K, V>> entrySet() { return entrySet; }
@Override public Set<K> keySet() { return keySet; }
@ForceInline
@Override
public final V get(Object key) {
return getOrDefault(key, null);
}
@SuppressWarnings("unchecked")
@ForceInline
final V orElseCompute(K key, int index) {
final long offset = offsetFor(index);
final V v = (V) UNSAFE.getReferenceAcquire(values, offset);
if (v != null) {
return v;
}
return orElseComputeSlowPath(values, index, mutexes, key, functionHolder);
}
@jdk.internal.ValueBased
static final class LazyMapEntrySet<K, V> extends ImmutableCollections.AbstractImmutableSet<Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final AbstractLazyMap<K, V> map;
private LazyMapEntrySet(AbstractLazyMap<K, V> map) {
this.map = map;
super();
}
@Override public Iterator<Entry<K, V>> iterator() { return LazyMapIterator.of(map); }
@Override public int size() { return map.size(); }
@Override public int hashCode() { return map.hashCode(); }
// For @ValueBased
private static <K, V> LazyMapEntrySet<K, V> of(AbstractLazyMap<K, V> outer) {
return new LazyMapEntrySet<>(outer);
}
@jdk.internal.ValueBased
static final class LazyMapIterator<K, V> implements Iterator<Entry<K, V>> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final AbstractLazyMap<K, V> map;
@Stable
private final Iterator<K> keyIterator;
private LazyMapIterator(AbstractLazyMap<K, V> map) {
this.map = map;
this.keyIterator = map.keySet.iterator();
super();
}
@Override public boolean hasNext() { return keyIterator.hasNext(); }
@Override
public Entry<K, V> next() {
final K k = keyIterator.next();
return new LazyEntry<>(k, map, map.functionHolder);
}
@Override
public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
final Consumer<? super K> innerAction =
new Consumer<>() {
@Override
public void accept(K key) {
action.accept(new LazyEntry<>(key, map, map.functionHolder));
}
};
keyIterator.forEachRemaining(innerAction);
}
// For @ValueBased
private static <K, V> LazyMapIterator<K, V> of(AbstractLazyMap<K, V> map) {
return new LazyMapIterator<>(map);
}
}
}
private record LazyEntry<K, V>(K getKey, // trick
AbstractLazyMap<K, V> map,
FunctionHolder<Function<? super K, ? extends V>> functionHolder) implements Entry<K, V> {
@Override public V setValue(V value) { throw ImmutableCollections.uoe(); }
@Override public V getValue() { return map.orElseCompute(getKey, map.indexFor(getKey)); }
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
@Override public String toString() { return getKey() + "=" + getValue(); }
@Override
public boolean equals(Object o) {
return o instanceof Map.Entry<?, ?> e
&& Objects.equals(getKey(), e.getKey())
// Invoke `getValue()` as late as possible to avoid evaluation
&& Objects.equals(getValue(), e.getValue());
}
private int hash(Object obj) {
return (obj == null) ? 0 : obj.hashCode();
}
}
@Override
public Collection<V> values() {
return LazyMapValues.of(this);
}
@jdk.internal.ValueBased
static final class LazyMapValues<K, V> extends ImmutableCollections.AbstractImmutableCollection<V> {
// Use a separate field for the outer class in order to facilitate
// a @Stable annotation.
@Stable
private final AbstractLazyMap<K, V> map;
private LazyMapValues(AbstractLazyMap<K, V> map) {
this.map = map;
super();
}
@Override public Iterator<V> iterator() { return map.new ValueIterator(); }
@Override public int size() { return map.size(); }
@Override public boolean isEmpty() { return map.isEmpty(); }
@Override public boolean contains(Object v) { return map.containsValue(v); }
// For @ValueBased
private static <K, V> LazyMapValues<K, V> of(AbstractLazyMap<K, V> outer) {
return new LazyMapValues<>(outer);
}
}
}
static final class Mutexes {
private static final Object TOMB_STONE = new Object();
// Filled on demand and then discarded once it is not needed anymore.
// A mutex element can only transition like so: `null` -> `new Object()` -> `TOMB_STONE`
private volatile Object[] mutexes;
// Used to detect we have computed all elements and no longer need the `mutexes` array
private volatile AtomicInteger counter;
private Mutexes(int length) {
this.mutexes = new Object[length];
this.counter = new AtomicInteger(length);
}
@ForceInline
private Object acquireMutex(long offset) {
assert mutexes != null;
// Check if there already is a mutex (Object or TOMB_STONE)
final Object mutex = UNSAFE.getReferenceVolatile(mutexes, offset);
if (mutex != null) {
return mutex;
}
// Protect against racy stores of mutex candidates
final Object candidate = new Object();
final Object witness = UNSAFE.compareAndExchangeReference(mutexes, offset, null, candidate);
return witness == null ? candidate : witness;
}
private void releaseMutex(long offset) {
// Replace the old mutex with a tomb stone since now the old mutex can be collected.
UNSAFE.putReference(mutexes, offset, TOMB_STONE);
if (counter != null && counter.decrementAndGet() == 0) {
mutexes = null;
counter = null;
}
}
}
@ForceInline
private static long offsetFor(long index) {
return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * index;
}
@SuppressWarnings("unchecked")
private static <E> E[] newGenericArray(int length) {
return (E[]) new Object[length];
}
public static <E> List<E> ofLazyList(int size,
IntFunction<? extends E> computingFunction) {
return new LazyList<>(size, computingFunction);
}
public static <K, V> Map<K, V> ofLazyMap(Set<K> keys,
Function<? super K, ? extends V> computingFunction) {
return new LazyMap<>(keys, computingFunction);
}
@SuppressWarnings("unchecked")
public static <K, E extends Enum<E>, V>
Map<K, V> ofLazyMapWithEnumKeys(Set<K> keys,
Function<? super K, ? extends V> computingFunction) {
// The input set is not empty
final Class<E> enumType = ((E) keys.iterator().next()).getDeclaringClass();
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (K t : keys) {
final int ordinal = ((E) t).ordinal();
min = Math.min(min, ordinal);
max = Math.max(max, ordinal);
bitSet.set(ordinal);
}
final int backingSize = max - min + 1;
final IntPredicate member = ImmutableBitSetPredicate.of(bitSet);
return (Map<K, V>) new LazyEnumMap<>((Set<E>) keys, enumType, min, backingSize, member, (Function<E, V>) computingFunction);
}
@SuppressWarnings("unchecked")
static <T> T orElseComputeSlowPath(final T[] array,
final int index,
final Mutexes mutexes,
final Object input,
final FunctionHolder<?> functionHolder) {
final long offset = offsetFor(index);
final Object mutex = mutexes.acquireMutex(offset);
preventReentry(mutex);
synchronized (mutex) {
final T t = array[index]; // Plain semantics suffice here
if (t == null) {
final T newValue = switch (functionHolder.function()) {
case IntFunction<?> iFun -> (T) iFun.apply((int) input);
case Function<?, ?> fun -> ((Function<Object, T>) fun).apply(input);
default -> throw new InternalError("cannot reach here");
};
Objects.requireNonNull(newValue);
// Reduce the counter and if it reaches zero, clear the reference
// to the underlying holder.
functionHolder.countDown();
// The mutex is not reentrant so we know newValue should be returned
set(array, index, mutex, newValue);
// We do not need the mutex anymore
mutexes.releaseMutex(offset);
return newValue;
}
return t;
}
}
static void preventReentry(Object mutex) {
if (Thread.holdsLock(mutex)) {
throw new IllegalStateException("Recursive initialization of a lazy collection is illegal");
}
}
static <T> void set(T[] array, int index, Object mutex, T newValue) {
assert Thread.holdsLock(mutex) : index + "didn't hold " + mutex;
// We know we hold the monitor here so plain semantic is enough
// This is an extra safety net to emulate a CAS op.
if (array[index] == null) {
UNSAFE.putReferenceRelease(array, Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * (long) index, newValue);
}
}
/**
* This class is thread safe. Any thread can create and use an instance of this class at
* any time. The `function` field is only accessed if `counter` is positive so the setting
* of function to `null` is safe.
*
* @param <U> the underlying function type
*/
@AOTSafeClassInitializer
static final class FunctionHolder<U> {
private static final long COUNTER_OFFSET = UNSAFE.objectFieldOffset(FunctionHolder.class, "counter");
// This field can only transition at most once from being set to a
// non-null reference to being `null`. Once `null`, it is never read.
private U function;
// Used reflectively via Unsafe
private int counter;
public FunctionHolder(U function, int counter) {
this.function = (counter == 0) ? null : function;
this.counter = counter;
// Safe publication
UNSAFE.storeStoreFence();
}
@ForceInline
public U function() {
return function;
}
public void countDown() {
if (UNSAFE.getAndAddInt(this, COUNTER_OFFSET, -1) == 1) {
// Do not reference the underlying function anymore so it can be collected.
function = null;
}
}
}
}

View File

@ -25,6 +25,11 @@
package java.util;
import jdk.internal.foreign.Utils;
import jdk.internal.javac.PreviewFeature;
import java.io.Serializable;
import java.util.function.IntFunction;
import java.util.function.UnaryOperator;
/**
@ -87,9 +92,9 @@ import java.util.function.UnaryOperator;
* interface.
*
* <h2><a id="unmodifiable">Unmodifiable Lists</a></h2>
* <p>The {@link List#of(Object...) List.of} and
* {@link List#copyOf List.copyOf} static factory methods
* provide a convenient way to create unmodifiable lists. The {@code List}
* <p>The {@link List#of(Object...) List.of},
* {@link List#copyOf List.copyOf}, and {@link List#ofLazy(int, IntFunction)} static
* factory methods provide a convenient way to create unmodifiable lists. The {@code List}
* instances created by these methods have the following characteristics:
*
* <ul>
@ -100,7 +105,7 @@ import java.util.function.UnaryOperator;
* this may cause the List's contents to appear to change.
* <li>They disallow {@code null} elements. Attempts to create them with
* {@code null} elements result in {@code NullPointerException}.
* <li>They are serializable if all elements are serializable.
* <li>Unless otherwise specified, they are serializable if all elements are serializable.
* <li>The order of elements in the list is the same as the order of the
* provided arguments, or of the elements in the provided array.
* <li>The lists and their {@link #subList(int, int) subList} views implement the
@ -1190,4 +1195,71 @@ public interface List<E> extends SequencedCollection<E> {
static <E> List<E> copyOf(Collection<? extends E> coll) {
return ImmutableCollections.listCopy(coll);
}
/**
* {@return a new lazily computed list of 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 lazily computed via the
* provided {@code computingFunction} when they are first accessed
* (e.g., via {@linkplain List#get(int) List::get}).
* <p>
* The provided computing 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 the computing function completes abnormally
* <p>
* If invoking the provided computing function throws an exception, it is rethrown
* to the initial caller and no value for the element is recorded.
* <p>
* If the provided computing function returns {@code null},
* a {@linkplain NullPointerException} will be thrown. Hence, just like other
* unmodifiable lists created via the {@code List::of} factories, a lazy list
* cannot contain {@code null} elements. Clients that want to use nullable elements
* can wrap elements into an {@linkplain Optional} holder.
* <p>
* The elements of any {@link List#subList(int, int) subList()} or
* {@link List#reversed()} views of the returned list are also lazily computed.
* <p>
* The returned list and its {@link List#subList(int, int) subList()} or
* {@link List#reversed()} views implement the {@link RandomAccess} interface.
* <p>
* If the provided computing function recursively calls itself or the returned
* lazy list for the same index, an {@linkplain IllegalStateException}
* will be thrown.
* <p>
* The returned list's {@linkplain Object Object methods};
* {@linkplain Object#equals(Object) equals()},
* {@linkplain Object#hashCode() hashCode()}, and
* {@linkplain Object#toString() toString()} methods may trigger initialization of
* one or more lazy elements.
* <p>
* The returned lazy list strongly references its computing
* function used to compute elements at least so long as there are uninitialized
* elements.
* <p>
* The returned List is <em>not</em> {@linkplain Serializable}.
*
* @implNote after all elements have been initialized successfully, the computing
* function is no longer strongly referenced and becomes eligible for
* garbage collection.
*
* @param size the size of the returned lazy list
* @param computingFunction to invoke whenever an element is first accessed
* (may not return {@code null})
* @param <E> the type of elements in the returned list
* @throws IllegalArgumentException if the provided {@code size} is negative.
*
* @see LazyConstant
* @since 26
*/
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
static <E> List<E> ofLazy(int size,
IntFunction<? extends E> computingFunction) {
Utils.checkNonNegativeArgument(size, "size");
Objects.requireNonNull(computingFunction);
// A computed list is not Serializable, so we cannot return `List.of()` if `size == 0`
return LazyCollections.ofLazyList(size, computingFunction);
}
}

View File

@ -992,8 +992,8 @@ public final class Locale implements Cloneable, Serializable {
}
}
private static final Supplier<ReferencedKeyMap<Object, Locale>> LOCALE_CACHE =
StableValue.supplier(new Supplier<>() {
private static final LazyConstant<ReferencedKeyMap<Object, Locale>> LOCALE_CACHE =
LazyConstant.of(new Supplier<>() {
@Override
public ReferencedKeyMap<Object, Locale> get() {
return ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier());
@ -2330,8 +2330,8 @@ public final class Locale implements Cloneable, Serializable {
private static volatile Locale defaultDisplayLocale;
private static volatile Locale defaultFormatLocale;
private final transient Supplier<String> languageTag =
StableValue.supplier(new Supplier<>() {
private final transient LazyConstant<String> languageTag =
LazyConstant.of(new Supplier<>() {
@Override
public String get() {
return computeLanguageTag();

View File

@ -30,32 +30,32 @@ import java.util.function.Supplier;
// Methods and suppliers for producing ISO 639/3166 resources used by Locale.
class LocaleISOData {
static final Supplier<String[]> ISO_639 =
StableValue.supplier(new Supplier<>() {
static final LazyConstant<String[]> ISO_639 =
LazyConstant.of(new Supplier<>() {
@Override
public String[] get() {
return getISO2Table(isoLanguageTable);
}
});
static final Supplier<String[]> ISO_3166_1_ALPHA2 =
StableValue.supplier(new Supplier<>() {
static final LazyConstant<String[]> ISO_3166_1_ALPHA2 =
LazyConstant.of(new Supplier<>() {
@Override
public String[] get() {
return getISO2Table(isoCountryTable);
}
});
static final Supplier<Set<String>> ISO_3166_1_ALPHA3 =
StableValue.supplier(new Supplier<>() {
static final LazyConstant<Set<String>> ISO_3166_1_ALPHA3 =
LazyConstant.of(new Supplier<>() {
@Override
public Set<String> get() {
return computeISO3166_1Alpha3Countries();
}
});
static final Supplier<Set<String>> ISO_3166_3 =
StableValue.supplier(new Supplier<>() {
static final LazyConstant<Set<String>> ISO_3166_3 =
LazyConstant.of(new Supplier<>() {
@Override
public Set<String> get() {
return Set.of(ISO3166_3);

View File

@ -25,6 +25,8 @@
package java.util;
import jdk.internal.javac.PreviewFeature;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
@ -114,8 +116,9 @@ import java.io.Serializable;
*
* <h2><a id="unmodifiable">Unmodifiable Maps</a></h2>
* <p>The {@link Map#of() Map.of},
* {@link Map#ofEntries(Map.Entry...) Map.ofEntries}, and
* {@link Map#copyOf Map.copyOf}
* {@link Map#ofEntries(Map.Entry...) Map.ofEntries},
* {@link Map#copyOf Map.copyOf}, and
* {@link Map#ofLazy(Set, Function)}
* static factory methods provide a convenient way to create unmodifiable maps.
* The {@code Map}
* instances created by these methods have the following characteristics:
@ -128,7 +131,8 @@ import java.io.Serializable;
* Map to behave inconsistently or its contents to appear to change.
* <li>They disallow {@code null} keys and values. Attempts to create them with
* {@code null} keys or values result in {@code NullPointerException}.
* <li>They are serializable if all keys and values are serializable.
* <li>Unless otherwise specified, they are serializable if all keys and values
* are serializable.
* <li>They reject duplicate keys at creation time. Duplicate keys
* passed to a static factory method result in {@code IllegalArgumentException}.
* <li>The iteration order of mappings is unspecified and is subject to change.
@ -1746,4 +1750,79 @@ public interface Map<K, V> {
return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
}
}
/**
* {@return a new lazily computed 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 lazily computed via the
* provided {@code computingFunction} when they are first accessed
* (e.g., via {@linkplain Map#get(Object) Map::get}).
* <p>
* The provided computing 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 the computing function completes abnormally.
* <p>
* If invoking the provided computing function throws an exception, it
* is rethrown to the initial caller and no value associated with the provided key
* is recorded.
* <p>
* If the provided computing function returns {@code null},
* a {@linkplain NullPointerException} will be thrown. Hence, just like other
* unmodifiable maps created via the {@code Map::of} factories, a lazy map
* cannot contain {@code null} values. Clients that want to use nullable values can
* wrap values into an {@linkplain Optional} holder.
* <p>
* The values of any {@link Map#values()} or {@link Map#entrySet()} views of
* the returned map are also lazily computed.
* <p>
* If the provided computing function recursively calls itself or
* the returned lazy map for the same key, an {@linkplain IllegalStateException}
* will be thrown.
* <p>
* The returned map's {@linkplain Object Object methods};
* {@linkplain Object#equals(Object) equals()},
* {@linkplain Object#hashCode() hashCode()}, and
* {@linkplain Object#toString() toString()} methods may trigger initialization of
* one or more lazy elements.
* <p>
* The returned lazy map strongly references its underlying
* computing function used to compute values at least so long as there are
* uncomputed values.
* <p>
* The returned Map is <em>not</em> {@linkplain Serializable}.
*
* @implNote after all values have been initialized successfully, the computing
* function is no longer strongly referenced and becomes eligible for
* garbage collection.
*
* @param keys the (non-null) keys in the returned computed map
* @param computingFunction to invoke whenever an associated value is first accessed
* @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 keys} is {@code null}
* or if the set of {@code keys} contains a {@code null} element.
*
* @see LazyConstant
* @since 26
*/
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
static <K, V> Map<K, V> ofLazy(Set<? extends K> keys,
Function<? super K, ? extends V> computingFunction) {
// Protect against TOC-TOU attacks.
// Also, implicit null check of `keys` and all its elements
final Set<K> keyCopies = Set.copyOf(keys);
Objects.requireNonNull(computingFunction);
// We need to check the instance type using the original `keys` parameter.
if (keys instanceof EnumSet<?> && !keyCopies.isEmpty()) {
@SuppressWarnings("unchecked")
var enumMap = (Map<K, V>) LazyCollections.ofLazyMapWithEnumKeys(keyCopies, computingFunction);
return enumMap;
} else {
// A computed map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
return LazyCollections.ofLazyMap(keyCopies, computingFunction);
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 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
@ -22,8 +22,11 @@
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.util;
import jdk.internal.vm.annotation.Stable;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
@ -68,6 +71,7 @@ public final class Optional<T> {
/**
* If non-null, the value; if null, indicates no value is present
*/
@Stable
private final T value;
/**

View File

@ -488,7 +488,7 @@ public abstract class ResourceBundle {
/**
* A Set of the keys contained only in this ResourceBundle.
*/
private final Supplier<Set<String>> keySet = StableValue.supplier(
private final LazyConstant<Set<String>> keySet = LazyConstant.of(
new Supplier<>() { public Set<String> get() { return keySet0(); }});
private Set<String> keySet0() {

View File

@ -26,14 +26,8 @@
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);
}

View File

@ -38,6 +38,8 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
@ -57,7 +59,7 @@ public final class CaptureStateUtil {
// of combinators needed to form an adapted method handle.
// The function is lazily computed.
//
private static final Function<SegmentExtractorKey, MethodHandle> SEGMENT_EXTRACTION_HANDLE_CACHE;
private static final Map<SegmentExtractorKey, MethodHandle> SEGMENT_EXTRACTION_HANDLE_CACHE;
static {
final Set<SegmentExtractorKey> inputs = new HashSet<>();
@ -77,7 +79,7 @@ public final class CaptureStateUtil {
}
};
SEGMENT_EXTRACTION_HANDLE_CACHE = StableValue.function(inputs, segmentExtractionHandle);
SEGMENT_EXTRACTION_HANDLE_CACHE = Map.ofLazy(inputs, segmentExtractionHandle);
}
// A key that holds both the `returnType` and the `stateName` needed to look up a
@ -188,7 +190,10 @@ public final class CaptureStateUtil {
final SegmentExtractorKey key = new SegmentExtractorKey(target, stateName);
// ((int | long), MemorySegment)(int | long)
final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.apply(key);
final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.get(key);
if (segmentExtractor == null) {
throw new IllegalArgumentException("Input not allowed: " + key);
}
// Make `target` specific adaptations of the basic handle
@ -208,7 +213,7 @@ public final class CaptureStateUtil {
// Use an `Arena` for the first argument instead and extract a segment from it.
// (C0=Arena, C1-Cn)(int|long)
innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.apply(ALLOCATE));
innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.get(ALLOCATE));
// Add an identity function for the result of the cleanup action.
// ((int|long))(int|long)
@ -221,7 +226,7 @@ public final class CaptureStateUtil {
// cleanup action and invoke `Arena::close` when it is run. The `cleanup` handle
// does not have to have all parameters. It can have zero or more.
// (Throwable, (int|long), Arena)(int|long)
cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.apply(ARENA_CLOSE));
cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.get(ARENA_CLOSE));
// Combine the `innerAdapted` and `cleanup` action into a try/finally block.
// (Arena, C1-Cn)(int|long)
@ -230,7 +235,7 @@ public final class CaptureStateUtil {
// Acquire the arena from the global pool.
// With this, we finally arrive at the intended method handle:
// (C1-Cn)(int|long)
return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.apply(ACQUIRE_ARENA));
return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.get(ACQUIRE_ARENA));
}
private static MethodHandle makeSegmentExtractionHandle(SegmentExtractorKey segmentExtractorKey) {
@ -260,15 +265,15 @@ public final class CaptureStateUtil {
if (segmentExtractorKey.returnType().equals(int.class)) {
// (int, MemorySegment)int
return MethodHandles.guardWithTest(
HANDLES_CACHE.apply(NON_NEGATIVE_INT),
HANDLES_CACHE.apply(SUCCESS_INT),
HANDLES_CACHE.apply(ERROR_INT).bindTo(intExtractor));
HANDLES_CACHE.get(NON_NEGATIVE_INT),
HANDLES_CACHE.get(SUCCESS_INT),
HANDLES_CACHE.get(ERROR_INT).bindTo(intExtractor));
} else {
// (long, MemorySegment)long
return MethodHandles.guardWithTest(
HANDLES_CACHE.apply(NON_NEGATIVE_LONG),
HANDLES_CACHE.apply(SUCCESS_LONG),
HANDLES_CACHE.apply(ERROR_LONG).bindTo(intExtractor));
HANDLES_CACHE.get(NON_NEGATIVE_LONG),
HANDLES_CACHE.get(SUCCESS_LONG),
HANDLES_CACHE.get(ERROR_LONG).bindTo(intExtractor));
}
}
@ -341,8 +346,8 @@ public final class CaptureStateUtil {
}
};
private static final IntFunction<MethodHandle> HANDLES_CACHE =
StableValue.intFunction(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE);
private static final List<MethodHandle> HANDLES_CACHE =
List.ofLazy(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE);
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();

View File

@ -39,6 +39,7 @@ import java.util.Formatter;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import jdk.internal.access.SharedSecrets;
import jdk.internal.util.StaticProperty;
@ -114,24 +115,29 @@ public final class JdkConsoleImpl implements JdkConsole {
// check if `System.console()` returns a Console instance and use it if available. Otherwise,
// it should call this method to obtain a JdkConsoleImpl. This ensures only one Console
// instance exists in the Java runtime.
private static final StableValue<Optional<JdkConsoleImpl>> INSTANCE = StableValue.of();
public static Optional<JdkConsoleImpl> passwordConsole() {
return INSTANCE.orElseSet(() -> {
// If there's already a proper console, throw an exception
if (System.console() != null) {
throw new IllegalStateException("Cant create a dedicated password " +
"console since a real console already exists");
}
private static final LazyConstant<Optional<JdkConsoleImpl>> PASSWORD_CONSOLE = LazyConstant.of(
new Supplier<Optional<JdkConsoleImpl>>() {
@Override
public Optional<JdkConsoleImpl> get() {
if (System.console() != null) {
throw new IllegalStateException("Cant create a dedicated password " +
"console since a real console already exists");
}
// If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl
// instance, otherwise an empty Optional.
return SharedSecrets.getJavaIOAccess().isStdinTty() ?
Optional.of(
new JdkConsoleImpl(
Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE),
Charset.forName(StaticProperty.stdoutEncoding(), UTF_8.INSTANCE))) :
Optional.empty();
});
// If stdin is NOT redirected, return an Optional containing a JdkConsoleImpl
// instance, otherwise an empty Optional.
return SharedSecrets.getJavaIOAccess().isStdinTty() ?
Optional.of(
new JdkConsoleImpl(
Charset.forName(StaticProperty.stdinEncoding(), UTF_8.INSTANCE),
Charset.forName(StaticProperty.stdoutEncoding(), UTF_8.INSTANCE))) :
Optional.empty();
}
}
);
public static Optional<JdkConsoleImpl> passwordConsole() {
return PASSWORD_CONSOLE.get();
}
// Dedicated entry for sun.security.util.Password when stdout is redirected.

View File

@ -85,8 +85,8 @@ public @interface PreviewFeature {
//---
@JEP(number=525, title="Structured Concurrency", status="Sixth Preview")
STRUCTURED_CONCURRENCY,
@JEP(number = 502, title = "Stable Values", status = "Preview")
STABLE_VALUES,
@JEP(number = 526, title = "Lazy Constants", status = "Second Preview")
LAZY_CONSTANTS,
@JEP(number=524, title="PEM Encodings of Cryptographic Objects",
status="Second Preview")
PEM_API,

View File

@ -0,0 +1,173 @@
/*
* 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;
import jdk.internal.misc.Unsafe;
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
import jdk.internal.vm.annotation.ForceInline;
import jdk.internal.vm.annotation.Stable;
import java.util.Objects;
import java.util.function.Supplier;
/**
* The sole implementation of the LazyConstant interface.
*
* @param <T> type of the constant
* @implNote This implementation can be used early in the boot sequence as it does not
* rely on reflection, MethodHandles, Streams etc.
*/
@AOTSafeClassInitializer
public final class LazyConstantImpl<T> implements LazyConstant<T> {
// Unsafe allows `LazyConstant` instances to be used early in the boot sequence
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
// Unsafe offset for access of the `constant` field
private static final long CONSTANT_OFFSET =
UNSAFE.objectFieldOffset(LazyConstantImpl.class, "constant");
// 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 reflectively via Unsafe using explicit memory semantics.
//
// | Value | Meaning |
// | --------------- | -------------- |
// | `null` | Unset |
// | `other` | Set to `other` |
//
@Stable
private T constant;
// Underlying computing function to be used to compute the `constant` field.
// The field needs to be `volatile` as a lazy constant can be
// created by one thread and computed by another thread.
// After the function is successfully invoked, the field is set to
// `null` to allow the function to be collected.
@Stable
private volatile Supplier<? extends T> computingFunction;
private LazyConstantImpl(Supplier<? extends T> computingFunction) {
this.computingFunction = computingFunction;
}
@ForceInline
@Override
public T get() {
final T t = getAcquire();
return (t != null) ? t : getSlowPath();
}
private T getSlowPath() {
preventReentry();
synchronized (this) {
T t = getAcquire();
if (t == null) {
t = computingFunction.get();
Objects.requireNonNull(t);
setRelease(t);
// Allow the underlying supplier to be collected after successful use
computingFunction = null;
}
return t;
}
}
@ForceInline
@Override
public T orElse(T other) {
final T t = getAcquire();
return (t == null) ? other : t;
}
@ForceInline
@Override
public boolean isInitialized() {
return getAcquire() != null;
}
@Override
public String toString() {
return super.toString() + "[" + toStringSuffix() + "]";
}
private String toStringSuffix() {
final T t = getAcquire();
if (t == this) {
return "(this LazyConstant)";
} else if (t != null) {
return t.toString();
}
// Volatile read
final Supplier<? extends T> cf = computingFunction;
// There could be a race here
if (cf != null) {
return "computing function=" + computingFunction.toString();
}
// As we know `computingFunction` is `null` via a volatile read, we
// can now be sure that this lazy constant is initialized
return getAcquire().toString();
}
// Discussion on the memory semantics used.
// ----------------------------------------
// Using acquire/release semantics on the `constant` field is the cheapest way to
// establish a happens-before (HB) relation between load and store operations. Every
// implementation of a method defined in the interface `LazyConstant` except
// `equals()` starts with a load of the `constant` field using acquire semantics.
//
// If the underlying supplier was guaranteed to always create a new object,
// a fence after creation and subsequent plain loads would suffice to ensure
// new objects' state are always correctly observed. However, no such restriction is
// imposed on the underlying supplier. Hence, the docs state there should be an
// HB relation meaning we will have to pay a price (on certain platforms) on every
// `get()` operation that is not constant-folded.
@SuppressWarnings("unchecked")
@ForceInline
private T getAcquire() {
return (T) UNSAFE.getReferenceAcquire(this, CONSTANT_OFFSET);
}
private void setRelease(T newValue) {
UNSAFE.putReferenceRelease(this, CONSTANT_OFFSET, newValue);
}
private void preventReentry() {
if (Thread.holdsLock(this)) {
throw new IllegalStateException("Recursive invocation of a LazyConstant's computing function: " + computingFunction);
}
}
// Factory
public static <T> LazyConstantImpl<T> ofLazy(Supplier<? extends T> computingFunction) {
return new LazyConstantImpl<>(computingFunction);
}
}

View File

@ -1,118 +0,0 @@
/*
* 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 enumType the class type of the Enum
* @param firstOrdinal the lowest ordinal used
* @param member an int predicate that can be used to test if an enum is a member
* of the valid inputs (as there might be "holes")
* @param delegates a delegate array of inputs to StableValue mappings
* @param original the original Function
* @param <E> the type of the input to the function
* @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;
// Since we did the member.test above, we know the index is in bounds
return delegates[index].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 Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(delegates.length);
final E[] enumElements = enumType.getEnumConstants();
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 = ((E) inputs.iterator().next()).getDeclaringClass();
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);
}
}

View File

@ -1,83 +0,0 @@
/*
* 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 `StableMap::get` with some additional logic
// instead of this class but explicitly providing a class like this provides better
// debug capability, exception handling, and may provide better performance.
/**
* 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);
}
}

View File

@ -1,80 +0,0 @@
/*
* 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;
/**
* 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);
}
}

View File

@ -1,69 +0,0 @@
/*
* 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.wrappedContentsAcquire();
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
}
public static <T> StableSupplier<T> of(Supplier<? extends T> original) {
return new StableSupplier<>(StableValueImpl.of(), original);
}
}

View File

@ -1,105 +0,0 @@
/*
* 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].wrappedContentsAcquire();
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().wrappedContentsAcquire();
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);
}
}
}

View File

@ -1,218 +0,0 @@
/*
* 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 CONTENTS_OFFSET =
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
// Used to indicate a holder value is `null` (see field `contents` below)
private static final Object NULL_SENTINEL = new Object();
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
// 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 (wrappedContentsAcquire() != null) {
return false;
}
// Prevent reentry via an orElseSet(supplier)
preventReentry();
// Mutual exclusion is required here as `orElseSet` might also
// attempt to modify `this.contents`
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 = wrappedContentsAcquire();
if (t == null) {
throw new NoSuchElementException("No contents set");
}
return unwrap(t);
}
@ForceInline
@Override
public T orElse(T other) {
final Object t = wrappedContentsAcquire();
return (t == null) ? other : unwrap(t);
}
@ForceInline
@Override
public boolean isSet() {
return wrappedContentsAcquire() != null;
}
@ForceInline
@Override
public T orElseSet(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier);
final Object t = wrappedContentsAcquire();
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 = wrappedContentsAcquire();
return t == this
? "(this StableValue)"
: renderWrapped(t);
}
// Internal methods shared with other internal classes
@ForceInline
public Object wrappedContentsAcquire() {
return UNSAFE.getReferenceAcquire(this, CONTENTS_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(T newValue) {
assert Thread.holdsLock(this);
// We know we hold the monitor here so plain semantic is enough
if (contents == null) {
UNSAFE.putReferenceRelease(this, CONTENTS_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<>();
}
}

View File

@ -48,6 +48,8 @@ import java.nio.channels.UnresolvedAddressException;
import java.nio.channels.UnsupportedAddressTypeException;
import java.util.Enumeration;
import java.util.Objects;
import java.lang.LazyConstant;
import java.util.function.Supplier;
import sun.net.ext.ExtendedSocketOptions;
import sun.net.util.IPAddressUtil;
@ -94,13 +96,14 @@ public class Net {
return EXCLUSIVE_BIND;
}
private static final StableValue<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of();
private static final LazyConstant<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = LazyConstant.of(new Supplier<Boolean>() {
@Override public Boolean get() { return shouldShutdownWriteBeforeClose0(); }});
/**
* Tells whether a TCP connection should be shutdown for writing before closing.
*/
static boolean shouldShutdownWriteBeforeClose() {
return SHUTDOWN_WRITE_BEFORE_CLOSE.orElseSet(Net::shouldShutdownWriteBeforeClose0);
return SHUTDOWN_WRITE_BEFORE_CLOSE.get();
}
/**

View File

@ -92,8 +92,8 @@ public final class BaseLocale {
}
// Interned BaseLocale cache
private static final Supplier<ReferencedKeySet<BaseLocale>> CACHE =
StableValue.supplier(new Supplier<>() {
private static final LazyConstant<ReferencedKeySet<BaseLocale>> CACHE =
LazyConstant.of(new Supplier<>() {
@Override
public ReferencedKeySet<BaseLocale> get() {
return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier());

View File

@ -50,7 +50,7 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle {
// those keys must be added to NON_DATA_KEYS.
private static final Set<String> NON_DATA_KEYS = Set.of("BreakIteratorClasses");
private final Supplier<Set<String>> keys = StableValue.supplier(
private final LazyConstant<Set<String>> keys = LazyConstant.of(
new Supplier<>() { public Set<String> get() { return keys0(); }});
private Set<String> keys0() {

View File

@ -119,7 +119,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle {
return new HashSet<>();
}
private final Supplier<Map<String, Object>> lookup = StableValue.supplier(
private final LazyConstant<Map<String, Object>> lookup = LazyConstant.of(
new Supplier<>() { public Map<String, Object> get() { return lookup0(); }});
private Map<String, Object> lookup0() {
@ -134,7 +134,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle {
return temp;
}
private final Supplier<Set<String>> keyset = StableValue.supplier(
private final LazyConstant<Set<String>> keyset = LazyConstant.of(
new Supplier<>() { public Set<String> get() { return keyset0(); }});
private Set<String> keyset0() {

View File

@ -0,0 +1,153 @@
/*
* 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 dependency injection
* @enablePreview
* @run junit DemoContainerInjectionTest
*/
import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.*;
final class DemoContainerInjectionTest {
interface Foo{}
interface Bar{};
static class FooImpl implements Foo{};
static class BarImpl implements Bar{};
// Provides a type safe way of associating a type to a supplier
record Provider<T>(Class<T> type, Supplier<? extends T> supplier){}
@Test
void ComputedComponentsViaLambda() {
Container container = ComputedContainer.of(Set.of(Foo.class, Bar.class), t -> switch (t) {
case Class<?> c when c.equals(Foo.class) -> new FooImpl();
case Class<?> c when c.equals(Bar.class) -> new BarImpl();
default -> throw new IllegalArgumentException();
});
assertContainerPopulated(container);
}
@Test
void SettableComponents() {
SettableContainer container = SettableScratchContainer.of(Set.of(Foo.class, Bar.class));
container.set(Foo.class, new FooImpl());
container.set(Bar.class, new BarImpl());
assertContainerPopulated(container);
}
@Test
void ProviderComponents() {
Container container = ProviderContainer.of(Map.of(
Foo.class, FooImpl::new,
Bar.class, BarImpl::new));
assertContainerPopulated(container);
}
@Test
void ProviderTypedComponents() {
Container container = providerTypedContainer(Set.of(
new Provider<>(Foo.class, FooImpl::new),
new Provider<>(Bar.class, BarImpl::new)
));
assertContainerPopulated(container);
}
private static void assertContainerPopulated(Container container) {
assertInstanceOf(FooImpl.class, container.get(Foo.class));
assertInstanceOf(BarImpl.class, container.get(Bar.class));
}
interface Container {
<T> T get(Class<T> type);
}
interface SettableContainer extends Container {
<T> void set(Class<T> type, T implementation);
}
record ComputedContainer(Map<Class<?>, ?> components) implements Container {
@Override
public <T> T get(Class<T> type) {
return type.cast(components.get(type));
}
static Container of(Set<Class<?>> components, Function<Class<?>, ?> mapper) {
return new ComputedContainer(Map.ofLazy(components, mapper));
}
}
record SettableScratchContainer(Map<Class<?>, Object> scratch, Map<Class<?>, ?> components) implements SettableContainer {
@Override
public <T> void set(Class<T> type, T implementation) {
if (scratch.putIfAbsent(type, type.cast(implementation)) != null) {
throw new IllegalStateException("Can only set once for " + type);
}
}
@Override
public <T> T get(Class<T> type) {
return type.cast(components.get(type));
}
static SettableContainer of(Set<Class<?>> components) {
Map<Class<?>, Object> scratch = new ConcurrentHashMap<>();
return new SettableScratchContainer(scratch, Map.ofLazy(components, scratch::get));
}
}
record ProviderContainer(Map<Class<?>, ?> components) implements Container {
@Override
public <T> T get(Class<T> type) {
return type.cast(components.get(type));
}
static Container of(Map<Class<?>, Supplier<?>> components) {
var map = Map.ofLazy(components.keySet(), t -> components.get(t).get());
return new ProviderContainer(map);
}
}
static Container providerTypedContainer(Set<Provider<?>> providers) {
return ProviderContainer.of(providers.stream()
.collect(Collectors.toMap(Provider::type, Provider::supplier)));
}
}

View File

@ -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.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/* @test
* @summary Test of a demo of an imperative stable value based on a lazy constant
* @enablePreview
* @run junit DemoImperativeTest
*/
import org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.*;
final class DemoImperativeTest {
interface ImperativeStableValue<T> {
T orElse(T other);
boolean isSet();
boolean trySet(T t);
T get();
static <T> ImperativeStableValue<T> of() {
var scratch = new AtomicReference<T>();
return new Impl<>(scratch, LazyConstant.of(scratch::get));
}
}
private record Impl<T>(AtomicReference<T> scratch,
LazyConstant<T> underlying) implements ImperativeStableValue<T> {
@Override
public boolean trySet(T t) {
final boolean result = scratch.compareAndSet(null, t);
if (result) {
// Actually set the value
get();
}
return result;
}
@Override public T orElse(T other) { return underlying.orElse(other); }
@Override public boolean isSet() { return underlying.isInitialized(); }
@Override public T get() { return underlying.get(); }
}
@Test
void basic() {
var stableValue = ImperativeStableValue.<Integer>of();
assertFalse(stableValue.isSet());
assertEquals(13, stableValue.orElse(13));
assertTrue(stableValue.trySet(42));
assertFalse(stableValue.trySet(13));
assertTrue(stableValue.isSet());
assertEquals(42, stableValue.get());
assertEquals(42, stableValue.orElse(13));
}
}

View File

@ -0,0 +1,119 @@
/*
* 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 Test of a lazy map application
* @enablePreview
* @run junit DemoMapTest
*/
import org.junit.jupiter.api.*;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.*;
final class DemoMapTest {
static class OrderController{}
// NEW:
static final Map<String, OrderController> ORDERS
= Map.ofLazy(
Set.of("Customers", "Internal", "Testing"),
_ -> new OrderController()
);
public static OrderController orders() {
String groupName = Thread.currentThread().getThreadGroup().getName();
return ORDERS.get(groupName);
}
@Test
void orderController() throws InterruptedException {
Thread t = Thread.ofPlatform()
.group(new ThreadGroup("Customers"))
.start(() -> {
String groupName = Thread.currentThread().getThreadGroup().getName();
OrderController orderController = ORDERS.get(groupName);
assertNotNull(orderController);
});
t.join();
}
private static final Map<Integer, String> SERVER_ERROR_PAGES = Map.ofLazy(
Set.of(500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511),
e -> {
try {
return Files.readString(Path.of("server_error_" + e + ".html"));
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
});
private static String htmlServerErrorPage(int errorCode) {
return SERVER_ERROR_PAGES.get(errorCode); // Eligible for constant folding
}
@Test
void page500() {
String page = htmlServerErrorPage(500); // Constant folds
assertEquals(DEFAULT_FS_MSG, page);
}
static final String DEFAULT_FS_MSG = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Internal Server Error (500)</title>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
There was a general problem with the server, code 500
</body>
</html>
""";
@BeforeAll
public static void setup() throws IOException {
var file = Path.of("server_error_500.html");
if (Files.notExists(file)) {
Files.createFile(file);
Files.writeString(file, DEFAULT_FS_MSG);
}
assertEquals(DEFAULT_FS_MSG, Files.readString(file));
}
@AfterAll
public static void cleanUp() throws IOException {
var file = Path.of("server_error_500.html");
Files.deleteIfExists(file);
}
}

View File

@ -22,10 +22,11 @@
*/
/* @test
* @summary Basic tests for making sure StableValue publishes values safely
* @summary Basic tests for making sure ComputedConstant publishes values safely
* @modules java.base/jdk.internal.misc
* @modules java.base/jdk.internal.lang
* @enablePreview
* @run junit StableValuesSafePublicationTest
* @run junit LazyConstantSafePublicationTest
*/
import org.junit.jupiter.api.Test;
@ -36,30 +37,22 @@ import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.lang.LazyConstant;
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 {
final class LazyConstantSafePublicationTest {
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.
// fully initialized thanks to the HB properties of ComputedConstants.
int a, b, c, d, e;
Holder() {
@ -69,17 +62,21 @@ final class StableValuesSafePublicationTest {
static final class Consumer implements Runnable {
final LazyConstant<Holder>[] constants;
final int[] observations = new int[SIZE];
final StableValue<Holder>[] stables = STABLES;
int i = 0;
public Consumer(LazyConstant<Holder>[] constants) {
this.constants = constants;
}
@Override
public void run() {
for (; i < SIZE; i++) {
StableValue<Holder> s = stables[i];
LazyConstant<Holder> s = constants[i];
Holder h;
// Wait until the StableValue has a holder value
while ((h = s.orElse(null)) == null) {}
// Wait until the ComputedConstant has a holder value
while ((h = s.orElse(null)) == null) { Thread.onSpinWait();}
int a = h.a;
int b = h.b;
int c = h.c;
@ -92,15 +89,19 @@ final class StableValuesSafePublicationTest {
static final class Producer implements Runnable {
final StableValue<Holder>[] stables = STABLES;
final LazyConstant<Holder>[] constants;
public Producer(LazyConstant<Holder>[] constants) {
this.constants = constants;
}
@Override
public void run() {
StableValue<Holder> s;
LazyConstant<Holder> s;
long deadlineNs = System.nanoTime();
for (int i = 0; i < SIZE; i++) {
s = stables[i];
s.trySet(new Holder());
s = constants[i];
s.get();
deadlineNs += 1000;
while (System.nanoTime() < deadlineNs) {
Thread.onSpinWait();
@ -110,9 +111,10 @@ final class StableValuesSafePublicationTest {
}
@Test
void main() {
void mainTest() {
final LazyConstant<Holder>[] constants = constants();
List<Consumer> consumers = IntStream.range(0, THREADS)
.mapToObj(_ -> new Consumer())
.mapToObj(_ -> new Consumer(constants))
.toList();
List<Thread> consumersThreads = IntStream.range(0, THREADS)
@ -121,14 +123,14 @@ final class StableValuesSafePublicationTest {
.start(consumers.get(i)))
.toList();
Producer producer = new Producer();
Producer producer = new Producer(constants);
Thread producerThread = Thread.ofPlatform()
.name("Producer Thread")
.start(producer);
join(consumers, producerThread);
join(consumers, consumersThreads.toArray(Thread[]::new));
join(constants, consumers, producerThread);
join(constants, consumers, consumersThreads.toArray(Thread[]::new));
int[] histogram = new int[64];
for (Consumer consumer : consumers) {
@ -146,7 +148,7 @@ final class StableValuesSafePublicationTest {
assertEquals(THREADS * SIZE, histogram[63]);
}
static void join(List<Consumer> consumers, Thread... threads) {
static void join(final LazyConstant<Holder>[] constants, List<Consumer> consumers, Thread... threads) {
try {
for (Thread t:threads) {
long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(1);
@ -163,11 +165,11 @@ final class StableValuesSafePublicationTest {
}
if (System.nanoTime() > deadline) {
long nonNulls = CompletableFuture.supplyAsync(() ->
Stream.of(STABLES)
Arrays.stream(constants)
.map(s -> s.orElse(null))
.filter(Objects::nonNull)
.count(), Executors.newSingleThreadExecutor()).join();
fail("Giving up! Set stables seen by a new thread: " + nonNulls);
fail("Giving up! Set lazy constants seen by a new thread: " + nonNulls);
}
}
}
@ -176,4 +178,13 @@ final class StableValuesSafePublicationTest {
}
}
static LazyConstant<Holder>[] constants() {
@SuppressWarnings("unchecked")
LazyConstant<Holder>[] constants = (LazyConstant<Holder>[]) new LazyConstant[SIZE];
for (int i = 0; i < SIZE; i++) {
constants[i] = LazyConstant.of(Holder::new);
}
return constants;
}
}

View File

@ -0,0 +1,236 @@
/*
* 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 the LazyConstant implementation
* @enablePreview
* @modules java.base/jdk.internal.lang
* @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED LazyConstantTest
*/
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.lang.LazyConstant;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
final class LazyConstantTest {
private static final int VALUE = 42;
private static final Supplier<Integer> SUPPLIER = () -> VALUE;
@Test
void factoryInvariants() {
assertThrows(NullPointerException.class, () -> LazyConstant.of(null));
}
@ParameterizedTest
@MethodSource("factories")
void basic(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER);
var lazy = factory.apply(cs);
assertFalse(lazy.isInitialized());
assertEquals(SUPPLIER.get(), lazy.get());
assertEquals(1, cs.cnt());
assertEquals(SUPPLIER.get(), lazy.get());
assertEquals(1, cs.cnt());
assertTrue(lazy.toString().contains(Integer.toString(SUPPLIER.get())));
}
@ParameterizedTest
@MethodSource("factories")
void exception(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(() -> {
throw new UnsupportedOperationException();
});
var lazy = factory.apply(cs);
assertThrows(UnsupportedOperationException.class, lazy::get);
assertEquals(1, cs.cnt());
assertThrows(UnsupportedOperationException.class, lazy::get);
assertEquals(2, cs.cnt());
assertTrue(lazy.toString().contains("computing function"));
}
@ParameterizedTest
@MethodSource("lazyConstants")
void orElse(LazyConstant<Integer> constant) {
assertNull(constant.orElse(null));
constant.get();
assertEquals(VALUE, constant.orElse(null));
}
@ParameterizedTest
@MethodSource("lazyConstants")
void get(LazyConstant<Integer> constant) {
assertEquals(VALUE, constant.get());
}
@ParameterizedTest
@MethodSource("lazyConstants")
void isInitialized(LazyConstant<Integer> constant) {
assertFalse(constant.isInitialized());
constant.get();
assertTrue(constant.isInitialized());
}
@ParameterizedTest
@MethodSource("lazyConstants")
void testHashCode(LazyConstant<Integer> constant) {
assertEquals(System.identityHashCode(constant), constant.hashCode());
}
@ParameterizedTest
@MethodSource("lazyConstants")
void testEquals(LazyConstant<Integer> c0) {
assertNotEquals(null, c0);
LazyConstant<Integer> different = LazyConstant.of(SUPPLIER);
assertNotEquals(different, c0);
assertNotEquals(c0, different);
assertNotEquals("a", c0);
}
@ParameterizedTest
@MethodSource("lazyConstants")
void testLazyConstantAsComputingFunction(LazyConstant<Integer> constant) {
LazyConstant<Integer> c1 = LazyConstant.of(constant);
assertSame(constant, c1);
}
@Test
void toStringTest() {
Supplier<String> supplier = () -> "str";
LazyConstant<String> lazy = LazyConstant.of(supplier);
var expectedSubstring = "computing function=" + supplier;
assertTrue(lazy.toString().contains(expectedSubstring));
lazy.get();
assertTrue(lazy.toString().contains("str"));
}
@ParameterizedTest
@MethodSource("lazyConstants")
void toStringUnset(LazyConstant<Integer> constant) {
String unInitializedToString = constant.toString();
int suffixEnd = unInitializedToString.indexOf("[");
String suffix = unInitializedToString.substring(0, suffixEnd);
String expectedUninitialized = suffix+"[computing function=";
assertTrue(unInitializedToString.startsWith(expectedUninitialized));
constant.get();
String expectedInitialized = suffix + "[" + VALUE + "]";
assertEquals(expectedInitialized, constant.toString());
}
@Test
void toStringCircular() {
AtomicReference<LazyConstant<?>> ref = new AtomicReference<>();
LazyConstant<LazyConstant<?>> constant = LazyConstant.of(ref::get);
ref.set(constant);
constant.get();
String toString = assertDoesNotThrow(constant::toString);
assertTrue(constant.toString().contains("(this LazyConstant)"), toString);
}
@Test
void recursiveCall() {
AtomicReference<LazyConstant<Integer>> ref = new AtomicReference<>();
LazyConstant<Integer> constant = LazyConstant.of(() -> ref.get().get());
LazyConstant<Integer> constant1 = LazyConstant.of(constant);
ref.set(constant1);
assertThrows(IllegalStateException.class, constant::get);
}
@ParameterizedTest
@MethodSource("factories")
void underlying(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER);
var f1 = factory.apply(cs);
Supplier<?> underlyingBefore = LazyConstantTestUtil.computingFunction(f1);
assertSame(cs, underlyingBefore);
int v = f1.get();
Supplier<?> underlyingAfter = LazyConstantTestUtil.computingFunction(f1);
assertNull(underlyingAfter);
}
@ParameterizedTest
@MethodSource("factories")
void functionHolderException(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(() -> {
throw new UnsupportedOperationException();
});
var f1 = factory.apply(cs);
Supplier<?> underlyingBefore = LazyConstantTestUtil.computingFunction(f1);
assertSame(cs, underlyingBefore);
try {
int v = f1.get();
} catch (UnsupportedOperationException _) {
// Expected
}
Supplier<?> underlyingAfter = LazyConstantTestUtil.computingFunction(f1);
assertSame(cs, underlyingAfter);
}
private static Stream<LazyConstant<Integer>> lazyConstants() {
return factories()
.map(f -> f.apply(() -> VALUE));
}
private static Stream<Function<Supplier<Integer>, LazyConstant<Integer>>> factories() {
return Stream.of(
supplier("ComputedConstant.of(<lambda>)", LazyConstant::of)
);
}
private static Function<Supplier<Integer>, LazyConstant<Integer>> supplier(String name,
Function<Supplier<Integer>, LazyConstant<Integer>> underlying) {
return new Function<Supplier<Integer>, LazyConstant<Integer>>() {
@Override
public LazyConstant<Integer> apply(Supplier<Integer> supplier) {
return underlying.apply(supplier);
}
@Override
public String toString() {
return name;
}
};
}
record Lazy<T>(LazyConstant<T> underlying) implements Supplier<T> {
@Override
public T get() { return underlying.get(); }
static <T> Lazy<T> of(Supplier<? extends T> computingFunction) {
return new Lazy<>(LazyConstant.of(computingFunction));
}
}
}

View File

@ -21,15 +21,18 @@
* questions.
*/
import java.lang.reflect.Field;
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 {
final class LazyConstantTestUtil {
private StableTestUtil() {}
private LazyConstantTestUtil() { }
public static final String UNINITIALIZED_TAG = ".uninitialized";
public static final class CountingSupplier<T>
extends AbstractCounting<Supplier<T>>
@ -117,4 +120,55 @@ final class StableTestUtil {
}
}
static Object functionHolder(Object o) {
try {
final Field field = field(o.getClass(), "functionHolder");
field.setAccessible(true);
return field.get(o);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
static Object functionHolderFunction(Object o) {
try {
final Field field = field(o.getClass(), "function");
field.setAccessible(true);
return field.get(o);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
static int functionHolderCounter(Object o) {
try {
final Field field = field(o.getClass(), "counter");
field.setAccessible(true);
return (int)field.get(o);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
static Supplier<?> computingFunction(LazyConstant<?> o) {
try {
final Field field = field(o.getClass(), "computingFunction");
field.setAccessible(true);
return (Supplier<?>) field.get(o);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
static Field field(Class<?> clazz, String name) {
if (clazz.equals(Object.class)) {
throw new RuntimeException("No " + name);
}
try {
return clazz.getDeclaredField(name);
} catch (NoSuchFieldException e) {
return field(clazz.getSuperclass(), name);
}
}
}

View File

@ -22,23 +22,18 @@
*/
/* @test
* @summary Basic tests for StableList methods
* @modules java.base/jdk.internal.lang.stable
* @summary Basic tests for lazy list methods
* @enablePreview
* @run junit StableListTest
* @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyListTest
*/
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.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;
@ -54,7 +49,7 @@ import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.*;
final class StableListTest {
final class LazyListTest {
private static final int ZERO = 0;
private static final int INDEX = 7;
@ -63,26 +58,26 @@ final class StableListTest {
@Test
void factoryInvariants() {
assertThrows(NullPointerException.class, () -> StableValue.list(SIZE, null));
assertThrows(IllegalArgumentException.class, () -> StableValue.list(-1, IDENTITY));
assertThrows(NullPointerException.class, () -> List.ofLazy(SIZE, null));
assertThrows(IllegalArgumentException.class, () -> List.ofLazy(-1, IDENTITY));
}
@Test
void isEmpty() {
assertFalse(newList().isEmpty());
assertTrue(newEmptyList().isEmpty());
assertFalse(newLazyList().isEmpty());
assertTrue(newEmptyLazyList().isEmpty());
}
@Test
void size() {
assertEquals(SIZE, newList().size());
assertEquals(ZERO, newEmptyList().size());
assertEquals(SIZE, newLazyList().size());
assertEquals(ZERO, newEmptyLazyList().size());
}
@Test
void get() {
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(IDENTITY);
var lazy = StableValue.list(SIZE, cif);
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<Integer>(IDENTITY);
var lazy = List.ofLazy(SIZE, cif);
for (int i = 0; i < SIZE; i++) {
assertEquals(i, lazy.get(i));
assertEquals(i + 1, cif.cnt());
@ -93,10 +88,10 @@ final class StableListTest {
@Test
void getException() {
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(_ -> {
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<Integer>(_ -> {
throw new UnsupportedOperationException();
});
var lazy = StableValue.list(SIZE, cif);
var lazy = List.ofLazy(SIZE, cif);
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
assertEquals(1, cif.cnt());
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
@ -105,8 +100,8 @@ final class StableListTest {
@Test
void toArray() {
assertArrayEquals(new Object[ZERO], newEmptyList().toArray());
assertArrayEquals(newRegularList().toArray(), newList().toArray());
assertArrayEquals(new Object[ZERO], newEmptyLazyList().toArray());
assertArrayEquals(newRegularList().toArray(), newLazyList().toArray());
}
@Test
@ -115,8 +110,8 @@ final class StableListTest {
for (int i = 0; i < SIZE; i++) {
actual[INDEX] = 100 + i;
}
var list = StableValue.list(INDEX, IDENTITY);
assertSame(actual, list.toArray(actual));
var lazy = List.ofLazy(INDEX, IDENTITY);
assertSame(actual, lazy.toArray(actual));
Integer[] expected = IntStream.range(0, SIZE)
.mapToObj(i -> i < INDEX ? i : null)
.toArray(Integer[]::new);
@ -126,7 +121,7 @@ final class StableListTest {
@Test
void toArrayWithArraySmaller() {
Integer[] arr = new Integer[INDEX];
Integer[] actual = newList().toArray(arr);
Integer[] actual = newLazyList().toArray(arr);
assertNotSame(arr, actual);
Integer[] expected = newRegularList().toArray(new Integer[0]);
assertArrayEquals(expected, actual);
@ -135,13 +130,13 @@ final class StableListTest {
@Test
void toArrayWithGenerator() {
Integer[] expected = newRegularList().toArray(Integer[]::new);
Integer[] actual = newList().toArray(Integer[]::new);
Integer[] actual = newLazyList().toArray(Integer[]::new);
assertArrayEquals(expected, actual);
}
@Test
void firstIndex() {
var lazy = newList();
var lazy = newLazyList();
for (int i = INDEX; i < SIZE; i++) {
assertEquals(i, lazy.indexOf(i));
}
@ -150,7 +145,7 @@ final class StableListTest {
@Test
void lastIndex() {
var lazy = newList();
var lazy = newLazyList();
for (int i = INDEX; i < SIZE; i++) {
assertEquals(i, lazy.lastIndexOf(i));
}
@ -159,42 +154,28 @@ final class StableListTest {
@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());
assertEquals("[]", newEmptyLazyList().toString());
assertEquals("[0, 1]", List.ofLazy(2, IDENTITY).toString());
}
@Test
void hashCodeTest() {
assertEquals(List.of().hashCode(), newEmptyList().hashCode());
assertEquals(newRegularList().hashCode(), newList().hashCode());
assertEquals(List.of().hashCode(), newEmptyLazyList().hashCode());
assertEquals(newRegularList().hashCode(), newLazyList().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());
assertTrue(newEmptyLazyList().equals(List.of()));
assertTrue(List.of().equals(newEmptyLazyList()));
assertTrue(newLazyList().equals(newRegularList()));
assertTrue(newRegularList().equals(newLazyList()));
assertFalse(newLazyList().equals("A"));
}
@Test
void iteratorTotal() {
var iterator = newList().iterator();
var iterator = newLazyList().iterator();
for (int i = 0; i < SIZE; i++) {
assertTrue(iterator.hasNext());
assertTrue(iterator.hasNext());
@ -209,7 +190,7 @@ final class StableListTest {
@Test
void iteratorPartial() {
var iterator = newList().iterator();
var iterator = newLazyList().iterator();
for (int i = 0; i < INDEX; i++) {
assertTrue(iterator.hasNext());
assertTrue(iterator.hasNext());
@ -225,7 +206,7 @@ final class StableListTest {
@Test
void subList() {
var lazy = newList();
var lazy = newLazyList();
var lazySubList = lazy.subList(1, SIZE);
assertInstanceOf(RandomAccess.class, lazySubList);
var regularList = newRegularList();
@ -235,36 +216,33 @@ final class StableListTest {
@Test
void subList2() {
var lazy = newList();
var lazy = newLazyList();
var lazySubList = lazy.subList(1, SIZE);
lazySubList.get(0);
var eq = newList();
var eq = newLazyList();
eq.get(1);
assertEquals(eq.toString(), lazy.toString());
}
void assertUnevaluated(List<Integer> subList) {
assertEquals(asString(".unset", subList), subList.toString());
}
@Test
void reversed() {
var reversed = newList().reversed();
assertInstanceOf(RandomAccess.class, reversed);
assertEquals(SIZE - 1, reversed.getFirst());
assertEquals(0, reversed.getLast());
var lazy = newLazyList();
var reversedLazy = lazy.reversed();
assertInstanceOf(RandomAccess.class, reversedLazy);
assertEquals(SIZE - 1, reversedLazy.getFirst());
assertEquals(0, reversedLazy.getLast());
var reversed2 = reversed.reversed();
assertInstanceOf(RandomAccess.class, reversed2);
assertEquals(0, reversed2.getFirst());
assertEquals(SIZE - 1, reversed2.getLast());
var reversed2Lazy = reversedLazy.reversed();
assertInstanceOf(RandomAccess.class, reversed2Lazy);
assertEquals(0, reversed2Lazy.getFirst());
assertEquals(SIZE - 1, reversed2Lazy.getLast());
// Make sure we get back a non-reversed implementation
assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName());
assertEquals(lazy.getClass().getName(), reversed2Lazy.getClass().getName());
}
@Test
void sublistReversedToString() {
var actual = StableValue.list(4, IDENTITY);
var actual = List.ofLazy(4, IDENTITY);
var expected = List.of(0, 1, 2, 3);
for (UnaryOperation op : List.of(
new UnaryOperation("subList", l -> l.subList(1, 3)),
@ -276,60 +254,17 @@ final class StableListTest {
actual.getLast();
var actualToString = actual.toString();
var expectedToString = expected.toString().replace("2", ".unset");
var expectedToString = expected.toString();
assertEquals(expectedToString, actualToString);
}
// This test makes sure successive view operations retains the property
// of being a Stable view.
@Test
void viewsStable() {
viewOperations().forEach(op0 -> {
viewOperations().forEach( op1 -> {
viewOperations().forEach(op2 -> {
var list = newList();
var view1 = op0.apply(list);
var view2 = op1.apply(view1);
var view3 = op2.apply(view2);
var className3 = className(view3);
var transitions = className(list) + ", " +
op0 + " -> " + className(view1) + ", " +
op1 + " -> " + className(view2) + ", " +
op2 + " -> " + className3;
assertTrue(className3.contains("Stable"), transitions);
assertUnevaluated(list);
assertUnevaluated(view1);
assertUnevaluated(view2);
assertUnevaluated(view3);
});
});
});
}
@Test
void recursiveCall() {
AtomicReference<IntFunction<Integer>> ref = new AtomicReference<>();
var lazy = StableValue.list(SIZE, i -> ref.get().apply(i));
var lazy = List.ofLazy(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());
}
@Test
void indexOfNullInViews() {
final int size = 5;
final int middle = 2;
viewOperations().forEach(op0 -> {
viewOperations().forEach( op1 -> {
viewOperations().forEach(op2 -> {
var list = StableValue.list(size, x -> x == middle ? null : x);;
var view1 = op0.apply(list);
var view2 = op1.apply(view1);
var view3 = op2.apply(view2);
assertEquals(middle, view3.indexOf(null));
});
});
});
assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage());
}
// Immutability
@ -355,7 +290,7 @@ final class StableListTest {
}
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
var lazy = newList();
var lazy = newLazyList();
assertThrows(expectedType, () -> operation.accept(lazy));
var sub = lazy.subList(1, SIZE / 2);
assertThrows(expectedType, () -> operation.accept(sub));
@ -367,14 +302,14 @@ final class StableListTest {
@Test
void serializable() {
serializable(newList());
serializable(newEmptyList());
serializable(newLazyList());
serializable(newEmptyLazyList());
}
void serializable(List<Integer> list) {
assertFalse(list instanceof Serializable);
if (list.size()>INDEX) {
assertFalse(newList().subList(1, INDEX) instanceof Serializable);
assertFalse(newLazyList().subList(1, INDEX) instanceof Serializable);
}
assertFalse(list.iterator() instanceof Serializable);
assertFalse(list.reversed() instanceof Serializable);
@ -383,54 +318,25 @@ final class StableListTest {
@Test
void randomAccess() {
assertInstanceOf(RandomAccess.class, newList());
assertInstanceOf(RandomAccess.class, newEmptyList());
assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX));
assertInstanceOf(RandomAccess.class, newLazyList());
assertInstanceOf(RandomAccess.class, newEmptyLazyList());
assertInstanceOf(RandomAccess.class, newLazyList().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);
void functionHolder() {
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<>(IDENTITY);
List<Integer> f1 = List.ofLazy(SIZE, cif);
Object holder = LazyConstantTestUtil.functionHolder(f1);
for (int i = 0; i < SIZE; i++) {
assertEquals(SIZE - i, LazyConstantTestUtil.functionHolderCounter(holder));
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
int v = f1.get(i);
int v2 = f1.get(i);
}
assertEquals(SIZE, idMap.size());
}
@Test
void childObjectOpsLazy() {
viewOperations().forEach(op0 -> {
viewOperations().forEach(op1 -> {
viewOperations().forEach(op2 -> {
childOperations().forEach(co -> {
var list = newList();
var view1 = op0.apply(list);
var view2 = op1.apply(view1);
var view3 = op2.apply(view2);
var child = co.apply(view3);
var childClassName = className(child);
var transitions = className(list) + ", " +
op0 + " -> " + className(view1) + ", " +
op1 + " -> " + className(view2) + ", " +
op2 + " -> " + className(view3) + ", " +
co + " -> " + childClassName;
// None of these operations should trigger evaluation
var childToString = child.toString();
int childHashCode = child.hashCode();
boolean childEqualToNewObj = child.equals(new Object());
assertUnevaluated(list);
assertUnevaluated(view1);
assertUnevaluated(view2);
assertUnevaluated(view3);
});
});
});
});
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
}
// Support constructs
@ -516,25 +422,15 @@ final class StableListTest {
);
}
static List<Integer> newList() {
return StableValue.list(SIZE, IDENTITY);
static List<Integer> newLazyList() {
return List.ofLazy(SIZE, IDENTITY);
}
static List<Integer> newEmptyList() {
return StableValue.list(ZERO, IDENTITY);
static List<Integer> newEmptyLazyList() {
return List.ofLazy(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(", ")) + "]";
}
static String className(Object o) {
return o.getClass().getName();
}
}

View File

@ -0,0 +1,573 @@
/*
* 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 lazy map methods
* @enablePreview
* @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyMapTest
*/
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.Arrays;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.mapping;
import static org.junit.jupiter.api.Assertions.*;
final class LazyMapTest {
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) {
@Override
public String toString() {
// getEnumConstants will be `null` for this enum as it is overridden
return super.toString()+" (Overridden)";
}
},
ILLEGAL_BETWEEN(-2),
FORTY_TWO(42),
// Illegal values (not in the input set)
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;
private static final Value KEY = Value.FORTY_TWO;
private static final Integer VALUE = MAPPER.apply(KEY);
@ParameterizedTest
@MethodSource("allSets")
void factoryInvariants(Set<Value> set) {
assertThrows(NullPointerException.class, () -> Map.ofLazy(set, null), set.getClass().getSimpleName());
assertThrows(NullPointerException.class, () -> Map.ofLazy(null, MAPPER));
Set<Value> setWithNull = new HashSet<>();
setWithNull.add(KEY);
setWithNull.add(null);
assertThrows(NullPointerException.class, () -> Map.ofLazy(setWithNull, MAPPER));
}
@ParameterizedTest
@MethodSource("emptySets")
void empty(Set<Value> set) {
var lazy = newLazyMap(set);
assertTrue(lazy.isEmpty());
assertEquals("{}", lazy.toString());
assertThrows(NullPointerException.class, () -> lazy.get(null));
assertNotEquals(null, lazy);
}
@ParameterizedTest
@MethodSource("allSets")
void size(Set<Value> set) {
assertEquals(newRegularMap(set).size(), newLazyMap(set).size());
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void get(Set<Value> set) {
LazyConstantTestUtil.CountingFunction<Value, Integer> cf = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
var lazy = Map.ofLazy(set, cf);
int cnt = 1;
for (Value v : set) {
assertEquals(MAPPER.apply(v), lazy.get(v));
assertEquals(cnt, cf.cnt());
assertEquals(MAPPER.apply(v), lazy.get(v));
assertEquals(cnt++, cf.cnt());
}
assertNull(lazy.get(Value.ILLEGAL_BETWEEN));
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void exception(Set<Value> set) {
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(_ -> {
throw new UnsupportedOperationException();
});
var lazy = Map.ofLazy(set, cif);
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
assertEquals(1, cif.cnt());
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
assertEquals(2, cif.cnt());
assertThrows(UnsupportedOperationException.class, lazy::toString);
assertEquals(3, cif.cnt());
}
@ParameterizedTest
@MethodSource("allSets")
void containsKey(Set<Value> set) {
var lazy = newLazyMap(set);
for (Value v : set) {
assertTrue(lazy.containsKey(v));
}
assertFalse(lazy.containsKey(Value.ILLEGAL_BETWEEN));
}
@ParameterizedTest
@MethodSource("allSets")
void containsValue(Set<Value> set) {
var lazy = newLazyMap(set);
for (Value v : set) {
assertTrue(lazy.containsValue(MAPPER.apply(v)));
}
assertFalse(lazy.containsValue(MAPPER.apply(Value.ILLEGAL_BETWEEN)));
}
@ParameterizedTest
@MethodSource("allSets")
void forEach(Set<Value> set) {
var lazy = newLazyMap(set);
var ref = newRegularMap(set);
Set<Map.Entry<Value, Integer>> expected = ref.entrySet();
Set<Map.Entry<Value, Integer>> actual = new HashSet<>();
lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v)));
assertEquals(expected, actual);
}
@ParameterizedTest
@MethodSource("emptySets")
void toStringTestEmpty(Set<Value> set) {
var lazy = newLazyMap(set);
assertEquals("{}", lazy.toString());
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void toStringTest(Set<Value> set) {
var lazy = newLazyMap(set);
var toString = lazy.toString();
assertTrue(toString.startsWith("{"));
assertTrue(toString.endsWith("}"));
// Key order is unspecified
for (Value key : set) {
toString = lazy.toString();
assertTrue(toString.contains(key + "=" + MAPPER.apply(key)), toString);
}
// One between the values
assertEquals(set.size() - 1, toString.chars().filter(ch -> ch == ',').count());
}
@ParameterizedTest
@MethodSource("allSets")
void hashCodeTest(Set<Value> set) {
var lazy = newLazyMap(set);
var regular = newRegularMap(set);
assertEquals(regular.hashCode(), lazy.hashCode());
}
@ParameterizedTest
@MethodSource("allSets")
void equality(Set<Value> set) {
var lazy = newLazyMap(set);
var regular = newRegularMap(set);
assertEquals(regular, lazy);
assertEquals(lazy, regular);
assertNotEquals("A", lazy);
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void circular(Set<Value> set) {
final AtomicReference<Map<?, ?>> ref = new AtomicReference<>();
Map<Value, Map<?, ?>> lazy = Map.ofLazy(set, _ -> ref.get());
ref.set(lazy);
lazy.get(KEY);
var toString = lazy.toString();
assertTrue(toString.contains("FORTY_TWO=(this Map)"), toString);
assertDoesNotThrow((() -> lazy.equals(lazy)));
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void recursiveCall(Set<Value> set) {
final AtomicReference<Map<Value, ?>> ref = new AtomicReference<>();
@SuppressWarnings("unchecked")
Map<Value, Map<Value, Object>> lazy = Map.ofLazy(set, k -> (Map<Value, Object>) ref.get().get(k));
ref.set(lazy);
var x = assertThrows(IllegalStateException.class, () -> lazy.get(KEY));
assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage());
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void entrySet(Set<Value> set) {
var lazy = newLazyMap(set).entrySet();
var regular = newRegularMap(set).entrySet();
assertTrue(regular.equals(lazy));
assertTrue(lazy.equals(regular));
assertTrue(regular.equals(lazy));
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void entrySetToString(Set<Value> set) {
var lazy = newLazyMap(set);
var lazyEntrySet = lazy.entrySet();
var toString = lazyEntrySet.toString();
for (var key : set) {
assertTrue(toString.contains(key + "=" + MAPPER.apply(key)));
}
assertTrue(toString.startsWith("["));
assertTrue(toString.endsWith("]"));
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void values(Set<Value> set) {
var lazy = newLazyMap(set);
var lazyValues = lazy.values();
// Look at one of the elements
var val = lazyValues.stream().iterator().next();
assertEquals(lazy.size() - 1, functionCounter(lazy));
// Mod ops
assertThrows(UnsupportedOperationException.class, () -> lazyValues.remove(val));
assertThrows(UnsupportedOperationException.class, () -> lazyValues.add(val));
assertThrows(UnsupportedOperationException.class, lazyValues::clear);
assertThrows(UnsupportedOperationException.class, () -> lazyValues.addAll(Set.of(VALUE)));
assertThrows(UnsupportedOperationException.class, () -> lazyValues.removeIf(i -> true));
assertThrows(UnsupportedOperationException.class, () -> lazyValues.retainAll(Set.of(VALUE)));
}
@ParameterizedTest
@MethodSource("allSets")
void valuesToString(Set<Value> set) {
var lazy = newLazyMap(set);
var lazyValues = lazy.values();
var toString = lazyValues.toString();
// Key order is unspecified
for (Value key : set) {
assertTrue(toString.contains(MAPPER.apply(key).toString()), toString);
}
assertTrue(toString.startsWith("["), toString);
assertTrue(toString.endsWith("]"), toString);
}
@ParameterizedTest
@MethodSource("allSets")
void iteratorNext(Set<Value> set) {
Set<Value> encountered = new HashSet<>();
var iterator = newLazyMap(set).entrySet().iterator();
while (iterator.hasNext()) {
var entry = iterator.next();
assertEquals(MAPPER.apply(entry.getKey()), entry.getValue());
encountered.add(entry.getKey());
}
assertEquals(set, encountered);
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void iteratorForEachRemaining(Set<Value> set) {
Set<Value> encountered = new HashSet<>();
var iterator = newLazyMap(set).entrySet().iterator();
var entry = iterator.next();
assertEquals(MAPPER.apply(entry.getKey()), entry.getValue());
encountered.add(entry.getKey());
iterator.forEachRemaining(e -> {
assertEquals(MAPPER.apply(e.getKey()), e.getValue());
encountered.add(e.getKey());
});
assertEquals(set, encountered);
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void lazyEntry(Set<Value> set) {
var lazy = newLazyMap(set);
var entry = lazy.entrySet().stream()
.filter(e -> e.getKey().equals(KEY))
.findAny()
.orElseThrow();
assertEquals(lazy.size(), functionCounter(lazy));
var otherDifferent = Map.entry(Value.ZERO, -1);
assertNotEquals(entry, otherDifferent);
assertEquals(lazy.size(), functionCounter(lazy));
var otherEqual = Map.entry(entry.getKey(), entry.getValue());
assertEquals(entry, otherEqual);
assertEquals(lazy.size() - 1, functionCounter(lazy));
assertEquals(entry.hashCode(), otherEqual.hashCode());
}
@ParameterizedTest
@MethodSource("nonEmptySets")
void lazyForEachEntry(Set<Value> set) {
var lazy = newLazyMap(set);
// Only touch the key.
lazy.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
assertEquals(lazy.size(), functionCounter(lazy)); // No evaluation
// Only touch the value.
lazy.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
assertEquals(0, functionCounter(lazy));
}
// 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) {
for (Set<Value> set : allSets().toList()) {
var lazy = newLazyMap(set);
assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation);
}
}
// Implementing interfaces
@ParameterizedTest
@MethodSource("allSets")
void serializable(Set<Value> set) {
var lazy = newLazyMap(set);
assertFalse(lazy instanceof Serializable);
assertFalse(lazy.entrySet() instanceof Serializable);
assertFalse(lazy.values() instanceof Serializable);
}
@Test
void nullResult() {
var lazy = Map.ofLazy(Set.of(0), _ -> null);
assertThrows(NullPointerException.class, () -> lazy.getOrDefault(0, 1));;
assertTrue(lazy.containsKey(0));
}
@ParameterizedTest
@MethodSource("allSets")
void functionHolder(Set<Value> set) {
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
Object holder = LazyConstantTestUtil.functionHolder(lazy);
int i = 0;
for (Value key : set) {
assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder));
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
int v = lazy.get(key);
int v2 = lazy.get(key);
i++;
}
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
}
@ParameterizedTest
@MethodSource("allSets")
void functionHolderViaEntrySet(Set<Value> set) {
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
Object holder = LazyConstantTestUtil.functionHolder(lazy);
int i = 0;
for (Map.Entry<Value, Integer> e : lazy.entrySet()) {
assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder));
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
int v = e.getValue();
int v2 = e.getValue();
i++;
}
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
}
@ParameterizedTest
@MethodSource("allSets")
void underlyingRefViaEntrySetForEach(Set<Value> set) {
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
Object holder = LazyConstantTestUtil.functionHolder(lazy);
final AtomicInteger i = new AtomicInteger();
lazy.entrySet().forEach(e -> {
assertEquals(set.size() - i.get(), LazyConstantTestUtil.functionHolderCounter(holder));
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
Integer val = e.getValue();
Integer val2 = e.getValue();
i.incrementAndGet();
});
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
}
@Test
void usesOptimizedVersion() {
Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt);
assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName());
Map<Value, Integer> emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt);
assertFalse(emptyMap.getClass().getName().contains("Enum"), emptyMap.getClass().getName());
Map<Value, Integer> regularMap = Map.ofLazy(Set.of(KEY), Value::asInt);
assertFalse(regularMap.getClass().getName().contains("Enum"), regularMap.getClass().getName());
}
@Test
void overriddenEnum() {
final var overridden = Value.THIRTEEN;
Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(overridden), MAPPER);
assertEquals(MAPPER.apply(overridden), enumMap.get(overridden), enumMap.toString());
}
@Test
void enumAliasing() {
enum MyEnum {FOO, BAR}
enum MySecondEnum{BAZ, QUX}
Map<MyEnum, Integer> mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
assertEquals(MyEnum.BAR.ordinal(), mapEnum.get(MyEnum.BAR));
// Make sure class is checked, not just `ordinal()`
assertNull(mapEnum.get(MySecondEnum.QUX));
}
// Support constructs
record Operation(String name,
Consumer<Map<Value, Integer>> consumer) implements Consumer<Map<Value, Integer>> {
@java.lang.Override
public void accept(Map<Value, 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, VALUE, (a, _) -> a)),
new Operation("put", m -> m.put(KEY, 0)),
new Operation("putAll", m -> m.putAll(Map.of())),
new Operation("remove1", m -> m.remove(KEY)),
new Operation("remove2", m -> m.remove(KEY, VALUE)),
new Operation("replace2", m -> m.replace(KEY, 1)),
new Operation("replace3", m -> m.replace(KEY, VALUE, 1)),
new Operation("replaceAll", m -> m.replaceAll((a, _) -> MAPPER.apply(a)))
);
}
static Map<Value, Integer> newLazyMap(Set<Value> set) {
return Map.ofLazy(set, MAPPER);
}
static Map<Value, Integer> newRegularMap(Set<Value> set) {
return set.stream()
.collect(Collectors.toMap(Function.identity(), MAPPER));
}
private static Stream<Set<Value>> nonEmptySets() {
return Stream.of(
Set.of(KEY, Value.THIRTEEN),
linkedHashSet(Value.THIRTEEN, KEY),
treeSet(KEY, Value.THIRTEEN),
EnumSet.of(KEY, 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;
}
private static int functionCounter(Map<?, ?> lazy) {
final Object holder = LazyConstantTestUtil.functionHolder(lazy);
return LazyConstantTestUtil.functionHolderCounter(holder);
}
}

View File

@ -24,14 +24,14 @@
/* @test
* @summary Basic tests for TrustedFieldType implementations
* @modules jdk.unsupported/sun.misc
* @modules java.base/jdk.internal.lang.stable
* @modules java.base/jdk.internal.lang
* @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 --add-opens java.base/jdk.internal.lang=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest
* @run junit/othervm -Dopens=false TrustedFieldTypeTest
*/
import jdk.internal.lang.stable.StableValueImpl;
import jdk.internal.lang.LazyConstantImpl;
import jdk.internal.misc.Unsafe;
import org.junit.jupiter.api.Test;
@ -39,84 +39,89 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.lang.reflect.Field;
import java.lang.reflect.InaccessibleObjectException;
import java.lang.LazyConstant;
import java.util.function.Supplier;
import static org.junit.jupiter.api.Assertions.*;
final class TrustedFieldTypeTest {
private static final int VALUE = 42;
private static final Supplier<Integer> SUPPLIER = () -> VALUE;
@Test
void varHandle() throws NoSuchFieldException, IllegalAccessException {
MethodHandles.Lookup lookup = MethodHandles.lookup();
StableValue<Integer> originalValue = StableValue.of();
LazyConstant<Integer> originalValue = LazyConstant.of(SUPPLIER);
@SuppressWarnings("unchecked")
StableValue<Integer>[] originalArrayValue = new StableValue[10];
LazyConstant<Integer>[] originalArrayValue = new LazyConstant[10];
final class Holder {
private final StableValue<Integer> value = originalValue;
private final LazyConstant<Integer> value = originalValue;
}
final class ArrayHolder {
private final StableValue<Integer>[] array = originalArrayValue;
private final LazyConstant<Integer>[] array = originalArrayValue;
}
VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class);
VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", LazyConstant.class);
Holder holder = new Holder();
assertThrows(UnsupportedOperationException.class, () ->
valueVarHandle.set(holder, StableValue.of())
valueVarHandle.set(holder, LazyConstant.of(SUPPLIER))
);
assertThrows(UnsupportedOperationException.class, () ->
valueVarHandle.compareAndSet(holder, originalValue, StableValue.of())
valueVarHandle.compareAndSet(holder, originalValue, LazyConstant.of(SUPPLIER))
);
VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class);
VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", LazyConstant[].class);
ArrayHolder arrayHolder = new ArrayHolder();
assertThrows(UnsupportedOperationException.class, () ->
arrayVarHandle.set(arrayHolder, new StableValue[1])
arrayVarHandle.set(arrayHolder, new LazyConstant[1])
);
assertThrows(UnsupportedOperationException.class, () ->
arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new StableValue[1])
arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new LazyConstant[1])
);
}
@Test
void updateStableValueContentVia_j_i_m_Unsafe() {
StableValue<Integer> stableValue = StableValue.of();
stableValue.trySet(42);
void updateComputedConstantContentVia_j_i_m_Unsafe() {
LazyConstant<Integer> lazyConstant = LazyConstant.of(SUPPLIER);
lazyConstant.get();
jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe();
long offset = unsafe.objectFieldOffset(stableValue.getClass(), "contents");
long offset = unsafe.objectFieldOffset(lazyConstant.getClass(), "constant");
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());
Object oldData = unsafe.getAndSetReference(lazyConstant, offset, 13);
assertEquals(VALUE, oldData);
assertEquals(13, lazyConstant.get());
}
@Test
void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException {
void updateComputedConstantContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException {
if (Boolean.getBoolean("opens")) {
// Unfortunately, add-opens allows direct access to the `value` field
Field field = StableValueImpl.class.getDeclaredField("contents");
Field field = LazyConstantImpl.class.getDeclaredField("constant");
field.setAccessible(true);
StableValue<Integer> stableValue = StableValue.of();
stableValue.trySet(42);
LazyConstant<Integer> lazyConstant = LazyConstant.of(SUPPLIER);
lazyConstant.get();
Object oldData = field.get(stableValue);
assertEquals(42, oldData);
Object oldData = field.get(lazyConstant);
assertEquals(VALUE, oldData);
field.set(stableValue, 13);
assertEquals(13, stableValue.orElseThrow());
field.set(lazyConstant, 13);
assertEquals(13, lazyConstant.get());
} else {
Field field = StableValueImpl.class.getDeclaredField("contents");
Field field = LazyConstantImpl.class.getDeclaredField("constant");
assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true));
}
}

View File

@ -1,250 +0,0 @@
/*
* 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) {
@Override
public String toString() {
// getEnumConstants will be `null` for this enum as it is overridden
return super.toString()+" (Overridden)";
}
},
ILLEGAL_BETWEEN(-2),
FORTY_TWO(42),
// Illegal values (not in the input set)
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());
}
@Test
void overriddenEnum() {
final var overridden = Value.THIRTEEN;
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt);
assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden));
}
private static Stream<Set<Value>> nonEmptySets() {
return Stream.of(
Set.of(Value.FORTY_TWO, Value.THIRTEEN),
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;
}
}

View File

@ -1,109 +0,0 @@
/*
* 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());
}
}

View File

@ -1,388 +0,0 @@
/*
* 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);
}
@Test
void stableEntry() {
var map = newMap();
var entry = map.entrySet().stream()
.filter(e -> e.getKey().equals(KEY))
.findAny()
.orElseThrow();
assertEquals(KEY + "=.unset", entry.toString());
var otherDifferent = Map.entry(-1, -1);
assertNotEquals(entry, otherDifferent);
assertEquals(KEY + "=.unset", entry.toString());
var otherEqual = Map.entry(KEY, KEY);
assertEquals(entry, otherEqual);
assertEquals(KEY + "=" + KEY, entry.toString());
assertEquals(entry.hashCode(), otherEqual.hashCode());
}
@Test
void stableForEachEntry() {
var map = newMap();
// Only touch the key.
map.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
map.entrySet().iterator()
.forEachRemaining(e -> assertTrue(e.toString().contains(".unset")));
// Only touch the value.
map.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
map.entrySet().iterator()
.forEachRemaining(e -> assertFalse(e.toString().contains(".unset")));
}
// Immutability
@ParameterizedTest
@MethodSource("unsupportedOperations")
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));
}
}

View File

@ -1,104 +0,0 @@
/*
* 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());
}
}

View File

@ -1,43 +0,0 @@
/*
* 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));
}
}

View File

@ -1,389 +0,0 @@
/*
* 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.ArrayList;
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(noThreads);
StableValue<Integer> stable = StableValue.of();
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
try {
// Ready ...
starter.countDown();
// ... set ...
starter.await();
// Here we go!
winners.put(i, winnerPredicate.test(stable, i));
} catch (Throwable t) {
fail(t);
}
}))
.toList();
threads.forEach(Thread::start);
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);
}
}
}

View File

@ -220,15 +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));
testEmptyList(List.ofLazy(0, i -> i));
testEmptyList(List.ofLazy(3, i -> i).subList(0, 0));
testListMutatorsAlwaysThrow(List.of());
testListMutatorsAlwaysThrow(List.<Integer>of().subList(0,0));
testListMutatorsAlwaysThrow(StableValue.list(0, i -> i));
testListMutatorsAlwaysThrow(List.ofLazy(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));
testEmptyListMutatorsAlwaysThrow(List.ofLazy(0, i -> i));
testEmptyListMutatorsAlwaysThrow(List.ofLazy(3, i -> i).subList(0, 0));
for (List<Integer> list : Arrays.asList(
List.<Integer>of(),
List.of(1),
@ -251,9 +251,9 @@ public class MOAT {
Stream.of(1, null).toList(),
Stream.of(1, null, 3).toList(),
Stream.of(1, null, 3, 4).toList(),
StableValue.list(0, i -> i),
StableValue.list(3, i -> i),
StableValue.list(10, i -> i))) {
List.ofLazy(0, i -> i),
List.ofLazy(3, i -> i),
List.ofLazy(10, i -> i))) {
testCollection(list);
testImmutableList(list);
testListMutatorsAlwaysThrow(list);
@ -365,9 +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));
testEmptyMap(Map.ofLazy(Set.of(), k -> k));
testMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k));
testEmptyMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k));
for (Map<Integer,Integer> map : Arrays.asList(
Map.<Integer,Integer>of(),
Map.of(1, 101),
@ -381,9 +381,9 @@ public class MOAT {
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),
StableValue.map(Set.<Integer>of(), k -> k),
StableValue.map(Set.of(1), k -> k),
StableValue.map(Set.of(1, 2, 3), k -> k))) {
Map.ofLazy(Set.<Integer>of(), k -> k),
Map.ofLazy(Set.of(1), k -> k),
Map.ofLazy(Set.of(1, 2, 3), k -> k))) {
testMap(map);
testImmutableMap(map);
testMapMutatorsAlwaysThrow(map);

View File

@ -947,7 +947,7 @@ public class CompletionSuggestionTest extends KullaTesting {
public void testMultiSnippet() {
assertCompletion("String s = \"\"; s.len|", true, "length()");
assertCompletion("String s() { return \"\"; } s().len|", true, "length()");
assertCompletion("String s() { return \"\"; } import java.util.List; List.o|", true, "of(");
assertCompletion("String s() { return \"\"; } import java.util.List; List.o|", true, "of(", "ofLazy(");
assertCompletion("String s() { return \"\"; } import java.ut| ", true, "util.");
assertCompletion("class S { public int length() { return 0; } } new S().len|", true, "length()");
assertSignature("void f() { } f(|", "void f()");

View File

@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring stable list performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -52,16 +52,16 @@ import java.util.function.IntFunction;
})
@Threads(Threads.MAX) // Benchmark under contention
@OperationsPerInvocation(100)
public class StableIntFunctionBenchmark {
public class StableListBenchmark {
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 static final List<Integer> LIST = List.ofLazy(SIZE, IDENTITY);
private static final IntFunction<Integer> INT_FUNCTION = LIST::get;
private final List<Integer> list = StableValue.list(SIZE, IDENTITY);
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
private final List<Integer> list = List.ofLazy(SIZE, IDENTITY);
private final IntFunction<Integer> intFunction = list::get;
@Benchmark
public int list() {

View File

@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
import java.util.function.IntFunction;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring stable list performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -50,16 +50,16 @@ import java.util.function.IntFunction;
"--enable-preview"
})
@Threads(Threads.MAX) // Benchmark under contention
public class StableIntFunctionSingleBenchmark {
public class StableListSingleBenchmark {
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 static final List<Integer> STABLE = List.ofLazy(SIZE, IDENTITY);
private static final IntFunction<Integer> INT_FUNCTION = STABLE::get;
private final List<Integer> stable = StableValue.list(SIZE, IDENTITY);
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
private final List<Integer> stable = List.ofLazy(SIZE, IDENTITY);
private final IntFunction<Integer> intFunction = stable::get;
@Benchmark
public int list() {

View File

@ -43,7 +43,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring stable map performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -55,16 +55,16 @@ import java.util.stream.IntStream;
})
@Threads(Threads.MAX) // Benchmark under contention
@OperationsPerInvocation(100)
public class StableFunctionBenchmark {
public class StableMapBenchmark {
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 static final Map<Integer, Integer> MAP = Map.ofLazy(SET, Function.identity());
private static final Function<Integer, Integer> FUNCTION = MAP::get;
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
private final Map<Integer, Integer> map = Map.ofLazy(SET, Function.identity());
private final Function<Integer, Integer> function = map::get;
@Benchmark
public int map() {
@ -85,7 +85,7 @@ public class StableFunctionBenchmark {
}
@Benchmark
public int staticSMap() {
public int staticMap() {
int sum = 0;
for (int i = 0; i < SIZE; i++) {
sum += MAP.get(i);

View File

@ -28,14 +28,15 @@ 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.EnumSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -43,7 +44,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring lazy map performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -54,16 +55,17 @@ import java.util.stream.IntStream;
"--enable-preview"
})
@Threads(Threads.MAX) // Benchmark under contention
public class StableFunctionSingleBenchmark {
public class StableMapSingleBenchmark {
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 static final Map<Integer, Integer> MAP = Map.ofLazy(SET, Function.identity());
private static final Map<MyEnum, Integer> MAP_ENUM = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
private static final Map<MyEnum, Optional<Integer>> MAP_ENUM_OPTIONAL = Map.ofLazy(EnumSet.allOf(MyEnum.class), e -> Optional.of(e.ordinal()));
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
private final Map<Integer, Integer> map = Map.ofLazy(SET, Function.identity());
private final Map<MyEnum, Integer> mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
@Benchmark
public int map() {
@ -71,18 +73,25 @@ public class StableFunctionSingleBenchmark {
}
@Benchmark
public int function() {
return function.apply(1);
public int mapEnum() {
return mapEnum.get(MyEnum.BAR);
}
@Benchmark
public int staticSMap() {
public int staticMap() {
return MAP.get(1);
}
@Benchmark
public int staticIntFunction() {
return FUNCTION.apply(1);
public int staticMapEnum() {
return MAP_ENUM.get(MyEnum.BAR);
}
@Benchmark
public int staticMapEnumOptional() {
return MAP_ENUM_OPTIONAL.get(MyEnum.BAR).orElseThrow();
}
private enum MyEnum {FOO, BAR}
}

View File

@ -35,22 +35,18 @@ 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.*;
import java.lang.LazyConstant;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring lazy value performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -64,17 +60,15 @@ import static java.lang.constant.ConstantDescs.*;
public class StableMethodHandleBenchmark {
private static final MethodHandle FINAL_MH = identityHandle();
private static final StableValue<MethodHandle> STABLE_MH;
private static final LazyConstant<MethodHandle> STABLE_MH = LazyConstant.of(StableMethodHandleBenchmark::identityHandle);
private static /* intentionally not final */ MethodHandle mh = identityHandle();
private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle);
private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
private static final Map<String, MethodHandle> STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle());
private static final Map<String, MethodHandle> STABLE_MAP = Map.ofLazy(Set.of("identityHandle"), _ -> identityHandle());
static {
STABLE_MH = StableValue.of();
STABLE_MH.setOrThrow(identityHandle());
MAP.put("identityHandle", identityHandle());
}
@ -110,7 +104,7 @@ public class StableMethodHandleBenchmark {
@Benchmark
public int stableMh() throws Throwable {
return (int) STABLE_MH.orElseThrow().invokeExact(1);
return (int) STABLE_MH.get().invokeExact(1);
}
static MethodHandle identityHandle() {

View File

@ -36,10 +36,10 @@ import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import java.lang.LazyConstant;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring lazy constant performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -56,39 +56,24 @@ 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 static final LazyConstant<Integer> STABLE = init(VALUE);
private static final LazyConstant<Integer> STABLE2 = init(VALUE2);
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);
private final LazyConstant<Integer> stable = init(VALUE);
private final LazyConstant<Integer> stable2 = init(VALUE2);
@Benchmark
public int stable() {
return stable.orElseThrow() + stable2.orElseThrow();
}
@Benchmark
public int supplier() {
return supplier.get() + supplier2.get();
return stable.get() + stable2.get();
}
@Benchmark
public int staticStable() {
return STABLE.orElseThrow() + STABLE2.orElseThrow();
return STABLE.get() + STABLE2.get();
}
@Benchmark
public int staticSupplier() {
return SUPPLIER.get() + SUPPLIER2.get();
}
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
m.trySet(value);
return m;
private static LazyConstant<Integer> init(Integer value) {
return LazyConstant.of(() -> value);
}
}

View File

@ -25,12 +25,14 @@ package org.openjdk.bench.java.lang.stable;
import org.openjdk.jmh.annotations.*;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.lang.LazyConstant;
import java.util.function.Supplier;
/**
* Benchmark measuring StableValue performance
* Benchmark measuring lazy constant performance
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@ -47,10 +49,10 @@ 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 LazyConstant<Integer> STABLE = init(VALUE);
private static final LazyConstant<Integer> STABLE2 = init(VALUE2);
private static final Supplier<Integer> DCL = new Dcl<>(() -> VALUE);
private static final Supplier<Integer> DCL2 = new Dcl<>(() -> 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);
@ -58,10 +60,13 @@ public class StableValueBenchmark {
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 static final LazyConstant<Optional<Integer>> OPTIONAL_42 = LazyConstant.of(() -> Optional.of(42));
private static final LazyConstant<Optional<Integer>> OPTIONAL_42_2 = LazyConstant.of(() -> Optional.of(42));
private static final LazyConstant<Optional<Integer>> OPTIONAL_EMPTY = LazyConstant.of(Optional::empty);
private static final LazyConstant<Optional<Integer>> OPTIONAL_EMPTY2 = LazyConstant.of(Optional::empty);
private final LazyConstant<Integer> stable = init(VALUE);
private final LazyConstant<Integer> stable2 = init(VALUE2);
private final Supplier<Integer> dcl = new Dcl<>(() -> VALUE);
private final Supplier<Integer> dcl2 = new Dcl<>(() -> VALUE2);
private final AtomicReference<Integer> atomic = new AtomicReference<>(VALUE);
@ -69,13 +74,6 @@ public class StableValueBenchmark {
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();
@ -88,12 +86,7 @@ public class StableValueBenchmark {
@Benchmark
public int stable() {
return stable.orElseThrow() + stable2.orElseThrow();
}
@Benchmark
public int stableNull() {
return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2);
return stable.get() + stable2.get();
}
// Reference case
@ -109,7 +102,7 @@ public class StableValueBenchmark {
@Benchmark
public int staticDcl() {
return DCL.orElseThrow() + DCL2.orElseThrow();
return DCL.get() + DCL2.get();
}
@Benchmark
@ -117,6 +110,16 @@ public class StableValueBenchmark {
return HOLDER.get() + HOLDER2.get();
}
@Benchmark
public int staticOptional42() {
return OPTIONAL_42.get().orElseThrow() + OPTIONAL_42_2.get().orElseThrow();
}
@Benchmark
public boolean staticOptionalEmpty() {
return OPTIONAL_EMPTY.get().isEmpty() ^ OPTIONAL_EMPTY2.get().isEmpty();
}
@Benchmark
public int staticRecordHolder() {
return RECORD_HOLDER.get() + RECORD_HOLDER2.get();
@ -124,50 +127,46 @@ public class StableValueBenchmark {
@Benchmark
public int staticStable() {
return STABLE.orElseThrow() + STABLE2.orElseThrow();
return STABLE.get() + STABLE2.get();
}
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
m.trySet(value);
return m;
private static LazyConstant<Integer> init(Integer value) {
return LazyConstant.of(() -> value);
}
private static final class Holder {
private final StableValue<Integer> delegate = StableValue.of();
private final LazyConstant<Integer> delegate;
Holder(int value) {
delegate.setOrThrow(value);
delegate = LazyConstant.of(() -> value);
}
int get() {
return delegate.orElseThrow();
return delegate.get();
}
}
private record RecordHolder(StableValue<Integer> delegate) {
private record RecordHolder(LazyConstant<Integer> delegate) {
RecordHolder(int value) {
this(StableValue.of());
delegate.setOrThrow(value);
this(LazyConstant.of(() -> value));
}
int get() {
return delegate.orElseThrow();
return delegate.get();
}
}
// 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;
@ -177,15 +176,10 @@ public class StableValueBenchmark {
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;
}
}
synchronized (this) {
v = value;
if (v == null) {
value = v = supplier.get();
}
}
}

View File

@ -41,7 +41,6 @@ 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.*;
@ -75,8 +74,8 @@ public class VarHandleHolderBenchmark {
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 LazyConstant<VarHandle> SV_X = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("x"));
private static final LazyConstant<VarHandle> SV_Y = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("y"));
private static final Map<String, VarHandle> U_MAP = Map.of(
"x", VH_X,
@ -86,13 +85,11 @@ public class VarHandleHolderBenchmark {
"x", LAYOUT.varHandle(groupElement("x")),
"y", LAYOUT.varHandle(groupElement("y")));
private static final Map<String, VarHandle> S_MAP = StableValue.map(
private static final Map<String, VarHandle> S_MAP = Map.ofLazy(
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 Function<String, VarHandle> S_FUN = S_MAP::get;
private static final MemorySegment confined;
static {