From f94644999766e752f7d60ce52c14a7db79005035 Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Tue, 18 Nov 2025 12:20:23 +0000 Subject: [PATCH] 8366178: Implement JEP 526: Lazy Constants (Second Preview) 8371882: Improve documentation for JEP 526: Lazy Constants Reviewed-by: jvernee, mcimadamore --- .../share/classes/java/lang/LazyConstant.java | 307 +++++++ .../share/classes/java/lang/StableValue.java | 756 ------------------ .../classes/java/nio/charset/Charset.java | 8 +- .../share/classes/java/util/Currency.java | 4 +- .../java/util/ImmutableCollections.java | 384 +-------- .../classes/java/util/LazyCollections.java | 584 ++++++++++++++ .../share/classes/java/util/List.java | 80 +- .../share/classes/java/util/Locale.java | 8 +- .../classes/java/util/LocaleISOData.java | 16 +- .../share/classes/java/util/Map.java | 85 +- .../share/classes/java/util/Optional.java | 6 +- .../classes/java/util/ResourceBundle.java | 2 +- .../access/JavaUtilCollectionAccess.java | 6 - .../internal/foreign/CaptureStateUtil.java | 33 +- .../jdk/internal/io/JdkConsoleImpl.java | 40 +- .../jdk/internal/javac/PreviewFeature.java | 4 +- .../jdk/internal/lang/LazyConstantImpl.java | 173 ++++ .../lang/stable/StableEnumFunction.java | 118 --- .../internal/lang/stable/StableFunction.java | 83 -- .../lang/stable/StableIntFunction.java | 80 -- .../internal/lang/stable/StableSupplier.java | 69 -- .../jdk/internal/lang/stable/StableUtil.java | 105 --- .../internal/lang/stable/StableValueImpl.java | 218 ----- .../share/classes/sun/nio/ch/Net.java | 7 +- .../classes/sun/util/locale/BaseLocale.java | 4 +- .../BreakIteratorResourceBundle.java | 2 +- .../resources/OpenListResourceBundle.java | 4 +- .../DemoContainerInjectionTest.java | 153 ++++ .../lang/LazyConstant/DemoImperativeTest.java | 83 ++ .../java/lang/LazyConstant/DemoMapTest.java | 119 +++ .../LazyConstantSafePublicationTest.java} | 71 +- .../lang/LazyConstant/LazyConstantTest.java | 236 ++++++ .../LazyConstantTestUtil.java} | 58 +- .../LazyListTest.java} | 248 ++---- .../java/lang/LazyConstant/LazyMapTest.java | 573 +++++++++++++ .../TrustedFieldTypeTest.java | 63 +- .../lang/StableValue/StableFunctionTest.java | 250 ------ .../StableValue/StableIntFunctionTest.java | 109 --- .../java/lang/StableValue/StableMapTest.java | 388 --------- .../lang/StableValue/StableSupplierTest.java | 104 --- .../StableValue/StableValueFactoriesTest.java | 43 - .../lang/StableValue/StableValueTest.java | 389 --------- test/jdk/java/util/Collection/MOAT.java | 28 +- .../jdk/jshell/CompletionSuggestionTest.java | 2 +- ...enchmark.java => StableListBenchmark.java} | 12 +- ...rk.java => StableListSingleBenchmark.java} | 12 +- ...Benchmark.java => StableMapBenchmark.java} | 14 +- ...ark.java => StableMapSingleBenchmark.java} | 33 +- .../stable/StableMethodHandleBenchmark.java | 16 +- .../lang/stable/StableSupplierBenchmark.java | 35 +- .../lang/stable/StableValueBenchmark.java | 84 +- .../lang/stable/VarHandleHolderBenchmark.java | 11 +- 52 files changed, 2784 insertions(+), 3536 deletions(-) create mode 100644 src/java.base/share/classes/java/lang/LazyConstant.java delete mode 100644 src/java.base/share/classes/java/lang/StableValue.java create mode 100644 src/java.base/share/classes/java/util/LazyCollections.java create mode 100644 src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java delete mode 100644 src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoImperativeTest.java create mode 100644 test/jdk/java/lang/LazyConstant/DemoMapTest.java rename test/jdk/java/lang/{StableValue/StableValuesSafePublicationTest.java => LazyConstant/LazyConstantSafePublicationTest.java} (73%) create mode 100644 test/jdk/java/lang/LazyConstant/LazyConstantTest.java rename test/jdk/java/lang/{StableValue/StableTestUtil.java => LazyConstant/LazyConstantTestUtil.java} (64%) rename test/jdk/java/lang/{StableValue/StableListTest.java => LazyConstant/LazyListTest.java} (60%) create mode 100644 test/jdk/java/lang/LazyConstant/LazyMapTest.java rename test/jdk/java/lang/{StableValue => LazyConstant}/TrustedFieldTypeTest.java (61%) delete mode 100644 test/jdk/java/lang/StableValue/StableFunctionTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableIntFunctionTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableMapTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableSupplierTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableValueFactoriesTest.java delete mode 100644 test/jdk/java/lang/StableValue/StableValueTest.java rename test/micro/org/openjdk/bench/java/lang/stable/{StableIntFunctionBenchmark.java => StableListBenchmark.java} (86%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableIntFunctionSingleBenchmark.java => StableListSingleBenchmark.java} (84%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableFunctionBenchmark.java => StableMapBenchmark.java} (85%) rename test/micro/org/openjdk/bench/java/lang/stable/{StableFunctionSingleBenchmark.java => StableMapSingleBenchmark.java} (70%) diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java new file mode 100644 index 00000000000..34f3d754a10 --- /dev/null +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -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. + *

+ * A lazy constant is created using the factory method + * {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code })}. + * When created, the lazy constant is not initialized, meaning it has no contents. + * The lazy constant (of type {@code T}) can then be initialized + * (and its contents retrieved) by calling {@linkplain #get() get()}. The first time + * {@linkplain #get() get()} is called, the underlying computing function + * (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 never change + * and will be retrieved over and over again upon subsequent {@linkplain #get() get()} + * invocations. + *

+ * 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 = + * // @link substring="of" target="#of" : + * LazyConstant.of( () -> Logger.create(Component.class) ); + * + * public void process() { + * logger.get().info("Process started"); + * // ... + * } + * } + *} + *

+ * Initially, the lazy constant is not initialized. 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 initialized before it returns, barring + * any exceptions. + *

+ * 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. + * + *

Exception handling

+ * 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. + *

+ * 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. + * + *

Composing lazy constants

+ * 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 = LazyConstant.of( Foo::new ); + * private static final LazyConstant 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. + * + *

Thread Safety

+ * 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 + * the computing thread), 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. + *

+ * The invocation of the computing function and the resulting initialization of + * the constant {@linkplain java.util.concurrent##MemoryVisibility happens-before} + * 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. + *

+ * 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 thread’s status, nor does it throw an {@linkplain InterruptedException}. + *

+ * If the computing function blocks indefinitely, other threads operating on this + * lazy constant may block indefinitely; no timeouts or cancellations are provided. + * + *

Performance

+ * 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 constant folding. + * 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 trusted fields (i.e., {@code static final} fields, + * {@linkplain Record record} fields, or final instance fields in hidden classes) -- + * to a lazy constant. + * + *

Miscellaneous

+ * Except for {@linkplain Object#equals(Object) equals(obj)} and + * {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters + * must be non-null, 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. + *

+ * 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. + *

+ * The {@code LazyConstant} type is not {@link Serializable}. + *

+ * 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 The Java Language Specification. + * + * @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 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 + extends Supplier + permits LazyConstantImpl { + + /** + * {@return the contents of this lazy constant if initialized, otherwise, + * returns {@code other}} + *

+ * 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} + *

+ * After this method returns successfully, the constant is guaranteed to be + * initialized. + *

+ * 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} + *

+ * 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}} + *

+ * 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 + * not equal. + *

+ * 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} + *

+ * 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). + *

+ * 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}} + *

+ * The returned lazy constant strongly references the provided + * {@code computingFunction} at least until initialization completes successfully. + *

+ * 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 type of the constant + * + */ + @SuppressWarnings("unchecked") + static LazyConstant of(Supplier computingFunction) { + Objects.requireNonNull(computingFunction); + if (computingFunction instanceof LazyConstant lc) { + return (LazyConstant) lc; + } + return LazyConstantImpl.ofLazy(computingFunction); + } + +} diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java deleted file mode 100644 index 1815cb1a5b1..00000000000 --- a/src/java.base/share/classes/java/lang/StableValue.java +++ /dev/null @@ -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. - *

- * A {@code StableValue} is typically created using the factory method - * {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way, - * the stable value is unset, which means it holds no contents. - * Its contents, of type {@code T}, can be set 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()}. - *

- * 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 unset, which means it holds no contents. Later in the example, the - * state of the "{@code logger}" field is checked and if it is still unset, - * the contents is set: - * - * {@snippet lang = java: - * public class Component { - * - * // Creates a new unset stable value with no contents - * // @link substring="of" target="#of" : - * private final StableValue 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"); - * // ... - * } - * } - *} - *

- * 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. - *

- * 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 = StableValue.of(); - * - * private Logger getLogger() { - * return logger.orElseSet( () -> Logger.create(Component.class) ); - * } - * - * public void process() { - * getLogger().info("Process started"); - * // ... - * } - * } - *} - *

- * The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to - * retrieve its contents. If the stable value is unset, 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 set before it returns. - *

- * 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. - * - *

Stable Functions

- * Stable values provide the foundation for higher-level functional abstractions. A - * stable supplier 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 = - * // @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()}. - *

- * A stable int function 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 UNDERLYING_POWER_OF_TWO = - * v -> 1 << v; - * - * private static final IntFunction 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 partial function that only - * allows a subset {@code [0, 5]} of the underlying function's {@code UNDERLYING_POWER_OF_TWO} - * input range. - * - *

- * A stable function 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 KEYS = - * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator UNDERLYING_LOG2 = - * i -> 31 - Integer.numberOfLeadingZeros(i); - * - * private static final Function 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 partial function that only allows - * a subset {@code {1, 2, 4, 8, 16, 32}} of the underlying function's - * {@code UNDERLYING_LOG2} input range. - * - *

Stable Collections

- * Stable values can also be used as backing storage for - * {@linkplain Collection##unmodifiable unmodifiable collections}. A stable list - * 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 UNDERLYING_POWER_OF_TWO = - * v -> 1 << v; - * - * private static final List 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 - * - * } - *

- * Similarly, a stable map 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 KEYS = - * Set.of(1, 2, 4, 8, 16, 32); - * private static final UnaryOperator UNDERLYING_LOG2 = - * i -> 31 - Integer.numberOfLeadingZeros(i); - * - * private static final Map 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 - * - *} - * - *

Composing stable values

- * 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 = StableValue.supplier(Foo::new); - * private static final Supplier 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. - *

- * 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 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)}. - *

- * 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. - * - *

Thread Safety

- * 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. - *

- * The at-most-once write operation on a stable value that succeeds - * (e.g. {@linkplain #trySet(Object) trySet()}) - * {@linkplain java.util.concurrent##MemoryVisibility happens-before} - * any successful read operation (e.g. {@linkplain #orElseThrow()}). - * A successful write operation can be either: - *

    - *
  • a {@link #trySet(Object)} that returns {@code true},
  • - *
  • a {@link #setOrThrow(Object)} that does not throw, or
  • - *
  • an {@link #orElseSet(Supplier)} that successfully runs the supplier
  • - *
- * A successful read operation can be either: - *
    - *
  • a {@link #orElseThrow()} that does not throw,
  • - *
  • a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value
  • - *
  • an {@link #orElseSet(Supplier)} that does not {@code throw}, or
  • - *
  • an {@link #isSet()} that returns {@code true}
  • - *
- *

- * 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. - * - *

Performance

- * 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. - *

- * 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 non-null 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. - *

- * 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. - *

- * 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. - *

- * 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 - * array reference as a stable value but not its components. - * 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. - *

- * Stable values, functions, and collections are not {@link Serializable}. - * - * @param type of the contents - * - * @since 25 - */ -@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES) -public sealed interface StableValue - 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. - *

- * 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}} - *

- * 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. - *

- * 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); - * } - *

- * When this method returns successfully, the contents is always set. - *

- * 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 supplier); - - /** - * Sets the contents of this StableValue to the provided {@code contents}, or, if - * already set, throws {@code IllegalStateException}. - *

- * 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} - *

- * An unset stable value has no contents. - * - * @param type of the contents - */ - static StableValue of() { - return StableValueImpl.of(); - } - - /** - * {@return a new pre-set stable value with the provided {@code contents}} - * - * @param contents to set - * @param type of the contents - */ - static StableValue of(T contents) { - final StableValue stableValue = StableValue.of(); - stableValue.trySet(contents); - return stableValue; - } - - /** - * {@return a new stable supplier} - *

- * 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. - *

- * 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. - *

- * If the provided {@code underlying} supplier throws an exception, it is rethrown - * to the initial caller and no contents is recorded. - *

- * 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 the type of results supplied by the returned supplier - */ - static Supplier supplier(Supplier underlying) { - Objects.requireNonNull(underlying); - return StableSupplier.of(underlying); - } - - /** - * {@return a new stable {@linkplain IntFunction}} - *

- * 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. - *

- * 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. - *

- * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no contents is recorded. - *

- * 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 the type of results delivered by the returned IntFunction - * @throws IllegalArgumentException if the provided {@code size} is negative. - */ - static IntFunction intFunction(int size, - IntFunction underlying) { - StableUtil.assertSizeNonNegative(size); - Objects.requireNonNull(underlying); - return StableIntFunction.of(size, underlying); - } - - /** - * {@return a new stable {@linkplain Function}} - *

- * 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. - *

- * 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. - *

- * If invoking the provided {@code underlying} function throws an exception, it is - * rethrown to the initial caller and no contents is recorded. - *

- * 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 the type of the input to the returned Function - * @param the type of results delivered by the returned Function - * @throws NullPointerException if the provided set of {@code inputs} contains a - * {@code null} element. - */ - static Function function(Set inputs, - Function 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}} - *

- * 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}). - *

- * 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. - *

- * 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. - *

- * Any {@link List#subList(int, int) subList} or {@link List#reversed()} views - * of the returned list are also stable. - *

- * The returned list and its {@link List#subList(int, int) subList} or - * {@link List#reversed()} views implement the {@link RandomAccess} interface. - *

- * The returned list is unmodifiable and does not implement the - * {@linkplain Collection##optional-operation optional operations} in the - * {@linkplain List} interface. - *

- * 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 the type of elements in the returned list - * @throws IllegalArgumentException if the provided {@code size} is negative. - */ - static List list(int size, - IntFunction mapper) { - StableUtil.assertSizeNonNegative(size); - Objects.requireNonNull(mapper); - return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper); - } - - /** - * {@return a new stable map with the provided {@code keys}} - *

- * 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}). - *

- * 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. - *

- * 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. - *

- * Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are - * also stable. - *

- * The returned map is unmodifiable and does not implement the - * {@linkplain Collection##optional-operations optional operations} in the - * {@linkplain Map} interface. - *

- * 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 the type of keys maintained by the returned map - * @param the type of mapped values in the returned map - * @throws NullPointerException if the provided set of {@code inputs} contains a - * {@code null} element. - */ - static Map map(Set keys, - Function 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); - } - -} diff --git a/src/java.base/share/classes/java/nio/charset/Charset.java b/src/java.base/share/classes/java/nio/charset/Charset.java index 1eb3c9c2094..736ed4f12d5 100644 --- a/src/java.base/share/classes/java/nio/charset/Charset.java +++ b/src/java.base/share/classes/java/nio/charset/Charset.java @@ -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> EXTENDED_PROVIDERS = StableValue.supplier( + private static final LazyConstant> EXTENDED_PROVIDERS = LazyConstant.of( new Supplier<>() { public List get() { return extendedProviders0(); }}); private static List extendedProviders0() { @@ -617,7 +615,7 @@ public abstract class Charset return Collections.unmodifiableSortedMap(m); } - private static final Supplier defaultCharset = StableValue.supplier( + private static final LazyConstant 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> aliasSet = StableValue.supplier( + private final LazyConstant> aliasSet = LazyConstant.of( new Supplier<>() { public Set get() { return Set.of(aliases); }}); /** diff --git a/src/java.base/share/classes/java/util/Currency.java b/src/java.base/share/classes/java/util/Currency.java index febae04a77b..b254bae32a1 100644 --- a/src/java.base/share/classes/java/util/Currency.java +++ b/src/java.base/share/classes/java/util/Currency.java @@ -142,8 +142,8 @@ public final class Currency implements Serializable { // class data: instance map private static ConcurrentMap instances = new ConcurrentHashMap<>(7); - private static final Supplier> available = - StableValue.supplier(Currency::computeAllCurrencies); + private static final LazyConstant> available = + LazyConstant.of(Currency::computeAllCurrencies); // Class data: currency data obtained from currency.data file. // Purpose: diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java index 1dd7808da20..abc48ff5ed9 100644 --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -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 List listFromTrustedArrayNullsAllowed(Object[] array) { return ImmutableCollections.listFromTrustedArrayNullsAllowed(array); } - public List stableList(int size, IntFunction mapper) { - // A stable list is not Serializable, so we cannot return `List.of()` if `size == 0` - return new StableList<>(size, mapper); - } - public Map stableMap(Set keys, Function 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 extends AbstractImmutableList + static final class SubList extends AbstractImmutableList implements RandomAccess { @Stable @@ -462,8 +448,10 @@ class ImmutableCollections { @Stable final int size; - private SubList(AbstractImmutableList root, int offset, int size) { - assert root instanceof List12 || root instanceof ListN || root instanceof StableList; + SubList(AbstractImmutableList 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 { - StableValueImpl[] delegates(); - } - - @jdk.internal.ValueBased - static final class StableList - extends AbstractImmutableList - implements HasStableDelegates { - - @Stable - private final IntFunction mapper; - @Stable - final StableValueImpl[] delegates; - - StableList(int size, IntFunction 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 delegate; - try { - delegate = delegates[i]; - } catch (ArrayIndexOutOfBoundsException aioobe) { - throw new IndexOutOfBoundsException(i); - } - return delegate.orElseSet(new Supplier() { - @Override public E get() { return mapper.apply(i); }}); - } - - @Override - @SuppressWarnings("unchecked") - public 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[] 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 reversed() { - return new StableReverseOrderListView<>(this); - } - - @Override - public List 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[] delegates() { - return delegates; - } - - private static final class StableSubList extends SubList - implements HasStableDelegates { - - private StableSubList(AbstractImmutableList root, int offset, int size) { - super(root, offset, size); - } - - @Override - public List reversed() { - return new StableReverseOrderListView<>(this); - } - - @Override - public List 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[] delegates() { - @SuppressWarnings("unchecked") - final var rootDelegates = ((HasStableDelegates) root).delegates(); - return Arrays.copyOfRange(rootDelegates, offset, offset + size); - } - - static SubList fromStableList(StableList list, int fromIndex, int toIndex) { - return new StableSubList<>(list, fromIndex, toIndex - fromIndex); - } - - static SubList fromStableSubList(StableSubList parent, int fromIndex, int toIndex) { - return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex); - } - - } - - private static final class StableReverseOrderListView - extends ReverseOrderListView.Rand - implements HasStableDelegates { - - private StableReverseOrderListView(List base) { - super(base, false); - } - - // This method does not evaluate the elements - @Override - public String toString() { - return StableUtil.renderElements(this, "StableCollection", delegates()); - } - - @Override - public List 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[] delegates() { - @SuppressWarnings("unchecked") - final var baseDelegates = ((HasStableDelegates) 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 - extends AbstractImmutableMap { - - @Stable - private final Function mapper; - @Stable - private final Map> delegate; - - StableMap(Set keys, Function 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> 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 stable = delegate.get(key); - if (stable == null) { - return defaultValue; - } - @SuppressWarnings("unchecked") - final K k = (K) key; - return stable.orElseSet(new Supplier() { - @Override public V get() { return mapper.apply(k); }}); - } - - @jdk.internal.ValueBased - static final class StableMapEntrySet extends AbstractImmutableSet> { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMap outer; - - @Stable - private final Set>> delegateEntrySet; - - private StableMapEntrySet(StableMap outer) { - this.outer = outer; - this.delegateEntrySet = outer.delegate.entrySet(); - } - - @Override public Iterator> 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 StableMapEntrySet of(StableMap outer) { - return new StableMapEntrySet<>(outer); - } - - @jdk.internal.ValueBased - static final class LazyMapIterator implements Iterator> { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMapEntrySet outer; - - @Stable - private final Iterator>> delegateIterator; - - private LazyMapIterator(StableMapEntrySet outer) { - this.outer = outer; - this.delegateIterator = outer.delegateEntrySet.iterator(); - } - - @Override public boolean hasNext() { return delegateIterator.hasNext(); } - - @Override - public Entry next() { - final Map.Entry> inner = delegateIterator.next(); - final K k = inner.getKey(); - return new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }}); - } - - @Override - public void forEachRemaining(Consumer> action) { - final Consumer>> innerAction = - new Consumer<>() { - @Override - public void accept(Entry> inner) { - final K k = inner.getKey(); - action.accept(new StableEntry<>(k, inner.getValue(), new Supplier() { - @Override public V get() { return outer.outer.mapper.apply(k); }})); - } - }; - delegateIterator.forEachRemaining(innerAction); - } - - // For @ValueBased - private static LazyMapIterator of(StableMapEntrySet outer) { - return new LazyMapIterator<>(outer); - } - - } - } - - private record StableEntry(K getKey, // trick - StableValueImpl stableValue, - Supplier supplier) implements Map.Entry { - - @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 values() { - return StableMapValues.of(this); - } - - @jdk.internal.ValueBased - static final class StableMapValues extends AbstractImmutableCollection { - - // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable - private final StableMap outer; - - private StableMapValues(StableMap outer) { - this.outer = outer; - } - - @Override public Iterator 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[]> GENERATOR = new IntFunction[]>() { - @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 StableMapValues of(StableMap outer) { - return new StableMapValues<>(outer); - } - - } - - @Override - public String toString() { - return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true); - } - - } - } // ---------- Serialization Proxy ---------- diff --git a/src/java.base/share/classes/java/util/LazyCollections.java b/src/java.base/share/classes/java/util/LazyCollections.java new file mode 100644 index 00000000000..0bbdad87ac4 --- /dev/null +++ b/src/java.base/share/classes/java/util/LazyCollections.java @@ -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 + extends ImmutableCollections.AbstractImmutableList { + + @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> functionHolder; + @Stable + private final Mutexes mutexes; + + private LazyList(int size, IntFunction 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[] 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[] 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, V> + extends AbstractLazyMap { + + @Stable + private final Class 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 set, + Class enumType, + int min, + int backingSize, + IntPredicate member, + Function 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 + extends AbstractLazyMap { + + // 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 indexMapper; + + public LazyMap(Set keys, Function computingFunction) { + @SuppressWarnings("unchecked") + final Entry[] entries = (Entry[]) 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 + extends ImmutableCollections.AbstractImmutableMap { + + // This field shadows AbstractMap.keySet which is not @Stable. + @Stable + Set 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> functionHolder; + @Stable + private final Set> entrySet; + + private AbstractLazyMap(Set keySet, + int size, + int backingSize, + Function 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> entrySet() { return entrySet; } + @Override public Set 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 extends ImmutableCollections.AbstractImmutableSet> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + + private LazyMapEntrySet(AbstractLazyMap map) { + this.map = map; + super(); + } + + @Override public Iterator> iterator() { return LazyMapIterator.of(map); } + @Override public int size() { return map.size(); } + @Override public int hashCode() { return map.hashCode(); } + + // For @ValueBased + private static LazyMapEntrySet of(AbstractLazyMap outer) { + return new LazyMapEntrySet<>(outer); + } + + @jdk.internal.ValueBased + static final class LazyMapIterator implements Iterator> { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + @Stable + private final Iterator keyIterator; + + private LazyMapIterator(AbstractLazyMap map) { + this.map = map; + this.keyIterator = map.keySet.iterator(); + super(); + } + + @Override public boolean hasNext() { return keyIterator.hasNext(); } + + @Override + public Entry next() { + final K k = keyIterator.next(); + return new LazyEntry<>(k, map, map.functionHolder); + } + + @Override + public void forEachRemaining(Consumer> action) { + final Consumer innerAction = + new Consumer<>() { + @Override + public void accept(K key) { + action.accept(new LazyEntry<>(key, map, map.functionHolder)); + } + }; + keyIterator.forEachRemaining(innerAction); + } + + // For @ValueBased + private static LazyMapIterator of(AbstractLazyMap map) { + return new LazyMapIterator<>(map); + } + + } + } + + private record LazyEntry(K getKey, // trick + AbstractLazyMap map, + FunctionHolder> functionHolder) implements Entry { + + @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 values() { + return LazyMapValues.of(this); + } + + @jdk.internal.ValueBased + static final class LazyMapValues extends ImmutableCollections.AbstractImmutableCollection { + + // Use a separate field for the outer class in order to facilitate + // a @Stable annotation. + @Stable + private final AbstractLazyMap map; + + private LazyMapValues(AbstractLazyMap map) { + this.map = map; + super(); + } + + @Override public Iterator 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 LazyMapValues of(AbstractLazyMap 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[] newGenericArray(int length) { + return (E[]) new Object[length]; + } + + public static List ofLazyList(int size, + IntFunction computingFunction) { + return new LazyList<>(size, computingFunction); + } + + public static Map ofLazyMap(Set keys, + Function computingFunction) { + return new LazyMap<>(keys, computingFunction); + } + + @SuppressWarnings("unchecked") + public static , V> + Map ofLazyMapWithEnumKeys(Set keys, + Function computingFunction) { + // The input set is not empty + final Class 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) new LazyEnumMap<>((Set) keys, enumType, min, backingSize, member, (Function) computingFunction); + } + + @SuppressWarnings("unchecked") + static 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) 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 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 the underlying function type + */ + @AOTSafeClassInitializer + static final class FunctionHolder { + + 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; + } + } + } + +} diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 32430298363..6ab80b83ef8 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -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. * *

Unmodifiable Lists

- *

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} + *

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: * *

    @@ -100,7 +105,7 @@ import java.util.function.UnaryOperator; * this may cause the List's contents to appear to change. *
  • They disallow {@code null} elements. Attempts to create them with * {@code null} elements result in {@code NullPointerException}. - *
  • They are serializable if all elements are serializable. + *
  • Unless otherwise specified, they are serializable if all elements are serializable. *
  • 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. *
  • The lists and their {@link #subList(int, int) subList} views implement the @@ -1190,4 +1195,71 @@ public interface List extends SequencedCollection { static List copyOf(Collection coll) { return ImmutableCollections.listCopy(coll); } + + /** + * {@return a new lazily computed list of the provided {@code size}} + *

    + * 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}). + *

    + * 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 + *

    + * If invoking the provided computing function throws an exception, it is rethrown + * to the initial caller and no value for the element is recorded. + *

    + * 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. + *

    + * The elements of any {@link List#subList(int, int) subList()} or + * {@link List#reversed()} views of the returned list are also lazily computed. + *

    + * The returned list and its {@link List#subList(int, int) subList()} or + * {@link List#reversed()} views implement the {@link RandomAccess} interface. + *

    + * If the provided computing function recursively calls itself or the returned + * lazy list for the same index, an {@linkplain IllegalStateException} + * will be thrown. + *

    + * 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. + *

    + * The returned lazy list strongly references its computing + * function used to compute elements at least so long as there are uninitialized + * elements. + *

    + * The returned List is not {@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 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 List ofLazy(int size, + IntFunction 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); + } + } diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 97278cdbafa..54863d58782 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -992,8 +992,8 @@ public final class Locale implements Cloneable, Serializable { } } - private static final Supplier> LOCALE_CACHE = - StableValue.supplier(new Supplier<>() { + private static final LazyConstant> LOCALE_CACHE = + LazyConstant.of(new Supplier<>() { @Override public ReferencedKeyMap 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 languageTag = - StableValue.supplier(new Supplier<>() { + private final transient LazyConstant languageTag = + LazyConstant.of(new Supplier<>() { @Override public String get() { return computeLanguageTag(); diff --git a/src/java.base/share/classes/java/util/LocaleISOData.java b/src/java.base/share/classes/java/util/LocaleISOData.java index 29e0b28be01..c99e88bd2bd 100644 --- a/src/java.base/share/classes/java/util/LocaleISOData.java +++ b/src/java.base/share/classes/java/util/LocaleISOData.java @@ -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 ISO_639 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant ISO_639 = + LazyConstant.of(new Supplier<>() { @Override public String[] get() { return getISO2Table(isoLanguageTable); } }); - static final Supplier ISO_3166_1_ALPHA2 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant ISO_3166_1_ALPHA2 = + LazyConstant.of(new Supplier<>() { @Override public String[] get() { return getISO2Table(isoCountryTable); } }); - static final Supplier> ISO_3166_1_ALPHA3 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant> ISO_3166_1_ALPHA3 = + LazyConstant.of(new Supplier<>() { @Override public Set get() { return computeISO3166_1Alpha3Countries(); } }); - static final Supplier> ISO_3166_3 = - StableValue.supplier(new Supplier<>() { + static final LazyConstant> ISO_3166_3 = + LazyConstant.of(new Supplier<>() { @Override public Set get() { return Set.of(ISO3166_3); diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index fbb41be5073..abee819069f 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -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; * *

    Unmodifiable Maps

    *

    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. *

  • They disallow {@code null} keys and values. Attempts to create them with * {@code null} keys or values result in {@code NullPointerException}. - *
  • They are serializable if all keys and values are serializable. + *
  • Unless otherwise specified, they are serializable if all keys and values + * are serializable. *
  • They reject duplicate keys at creation time. Duplicate keys * passed to a static factory method result in {@code IllegalArgumentException}. *
  • The iteration order of mappings is unspecified and is subject to change. @@ -1746,4 +1750,79 @@ public interface Map { return (Map)Map.ofEntries(map.entrySet().toArray(new Entry[0])); } } + + /** + * {@return a new lazily computed map with the provided {@code keys}} + *

    + * 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}). + *

    + * 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. + *

    + * 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. + *

    + * 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. + *

    + * The values of any {@link Map#values()} or {@link Map#entrySet()} views of + * the returned map are also lazily computed. + *

    + * If the provided computing function recursively calls itself or + * the returned lazy map for the same key, an {@linkplain IllegalStateException} + * will be thrown. + *

    + * 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. + *

    + * The returned lazy map strongly references its underlying + * computing function used to compute values at least so long as there are + * uncomputed values. + *

    + * The returned Map is not {@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 the type of keys maintained by the returned map + * @param 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 Map ofLazy(Set keys, + Function computingFunction) { + // Protect against TOC-TOU attacks. + // Also, implicit null check of `keys` and all its elements + final Set 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) 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); + } + } + } diff --git a/src/java.base/share/classes/java/util/Optional.java b/src/java.base/share/classes/java/util/Optional.java index e19dde6383e..3e577bd379c 100644 --- a/src/java.base/share/classes/java/util/Optional.java +++ b/src/java.base/share/classes/java/util/Optional.java @@ -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 { /** * If non-null, the value; if null, indicates no value is present */ + @Stable private final T value; /** diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index b375a8ba941..db19eda6399 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -488,7 +488,7 @@ public abstract class ResourceBundle { /** * A Set of the keys contained only in this ResourceBundle. */ - private final Supplier> keySet = StableValue.supplier( + private final LazyConstant> keySet = LazyConstant.of( new Supplier<>() { public Set get() { return keySet0(); }}); private Set keySet0() { diff --git a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java index cac8785b158..5e7647d6106 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilCollectionAccess.java @@ -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 { List listFromTrustedArray(Object[] array); List listFromTrustedArrayNullsAllowed(Object[] array); - List stableList(int size, IntFunction mapper); - Map stableMap(Set keys, Function mapper); } diff --git a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java index 2f3ba7d9e05..b3a19340fb5 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java +++ b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java @@ -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 SEGMENT_EXTRACTION_HANDLE_CACHE; + private static final Map SEGMENT_EXTRACTION_HANDLE_CACHE; static { final Set 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 HANDLES_CACHE = - StableValue.intFunction(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE); + private static final List HANDLES_CACHE = + List.ofLazy(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); diff --git a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java index 820dfe90073..a6f4174324e 100644 --- a/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java +++ b/src/java.base/share/classes/jdk/internal/io/JdkConsoleImpl.java @@ -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> INSTANCE = StableValue.of(); - public static Optional passwordConsole() { - return INSTANCE.orElseSet(() -> { - // If there's already a proper console, throw an exception - if (System.console() != null) { - throw new IllegalStateException("Can’t create a dedicated password " + - "console since a real console already exists"); - } + private static final LazyConstant> PASSWORD_CONSOLE = LazyConstant.of( + new Supplier>() { + @Override + public Optional get() { + if (System.console() != null) { + throw new IllegalStateException("Can’t 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 passwordConsole() { + return PASSWORD_CONSOLE.get(); } // Dedicated entry for sun.security.util.Password when stdout is redirected. diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 1316b8b946f..5942cefa2a1 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -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, diff --git a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java new file mode 100644 index 00000000000..59d0174c4c9 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java @@ -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 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 implements LazyConstant { + + // 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 computingFunction; + + private LazyConstantImpl(Supplier 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 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 LazyConstantImpl ofLazy(Supplier computingFunction) { + return new LazyConstantImpl<>(computingFunction); + } + +} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java deleted file mode 100644 index d6893438be5..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableEnumFunction.java +++ /dev/null @@ -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 the type of the input to the function - * @param the type of the result of the function - */ -public record StableEnumFunction, R>(Class enumType, - int firstOrdinal, - IntPredicate member, - @Stable StableValueImpl[] delegates, - Function original) implements Function { - @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() { - @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>> 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 , R> Function of(Set inputs, - Function original) { - // The input set is not empty - final Class 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) new StableEnumFunction(enumType, min, member, StableUtil.array(size), (Function) original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java deleted file mode 100644 index e36b4e9b25a..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableFunction.java +++ /dev/null @@ -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 the type of the input to the function - * @param the type of the result of the function - */ -public record StableFunction(Map> values, - Function original) implements Function { - - @ForceInline - @Override - public R apply(T value) { - final StableValueImpl stable = values.get(value); - if (stable == null) { - throw new IllegalArgumentException("Input not allowed: " + value); - } - return stable.orElseSet(new Supplier() { - @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 StableFunction of(Set inputs, - Function original) { - return new StableFunction<>(StableUtil.map(inputs), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java deleted file mode 100644 index a921a4de87b..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableIntFunction.java +++ /dev/null @@ -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. - *

    - * 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 the return type - */ -public record StableIntFunction(@Stable StableValueImpl[] delegates, - IntFunction original) implements IntFunction { - - @ForceInline - @Override - public R apply(int index) { - final StableValueImpl delegate; - try { - delegate = delegates[index]; - } catch (ArrayIndexOutOfBoundsException ioob) { - throw new IllegalArgumentException("Input not allowed: " + index, ioob); - } - return delegate.orElseSet(new Supplier() { - @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 StableIntFunction of(int size, IntFunction original) { - return new StableIntFunction<>(StableUtil.array(size), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java deleted file mode 100644 index 631a41c5710..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableSupplier.java +++ /dev/null @@ -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. - *

    - * @implNote This implementation can be used early in the boot sequence as it does not - * rely on reflection, MethodHandles, Streams etc. - * - * @param the return type - */ -public record StableSupplier(StableValueImpl delegate, - Supplier original) implements Supplier { - - @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 StableSupplier of(Supplier original) { - return new StableSupplier<>(StableValueImpl.of(), original); - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java deleted file mode 100644 index 74104ddbb49..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableUtil.java +++ /dev/null @@ -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 String renderElements(Object self, - String selfName, - StableValueImpl[] delegates) { - return renderElements(self, selfName, delegates, 0, delegates.length); - } - - public static 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 String renderMappings(Object self, - String selfName, - Iterable>> 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 StableValueImpl[] array(int size) { - assertSizeNonNegative(size); - @SuppressWarnings("unchecked") - final var stableValues = (StableValueImpl[]) new StableValueImpl[size]; - for (int i = 0; i < size; i++) { - stableValues[i] = StableValueImpl.of(); - } - return stableValues; - } - - public static Map> map(Set keys) { - Objects.requireNonNull(keys); - @SuppressWarnings("unchecked") - final var entries = (Map.Entry>[]) 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); - } - } - -} diff --git a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java b/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java deleted file mode 100644 index 1413fd7446e..00000000000 --- a/src/java.base/share/classes/jdk/internal/lang/stable/StableValueImpl.java +++ /dev/null @@ -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 type of the contents - */ -public final class StableValueImpl implements StableValue { - - 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 supplier) { - Objects.requireNonNull(supplier); - final Object t = wrappedContentsAcquire(); - return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t); - } - - @DontInline - private T orElseSetSlowPath(Supplier 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. - *

    - * 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 unwrap(Object t) { - return t != NULL_SENTINEL ? (T) t : null; - } - - // Factory - - public static StableValueImpl of() { - return new StableValueImpl<>(); - } - -} diff --git a/src/java.base/share/classes/sun/nio/ch/Net.java b/src/java.base/share/classes/sun/nio/ch/Net.java index 9ec7975a35c..4564579d054 100644 --- a/src/java.base/share/classes/sun/nio/ch/Net.java +++ b/src/java.base/share/classes/sun/nio/ch/Net.java @@ -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 SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of(); + private static final LazyConstant SHUTDOWN_WRITE_BEFORE_CLOSE = LazyConstant.of(new Supplier() { + @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(); } /** diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index 529ca1b0c13..91efc61d1bf 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -92,8 +92,8 @@ public final class BaseLocale { } // Interned BaseLocale cache - private static final Supplier> CACHE = - StableValue.supplier(new Supplier<>() { + private static final LazyConstant> CACHE = + LazyConstant.of(new Supplier<>() { @Override public ReferencedKeySet get() { return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier()); diff --git a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java index d7c575962f6..49e882c5ca6 100644 --- a/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/BreakIteratorResourceBundle.java @@ -50,7 +50,7 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle { // those keys must be added to NON_DATA_KEYS. private static final Set NON_DATA_KEYS = Set.of("BreakIteratorClasses"); - private final Supplier> keys = StableValue.supplier( + private final LazyConstant> keys = LazyConstant.of( new Supplier<>() { public Set get() { return keys0(); }}); private Set keys0() { diff --git a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java index 0c16f508018..9d0c1c015ce 100644 --- a/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java +++ b/src/java.base/share/classes/sun/util/resources/OpenListResourceBundle.java @@ -119,7 +119,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle { return new HashSet<>(); } - private final Supplier> lookup = StableValue.supplier( + private final LazyConstant> lookup = LazyConstant.of( new Supplier<>() { public Map get() { return lookup0(); }}); private Map lookup0() { @@ -134,7 +134,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle { return temp; } - private final Supplier> keyset = StableValue.supplier( + private final LazyConstant> keyset = LazyConstant.of( new Supplier<>() { public Set get() { return keyset0(); }}); private Set keyset0() { diff --git a/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java new file mode 100644 index 00000000000..df51a562210 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java @@ -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(Class type, Supplier 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 get(Class type); + } + + interface SettableContainer extends Container { + void set(Class type, T implementation); + } + + record ComputedContainer(Map, ?> components) implements Container { + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static Container of(Set> components, Function, ?> mapper) { + return new ComputedContainer(Map.ofLazy(components, mapper)); + } + + } + + record SettableScratchContainer(Map, Object> scratch, Map, ?> components) implements SettableContainer { + + @Override + public void set(Class type, T implementation) { + if (scratch.putIfAbsent(type, type.cast(implementation)) != null) { + throw new IllegalStateException("Can only set once for " + type); + } + } + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static SettableContainer of(Set> components) { + Map, Object> scratch = new ConcurrentHashMap<>(); + return new SettableScratchContainer(scratch, Map.ofLazy(components, scratch::get)); + } + + } + + record ProviderContainer(Map, ?> components) implements Container { + + @Override + public T get(Class type) { + return type.cast(components.get(type)); + } + + static Container of(Map, Supplier> components) { + var map = Map.ofLazy(components.keySet(), t -> components.get(t).get()); + return new ProviderContainer(map); + } + + } + + static Container providerTypedContainer(Set> providers) { + return ProviderContainer.of(providers.stream() + .collect(Collectors.toMap(Provider::type, Provider::supplier))); + } + +} diff --git a/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java b/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java new file mode 100644 index 00000000000..bc1208e67f8 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java @@ -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 orElse(T other); + boolean isSet(); + boolean trySet(T t); + T get(); + + static ImperativeStableValue of() { + var scratch = new AtomicReference(); + return new Impl<>(scratch, LazyConstant.of(scratch::get)); + } + + } + + + private record Impl(AtomicReference scratch, + LazyConstant underlying) implements ImperativeStableValue { + + @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.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)); + } + +} diff --git a/test/jdk/java/lang/LazyConstant/DemoMapTest.java b/test/jdk/java/lang/LazyConstant/DemoMapTest.java new file mode 100644 index 00000000000..25e37494cb9 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/DemoMapTest.java @@ -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 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 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 = """ + + + + + Internal Server Error (500) + + + + + There was a general problem with the server, code 500 + + + """; + + @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); + } + +} diff --git a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java similarity index 73% rename from test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java rename to test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java index 151d6f9c805..4d88169b155 100644 --- a/test/jdk/java/lang/StableValue/StableValuesSafePublicationTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java @@ -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[] STABLES = stables(); - - static StableValue[] stables() { - @SuppressWarnings("unchecked") - StableValue[] stables = (StableValue[]) 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[] constants; final int[] observations = new int[SIZE]; - final StableValue[] stables = STABLES; int i = 0; + public Consumer(LazyConstant[] constants) { + this.constants = constants; + } + @Override public void run() { for (; i < SIZE; i++) { - StableValue s = stables[i]; + LazyConstant 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[] stables = STABLES; + final LazyConstant[] constants; + + public Producer(LazyConstant[] constants) { + this.constants = constants; + } @Override public void run() { - StableValue s; + LazyConstant 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[] constants = constants(); List consumers = IntStream.range(0, THREADS) - .mapToObj(_ -> new Consumer()) + .mapToObj(_ -> new Consumer(constants)) .toList(); List 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 consumers, Thread... threads) { + static void join(final LazyConstant[] constants, List 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[] constants() { + @SuppressWarnings("unchecked") + LazyConstant[] constants = (LazyConstant[]) new LazyConstant[SIZE]; + for (int i = 0; i < SIZE; i++) { + constants[i] = LazyConstant.of(Holder::new); + } + return constants; + } + } diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java new file mode 100644 index 00000000000..af194cbcc01 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java @@ -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 SUPPLIER = () -> VALUE; + + @Test + void factoryInvariants() { + assertThrows(NullPointerException.class, () -> LazyConstant.of(null)); + } + + @ParameterizedTest + @MethodSource("factories") + void basic(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier 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, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier 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 constant) { + assertNull(constant.orElse(null)); + constant.get(); + assertEquals(VALUE, constant.orElse(null)); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void get(LazyConstant constant) { + assertEquals(VALUE, constant.get()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void isInitialized(LazyConstant constant) { + assertFalse(constant.isInitialized()); + constant.get(); + assertTrue(constant.isInitialized()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testHashCode(LazyConstant constant) { + assertEquals(System.identityHashCode(constant), constant.hashCode()); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testEquals(LazyConstant c0) { + assertNotEquals(null, c0); + LazyConstant different = LazyConstant.of(SUPPLIER); + assertNotEquals(different, c0); + assertNotEquals(c0, different); + assertNotEquals("a", c0); + } + + @ParameterizedTest + @MethodSource("lazyConstants") + void testLazyConstantAsComputingFunction(LazyConstant constant) { + LazyConstant c1 = LazyConstant.of(constant); + assertSame(constant, c1); + } + + @Test + void toStringTest() { + Supplier supplier = () -> "str"; + LazyConstant 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 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> ref = new AtomicReference<>(); + 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> ref = new AtomicReference<>(); + LazyConstant constant = LazyConstant.of(() -> ref.get().get()); + LazyConstant constant1 = LazyConstant.of(constant); + ref.set(constant1); + assertThrows(IllegalStateException.class, constant::get); + } + + @ParameterizedTest + @MethodSource("factories") + void underlying(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier 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, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier 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> lazyConstants() { + return factories() + .map(f -> f.apply(() -> VALUE)); + } + + private static Stream, LazyConstant>> factories() { + return Stream.of( + supplier("ComputedConstant.of()", LazyConstant::of) + ); + } + + private static Function, LazyConstant> supplier(String name, + Function, LazyConstant> underlying) { + return new Function, LazyConstant>() { + @Override + public LazyConstant apply(Supplier supplier) { + return underlying.apply(supplier); + } + + @Override + public String toString() { + return name; + } + }; + } + + record Lazy(LazyConstant underlying) implements Supplier { + @Override + public T get() { return underlying.get(); } + + static Lazy of(Supplier computingFunction) { + return new Lazy<>(LazyConstant.of(computingFunction)); + } + } + +} diff --git a/test/jdk/java/lang/StableValue/StableTestUtil.java b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java similarity index 64% rename from test/jdk/java/lang/StableValue/StableTestUtil.java rename to test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java index f71915c28ee..502f46b726d 100644 --- a/test/jdk/java/lang/StableValue/StableTestUtil.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java @@ -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 extends AbstractCounting> @@ -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); + } + } + } diff --git a/test/jdk/java/lang/StableValue/StableListTest.java b/test/jdk/java/lang/LazyConstant/LazyListTest.java similarity index 60% rename from test/jdk/java/lang/StableValue/StableListTest.java rename to test/jdk/java/lang/LazyConstant/LazyListTest.java index 2abe305b0e7..046f9107b17 100644 --- a/test/jdk/java/lang/StableValue/StableListTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyListTest.java @@ -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 cif = new StableTestUtil.CountingIntFunction<>(IDENTITY); - var lazy = StableValue.list(SIZE, cif); + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(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 cif = new StableTestUtil.CountingIntFunction<>(_ -> { + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(_ -> { 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 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> 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 void assertThrowsForOperation(Class 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 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[] array = StableUtil.array(SIZE); - assertEquals(SIZE, array.length); - // Check, every StableValue is distinct - Map, Boolean> idMap = new IdentityHashMap<>(); - for (var e: array) { - idMap.put(e, true); + void functionHolder() { + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction<>(IDENTITY); + List 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 newList() { - return StableValue.list(SIZE, IDENTITY); + static List newLazyList() { + return List.ofLazy(SIZE, IDENTITY); } - static List newEmptyList() { - return StableValue.list(ZERO, IDENTITY); + static List newEmptyLazyList() { + return List.ofLazy(ZERO, IDENTITY); } static List newRegularList() { return IntStream.range(0, SIZE).boxed().toList(); } - - static String asString(String first, List list) { - return "[" + first + ", " + Stream.generate(() -> ".unset") - .limit(list.size() - 1) - .collect(Collectors.joining(", ")) + "]"; - } - - static String className(Object o) { - return o.getClass().getName(); - } } diff --git a/test/jdk/java/lang/LazyConstant/LazyMapTest.java b/test/jdk/java/lang/LazyConstant/LazyMapTest.java new file mode 100644 index 00000000000..766313c9173 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazyMapTest.java @@ -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 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 set) { + assertThrows(NullPointerException.class, () -> Map.ofLazy(set, null), set.getClass().getSimpleName()); + assertThrows(NullPointerException.class, () -> Map.ofLazy(null, MAPPER)); + Set setWithNull = new HashSet<>(); + setWithNull.add(KEY); + setWithNull.add(null); + assertThrows(NullPointerException.class, () -> Map.ofLazy(setWithNull, MAPPER)); + } + + @ParameterizedTest + @MethodSource("emptySets") + void empty(Set 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 set) { + assertEquals(newRegularMap(set).size(), newLazyMap(set).size()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void get(Set set) { + LazyConstantTestUtil.CountingFunction 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 set) { + LazyConstantTestUtil.CountingFunction 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 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 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 set) { + var lazy = newLazyMap(set); + var ref = newRegularMap(set); + Set> expected = ref.entrySet(); + Set> actual = new HashSet<>(); + lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v))); + assertEquals(expected, actual); + } + + @ParameterizedTest + @MethodSource("emptySets") + void toStringTestEmpty(Set set) { + var lazy = newLazyMap(set); + assertEquals("{}", lazy.toString()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void toStringTest(Set 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 set) { + var lazy = newLazyMap(set); + var regular = newRegularMap(set); + assertEquals(regular.hashCode(), lazy.hashCode()); + } + + @ParameterizedTest + @MethodSource("allSets") + void equality(Set set) { + var lazy = newLazyMap(set); + var regular = newRegularMap(set); + assertEquals(regular, lazy); + assertEquals(lazy, regular); + assertNotEquals("A", lazy); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void circular(Set set) { + final AtomicReference> ref = new AtomicReference<>(); + 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 set) { + final AtomicReference> ref = new AtomicReference<>(); + @SuppressWarnings("unchecked") + Map> lazy = Map.ofLazy(set, k -> (Map) 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 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 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 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 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 set) { + Set 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 set) { + Set 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 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 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 void assertThrowsForOperation(Class expectedType, Operation operation) { + for (Set set : allSets().toList()) { + var lazy = newLazyMap(set); + assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation); + } + } + + // Implementing interfaces + + @ParameterizedTest + @MethodSource("allSets") + void serializable(Set 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 set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map 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 set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map lazy = Map.ofLazy(set, cif); + + Object holder = LazyConstantTestUtil.functionHolder(lazy); + + int i = 0; + for (Map.Entry 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 set) { + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + Map 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 enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt); + assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName()); + Map emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt); + assertFalse(emptyMap.getClass().getName().contains("Enum"), emptyMap.getClass().getName()); + Map 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 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 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> consumer) implements Consumer> { + @java.lang.Override + public void accept(Map map) { consumer.accept(map); } + @java.lang.Override + public String toString() { return name; } + } + + static Stream nullAverseOperations() { + return Stream.of( + new Operation("forEach", m -> m.forEach(null)) + ); + } + + static Stream 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 newLazyMap(Set set) { + return Map.ofLazy(set, MAPPER); + } + static Map newRegularMap(Set set) { + return set.stream() + .collect(Collectors.toMap(Function.identity(), MAPPER)); + } + + private static Stream> 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> emptySets() { + return Stream.of( + Set.of(), + linkedHashSet(), + treeSet(), + EnumSet.noneOf(Value.class) + ); + } + + private static Stream> allSets() { + return Stream.concat( + nonEmptySets(), + emptySets() + ); + } + + static Set treeSet(Value... values) { + return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); + } + + static Set linkedHashSet(Value... values) { + return populate(new LinkedHashSet<>(), values); + } + + static Set populate(Set 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); + } + +} diff --git a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java b/test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java similarity index 61% rename from test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java rename to test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java index 205e5ed3a77..9e63a9b275c 100644 --- a/test/jdk/java/lang/StableValue/TrustedFieldTypeTest.java +++ b/test/jdk/java/lang/LazyConstant/TrustedFieldTypeTest.java @@ -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 SUPPLIER = () -> VALUE; + @Test void varHandle() throws NoSuchFieldException, IllegalAccessException { MethodHandles.Lookup lookup = MethodHandles.lookup(); - StableValue originalValue = StableValue.of(); + LazyConstant originalValue = LazyConstant.of(SUPPLIER); @SuppressWarnings("unchecked") - StableValue[] originalArrayValue = new StableValue[10]; + LazyConstant[] originalArrayValue = new LazyConstant[10]; final class Holder { - private final StableValue value = originalValue; + private final LazyConstant value = originalValue; } final class ArrayHolder { - private final StableValue[] array = originalArrayValue; + private final LazyConstant[] 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 stableValue = StableValue.of(); - stableValue.trySet(42); + void updateComputedConstantContentVia_j_i_m_Unsafe() { + LazyConstant 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 stableValue = StableValue.of(); - stableValue.trySet(42); + LazyConstant 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)); } } diff --git a/test/jdk/java/lang/StableValue/StableFunctionTest.java b/test/jdk/java/lang/StableValue/StableFunctionTest.java deleted file mode 100644 index 07f51470a0b..00000000000 --- a/test/jdk/java/lang/StableValue/StableFunctionTest.java +++ /dev/null @@ -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 MAPPER = Value::asInt; - - @ParameterizedTest - @MethodSource("allSets") - void factoryInvariants(Set inputs) { - assertThrows(NullPointerException.class, () -> StableValue.function(null, MAPPER)); - assertThrows(NullPointerException.class, () -> StableValue.function(inputs, null)); - } - - @ParameterizedTest - @MethodSource("nonEmptySets") - void basic(Set inputs) { - basic(inputs, MAPPER); - toStringTest(inputs, MAPPER); - basic(inputs, _ -> null); - toStringTest(inputs, _ -> null); - } - - void basic(Set inputs, Function mapper) { - StableTestUtil.CountingFunction 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 inputs, Function 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 inputs) { - Function f0 = StableValue.function(inputs, Value::asInt); - Function 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 inputs) { - StableTestUtil.CountingFunction 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 inputs) { - final AtomicReference> ref = new AtomicReference<>(); - 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 inputs) { - Function mapper = Value::asInt; - Function f0 = StableValue.function(inputs, mapper); - Function f1 = StableValue.function(inputs, mapper); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @ParameterizedTest - @MethodSource("allSets") - void hashCodeStable(Set inputs) { - Function 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 inputs = new HashSet<>(); - inputs.add(Value.FORTY_TWO); - inputs.add(null); - assertThrows(NullPointerException.class, () -> StableValue.function(inputs, MAPPER)); - } - - @Test - void usesOptimizedVersion() { - Function enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt); - assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName()); - Function 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 enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt); - assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden)); - } - - private static Stream> 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> emptySets() { - return Stream.of( - Set.of(), - linkedHashSet(), - treeSet(), - EnumSet.noneOf(Value.class) - ); - } - - private static Stream> allSets() { - return Stream.concat( - nonEmptySets(), - emptySets() - ); - } - - static Set treeSet(Value... values) { - return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); - } - - static Set linkedHashSet(Value... values) { - return populate(new LinkedHashSet<>(), values); - } - - static Set populate(Set set, Value... values) { - set.addAll(Arrays.asList(values)); - return set; - } - -} diff --git a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java b/test/jdk/java/lang/StableValue/StableIntFunctionTest.java deleted file mode 100644 index 7397a688ee6..00000000000 --- a/test/jdk/java/lang/StableValue/StableIntFunctionTest.java +++ /dev/null @@ -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 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 mapper) { - StableTestUtil.CountingIntFunction 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 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> ref = new AtomicReference<>(); - 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 f0 = StableValue.intFunction(8, MAPPER); - IntFunction f1 = StableValue.intFunction(8, MAPPER); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @Test - void hashCodeStable() { - IntFunction f0 = StableValue.intFunction(8, MAPPER); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - f0.apply(4); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableMapTest.java b/test/jdk/java/lang/StableValue/StableMapTest.java deleted file mode 100644 index 86cf4ab3643..00000000000 --- a/test/jdk/java/lang/StableValue/StableMapTest.java +++ /dev/null @@ -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 KEYS = Set.of(0, KEY, 13); - private static final Set EMPTY = Set.of(); - private static final Function 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 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 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> expected = KEYS.stream() - .map(i -> new AbstractMap.SimpleImmutableEntry<>(i , i)) - .collect(Collectors.toSet()); - Set> 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 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 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 void assertThrowsForOperation(Class expectedType, Operation operation) { - var lazy = newMap(); - assertThrows(expectedType, () -> operation.accept(lazy)); - } - - // Implementing interfaces - - @Test - void serializable() { - serializable(newMap()); - serializable(newEmptyMap()); - } - - void serializable(Map map) { - assertFalse(map instanceof Serializable); - assertFalse(map.entrySet() instanceof Serializable); - assertFalse(map.keySet() instanceof Serializable); - assertFalse(map.values() instanceof Serializable); - } - - @Test - void distinct() { - Map> map = StableUtil.map(Set.of(1, 2, 3)); - assertEquals(3, map.size()); - // Check, every StableValue is distinct - Map, 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 inputs = new HashSet<>(); - inputs.add(0); - inputs.add(null); - assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY)); - } - - // Support constructs - - record Operation(String name, - Consumer> consumer) implements Consumer> { - @java.lang.Override - public void accept(Map map) { consumer.accept(map); } - @java.lang.Override - public String toString() { return name; } - } - - static Stream nullAverseOperations() { - return Stream.of( - new Operation("forEach", m -> m.forEach(null)) - ); - } - - static Stream 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 newMap() { - return StableValue.map(KEYS, IDENTITY); - } - - static Map newEmptyMap() { - return StableValue.map(EMPTY, IDENTITY); - } - - static Map newRegularMap() { - return KEYS.stream().collect(Collectors.toMap(IDENTITY, IDENTITY)); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableSupplierTest.java b/test/jdk/java/lang/StableValue/StableSupplierTest.java deleted file mode 100644 index 2d542fbf6ca..00000000000 --- a/test/jdk/java/lang/StableValue/StableSupplierTest.java +++ /dev/null @@ -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 SUPPLIER = () -> 42; - - @Test - void factoryInvariants() { - assertThrows(NullPointerException.class, () -> StableValue.supplier(null)); - } - - @Test - void basic() { - basic(SUPPLIER); - basic(() -> null); - } - - void basic(Supplier supplier) { - StableTestUtil.CountingSupplier 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 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> ref = new AtomicReference<>(); - 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 f0 = StableValue.supplier(SUPPLIER); - Supplier f1 = StableValue.supplier(SUPPLIER); - // No function is equal to another function - assertNotEquals(f0, f1); - } - - @Test - void hashCodeStable() { - Supplier f0 = StableValue.supplier(SUPPLIER); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - f0.get(); - assertEquals(System.identityHashCode(f0), f0.hashCode()); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java b/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java deleted file mode 100644 index 85aee0cbeec..00000000000 --- a/test/jdk/java/lang/StableValue/StableValueFactoriesTest.java +++ /dev/null @@ -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)); - } - -} diff --git a/test/jdk/java/lang/StableValue/StableValueTest.java b/test/jdk/java/lang/StableValue/StableValueTest.java deleted file mode 100644 index 4290c8716a0..00000000000 --- a/test/jdk/java/lang/StableValue/StableValueTest.java +++ /dev/null @@ -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 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 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 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 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 stable = StableValue.of(); - assertEquals(VALUE, stable.orElse(VALUE)); - assertNull(stable.orElse(null)); - stable.trySet(VALUE); - assertEquals(VALUE, stable.orElse(VALUE2)); - } - - @Test - void orElseThrow() { - StableValue 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 stable = StableValue.of(); - assertFalse(stable.isSet()); - stable.trySet(initial); - assertTrue(stable.isSet()); - } - - @Test - void testOrElseSetSupplier() { - StableTestUtil.CountingSupplier cs = new StableTestUtil.CountingSupplier<>(() -> VALUE); - StableValue 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 stableValue = StableValue.of(); - // Should be Object::hashCode - assertEquals(System.identityHashCode(stableValue), stableValue.hashCode()); - } - - @Test - void testEquals() { - StableValue s0 = StableValue.of(); - assertNotEquals(null, s0); - StableValue s1 = StableValue.of(); - assertNotEquals(s0, s1); // Identity based - s0.setOrThrow(42); - s1.setOrThrow(42); - assertNotEquals(s0, s1); - assertNotEquals("a", s0); - StableValue null0 = StableValue.of(); - StableValue null1 = StableValue.of(); - null0.setOrThrow(null); - null1.setOrThrow(null); - assertNotEquals(null0, null1); - } - - @Test - void toStringUnset() { - StableValue stable = StableValue.of(); - assertEquals(".unset", stable.toString()); - } - - @Test - void toStringNull() { - StableValue stable = StableValue.of(); - assertTrue(stable.trySet(null)); - assertEquals("null", stable.toString()); - } - - @Test - void toStringNonNull() { - StableValue stable = StableValue.of(); - assertTrue(stable.trySet(VALUE)); - assertEquals(Objects.toString(VALUE), stable.toString()); - } - - @Test - void toStringCircular() { - 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 stable = StableValue.of(); - AtomicReference> 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 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 ORIGINAL_POWER_OF_TWO = - v -> 1 << v; - - private static final IntFunction 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 CACHED_KEYS = - Set.of(1, 2, 4, 8, 16, 32); - private static final UnaryOperator LOG2_ORIGINAL = - i -> 31 - Integer.numberOfLeadingZeros(i); - - private static final Function 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 KEYS = - Set.of(1, 2, 4, 8); - private static final UnaryOperator LOG2_ORIGINAL = - i -> 31 - Integer.numberOfLeadingZeros(i); - - private static final Function 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, Integer> TRY_SET = StableValue::trySet; - private static final BiPredicate, 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, Integer> winnerPredicate) { - int noThreads = 10; - CountDownLatch starter = new CountDownLatch(noThreads); - StableValue stable = StableValue.of(); - Map winners = new ConcurrentHashMap<>(); - List 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); - } - } - -} diff --git a/test/jdk/java/util/Collection/MOAT.java b/test/jdk/java/util/Collection/MOAT.java index ab9e4b4309d..d0d27c8f91e 100644 --- a/test/jdk/java/util/Collection/MOAT.java +++ b/test/jdk/java/util/Collection/MOAT.java @@ -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.of().subList(0,0)); - testListMutatorsAlwaysThrow(StableValue.list(0, i -> i)); + testListMutatorsAlwaysThrow(List.ofLazy(0, i -> i)); testEmptyListMutatorsAlwaysThrow(List.of()); testEmptyListMutatorsAlwaysThrow(List.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 list : Arrays.asList( List.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 map : Arrays.asList( Map.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.of(), k -> k), - StableValue.map(Set.of(1), k -> k), - StableValue.map(Set.of(1, 2, 3), k -> k))) { + Map.ofLazy(Set.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); diff --git a/test/langtools/jdk/jshell/CompletionSuggestionTest.java b/test/langtools/jdk/jshell/CompletionSuggestionTest.java index d31a32b63f8..7a960258cc1 100644 --- a/test/langtools/jdk/jshell/CompletionSuggestionTest.java +++ b/test/langtools/jdk/jshell/CompletionSuggestionTest.java @@ -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()"); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java similarity index 86% rename from test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java index 0b8e5d97cac..67a60755682 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableListBenchmark.java @@ -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 IDENTITY = i -> i; - private static final List LIST = StableValue.list(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); + private static final List LIST = List.ofLazy(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = LIST::get; - private final List list = StableValue.list(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); + private final List list = List.ofLazy(SIZE, IDENTITY); + private final IntFunction intFunction = list::get; @Benchmark public int list() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java similarity index 84% rename from test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java index 1e8e250ba8a..9210d8a86be 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableIntFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableListSingleBenchmark.java @@ -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 IDENTITY = i -> i; - private static final List STABLE = StableValue.list(SIZE, IDENTITY); - private static final IntFunction INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY); + private static final List STABLE = List.ofLazy(SIZE, IDENTITY); + private static final IntFunction INT_FUNCTION = STABLE::get; - private final List stable = StableValue.list(SIZE, IDENTITY); - private final IntFunction intFunction = StableValue.intFunction(SIZE, IDENTITY); + private final List stable = List.ofLazy(SIZE, IDENTITY); + private final IntFunction intFunction = stable::get; @Benchmark public int list() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java similarity index 85% rename from test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java index 44fd3f2c18e..2b8e90eee17 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMapBenchmark.java @@ -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 SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map MAP = StableValue.map(SET, Function.identity()); - private static final Function FUNCTION = StableValue.function(SET, Function.identity()); + private static final Map MAP = Map.ofLazy(SET, Function.identity()); + private static final Function FUNCTION = MAP::get; - private final Map map = StableValue.map(SET, Function.identity()); - private final Function function = StableValue.function(SET, Function.identity()); + private final Map map = Map.ofLazy(SET, Function.identity()); + private final Function 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); diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java similarity index 70% rename from test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java rename to test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java index 1cb1a04582f..4965ec649eb 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableFunctionSingleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMapSingleBenchmark.java @@ -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 SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet()); - private static final Map MAP = StableValue.map(SET, Function.identity()); - private static final Function FUNCTION = StableValue.function(SET, Function.identity()); + private static final Map MAP = Map.ofLazy(SET, Function.identity()); + private static final Map MAP_ENUM = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal); + private static final Map> MAP_ENUM_OPTIONAL = Map.ofLazy(EnumSet.allOf(MyEnum.class), e -> Optional.of(e.ordinal())); - private final Map map = StableValue.map(SET, Function.identity()); - private final Function function = StableValue.function(SET, Function.identity()); + private final Map map = Map.ofLazy(SET, Function.identity()); + private final Map 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} + } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java index 95721d88cec..c61e7b1861f 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableMethodHandleBenchmark.java @@ -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 STABLE_MH; + private static final LazyConstant STABLE_MH = LazyConstant.of(StableMethodHandleBenchmark::identityHandle); private static /* intentionally not final */ MethodHandle mh = identityHandle(); private static final Dcl DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle); private static final AtomicReference ATOMIC_REFERENCE = new AtomicReference<>(identityHandle()); private static final Map MAP = new ConcurrentHashMap<>(); - private static final Map STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle()); + private static final Map 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() { diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java index 883f13da05a..59aaa9c014e 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableSupplierBenchmark.java @@ -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 STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); - private static final Supplier SUPPLIER = StableValue.supplier(() -> VALUE); - private static final Supplier SUPPLIER2 = StableValue.supplier(() -> VALUE); + private static final LazyConstant STABLE = init(VALUE); + private static final LazyConstant STABLE2 = init(VALUE2); - private final StableValue stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); - private final Supplier supplier = StableValue.supplier(() -> VALUE); - private final Supplier supplier2 = StableValue.supplier(() -> VALUE2); + private final LazyConstant stable = init(VALUE); + private final LazyConstant 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 init(StableValue m, Integer value) { - m.trySet(value); - return m; + private static LazyConstant init(Integer value) { + return LazyConstant.of(() -> value); } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java index 505cfffdc2c..5323af36c22 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/StableValueBenchmark.java @@ -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 STABLE = init(StableValue.of(), VALUE); - private static final StableValue STABLE2 = init(StableValue.of(), VALUE2); - private static final StableValue DCL = init(StableValue.of(), VALUE); - private static final StableValue DCL2 = init(StableValue.of(), VALUE2); + private static final LazyConstant STABLE = init(VALUE); + private static final LazyConstant STABLE2 = init(VALUE2); + private static final Supplier DCL = new Dcl<>(() -> VALUE); + private static final Supplier DCL2 = new Dcl<>(() -> VALUE2); private static final AtomicReference ATOMIC = new AtomicReference<>(VALUE); private static final AtomicReference 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 stable = init(StableValue.of(), VALUE); - private final StableValue stable2 = init(StableValue.of(), VALUE2); - private final StableValue stableNull = StableValue.of(); - private final StableValue stableNull2 = StableValue.of(); + private static final LazyConstant> OPTIONAL_42 = LazyConstant.of(() -> Optional.of(42)); + private static final LazyConstant> OPTIONAL_42_2 = LazyConstant.of(() -> Optional.of(42)); + private static final LazyConstant> OPTIONAL_EMPTY = LazyConstant.of(Optional::empty); + private static final LazyConstant> OPTIONAL_EMPTY2 = LazyConstant.of(Optional::empty); + + private final LazyConstant stable = init(VALUE); + private final LazyConstant stable2 = init(VALUE2); private final Supplier dcl = new Dcl<>(() -> VALUE); private final Supplier dcl2 = new Dcl<>(() -> VALUE2); private final AtomicReference atomic = new AtomicReference<>(VALUE); @@ -69,13 +74,6 @@ public class StableValueBenchmark { private final Supplier supplier = () -> VALUE; private final Supplier 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 init(StableValue m, Integer value) { - m.trySet(value); - return m; + private static LazyConstant init(Integer value) { + return LazyConstant.of(() -> value); } private static final class Holder { - private final StableValue delegate = StableValue.of(); + private final LazyConstant delegate; Holder(int value) { - delegate.setOrThrow(value); + delegate = LazyConstant.of(() -> value); } int get() { - return delegate.orElseThrow(); + return delegate.get(); } } - private record RecordHolder(StableValue delegate) { + private record RecordHolder(LazyConstant 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 implements Supplier { private final Supplier supplier; private volatile V value; - private boolean bound; public Dcl(Supplier 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(); } } } diff --git a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java index 65d53a42e88..759cb25b592 100644 --- a/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java +++ b/test/micro/org/openjdk/bench/java/lang/stable/VarHandleHolderBenchmark.java @@ -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 SV_X = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("x")); - private static final Supplier SV_Y = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("y")); + private static final LazyConstant SV_X = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("x")); + private static final LazyConstant SV_Y = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("y")); private static final Map 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 S_MAP = StableValue.map( + private static final Map S_MAP = Map.ofLazy( Set.of("x", "y"), VAR_HANDLE_FUNCTION); - private static final Function S_FUN = StableValue.function( - Set.of("x", "y"), - VAR_HANDLE_FUNCTION); + private static final Function S_FUN = S_MAP::get; private static final MemorySegment confined; static {