mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8366178: Implement JEP 526: Lazy Constants (Second Preview)
8371882: Improve documentation for JEP 526: Lazy Constants Reviewed-by: jvernee, mcimadamore
This commit is contained in:
parent
df5b105bbb
commit
f946449997
307
src/java.base/share/classes/java/lang/LazyConstant.java
Normal file
307
src/java.base/share/classes/java/lang/LazyConstant.java
Normal file
@ -0,0 +1,307 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.lang;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.lang.LazyConstantImpl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A lazy constant is a holder of contents that can be set at most once.
|
||||
* <p>
|
||||
* A lazy constant is created using the factory method
|
||||
* {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code <computing function>})}.
|
||||
* When created, the lazy constant is <em>not initialized</em>, meaning it has no contents.
|
||||
* The lazy constant (of type {@code T}) can then be <em>initialized</em>
|
||||
* (and its contents retrieved) by calling {@linkplain #get() get()}. The first time
|
||||
* {@linkplain #get() get()} is called, the underlying <em>computing function</em>
|
||||
* (provided at construction) will be invoked and the result will be used to initialize
|
||||
* the constant. Once a lazy constant is initialized, its contents can <em>never change</em>
|
||||
* and will be retrieved over and over again upon subsequent {@linkplain #get() get()}
|
||||
* invocations.
|
||||
* <p>
|
||||
* Consider the following example where a lazy constant field "{@code logger}" holds
|
||||
* an object of type {@code Logger}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* // Creates a new uninitialized lazy constant
|
||||
* private final LazyConstant<Logger> logger =
|
||||
* // @link substring="of" target="#of" :
|
||||
* LazyConstant.of( () -> Logger.create(Component.class) );
|
||||
*
|
||||
* public void process() {
|
||||
* logger.get().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* <p>
|
||||
* Initially, the lazy constant is <em>not initialized</em>. When {@code logger.get()}
|
||||
* is first invoked, it evaluates the computing function and initializes the constant to
|
||||
* the result; the result is then returned to the client. Hence, {@linkplain #get() get()}
|
||||
* guarantees that the constant is <em>initialized</em> before it returns, barring
|
||||
* any exceptions.
|
||||
* <p>
|
||||
* Furthermore, {@linkplain #get() get()} guarantees that, out of several threads trying to
|
||||
* invoke the computing function simultaneously, {@linkplain ##thread-safety only one is
|
||||
* ever selected} for computation. This property is crucial as evaluation of the computing
|
||||
* function may have side effects, for example, the call above to {@code Logger.create()}
|
||||
* may result in storage resources being prepared.
|
||||
*
|
||||
* <h2 id="exception-handling">Exception handling</h2>
|
||||
* If the computing function returns {@code null}, a {@linkplain NullPointerException}
|
||||
* is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who
|
||||
* want to use a nullable constant can wrap the value into an {@linkplain Optional} holder.
|
||||
* <p>
|
||||
* If the computing function recursively invokes itself (directly or indirectly via
|
||||
* the lazy constant), an {@linkplain IllegalStateException} is thrown, and the lazy
|
||||
* constant is not initialized.
|
||||
*
|
||||
* <h2 id="composition">Composing lazy constants</h2>
|
||||
* A lazy constant can depend on other lazy constants, forming a dependency graph
|
||||
* that can be lazily computed but where access to individual elements can still be
|
||||
* performant. In the following example, a single {@code Foo} and a {@code Bar}
|
||||
* instance (that is dependent on the {@code Foo} instance) are lazily created, both of
|
||||
* which are held by lazy constants:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public final class DependencyUtil {
|
||||
*
|
||||
* private DependencyUtil() {}
|
||||
*
|
||||
* public static class Foo {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* public static class Bar {
|
||||
* public Bar(Foo foo) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* private static final LazyConstant<Foo> FOO = LazyConstant.of( Foo::new );
|
||||
* private static final LazyConstant<Bar> BAR = LazyConstant.of( () -> new Bar(FOO.get()) );
|
||||
*
|
||||
* public static Foo foo() {
|
||||
* return FOO.get();
|
||||
* }
|
||||
*
|
||||
* public static Bar bar() {
|
||||
* return BAR.get();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*}
|
||||
* Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already
|
||||
* created. Upon such a creation, a dependent {@code Foo} will first be created if
|
||||
* the {@code Foo} does not already exist.
|
||||
*
|
||||
* <h2 id="thread-safety">Thread Safety</h2>
|
||||
* A lazy constant is guaranteed to be initialized atomically and at most once. If
|
||||
* competing threads are racing to initialize a lazy constant, only one updating thread
|
||||
* runs the computing function (which runs on the caller's thread and is hereafter denoted
|
||||
* <em>the computing thread</em>), while the other threads are blocked until the constant
|
||||
* is initialized, after which the other threads observe the lazy constant is initialized
|
||||
* and leave the constant unchanged and will never invoke any computation.
|
||||
* <p>
|
||||
* The invocation of the computing function and the resulting initialization of
|
||||
* the constant {@linkplain java.util.concurrent##MemoryVisibility <em>happens-before</em>}
|
||||
* the initialized constant's content is read. Hence, the initialized constant's content,
|
||||
* including any {@code final} fields of any newly created objects, is safely published.
|
||||
* <p>
|
||||
* Thread interruption does not cancel the initialization of a lazy constant. In other
|
||||
* words, if the computing thread is interrupted, {@code LazyConstant::get} doesn't clear
|
||||
* the interrupted thread’s status, nor does it throw an {@linkplain InterruptedException}.
|
||||
* <p>
|
||||
* If the computing function blocks indefinitely, other threads operating on this
|
||||
* lazy constant may block indefinitely; no timeouts or cancellations are provided.
|
||||
*
|
||||
* <h2 id="performance">Performance</h2>
|
||||
* The contents of a lazy constant can never change after the lazy constant has been
|
||||
* initialized. Therefore, a JVM implementation may, for an initialized lazy constant,
|
||||
* elide all future reads of that lazy constant's contents and instead use the contents
|
||||
* that has been previously observed. We call this optimization <em>constant folding</em>.
|
||||
* This is only possible if there is a direct reference from a {@code static final} field
|
||||
* to a lazy constant or if there is a chain from a {@code static final} field -- via one
|
||||
* or more <em>trusted fields</em> (i.e., {@code static final} fields,
|
||||
* {@linkplain Record record} fields, or final instance fields in hidden classes) --
|
||||
* to a lazy constant.
|
||||
*
|
||||
* <h2 id="miscellaneous">Miscellaneous</h2>
|
||||
* Except for {@linkplain Object#equals(Object) equals(obj)} and
|
||||
* {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters
|
||||
* must be <em>non-null</em>, or a {@link NullPointerException} will be thrown.
|
||||
*
|
||||
* @apiNote Once a lazy constant is initialized, its contents cannot ever be removed.
|
||||
* This can be a source of an unintended memory leak. More specifically,
|
||||
* a lazy constant {@linkplain java.lang.ref##reachability strongly references}
|
||||
* it contents. Hence, the contents of a lazy constant will be reachable as long
|
||||
* as the lazy constant itself is reachable.
|
||||
* <p>
|
||||
* While it's possible to store an array inside a lazy constant, doing so will
|
||||
* not result in improved access performance of the array elements. Instead, a
|
||||
* {@linkplain List#ofLazy(int, IntFunction) lazy list} of arbitrary depth can
|
||||
* be used, which provides constant components.
|
||||
* <p>
|
||||
* The {@code LazyConstant} type is not {@link Serializable}.
|
||||
* <p>
|
||||
* Use in static initializers may interact with class initialization order;
|
||||
* cyclic initialization may result in initialization errors as described
|
||||
* in section {@jls 12.4} of <cite>The Java Language Specification</cite>.
|
||||
*
|
||||
* @implNote
|
||||
* A lazy constant is free to synchronize on itself. Hence, care must be
|
||||
* taken when directly or indirectly synchronizing on a lazy constant.
|
||||
* A lazy constant is unmodifiable but its contents may or may not be
|
||||
* immutable (e.g., it may hold an {@linkplain ArrayList}).
|
||||
*
|
||||
* @param <T> type of the constant
|
||||
*
|
||||
* @since 26
|
||||
*
|
||||
* @see Optional
|
||||
* @see Supplier
|
||||
* @see List#ofLazy(int, IntFunction)
|
||||
* @see Map#ofLazy(Set, Function)
|
||||
* @jls 12.4 Initialization of Classes and Interfaces
|
||||
* @jls 17.4.5 Happens-before Order
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
|
||||
public sealed interface LazyConstant<T>
|
||||
extends Supplier<T>
|
||||
permits LazyConstantImpl {
|
||||
|
||||
/**
|
||||
* {@return the contents of this lazy constant if initialized, otherwise,
|
||||
* returns {@code other}}
|
||||
* <p>
|
||||
* This method never triggers initialization of this lazy constant and will observe
|
||||
* initialization by other threads atomically (i.e., it returns the contents
|
||||
* if and only if the initialization has already completed).
|
||||
*
|
||||
* @param other value to return if the content is not initialized
|
||||
* (can be {@code null})
|
||||
*/
|
||||
T orElse(T other);
|
||||
|
||||
/**
|
||||
* {@return the contents of this initialized constant. If not initialized, first
|
||||
* computes and initializes this constant using the computing function}
|
||||
* <p>
|
||||
* After this method returns successfully, the constant is guaranteed to be
|
||||
* initialized.
|
||||
* <p>
|
||||
* If the computing function throws, the throwable is relayed to the caller and
|
||||
* the lazy constant remains uninitialized; a subsequent call to get() may then
|
||||
* attempt the computation again.
|
||||
*/
|
||||
T get();
|
||||
|
||||
/**
|
||||
* {@return {@code true} if the constant is initialized, {@code false} otherwise}
|
||||
* <p>
|
||||
* This method never triggers initialization of this lazy constant and will observe
|
||||
* changes in the initialization state made by other threads atomically.
|
||||
*/
|
||||
boolean isInitialized();
|
||||
|
||||
// Object methods
|
||||
|
||||
/**
|
||||
* {@return if this lazy constant is the same as the provided {@code obj}}
|
||||
* <p>
|
||||
* In other words, equals compares the identity of this lazy constant and {@code obj}
|
||||
* to determine equality. Hence, two lazy constants with the same contents are
|
||||
* <em>not</em> equal.
|
||||
* <p>
|
||||
* This method never triggers initialization of this lazy constant.
|
||||
*/
|
||||
@Override
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* {@return the {@linkplain System#identityHashCode(Object) identity hash code} for
|
||||
* this lazy constant}
|
||||
*
|
||||
* This method never triggers initialization of this lazy constant.
|
||||
*/
|
||||
@Override
|
||||
int hashCode();
|
||||
|
||||
/**
|
||||
* {@return a string suitable for debugging}
|
||||
* <p>
|
||||
* This method never triggers initialization of this lazy constant and will observe
|
||||
* initialization by other threads atomically (i.e., it observes the
|
||||
* contents if and only if the initialization has already completed).
|
||||
* <p>
|
||||
* If this lazy constant is initialized, an implementation-dependent string
|
||||
* containing the {@linkplain Object#toString()} of the
|
||||
* contents will be returned; otherwise, an implementation-dependent string is
|
||||
* returned that indicates this lazy constant is not yet initialized.
|
||||
*/
|
||||
@Override
|
||||
String toString();
|
||||
|
||||
// Factory
|
||||
|
||||
/**
|
||||
* {@return a lazy constant whose contents is to be computed later via the provided
|
||||
* {@code computingFunction}}
|
||||
* <p>
|
||||
* The returned lazy constant strongly references the provided
|
||||
* {@code computingFunction} at least until initialization completes successfully.
|
||||
* <p>
|
||||
* If the provided computing function is already an instance of
|
||||
* {@code LazyConstant}, the method is free to return the provided computing function
|
||||
* directly.
|
||||
*
|
||||
* @implNote after initialization completes successfully, the computing function is
|
||||
* no longer strongly referenced and becomes eligible for
|
||||
* garbage collection.
|
||||
*
|
||||
* @param computingFunction in the form of a {@linkplain Supplier} to be used
|
||||
* to initialize the constant
|
||||
* @param <T> type of the constant
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> LazyConstant<T> of(Supplier<? extends T> computingFunction) {
|
||||
Objects.requireNonNull(computingFunction);
|
||||
if (computingFunction instanceof LazyConstant<? extends T> lc) {
|
||||
return (LazyConstant<T>) lc;
|
||||
}
|
||||
return LazyConstantImpl.ofLazy(computingFunction);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,756 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.lang;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.lang.stable.StableEnumFunction;
|
||||
import jdk.internal.lang.stable.StableFunction;
|
||||
import jdk.internal.lang.stable.StableIntFunction;
|
||||
import jdk.internal.lang.stable.StableSupplier;
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* A stable value is a holder of contents that can be set at most once.
|
||||
* <p>
|
||||
* A {@code StableValue<T>} is typically created using the factory method
|
||||
* {@linkplain StableValue#of() {@code StableValue.of()}}. When created this way,
|
||||
* the stable value is <em>unset</em>, which means it holds no <em>contents</em>.
|
||||
* Its contents, of type {@code T}, can be <em>set</em> by calling
|
||||
* {@linkplain #trySet(Object) trySet()}, {@linkplain #setOrThrow(Object) setOrThrow()},
|
||||
* or {@linkplain #orElseSet(Supplier) orElseSet()}. Once set, the contents
|
||||
* can never change and can be retrieved by calling {@linkplain #orElseThrow() orElseThrow()}
|
||||
* , {@linkplain #orElse(Object) orElse()}, or {@linkplain #orElseSet(Supplier) orElseSet()}.
|
||||
* <p>
|
||||
* Consider the following example where a stable value field "{@code logger}" is a
|
||||
* shallowly immutable holder of contents of type {@code Logger} and that is initially
|
||||
* created as <em>unset</em>, which means it holds no contents. Later in the example, the
|
||||
* state of the "{@code logger}" field is checked and if it is still <em>unset</em>,
|
||||
* the contents is <em>set</em>:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* // Creates a new unset stable value with no contents
|
||||
* // @link substring="of" target="#of" :
|
||||
* private final StableValue<Logger> logger = StableValue.of();
|
||||
*
|
||||
* private Logger getLogger() {
|
||||
* if (!logger.isSet()) {
|
||||
* logger.trySet(Logger.create(Component.class));
|
||||
* }
|
||||
* return logger.orElseThrow();
|
||||
* }
|
||||
*
|
||||
* public void process() {
|
||||
* getLogger().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* <p>
|
||||
* If {@code getLogger()} is called from several threads, several instances of
|
||||
* {@code Logger} might be created. However, the contents can only be set at most once
|
||||
* meaning the first writer wins.
|
||||
* <p>
|
||||
* In order to guarantee that, even under races, only one instance of {@code Logger} is
|
||||
* ever created, the {@linkplain #orElseSet(Supplier) orElseSet()} method can be used
|
||||
* instead, where the contents are lazily computed, and atomically set, via a
|
||||
* {@linkplain Supplier supplier}. In the example below, the supplier is provided in the
|
||||
* form of a lambda expression:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* // Creates a new unset stable value with no contents
|
||||
* // @link substring="of" target="#of" :
|
||||
* private final StableValue<Logger> logger = StableValue.of();
|
||||
*
|
||||
* private Logger getLogger() {
|
||||
* return logger.orElseSet( () -> Logger.create(Component.class) );
|
||||
* }
|
||||
*
|
||||
* public void process() {
|
||||
* getLogger().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* <p>
|
||||
* The {@code getLogger()} method calls {@code logger.orElseSet()} on the stable value to
|
||||
* retrieve its contents. If the stable value is <em>unset</em>, then {@code orElseSet()}
|
||||
* evaluates the given supplier, and sets the contents to the result; the result is then
|
||||
* returned to the client. In other words, {@code orElseSet()} guarantees that a
|
||||
* stable value's contents is <em>set</em> before it returns.
|
||||
* <p>
|
||||
* Furthermore, {@code orElseSet()} guarantees that out of one or more suppliers provided,
|
||||
* only at most one is ever evaluated, and that one is only ever evaluated once,
|
||||
* even when {@code logger.orElseSet()} is invoked concurrently. This property is crucial
|
||||
* as evaluation of the supplier may have side effects, for example, the call above to
|
||||
* {@code Logger.create()} may result in storage resources being prepared.
|
||||
*
|
||||
* <h2 id="stable-functions">Stable Functions</h2>
|
||||
* Stable values provide the foundation for higher-level functional abstractions. A
|
||||
* <em>stable supplier</em> is a supplier that computes a value and then caches it into
|
||||
* a backing stable value storage for subsequent use. A stable supplier is created via the
|
||||
* {@linkplain StableValue#supplier(Supplier) StableValue.supplier()} factory, by
|
||||
* providing an underlying {@linkplain Supplier} which is invoked when the stable supplier
|
||||
* is first accessed:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* public class Component {
|
||||
*
|
||||
* private final Supplier<Logger> logger =
|
||||
* // @link substring="supplier" target="#supplier(Supplier)" :
|
||||
* StableValue.supplier( () -> Logger.getLogger(Component.class) );
|
||||
*
|
||||
* public void process() {
|
||||
* logger.get().info("Process started");
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* A stable supplier encapsulates access to its backing stable value storage. This means
|
||||
* that code inside {@code Component} can obtain the logger object directly from the
|
||||
* stable supplier, without having to go through an accessor method like {@code getLogger()}.
|
||||
* <p>
|
||||
* A <em>stable int function</em> is a function that takes an {@code int} parameter and
|
||||
* uses it to compute a result that is then cached by the backing stable value storage
|
||||
* for that parameter value. A stable {@link IntFunction} is created via the
|
||||
* {@linkplain StableValue#intFunction(int, IntFunction) StableValue.intFunction()}
|
||||
* factory. Upon creation, the input range (i.e. {@code [0, size)}) is specified together
|
||||
* with an underlying {@linkplain IntFunction} which is invoked at most once per input
|
||||
* value. In effect, the stable int function will act like a cache for the underlying
|
||||
* {@linkplain IntFunction}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* final class PowerOf2Util {
|
||||
*
|
||||
* private PowerOf2Util() {}
|
||||
*
|
||||
* private static final int SIZE = 6;
|
||||
* private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
|
||||
* v -> 1 << v;
|
||||
*
|
||||
* private static final IntFunction<Integer> POWER_OF_TWO =
|
||||
* // @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
* StableValue.intFunction(SIZE, UNDERLYING_POWER_OF_TWO);
|
||||
*
|
||||
* public static int powerOfTwo(int a) {
|
||||
* return POWER_OF_TWO.apply(a);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
*
|
||||
*}
|
||||
* The {@code PowerOf2Util.powerOfTwo()} function is a <em>partial function</em> that only
|
||||
* allows a subset {@code [0, 5]} of the underlying function's {@code UNDERLYING_POWER_OF_TWO}
|
||||
* input range.
|
||||
*
|
||||
* <p>
|
||||
* A <em>stable function</em> is a function that takes a parameter (of type {@code T}) and
|
||||
* uses it to compute a result (of type {@code R}) that is then cached by the backing
|
||||
* stable value storage for that parameter value. A stable function is created via the
|
||||
* {@linkplain StableValue#function(Set, Function) StableValue.function()} factory.
|
||||
* Upon creation, the input {@linkplain Set} is specified together with an underlying
|
||||
* {@linkplain Function} which is invoked at most once per input value. In effect, the
|
||||
* stable function will act like a cache for the underlying {@linkplain Function}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* class Log2Util {
|
||||
*
|
||||
* private Log2Util() {}
|
||||
*
|
||||
* private static final Set<Integer> KEYS =
|
||||
* Set.of(1, 2, 4, 8, 16, 32);
|
||||
* private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
|
||||
* i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
*
|
||||
* private static final Function<Integer, Integer> LOG2 =
|
||||
* // @link substring="function" target="#function(Set,Function)" :
|
||||
* StableValue.function(KEYS, UNDERLYING_LOG2);
|
||||
*
|
||||
* public static int log2(int a) {
|
||||
* return LOG2.apply(a);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
|
||||
*}
|
||||
*
|
||||
* The {@code Log2Util.log2()} function is a <em>partial function</em> that only allows
|
||||
* a subset {@code {1, 2, 4, 8, 16, 32}} of the underlying function's
|
||||
* {@code UNDERLYING_LOG2} input range.
|
||||
*
|
||||
* <h2 id="stable-collections">Stable Collections</h2>
|
||||
* Stable values can also be used as backing storage for
|
||||
* {@linkplain Collection##unmodifiable unmodifiable collections}. A <em>stable list</em>
|
||||
* is an unmodifiable list, backed by an array of stable values. The stable list elements
|
||||
* are computed when they are first accessed, using a provided {@linkplain IntFunction}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* final class PowerOf2Util {
|
||||
*
|
||||
* private PowerOf2Util() {}
|
||||
*
|
||||
* private static final int SIZE = 6;
|
||||
* private static final IntFunction<Integer> UNDERLYING_POWER_OF_TWO =
|
||||
* v -> 1 << v;
|
||||
*
|
||||
* private static final List<Integer> POWER_OF_TWO =
|
||||
* // @link substring="list" target="#list(int,IntFunction)" :
|
||||
* StableValue.list(SIZE, UNDERLYING_POWER_OF_TWO);
|
||||
*
|
||||
* public static int powerOfTwo(int a) {
|
||||
* return POWER_OF_TWO.get(a);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* int result = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
*
|
||||
* }
|
||||
* <p>
|
||||
* Similarly, a <em>stable map</em> is an unmodifiable map whose keys are known at
|
||||
* construction. The stable map values are computed when they are first accessed,
|
||||
* using a provided {@linkplain Function}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* class Log2Util {
|
||||
*
|
||||
* private Log2Util() {}
|
||||
*
|
||||
* private static final Set<Integer> KEYS =
|
||||
* Set.of(1, 2, 4, 8, 16, 32);
|
||||
* private static final UnaryOperator<Integer> UNDERLYING_LOG2 =
|
||||
* i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
*
|
||||
* private static final Map<Integer, INTEGER> LOG2 =
|
||||
* // @link substring="map" target="#map(Set,Function)" :
|
||||
* StableValue.map(CACHED_KEYS, UNDERLYING_LOG2);
|
||||
*
|
||||
* public static int log2(int a) {
|
||||
* return LOG2.get(a);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*
|
||||
* int result = Log2Util.log2(16); // May eventually constant fold to 4 at runtime
|
||||
*
|
||||
*}
|
||||
*
|
||||
* <h2 id="composition">Composing stable values</h2>
|
||||
* A stable value can depend on other stable values, forming a dependency graph
|
||||
* that can be lazily computed but where access to individual elements can still be
|
||||
* performant. In the following example, a single {@code Foo} and a {@code Bar}
|
||||
* instance (that is dependent on the {@code Foo} instance) are lazily created, both of
|
||||
* which are held by stable values:
|
||||
* {@snippet lang = java:
|
||||
* public final class DependencyUtil {
|
||||
*
|
||||
* private DependencyUtil() {}
|
||||
*
|
||||
* public static class Foo {
|
||||
* // ...
|
||||
* }
|
||||
*
|
||||
* public static class Bar {
|
||||
* public Bar(Foo foo) {
|
||||
* // ...
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* private static final Supplier<Foo> FOO = StableValue.supplier(Foo::new);
|
||||
* private static final Supplier<Bar> BAR = StableValue.supplier(() -> new Bar(FOO.get()));
|
||||
*
|
||||
* public static Foo foo() {
|
||||
* return FOO.get();
|
||||
* }
|
||||
*
|
||||
* public static Bar bar() {
|
||||
* return BAR.get();
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*}
|
||||
* Calling {@code bar()} will create the {@code Bar} singleton if it is not already
|
||||
* created. Upon such a creation, the dependent {@code Foo} will first be created if
|
||||
* the {@code Foo} does not already exist.
|
||||
* <p>
|
||||
* Another example, which has a more complex dependency graph, is to compute the
|
||||
* Fibonacci sequence lazily:
|
||||
* {@snippet lang = java:
|
||||
* public final class Fibonacci {
|
||||
*
|
||||
* private Fibonacci() {}
|
||||
*
|
||||
* private static final int MAX_SIZE_INT = 46;
|
||||
*
|
||||
* private static final IntFunction<Integer> FIB =
|
||||
* StableValue.intFunction(MAX_SIZE_INT, Fibonacci::fib);
|
||||
*
|
||||
* public static int fib(int n) {
|
||||
* return n < 2
|
||||
* ? n
|
||||
* : FIB.apply(n - 1) + FIB.apply(n - 2);
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*}
|
||||
* Both {@code FIB} and {@code Fibonacci::fib} recurse into each other. Because the
|
||||
* stable int function {@code FIB} caches intermediate results, the initial
|
||||
* computational complexity is reduced from exponential to linear compared to a
|
||||
* traditional non-caching recursive fibonacci method. Once computed, the VM is free to
|
||||
* constant-fold expressions like {@code Fibonacci.fib(5)}.
|
||||
* <p>
|
||||
* The fibonacci example above is a directed acyclic graph (i.e.,
|
||||
* it has no circular dependencies and is therefore a dependency tree):
|
||||
*{@snippet lang=text :
|
||||
*
|
||||
* ___________fib(5)____________
|
||||
* / \
|
||||
* ____fib(4)____ ____fib(3)____
|
||||
* / \ / \
|
||||
* fib(3) fib(2) fib(2) fib(1)
|
||||
* / \ / \ / \
|
||||
* fib(2) fib(1) fib(1) fib(0) fib(1) fib(0)
|
||||
*}
|
||||
*
|
||||
* If there are circular dependencies in a dependency graph, a stable value will
|
||||
* eventually throw an {@linkplain IllegalStateException} upon referencing elements in
|
||||
* a circularity.
|
||||
*
|
||||
* <h2 id="thread-safety">Thread Safety</h2>
|
||||
* The contents of a stable value is guaranteed to be set at most once. If competing
|
||||
* threads are racing to set a stable value, only one update succeeds, while the other
|
||||
* updates are blocked until the stable value is set, whereafter the other updates
|
||||
* observes the stable value is set and leave the stable value unchanged.
|
||||
* <p>
|
||||
* The at-most-once write operation on a stable value that succeeds
|
||||
* (e.g. {@linkplain #trySet(Object) trySet()})
|
||||
* {@linkplain java.util.concurrent##MemoryVisibility <em>happens-before</em>}
|
||||
* any successful read operation (e.g. {@linkplain #orElseThrow()}).
|
||||
* A successful write operation can be either:
|
||||
* <ul>
|
||||
* <li>a {@link #trySet(Object)} that returns {@code true},</li>
|
||||
* <li>a {@link #setOrThrow(Object)} that does not throw, or</li>
|
||||
* <li>an {@link #orElseSet(Supplier)} that successfully runs the supplier</li>
|
||||
* </ul>
|
||||
* A successful read operation can be either:
|
||||
* <ul>
|
||||
* <li>a {@link #orElseThrow()} that does not throw,</li>
|
||||
* <li>a {@link #orElse(Object) orElse(other)} that does not return the {@code other} value</li>
|
||||
* <li>an {@link #orElseSet(Supplier)} that does not {@code throw}, or</li>
|
||||
* <li>an {@link #isSet()} that returns {@code true}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* The method {@link #orElseSet(Supplier)} guarantees that the provided
|
||||
* {@linkplain Supplier} is invoked successfully at most once, even under race.
|
||||
* Invocations of {@link #orElseSet(Supplier)} form a total order of zero or
|
||||
* more exceptional invocations followed by zero (if the contents were already set) or one
|
||||
* successful invocation. Since stable functions and stable collections are built on top
|
||||
* of the same principles as {@linkplain StableValue#orElseSet(Supplier) orElseSet()} they
|
||||
* too are thread safe and guarantee at-most-once-per-input invocation.
|
||||
*
|
||||
* <h2 id="performance">Performance</h2>
|
||||
* As the contents of a stable value can never change after it has been set, a JVM
|
||||
* implementation may, for a set stable value, elide all future reads of that
|
||||
* stable value, and instead directly use any contents that it has previously observed.
|
||||
* This is true if the reference to the stable value is a constant (e.g. in cases where
|
||||
* the stable value itself is stored in a {@code static final} field). Stable functions
|
||||
* and collections are built on top of StableValue. As such, they might also be eligible
|
||||
* for the same JVM optimizations as for StableValue.
|
||||
*
|
||||
* @implSpec Implementing classes of {@code StableValue} are free to synchronize on
|
||||
* {@code this} and consequently, it should be avoided to
|
||||
* (directly or indirectly) synchronize on a {@code StableValue}. Hence,
|
||||
* synchronizing on {@code this} may lead to deadlock.
|
||||
* <p>
|
||||
* Except for a {@code StableValue}'s contents itself,
|
||||
* an {@linkplain #orElse(Object) orElse(other)} parameter, and
|
||||
* an {@linkplain #equals(Object) equals(obj)} parameter; all
|
||||
* method parameters must be <em>non-null</em> or a {@link NullPointerException}
|
||||
* will be thrown.
|
||||
*
|
||||
* @implNote A {@code StableValue} is mainly intended to be a non-public field in
|
||||
* a class and is usually neither exposed directly via accessors nor passed as
|
||||
* a method parameter.
|
||||
* <p>
|
||||
* Stable functions and collections make reasonable efforts to provide
|
||||
* {@link Object#toString()} operations that do not trigger evaluation
|
||||
* of the internal stable values when called.
|
||||
* Stable collections have {@link Object#equals(Object)} operations that try
|
||||
* to minimize evaluation of the internal stable values when called.
|
||||
* <p>
|
||||
* As objects can be set via stable values but never removed, this can be a
|
||||
* source of unintended memory leaks. A stable value's contents are
|
||||
* {@linkplain java.lang.ref##reachability strongly reachable}.
|
||||
* Be advised that reachable stable values will hold their set contents until
|
||||
* the stable value itself is collected.
|
||||
* <p>
|
||||
* A {@code StableValue} that has a type parameter {@code T} that is an array
|
||||
* type (of arbitrary rank) will only allow the JVM to treat the
|
||||
* <em>array reference</em> as a stable value but <em>not its components</em>.
|
||||
* Instead, a {@linkplain #list(int, IntFunction) a stable list} of arbitrary
|
||||
* depth can be used, which provides stable components. More generally, a
|
||||
* stable value can hold other stable values of arbitrary depth and still
|
||||
* provide transitive constantness.
|
||||
* <p>
|
||||
* Stable values, functions, and collections are not {@link Serializable}.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*
|
||||
* @since 25
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES)
|
||||
public sealed interface StableValue<T>
|
||||
permits StableValueImpl {
|
||||
|
||||
/**
|
||||
* Tries to set the contents of this StableValue to the provided {@code contents}.
|
||||
* The contents of this StableValue can only be set once, implying this method only
|
||||
* returns {@code true} once.
|
||||
* <p>
|
||||
* When this method returns, the contents of this StableValue is always set.
|
||||
*
|
||||
* @return {@code true} if the contents of this StableValue was set to the
|
||||
* provided {@code contents}, {@code false} otherwise
|
||||
* @param contents to set
|
||||
* @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)}
|
||||
* recursively attempts to set this stable value by calling this method
|
||||
* directly or indirectly.
|
||||
*/
|
||||
boolean trySet(T contents);
|
||||
|
||||
/**
|
||||
* {@return the contents if set, otherwise, returns the provided {@code other} value}
|
||||
*
|
||||
* @param other to return if the contents is not set
|
||||
*/
|
||||
T orElse(T other);
|
||||
|
||||
/**
|
||||
* {@return the contents if set, otherwise, throws {@code NoSuchElementException}}
|
||||
*
|
||||
* @throws NoSuchElementException if no contents is set
|
||||
*/
|
||||
T orElseThrow();
|
||||
|
||||
/**
|
||||
* {@return {@code true} if the contents is set, {@code false} otherwise}
|
||||
*/
|
||||
boolean isSet();
|
||||
|
||||
/**
|
||||
* {@return the contents; if unset, first attempts to compute and set the
|
||||
* contents using the provided {@code supplier}}
|
||||
* <p>
|
||||
* The provided {@code supplier} is guaranteed to be invoked at most once if it
|
||||
* completes without throwing an exception. If this method is invoked several times
|
||||
* with different suppliers, only one of them will be invoked provided it completes
|
||||
* without throwing an exception.
|
||||
* <p>
|
||||
* If the supplier throws an (unchecked) exception, the exception is rethrown and no
|
||||
* contents is set. The most common usage is to construct a new object serving
|
||||
* as a lazily computed value or memoized result, as in:
|
||||
*
|
||||
* {@snippet lang=java:
|
||||
* Value v = stable.orElseSet(Value::new);
|
||||
* }
|
||||
* <p>
|
||||
* When this method returns successfully, the contents is always set.
|
||||
* <p>
|
||||
* The provided {@code supplier} will only be invoked once even if invoked from
|
||||
* several threads unless the {@code supplier} throws an exception.
|
||||
*
|
||||
* @param supplier to be used for computing the contents, if not previously set
|
||||
* @throws IllegalStateException if the provided {@code supplier} recursively
|
||||
* attempts to set this stable value.
|
||||
*/
|
||||
T orElseSet(Supplier<? extends T> supplier);
|
||||
|
||||
/**
|
||||
* Sets the contents of this StableValue to the provided {@code contents}, or, if
|
||||
* already set, throws {@code IllegalStateException}.
|
||||
* <p>
|
||||
* When this method returns (or throws an exception), the contents is always set.
|
||||
*
|
||||
* @param contents to set
|
||||
* @throws IllegalStateException if the contents was already set
|
||||
* @throws IllegalStateException if a supplier invoked by {@link #orElseSet(Supplier)}
|
||||
* recursively attempts to set this stable value by calling this method
|
||||
* directly or indirectly.
|
||||
*/
|
||||
void setOrThrow(T contents);
|
||||
|
||||
// Object methods
|
||||
|
||||
/**
|
||||
* {@return {@code true} if {@code this == obj}, {@code false} otherwise}
|
||||
*
|
||||
* @param obj to check for equality
|
||||
*/
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* {@return the {@linkplain System#identityHashCode(Object) identity hash code} of
|
||||
* {@code this} object}
|
||||
*/
|
||||
int hashCode();
|
||||
|
||||
// Factories
|
||||
|
||||
/**
|
||||
* {@return a new unset stable value}
|
||||
* <p>
|
||||
* An unset stable value has no contents.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
static <T> StableValue<T> of() {
|
||||
return StableValueImpl.of();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new pre-set stable value with the provided {@code contents}}
|
||||
*
|
||||
* @param contents to set
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
static <T> StableValue<T> of(T contents) {
|
||||
final StableValue<T> stableValue = StableValue.of();
|
||||
stableValue.trySet(contents);
|
||||
return stableValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable supplier}
|
||||
* <p>
|
||||
* The returned {@linkplain Supplier supplier} is a caching supplier that records
|
||||
* the value of the provided {@code underlying} supplier upon being first accessed via
|
||||
* the returned supplier's {@linkplain Supplier#get() get()} method.
|
||||
* <p>
|
||||
* The provided {@code underlying} supplier is guaranteed to be successfully invoked
|
||||
* at most once even in a multi-threaded environment. Competing threads invoking the
|
||||
* returned supplier's {@linkplain Supplier#get() get()} method when a value is
|
||||
* already under computation will block until a value is computed or an exception is
|
||||
* thrown by the computing thread. The competing threads will then observe the newly
|
||||
* computed value (if any) and will then never execute the {@code underlying} supplier.
|
||||
* <p>
|
||||
* If the provided {@code underlying} supplier throws an exception, it is rethrown
|
||||
* to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} supplier recursively calls the returned
|
||||
* supplier, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param underlying supplier used to compute a cached value
|
||||
* @param <T> the type of results supplied by the returned supplier
|
||||
*/
|
||||
static <T> Supplier<T> supplier(Supplier<? extends T> underlying) {
|
||||
Objects.requireNonNull(underlying);
|
||||
return StableSupplier.of(underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable {@linkplain IntFunction}}
|
||||
* <p>
|
||||
* The returned function is a caching function that, for each allowed {@code int}
|
||||
* input, records the values of the provided {@code underlying}
|
||||
* function upon being first accessed via the returned function's
|
||||
* {@linkplain IntFunction#apply(int) apply()} method. If the returned function is
|
||||
* invoked with an input that is not in the range {@code [0, size)}, an
|
||||
* {@link IllegalArgumentException} will be thrown.
|
||||
* <p>
|
||||
* The provided {@code underlying} function is guaranteed to be successfully invoked
|
||||
* at most once per allowed input, even in a multi-threaded environment. Competing
|
||||
* threads invoking the returned function's
|
||||
* {@linkplain IntFunction#apply(int) apply()} method when a value is already under
|
||||
* computation will block until a value is computed or an exception is thrown by
|
||||
* the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code underlying} function throws an exception, it is
|
||||
* rethrown to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} function recursively calls the returned
|
||||
* function for the same input, an {@linkplain IllegalStateException} will
|
||||
* be thrown.
|
||||
*
|
||||
* @param size the upper bound of the range {@code [0, size)} indicating
|
||||
* the allowed inputs
|
||||
* @param underlying {@code IntFunction} used to compute cached values
|
||||
* @param <R> the type of results delivered by the returned IntFunction
|
||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||
*/
|
||||
static <R> IntFunction<R> intFunction(int size,
|
||||
IntFunction<? extends R> underlying) {
|
||||
StableUtil.assertSizeNonNegative(size);
|
||||
Objects.requireNonNull(underlying);
|
||||
return StableIntFunction.of(size, underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable {@linkplain Function}}
|
||||
* <p>
|
||||
* The returned function is a caching function that, for each allowed
|
||||
* input in the given set of {@code inputs}, records the values of the provided
|
||||
* {@code underlying} function upon being first accessed via the returned function's
|
||||
* {@linkplain Function#apply(Object) apply()} method. If the returned function is
|
||||
* invoked with an input that is not in {@code inputs}, an {@link IllegalArgumentException}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The provided {@code underlying} function is guaranteed to be successfully invoked
|
||||
* at most once per allowed input, even in a multi-threaded environment. Competing
|
||||
* threads invoking the returned function's {@linkplain Function#apply(Object) apply()}
|
||||
* method when a value is already under computation will block until a value is
|
||||
* computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code underlying} function throws an exception, it is
|
||||
* rethrown to the initial caller and no contents is recorded.
|
||||
* <p>
|
||||
* If the provided {@code underlying} function recursively calls the returned
|
||||
* function for the same input, an {@linkplain IllegalStateException} will
|
||||
* be thrown.
|
||||
*
|
||||
* @param inputs the set of (non-null) allowed input values
|
||||
* @param underlying {@code Function} used to compute cached values
|
||||
* @param <T> the type of the input to the returned Function
|
||||
* @param <R> the type of results delivered by the returned Function
|
||||
* @throws NullPointerException if the provided set of {@code inputs} contains a
|
||||
* {@code null} element.
|
||||
*/
|
||||
static <T, R> Function<T, R> function(Set<? extends T> inputs,
|
||||
Function<? super T, ? extends R> underlying) {
|
||||
Objects.requireNonNull(inputs);
|
||||
// Checking that the Set of inputs does not contain a `null` value is made in the
|
||||
// implementing classes.
|
||||
Objects.requireNonNull(underlying);
|
||||
return inputs instanceof EnumSet<?> && !inputs.isEmpty()
|
||||
? StableEnumFunction.of(inputs, underlying)
|
||||
: StableFunction.of(inputs, underlying);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable list with the provided {@code size}}
|
||||
* <p>
|
||||
* The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list
|
||||
* with the provided {@code size}. The list's elements are computed via the
|
||||
* provided {@code mapper} when they are first accessed
|
||||
* (e.g. via {@linkplain List#get(int) List::get}).
|
||||
* <p>
|
||||
* The provided {@code mapper} function is guaranteed to be successfully invoked
|
||||
* at most once per list index, even in a multi-threaded environment. Competing
|
||||
* threads accessing an element already under computation will block until an element
|
||||
* is computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code mapper} function throws an exception, it
|
||||
* is rethrown to the initial caller and no value for the element is recorded.
|
||||
* <p>
|
||||
* Any {@link List#subList(int, int) subList} or {@link List#reversed()} views
|
||||
* of the returned list are also stable.
|
||||
* <p>
|
||||
* The returned list and its {@link List#subList(int, int) subList} or
|
||||
* {@link List#reversed()} views implement the {@link RandomAccess} interface.
|
||||
* <p>
|
||||
* The returned list is unmodifiable and does not implement the
|
||||
* {@linkplain Collection##optional-operation optional operations} in the
|
||||
* {@linkplain List} interface.
|
||||
* <p>
|
||||
* If the provided {@code mapper} recursively calls the returned list for the
|
||||
* same index, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param size the size of the returned list
|
||||
* @param mapper to invoke whenever an element is first accessed
|
||||
* (may return {@code null})
|
||||
* @param <E> the type of elements in the returned list
|
||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||
*/
|
||||
static <E> List<E> list(int size,
|
||||
IntFunction<? extends E> mapper) {
|
||||
StableUtil.assertSizeNonNegative(size);
|
||||
Objects.requireNonNull(mapper);
|
||||
return SharedSecrets.getJavaUtilCollectionAccess().stableList(size, mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new stable map with the provided {@code keys}}
|
||||
* <p>
|
||||
* The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose
|
||||
* keys are known at construction. The map's values are computed via the provided
|
||||
* {@code mapper} when they are first accessed
|
||||
* (e.g. via {@linkplain Map#get(Object) Map::get}).
|
||||
* <p>
|
||||
* The provided {@code mapper} function is guaranteed to be successfully invoked
|
||||
* at most once per key, even in a multi-threaded environment. Competing
|
||||
* threads accessing a value already under computation will block until an element
|
||||
* is computed or an exception is thrown by the computing thread.
|
||||
* <p>
|
||||
* If invoking the provided {@code mapper} function throws an exception, it
|
||||
* is rethrown to the initial caller and no value associated with the provided key
|
||||
* is recorded.
|
||||
* <p>
|
||||
* Any {@link Map#values()} or {@link Map#entrySet()} views of the returned map are
|
||||
* also stable.
|
||||
* <p>
|
||||
* The returned map is unmodifiable and does not implement the
|
||||
* {@linkplain Collection##optional-operations optional operations} in the
|
||||
* {@linkplain Map} interface.
|
||||
* <p>
|
||||
* If the provided {@code mapper} recursively calls the returned map for
|
||||
* the same key, an {@linkplain IllegalStateException} will be thrown.
|
||||
*
|
||||
* @param keys the (non-null) keys in the returned map
|
||||
* @param mapper to invoke whenever an associated value is first accessed
|
||||
* (may return {@code null})
|
||||
* @param <K> the type of keys maintained by the returned map
|
||||
* @param <V> the type of mapped values in the returned map
|
||||
* @throws NullPointerException if the provided set of {@code inputs} contains a
|
||||
* {@code null} element.
|
||||
*/
|
||||
static <K, V> Map<K, V> map(Set<K> keys,
|
||||
Function<? super K, ? extends V> mapper) {
|
||||
Objects.requireNonNull(keys);
|
||||
// Checking that the Set of keys does not contain a `null` value is made in the
|
||||
// implementing class.
|
||||
Objects.requireNonNull(mapper);
|
||||
return SharedSecrets.getJavaUtilCollectionAccess().stableMap(keys, mapper);
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,7 +25,6 @@
|
||||
|
||||
package java.nio.charset;
|
||||
|
||||
import jdk.internal.misc.ThreadTracker;
|
||||
import jdk.internal.misc.VM;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
@ -41,7 +40,6 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
@ -426,7 +424,7 @@ public abstract class Charset
|
||||
}
|
||||
|
||||
/* The extended set of charsets */
|
||||
private static final Supplier<List<CharsetProvider>> EXTENDED_PROVIDERS = StableValue.supplier(
|
||||
private static final LazyConstant<List<CharsetProvider>> EXTENDED_PROVIDERS = LazyConstant.of(
|
||||
new Supplier<>() { public List<CharsetProvider> get() { return extendedProviders0(); }});
|
||||
|
||||
private static List<CharsetProvider> extendedProviders0() {
|
||||
@ -617,7 +615,7 @@ public abstract class Charset
|
||||
return Collections.unmodifiableSortedMap(m);
|
||||
}
|
||||
|
||||
private static final Supplier<Charset> defaultCharset = StableValue.supplier(
|
||||
private static final LazyConstant<Charset> defaultCharset = LazyConstant.of(
|
||||
new Supplier<>() { public Charset get() { return defaultCharset0(); }});
|
||||
|
||||
private static Charset defaultCharset0() {
|
||||
@ -658,7 +656,7 @@ public abstract class Charset
|
||||
@Stable
|
||||
private final String[] aliases;
|
||||
@Stable
|
||||
private final Supplier<Set<String>> aliasSet = StableValue.supplier(
|
||||
private final LazyConstant<Set<String>> aliasSet = LazyConstant.of(
|
||||
new Supplier<>() { public Set<String> get() { return Set.of(aliases); }});
|
||||
|
||||
/**
|
||||
|
||||
@ -142,8 +142,8 @@ public final class Currency implements Serializable {
|
||||
// class data: instance map
|
||||
|
||||
private static ConcurrentMap<String, Currency> instances = new ConcurrentHashMap<>(7);
|
||||
private static final Supplier<HashSet<Currency>> available =
|
||||
StableValue.supplier(Currency::computeAllCurrencies);
|
||||
private static final LazyConstant<HashSet<Currency>> available =
|
||||
LazyConstant.of(Currency::computeAllCurrencies);
|
||||
|
||||
// Class data: currency data obtained from currency.data file.
|
||||
// Purpose:
|
||||
|
||||
@ -36,18 +36,12 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import jdk.internal.access.JavaUtilCollectionAccess;
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import jdk.internal.misc.CDS;
|
||||
import jdk.internal.util.ArraysSupport;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
/**
|
||||
@ -135,14 +129,6 @@ class ImmutableCollections {
|
||||
public <E> List<E> listFromTrustedArrayNullsAllowed(Object[] array) {
|
||||
return ImmutableCollections.listFromTrustedArrayNullsAllowed(array);
|
||||
}
|
||||
public <E> List<E> stableList(int size, IntFunction<? extends E> mapper) {
|
||||
// A stable list is not Serializable, so we cannot return `List.of()` if `size == 0`
|
||||
return new StableList<>(size, mapper);
|
||||
}
|
||||
public <K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
||||
// A stable map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
|
||||
return new StableMap<>(keys, mapper);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -450,7 +436,7 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
static sealed class SubList<E> extends AbstractImmutableList<E>
|
||||
static final class SubList<E> extends AbstractImmutableList<E>
|
||||
implements RandomAccess {
|
||||
|
||||
@Stable
|
||||
@ -462,8 +448,10 @@ class ImmutableCollections {
|
||||
@Stable
|
||||
final int size;
|
||||
|
||||
private SubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||
assert root instanceof List12 || root instanceof ListN || root instanceof StableList;
|
||||
SubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||
assert root instanceof List12
|
||||
|| root instanceof ListN
|
||||
|| root instanceof LazyCollections.LazyList;
|
||||
this.root = root;
|
||||
this.offset = offset;
|
||||
this.size = size;
|
||||
@ -795,187 +783,6 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface HasStableDelegates<E> {
|
||||
StableValueImpl<E>[] delegates();
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableList<E>
|
||||
extends AbstractImmutableList<E>
|
||||
implements HasStableDelegates<E> {
|
||||
|
||||
@Stable
|
||||
private final IntFunction<? extends E> mapper;
|
||||
@Stable
|
||||
final StableValueImpl<E>[] delegates;
|
||||
|
||||
StableList(int size, IntFunction<? extends E> mapper) {
|
||||
this.mapper = mapper;
|
||||
this.delegates = StableUtil.array(size);
|
||||
}
|
||||
|
||||
@Override public boolean isEmpty() { return delegates.length == 0;}
|
||||
@Override public int size() { return delegates.length; }
|
||||
@Override public Object[] toArray() { return copyInto(new Object[size()]); }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public E get(int i) {
|
||||
final StableValueImpl<E> delegate;
|
||||
try {
|
||||
delegate = delegates[i];
|
||||
} catch (ArrayIndexOutOfBoundsException aioobe) {
|
||||
throw new IndexOutOfBoundsException(i);
|
||||
}
|
||||
return delegate.orElseSet(new Supplier<E>() {
|
||||
@Override public E get() { return mapper.apply(i); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
final int size = delegates.length;
|
||||
if (a.length < size) {
|
||||
// Make a new array of a's runtime type, but my contents:
|
||||
T[] n = (T[])Array.newInstance(a.getClass().getComponentType(), size);
|
||||
return copyInto(n);
|
||||
}
|
||||
copyInto(a);
|
||||
if (a.length > size) {
|
||||
a[size] = null; // null-terminate
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
final int size = size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
for (int i = size() - 1; i >= 0; i--) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T[] copyInto(Object[] a) {
|
||||
final int len = delegates.length;
|
||||
for (int i = 0; i < len; i++) {
|
||||
a[i] = get(i);
|
||||
}
|
||||
return (T[]) a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> reversed() {
|
||||
return new StableReverseOrderListView<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
subListRangeCheck(fromIndex, toIndex, size());
|
||||
return StableSubList.fromStableList(this, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableCollection", delegates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StableValueImpl<E>[] delegates() {
|
||||
return delegates;
|
||||
}
|
||||
|
||||
private static final class StableSubList<E> extends SubList<E>
|
||||
implements HasStableDelegates<E> {
|
||||
|
||||
private StableSubList(AbstractImmutableList<E> root, int offset, int size) {
|
||||
super(root, offset, size);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> reversed() {
|
||||
return new StableReverseOrderListView<>(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
subListRangeCheck(fromIndex, toIndex, size());
|
||||
return StableSubList.fromStableSubList(this, fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableCollection", delegates());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean allowNulls() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StableValueImpl<E>[] delegates() {
|
||||
@SuppressWarnings("unchecked")
|
||||
final var rootDelegates = ((HasStableDelegates<E>) root).delegates();
|
||||
return Arrays.copyOfRange(rootDelegates, offset, offset + size);
|
||||
}
|
||||
|
||||
static <E> SubList<E> fromStableList(StableList<E> list, int fromIndex, int toIndex) {
|
||||
return new StableSubList<>(list, fromIndex, toIndex - fromIndex);
|
||||
}
|
||||
|
||||
static <E> SubList<E> fromStableSubList(StableSubList<E> parent, int fromIndex, int toIndex) {
|
||||
return new StableSubList<>(parent.root, parent.offset + fromIndex, toIndex - fromIndex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class StableReverseOrderListView<E>
|
||||
extends ReverseOrderListView.Rand<E>
|
||||
implements HasStableDelegates<E> {
|
||||
|
||||
private StableReverseOrderListView(List<E> base) {
|
||||
super(base, false);
|
||||
}
|
||||
|
||||
// This method does not evaluate the elements
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableCollection", delegates());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<E> subList(int fromIndex, int toIndex) {
|
||||
final int size = base.size();
|
||||
subListRangeCheck(fromIndex, toIndex, size);
|
||||
return new StableReverseOrderListView<>(base.subList(size - toIndex, size - fromIndex));
|
||||
}
|
||||
|
||||
@Override
|
||||
public StableValueImpl<E>[] delegates() {
|
||||
@SuppressWarnings("unchecked")
|
||||
final var baseDelegates = ((HasStableDelegates<E>) base).delegates();
|
||||
return ArraysSupport.reverse(
|
||||
Arrays.copyOf(baseDelegates, baseDelegates.length));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------- Set Implementations ----------
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
@ -1614,187 +1421,6 @@ class ImmutableCollections {
|
||||
}
|
||||
}
|
||||
|
||||
static final class StableMap<K, V>
|
||||
extends AbstractImmutableMap<K, V> {
|
||||
|
||||
@Stable
|
||||
private final Function<? super K, ? extends V> mapper;
|
||||
@Stable
|
||||
private final Map<K, StableValueImpl<V>> delegate;
|
||||
|
||||
StableMap(Set<K> keys, Function<? super K, ? extends V> mapper) {
|
||||
this.mapper = mapper;
|
||||
this.delegate = StableUtil.map(keys);
|
||||
}
|
||||
|
||||
@Override public boolean containsKey(Object o) { return delegate.containsKey(o); }
|
||||
@Override public int size() { return delegate.size(); }
|
||||
@Override public Set<Map.Entry<K, V>> entrySet() { return StableMapEntrySet.of(this); }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
return getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
final StableValueImpl<V> stable = delegate.get(key);
|
||||
if (stable == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
final K k = (K) key;
|
||||
return stable.orElseSet(new Supplier<V>() {
|
||||
@Override public V get() { return mapper.apply(k); }});
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableMapEntrySet<K, V> extends AbstractImmutableSet<Map.Entry<K, V>> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final StableMap<K, V> outer;
|
||||
|
||||
@Stable
|
||||
private final Set<Map.Entry<K, StableValueImpl<V>>> delegateEntrySet;
|
||||
|
||||
private StableMapEntrySet(StableMap<K, V> outer) {
|
||||
this.outer = outer;
|
||||
this.delegateEntrySet = outer.delegate.entrySet();
|
||||
}
|
||||
|
||||
@Override public Iterator<Map.Entry<K, V>> iterator() { return LazyMapIterator.of(this); }
|
||||
@Override public int size() { return delegateEntrySet.size(); }
|
||||
@Override public int hashCode() { return outer.hashCode(); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableCollection", delegateEntrySet, false);
|
||||
}
|
||||
|
||||
// For @ValueBased
|
||||
private static <K, V> StableMapEntrySet<K, V> of(StableMap<K, V> outer) {
|
||||
return new StableMapEntrySet<>(outer);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class LazyMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final StableMapEntrySet<K, V> outer;
|
||||
|
||||
@Stable
|
||||
private final Iterator<Map.Entry<K, StableValueImpl<V>>> delegateIterator;
|
||||
|
||||
private LazyMapIterator(StableMapEntrySet<K, V> outer) {
|
||||
this.outer = outer;
|
||||
this.delegateIterator = outer.delegateEntrySet.iterator();
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() { return delegateIterator.hasNext(); }
|
||||
|
||||
@Override
|
||||
public Entry<K, V> next() {
|
||||
final Map.Entry<K, StableValueImpl<V>> inner = delegateIterator.next();
|
||||
final K k = inner.getKey();
|
||||
return new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||
@Override public V get() { return outer.outer.mapper.apply(k); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super Map.Entry<K, V>> action) {
|
||||
final Consumer<? super Map.Entry<K, StableValueImpl<V>>> innerAction =
|
||||
new Consumer<>() {
|
||||
@Override
|
||||
public void accept(Entry<K, StableValueImpl<V>> inner) {
|
||||
final K k = inner.getKey();
|
||||
action.accept(new StableEntry<>(k, inner.getValue(), new Supplier<V>() {
|
||||
@Override public V get() { return outer.outer.mapper.apply(k); }}));
|
||||
}
|
||||
};
|
||||
delegateIterator.forEachRemaining(innerAction);
|
||||
}
|
||||
|
||||
// For @ValueBased
|
||||
private static <K, V> LazyMapIterator<K, V> of(StableMapEntrySet<K, V> outer) {
|
||||
return new LazyMapIterator<>(outer);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private record StableEntry<K, V>(K getKey, // trick
|
||||
StableValueImpl<V> stableValue,
|
||||
Supplier<? extends V> supplier) implements Map.Entry<K, V> {
|
||||
|
||||
@Override public V setValue(V value) { throw uoe(); }
|
||||
@Override public V getValue() { return stableValue.orElseSet(supplier); }
|
||||
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
|
||||
@Override public String toString() { return getKey() + "=" + stableValue.toString(); }
|
||||
@Override public boolean equals(Object o) {
|
||||
return o instanceof Map.Entry<?, ?> e
|
||||
&& Objects.equals(getKey(), e.getKey())
|
||||
// Invoke `getValue()` as late as possible to avoid evaluation
|
||||
&& Objects.equals(getValue(), e.getValue());
|
||||
}
|
||||
|
||||
private int hash(Object obj) { return (obj == null) ? 0 : obj.hashCode(); }
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return StableMapValues.of(this);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class StableMapValues<V> extends AbstractImmutableCollection<V> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final StableMap<?, V> outer;
|
||||
|
||||
private StableMapValues(StableMap<?, V> outer) {
|
||||
this.outer = outer;
|
||||
}
|
||||
|
||||
@Override public Iterator<V> iterator() { return outer.new ValueIterator(); }
|
||||
@Override public int size() { return outer.size(); }
|
||||
@Override public boolean isEmpty() { return outer.isEmpty();}
|
||||
@Override public boolean contains(Object v) { return outer.containsValue(v); }
|
||||
|
||||
private static final IntFunction<StableValueImpl<?>[]> GENERATOR = new IntFunction<StableValueImpl<?>[]>() {
|
||||
@Override
|
||||
public StableValueImpl<?>[] apply(int len) {
|
||||
return new StableValueImpl<?>[len];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StableValueImpl<?>[] values = outer.delegate.values().toArray(GENERATOR);
|
||||
return StableUtil.renderElements(this, "StableCollection", values);
|
||||
}
|
||||
|
||||
// For @ValueBased
|
||||
private static <V> StableMapValues<V> of(StableMap<?, V> outer) {
|
||||
return new StableMapValues<>(outer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableMap", delegate.entrySet(), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---------- Serialization Proxy ----------
|
||||
|
||||
584
src/java.base/share/classes/java/util/LazyCollections.java
Normal file
584
src/java.base/share/classes/java/util/LazyCollections.java
Normal file
@ -0,0 +1,584 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.util.ImmutableBitSetPredicate;
|
||||
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.lang.LazyConstant;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Container class for lazy collections implementations. Not part of the public API.
|
||||
*/
|
||||
@AOTSafeClassInitializer
|
||||
final class LazyCollections {
|
||||
|
||||
/**
|
||||
* No instances.
|
||||
*/
|
||||
private LazyCollections() { }
|
||||
|
||||
// Unsafe allows LazyCollection classes to be used early in the boot sequence
|
||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class LazyList<E>
|
||||
extends ImmutableCollections.AbstractImmutableList<E> {
|
||||
|
||||
@Stable
|
||||
private final E[] elements;
|
||||
// Keeping track of `size` separately reduces bytecode size compared to
|
||||
// using `elements.length`.
|
||||
@Stable
|
||||
private final int size;
|
||||
@Stable
|
||||
final FunctionHolder<IntFunction<? extends E>> functionHolder;
|
||||
@Stable
|
||||
private final Mutexes mutexes;
|
||||
|
||||
private LazyList(int size, IntFunction<? extends E> computingFunction) {
|
||||
this.elements = newGenericArray(size);
|
||||
this.size = size;
|
||||
this.functionHolder = new FunctionHolder<>(computingFunction, size);
|
||||
this.mutexes = new Mutexes(size);
|
||||
super();
|
||||
}
|
||||
|
||||
@Override public boolean isEmpty() { return size == 0; }
|
||||
@Override public int size() { return size; }
|
||||
@Override public Object[] toArray() { return copyInto(new Object[size]); }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public E get(int i) {
|
||||
final E e = contentsAcquire(offsetFor(Objects.checkIndex(i, size)));
|
||||
return (e != null) ? e : getSlowPath(i);
|
||||
}
|
||||
|
||||
private E getSlowPath(int i) {
|
||||
return orElseComputeSlowPath(elements, i, mutexes, i, functionHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] toArray(T[] a) {
|
||||
if (a.length < size) {
|
||||
// Make a new array of a's runtime type, but my contents:
|
||||
T[] n = (T[]) Array.newInstance(a.getClass().getComponentType(), size);
|
||||
return copyInto(n);
|
||||
}
|
||||
copyInto(a);
|
||||
if (a.length > size) {
|
||||
a[size] = null; // null-terminate
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
for (int i = size - 1; i >= 0; i--) {
|
||||
if (Objects.equals(o, get(i))) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T[] copyInto(Object[] a) {
|
||||
for (int i = 0; i < size; i++) {
|
||||
a[i] = get(i);
|
||||
}
|
||||
return (T[]) a;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ForceInline
|
||||
private E contentsAcquire(long offset) {
|
||||
return (E) UNSAFE.getReferenceAcquire(elements, offset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class LazyEnumMap<K extends Enum<K>, V>
|
||||
extends AbstractLazyMap<K, V> {
|
||||
|
||||
@Stable
|
||||
private final Class<K> enumType;
|
||||
@Stable
|
||||
// We are using a wrapper class here to be able to use a min value of zero that
|
||||
// is also stable.
|
||||
private final Integer min;
|
||||
@Stable
|
||||
private final IntPredicate member;
|
||||
|
||||
public LazyEnumMap(Set<K> set,
|
||||
Class<K> enumType,
|
||||
int min,
|
||||
int backingSize,
|
||||
IntPredicate member,
|
||||
Function<? super K, ? extends V> computingFunction) {
|
||||
this.enumType = enumType;
|
||||
this.min = min;
|
||||
this.member = member;
|
||||
super(set, set.size(), backingSize, computingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ForceInline
|
||||
public boolean containsKey(Object o) {
|
||||
return enumType.isAssignableFrom(o.getClass())
|
||||
&& member.test(((Enum<?>) o).ordinal());
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
if (enumType.isAssignableFrom(key.getClass())) {
|
||||
final int ordinal = ((Enum<?>) key).ordinal();
|
||||
if (member.test(ordinal)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final K k = (K) key;
|
||||
return orElseCompute(k, indexForAsInt(k));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
Integer indexFor(K key) {
|
||||
return indexForAsInt(key);
|
||||
}
|
||||
|
||||
private int indexForAsInt(K key) {
|
||||
return key.ordinal() - min;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class LazyMap<K, V>
|
||||
extends AbstractLazyMap<K, V> {
|
||||
|
||||
// Use an unmodifiable map with known entries that are @Stable. Lookups through this map can be folded because
|
||||
// it is created using Map.ofEntrie. This allows us to avoid creating a separate hashing function.
|
||||
@Stable
|
||||
private final Map<K, Integer> indexMapper;
|
||||
|
||||
public LazyMap(Set<K> keys, Function<? super K, ? extends V> computingFunction) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final Entry<K, Integer>[] entries = (Entry<K, Integer>[]) new Entry<?, ?>[keys.size()];
|
||||
int i = 0;
|
||||
for (K k : keys) {
|
||||
entries[i] = Map.entry(k, i++);
|
||||
}
|
||||
this.indexMapper = Map.ofEntries(entries);
|
||||
super(keys, i, i, computingFunction);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
final Integer index = indexMapper.get(key);
|
||||
if (index != null) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final K k = (K) key;
|
||||
return orElseCompute(k, index);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@Override public boolean containsKey(Object o) { return indexMapper.containsKey(o); }
|
||||
|
||||
@Override
|
||||
Integer indexFor(K key) {
|
||||
return indexMapper.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
static sealed abstract class AbstractLazyMap<K, V>
|
||||
extends ImmutableCollections.AbstractImmutableMap<K, V> {
|
||||
|
||||
// This field shadows AbstractMap.keySet which is not @Stable.
|
||||
@Stable
|
||||
Set<K> keySet;
|
||||
// This field shadows AbstractMap.values which is of another type
|
||||
@Stable
|
||||
final V[] values;
|
||||
@Stable
|
||||
Mutexes mutexes;
|
||||
@Stable
|
||||
private final int size;
|
||||
@Stable
|
||||
final FunctionHolder<Function<? super K, ? extends V>> functionHolder;
|
||||
@Stable
|
||||
private final Set<Entry<K, V>> entrySet;
|
||||
|
||||
private AbstractLazyMap(Set<K> keySet,
|
||||
int size,
|
||||
int backingSize,
|
||||
Function<? super K, ? extends V> computingFunction) {
|
||||
this.size = size;
|
||||
this.functionHolder = new FunctionHolder<>(computingFunction, size);
|
||||
this.values = newGenericArray(backingSize);
|
||||
this.mutexes = new Mutexes(backingSize);
|
||||
super();
|
||||
this.keySet = keySet;
|
||||
this.entrySet = LazyMapEntrySet.of(this);
|
||||
}
|
||||
|
||||
// Abstract methods
|
||||
@Override public abstract boolean containsKey(Object o);
|
||||
abstract Integer indexFor(K key);
|
||||
|
||||
// Public methods
|
||||
@Override public final int size() { return size; }
|
||||
@Override public final boolean isEmpty() { return size == 0; }
|
||||
@Override public final Set<Entry<K, V>> entrySet() { return entrySet; }
|
||||
@Override public Set<K> keySet() { return keySet; }
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public final V get(Object key) {
|
||||
return getOrDefault(key, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ForceInline
|
||||
final V orElseCompute(K key, int index) {
|
||||
final long offset = offsetFor(index);
|
||||
final V v = (V) UNSAFE.getReferenceAcquire(values, offset);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
return orElseComputeSlowPath(values, index, mutexes, key, functionHolder);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class LazyMapEntrySet<K, V> extends ImmutableCollections.AbstractImmutableSet<Entry<K, V>> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final AbstractLazyMap<K, V> map;
|
||||
|
||||
private LazyMapEntrySet(AbstractLazyMap<K, V> map) {
|
||||
this.map = map;
|
||||
super();
|
||||
}
|
||||
|
||||
@Override public Iterator<Entry<K, V>> iterator() { return LazyMapIterator.of(map); }
|
||||
@Override public int size() { return map.size(); }
|
||||
@Override public int hashCode() { return map.hashCode(); }
|
||||
|
||||
// For @ValueBased
|
||||
private static <K, V> LazyMapEntrySet<K, V> of(AbstractLazyMap<K, V> outer) {
|
||||
return new LazyMapEntrySet<>(outer);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class LazyMapIterator<K, V> implements Iterator<Entry<K, V>> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final AbstractLazyMap<K, V> map;
|
||||
@Stable
|
||||
private final Iterator<K> keyIterator;
|
||||
|
||||
private LazyMapIterator(AbstractLazyMap<K, V> map) {
|
||||
this.map = map;
|
||||
this.keyIterator = map.keySet.iterator();
|
||||
super();
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() { return keyIterator.hasNext(); }
|
||||
|
||||
@Override
|
||||
public Entry<K, V> next() {
|
||||
final K k = keyIterator.next();
|
||||
return new LazyEntry<>(k, map, map.functionHolder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEachRemaining(Consumer<? super Entry<K, V>> action) {
|
||||
final Consumer<? super K> innerAction =
|
||||
new Consumer<>() {
|
||||
@Override
|
||||
public void accept(K key) {
|
||||
action.accept(new LazyEntry<>(key, map, map.functionHolder));
|
||||
}
|
||||
};
|
||||
keyIterator.forEachRemaining(innerAction);
|
||||
}
|
||||
|
||||
// For @ValueBased
|
||||
private static <K, V> LazyMapIterator<K, V> of(AbstractLazyMap<K, V> map) {
|
||||
return new LazyMapIterator<>(map);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private record LazyEntry<K, V>(K getKey, // trick
|
||||
AbstractLazyMap<K, V> map,
|
||||
FunctionHolder<Function<? super K, ? extends V>> functionHolder) implements Entry<K, V> {
|
||||
|
||||
@Override public V setValue(V value) { throw ImmutableCollections.uoe(); }
|
||||
@Override public V getValue() { return map.orElseCompute(getKey, map.indexFor(getKey)); }
|
||||
@Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); }
|
||||
@Override public String toString() { return getKey() + "=" + getValue(); }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof Map.Entry<?, ?> e
|
||||
&& Objects.equals(getKey(), e.getKey())
|
||||
// Invoke `getValue()` as late as possible to avoid evaluation
|
||||
&& Objects.equals(getValue(), e.getValue());
|
||||
}
|
||||
|
||||
private int hash(Object obj) {
|
||||
return (obj == null) ? 0 : obj.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return LazyMapValues.of(this);
|
||||
}
|
||||
|
||||
@jdk.internal.ValueBased
|
||||
static final class LazyMapValues<K, V> extends ImmutableCollections.AbstractImmutableCollection<V> {
|
||||
|
||||
// Use a separate field for the outer class in order to facilitate
|
||||
// a @Stable annotation.
|
||||
@Stable
|
||||
private final AbstractLazyMap<K, V> map;
|
||||
|
||||
private LazyMapValues(AbstractLazyMap<K, V> map) {
|
||||
this.map = map;
|
||||
super();
|
||||
}
|
||||
|
||||
@Override public Iterator<V> iterator() { return map.new ValueIterator(); }
|
||||
@Override public int size() { return map.size(); }
|
||||
@Override public boolean isEmpty() { return map.isEmpty(); }
|
||||
@Override public boolean contains(Object v) { return map.containsValue(v); }
|
||||
|
||||
// For @ValueBased
|
||||
private static <K, V> LazyMapValues<K, V> of(AbstractLazyMap<K, V> outer) {
|
||||
return new LazyMapValues<>(outer);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static final class Mutexes {
|
||||
|
||||
private static final Object TOMB_STONE = new Object();
|
||||
|
||||
// Filled on demand and then discarded once it is not needed anymore.
|
||||
// A mutex element can only transition like so: `null` -> `new Object()` -> `TOMB_STONE`
|
||||
private volatile Object[] mutexes;
|
||||
// Used to detect we have computed all elements and no longer need the `mutexes` array
|
||||
private volatile AtomicInteger counter;
|
||||
|
||||
private Mutexes(int length) {
|
||||
this.mutexes = new Object[length];
|
||||
this.counter = new AtomicInteger(length);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private Object acquireMutex(long offset) {
|
||||
assert mutexes != null;
|
||||
// Check if there already is a mutex (Object or TOMB_STONE)
|
||||
final Object mutex = UNSAFE.getReferenceVolatile(mutexes, offset);
|
||||
if (mutex != null) {
|
||||
return mutex;
|
||||
}
|
||||
// Protect against racy stores of mutex candidates
|
||||
final Object candidate = new Object();
|
||||
final Object witness = UNSAFE.compareAndExchangeReference(mutexes, offset, null, candidate);
|
||||
return witness == null ? candidate : witness;
|
||||
}
|
||||
|
||||
private void releaseMutex(long offset) {
|
||||
// Replace the old mutex with a tomb stone since now the old mutex can be collected.
|
||||
UNSAFE.putReference(mutexes, offset, TOMB_STONE);
|
||||
if (counter != null && counter.decrementAndGet() == 0) {
|
||||
mutexes = null;
|
||||
counter = null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static long offsetFor(long index) {
|
||||
return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * index;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E> E[] newGenericArray(int length) {
|
||||
return (E[]) new Object[length];
|
||||
}
|
||||
|
||||
public static <E> List<E> ofLazyList(int size,
|
||||
IntFunction<? extends E> computingFunction) {
|
||||
return new LazyList<>(size, computingFunction);
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> ofLazyMap(Set<K> keys,
|
||||
Function<? super K, ? extends V> computingFunction) {
|
||||
return new LazyMap<>(keys, computingFunction);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <K, E extends Enum<E>, V>
|
||||
Map<K, V> ofLazyMapWithEnumKeys(Set<K> keys,
|
||||
Function<? super K, ? extends V> computingFunction) {
|
||||
// The input set is not empty
|
||||
final Class<E> enumType = ((E) keys.iterator().next()).getDeclaringClass();
|
||||
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
for (K t : keys) {
|
||||
final int ordinal = ((E) t).ordinal();
|
||||
min = Math.min(min, ordinal);
|
||||
max = Math.max(max, ordinal);
|
||||
bitSet.set(ordinal);
|
||||
}
|
||||
final int backingSize = max - min + 1;
|
||||
final IntPredicate member = ImmutableBitSetPredicate.of(bitSet);
|
||||
return (Map<K, V>) new LazyEnumMap<>((Set<E>) keys, enumType, min, backingSize, member, (Function<E, V>) computingFunction);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T> T orElseComputeSlowPath(final T[] array,
|
||||
final int index,
|
||||
final Mutexes mutexes,
|
||||
final Object input,
|
||||
final FunctionHolder<?> functionHolder) {
|
||||
final long offset = offsetFor(index);
|
||||
final Object mutex = mutexes.acquireMutex(offset);
|
||||
preventReentry(mutex);
|
||||
synchronized (mutex) {
|
||||
final T t = array[index]; // Plain semantics suffice here
|
||||
if (t == null) {
|
||||
final T newValue = switch (functionHolder.function()) {
|
||||
case IntFunction<?> iFun -> (T) iFun.apply((int) input);
|
||||
case Function<?, ?> fun -> ((Function<Object, T>) fun).apply(input);
|
||||
default -> throw new InternalError("cannot reach here");
|
||||
};
|
||||
Objects.requireNonNull(newValue);
|
||||
// Reduce the counter and if it reaches zero, clear the reference
|
||||
// to the underlying holder.
|
||||
functionHolder.countDown();
|
||||
|
||||
// The mutex is not reentrant so we know newValue should be returned
|
||||
set(array, index, mutex, newValue);
|
||||
// We do not need the mutex anymore
|
||||
mutexes.releaseMutex(offset);
|
||||
return newValue;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
static void preventReentry(Object mutex) {
|
||||
if (Thread.holdsLock(mutex)) {
|
||||
throw new IllegalStateException("Recursive initialization of a lazy collection is illegal");
|
||||
}
|
||||
}
|
||||
|
||||
static <T> void set(T[] array, int index, Object mutex, T newValue) {
|
||||
assert Thread.holdsLock(mutex) : index + "didn't hold " + mutex;
|
||||
// We know we hold the monitor here so plain semantic is enough
|
||||
// This is an extra safety net to emulate a CAS op.
|
||||
if (array[index] == null) {
|
||||
UNSAFE.putReferenceRelease(array, Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * (long) index, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is thread safe. Any thread can create and use an instance of this class at
|
||||
* any time. The `function` field is only accessed if `counter` is positive so the setting
|
||||
* of function to `null` is safe.
|
||||
*
|
||||
* @param <U> the underlying function type
|
||||
*/
|
||||
@AOTSafeClassInitializer
|
||||
static final class FunctionHolder<U> {
|
||||
|
||||
private static final long COUNTER_OFFSET = UNSAFE.objectFieldOffset(FunctionHolder.class, "counter");
|
||||
|
||||
// This field can only transition at most once from being set to a
|
||||
// non-null reference to being `null`. Once `null`, it is never read.
|
||||
private U function;
|
||||
// Used reflectively via Unsafe
|
||||
private int counter;
|
||||
|
||||
public FunctionHolder(U function, int counter) {
|
||||
this.function = (counter == 0) ? null : function;
|
||||
this.counter = counter;
|
||||
// Safe publication
|
||||
UNSAFE.storeStoreFence();
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
public U function() {
|
||||
return function;
|
||||
}
|
||||
|
||||
public void countDown() {
|
||||
if (UNSAFE.getAndAddInt(this, COUNTER_OFFSET, -1) == 1) {
|
||||
// Do not reference the underlying function anymore so it can be collected.
|
||||
function = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -25,6 +25,11 @@
|
||||
|
||||
package java.util;
|
||||
|
||||
import jdk.internal.foreign.Utils;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/**
|
||||
@ -87,9 +92,9 @@ import java.util.function.UnaryOperator;
|
||||
* interface.
|
||||
*
|
||||
* <h2><a id="unmodifiable">Unmodifiable Lists</a></h2>
|
||||
* <p>The {@link List#of(Object...) List.of} and
|
||||
* {@link List#copyOf List.copyOf} static factory methods
|
||||
* provide a convenient way to create unmodifiable lists. The {@code List}
|
||||
* <p>The {@link List#of(Object...) List.of},
|
||||
* {@link List#copyOf List.copyOf}, and {@link List#ofLazy(int, IntFunction)} static
|
||||
* factory methods provide a convenient way to create unmodifiable lists. The {@code List}
|
||||
* instances created by these methods have the following characteristics:
|
||||
*
|
||||
* <ul>
|
||||
@ -100,7 +105,7 @@ import java.util.function.UnaryOperator;
|
||||
* this may cause the List's contents to appear to change.
|
||||
* <li>They disallow {@code null} elements. Attempts to create them with
|
||||
* {@code null} elements result in {@code NullPointerException}.
|
||||
* <li>They are serializable if all elements are serializable.
|
||||
* <li>Unless otherwise specified, they are serializable if all elements are serializable.
|
||||
* <li>The order of elements in the list is the same as the order of the
|
||||
* provided arguments, or of the elements in the provided array.
|
||||
* <li>The lists and their {@link #subList(int, int) subList} views implement the
|
||||
@ -1190,4 +1195,71 @@ public interface List<E> extends SequencedCollection<E> {
|
||||
static <E> List<E> copyOf(Collection<? extends E> coll) {
|
||||
return ImmutableCollections.listCopy(coll);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new lazily computed list of the provided {@code size}}
|
||||
* <p>
|
||||
* The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list
|
||||
* with the provided {@code size}. The list's elements are lazily computed via the
|
||||
* provided {@code computingFunction} when they are first accessed
|
||||
* (e.g., via {@linkplain List#get(int) List::get}).
|
||||
* <p>
|
||||
* The provided computing function is guaranteed to be successfully
|
||||
* invoked at most once per list index, even in a multi-threaded environment.
|
||||
* Competing threads accessing an element already under computation will block until
|
||||
* an element is computed or the computing function completes abnormally
|
||||
* <p>
|
||||
* If invoking the provided computing function throws an exception, it is rethrown
|
||||
* to the initial caller and no value for the element is recorded.
|
||||
* <p>
|
||||
* If the provided computing function returns {@code null},
|
||||
* a {@linkplain NullPointerException} will be thrown. Hence, just like other
|
||||
* unmodifiable lists created via the {@code List::of} factories, a lazy list
|
||||
* cannot contain {@code null} elements. Clients that want to use nullable elements
|
||||
* can wrap elements into an {@linkplain Optional} holder.
|
||||
* <p>
|
||||
* The elements of any {@link List#subList(int, int) subList()} or
|
||||
* {@link List#reversed()} views of the returned list are also lazily computed.
|
||||
* <p>
|
||||
* The returned list and its {@link List#subList(int, int) subList()} or
|
||||
* {@link List#reversed()} views implement the {@link RandomAccess} interface.
|
||||
* <p>
|
||||
* If the provided computing function recursively calls itself or the returned
|
||||
* lazy list for the same index, an {@linkplain IllegalStateException}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The returned list's {@linkplain Object Object methods};
|
||||
* {@linkplain Object#equals(Object) equals()},
|
||||
* {@linkplain Object#hashCode() hashCode()}, and
|
||||
* {@linkplain Object#toString() toString()} methods may trigger initialization of
|
||||
* one or more lazy elements.
|
||||
* <p>
|
||||
* The returned lazy list strongly references its computing
|
||||
* function used to compute elements at least so long as there are uninitialized
|
||||
* elements.
|
||||
* <p>
|
||||
* The returned List is <em>not</em> {@linkplain Serializable}.
|
||||
*
|
||||
* @implNote after all elements have been initialized successfully, the computing
|
||||
* function is no longer strongly referenced and becomes eligible for
|
||||
* garbage collection.
|
||||
*
|
||||
* @param size the size of the returned lazy list
|
||||
* @param computingFunction to invoke whenever an element is first accessed
|
||||
* (may not return {@code null})
|
||||
* @param <E> the type of elements in the returned list
|
||||
* @throws IllegalArgumentException if the provided {@code size} is negative.
|
||||
*
|
||||
* @see LazyConstant
|
||||
* @since 26
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
|
||||
static <E> List<E> ofLazy(int size,
|
||||
IntFunction<? extends E> computingFunction) {
|
||||
Utils.checkNonNegativeArgument(size, "size");
|
||||
Objects.requireNonNull(computingFunction);
|
||||
// A computed list is not Serializable, so we cannot return `List.of()` if `size == 0`
|
||||
return LazyCollections.ofLazyList(size, computingFunction);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -992,8 +992,8 @@ public final class Locale implements Cloneable, Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Supplier<ReferencedKeyMap<Object, Locale>> LOCALE_CACHE =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
private static final LazyConstant<ReferencedKeyMap<Object, Locale>> LOCALE_CACHE =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public ReferencedKeyMap<Object, Locale> get() {
|
||||
return ReferencedKeyMap.create(true, ReferencedKeyMap.concurrentHashMapSupplier());
|
||||
@ -2330,8 +2330,8 @@ public final class Locale implements Cloneable, Serializable {
|
||||
private static volatile Locale defaultDisplayLocale;
|
||||
private static volatile Locale defaultFormatLocale;
|
||||
|
||||
private final transient Supplier<String> languageTag =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
private final transient LazyConstant<String> languageTag =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public String get() {
|
||||
return computeLanguageTag();
|
||||
|
||||
@ -30,32 +30,32 @@ import java.util.function.Supplier;
|
||||
// Methods and suppliers for producing ISO 639/3166 resources used by Locale.
|
||||
class LocaleISOData {
|
||||
|
||||
static final Supplier<String[]> ISO_639 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
static final LazyConstant<String[]> ISO_639 =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public String[] get() {
|
||||
return getISO2Table(isoLanguageTable);
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<String[]> ISO_3166_1_ALPHA2 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
static final LazyConstant<String[]> ISO_3166_1_ALPHA2 =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public String[] get() {
|
||||
return getISO2Table(isoCountryTable);
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<Set<String>> ISO_3166_1_ALPHA3 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
static final LazyConstant<Set<String>> ISO_3166_1_ALPHA3 =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public Set<String> get() {
|
||||
return computeISO3166_1Alpha3Countries();
|
||||
}
|
||||
});
|
||||
|
||||
static final Supplier<Set<String>> ISO_3166_3 =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
static final LazyConstant<Set<String>> ISO_3166_3 =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public Set<String> get() {
|
||||
return Set.of(ISO3166_3);
|
||||
|
||||
@ -25,6 +25,8 @@
|
||||
|
||||
package java.util;
|
||||
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
@ -114,8 +116,9 @@ import java.io.Serializable;
|
||||
*
|
||||
* <h2><a id="unmodifiable">Unmodifiable Maps</a></h2>
|
||||
* <p>The {@link Map#of() Map.of},
|
||||
* {@link Map#ofEntries(Map.Entry...) Map.ofEntries}, and
|
||||
* {@link Map#copyOf Map.copyOf}
|
||||
* {@link Map#ofEntries(Map.Entry...) Map.ofEntries},
|
||||
* {@link Map#copyOf Map.copyOf}, and
|
||||
* {@link Map#ofLazy(Set, Function)}
|
||||
* static factory methods provide a convenient way to create unmodifiable maps.
|
||||
* The {@code Map}
|
||||
* instances created by these methods have the following characteristics:
|
||||
@ -128,7 +131,8 @@ import java.io.Serializable;
|
||||
* Map to behave inconsistently or its contents to appear to change.
|
||||
* <li>They disallow {@code null} keys and values. Attempts to create them with
|
||||
* {@code null} keys or values result in {@code NullPointerException}.
|
||||
* <li>They are serializable if all keys and values are serializable.
|
||||
* <li>Unless otherwise specified, they are serializable if all keys and values
|
||||
* are serializable.
|
||||
* <li>They reject duplicate keys at creation time. Duplicate keys
|
||||
* passed to a static factory method result in {@code IllegalArgumentException}.
|
||||
* <li>The iteration order of mappings is unspecified and is subject to change.
|
||||
@ -1746,4 +1750,79 @@ public interface Map<K, V> {
|
||||
return (Map<K,V>)Map.ofEntries(map.entrySet().toArray(new Entry[0]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new lazily computed map with the provided {@code keys}}
|
||||
* <p>
|
||||
* The returned map is an {@linkplain Collection##unmodifiable unmodifiable} map whose
|
||||
* keys are known at construction. The map's values are lazily computed via the
|
||||
* provided {@code computingFunction} when they are first accessed
|
||||
* (e.g., via {@linkplain Map#get(Object) Map::get}).
|
||||
* <p>
|
||||
* The provided computing function is guaranteed to be successfully invoked
|
||||
* at most once per key, even in a multi-threaded environment. Competing
|
||||
* threads accessing a value already under computation will block until an element
|
||||
* is computed or the computing function completes abnormally.
|
||||
* <p>
|
||||
* If invoking the provided computing function throws an exception, it
|
||||
* is rethrown to the initial caller and no value associated with the provided key
|
||||
* is recorded.
|
||||
* <p>
|
||||
* If the provided computing function returns {@code null},
|
||||
* a {@linkplain NullPointerException} will be thrown. Hence, just like other
|
||||
* unmodifiable maps created via the {@code Map::of} factories, a lazy map
|
||||
* cannot contain {@code null} values. Clients that want to use nullable values can
|
||||
* wrap values into an {@linkplain Optional} holder.
|
||||
* <p>
|
||||
* The values of any {@link Map#values()} or {@link Map#entrySet()} views of
|
||||
* the returned map are also lazily computed.
|
||||
* <p>
|
||||
* If the provided computing function recursively calls itself or
|
||||
* the returned lazy map for the same key, an {@linkplain IllegalStateException}
|
||||
* will be thrown.
|
||||
* <p>
|
||||
* The returned map's {@linkplain Object Object methods};
|
||||
* {@linkplain Object#equals(Object) equals()},
|
||||
* {@linkplain Object#hashCode() hashCode()}, and
|
||||
* {@linkplain Object#toString() toString()} methods may trigger initialization of
|
||||
* one or more lazy elements.
|
||||
* <p>
|
||||
* The returned lazy map strongly references its underlying
|
||||
* computing function used to compute values at least so long as there are
|
||||
* uncomputed values.
|
||||
* <p>
|
||||
* The returned Map is <em>not</em> {@linkplain Serializable}.
|
||||
*
|
||||
* @implNote after all values have been initialized successfully, the computing
|
||||
* function is no longer strongly referenced and becomes eligible for
|
||||
* garbage collection.
|
||||
*
|
||||
* @param keys the (non-null) keys in the returned computed map
|
||||
* @param computingFunction to invoke whenever an associated value is first accessed
|
||||
* @param <K> the type of keys maintained by the returned map
|
||||
* @param <V> the type of mapped values in the returned map
|
||||
* @throws NullPointerException if the provided set of {@code keys} is {@code null}
|
||||
* or if the set of {@code keys} contains a {@code null} element.
|
||||
*
|
||||
* @see LazyConstant
|
||||
* @since 26
|
||||
*/
|
||||
@PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS)
|
||||
static <K, V> Map<K, V> ofLazy(Set<? extends K> keys,
|
||||
Function<? super K, ? extends V> computingFunction) {
|
||||
// Protect against TOC-TOU attacks.
|
||||
// Also, implicit null check of `keys` and all its elements
|
||||
final Set<K> keyCopies = Set.copyOf(keys);
|
||||
Objects.requireNonNull(computingFunction);
|
||||
// We need to check the instance type using the original `keys` parameter.
|
||||
if (keys instanceof EnumSet<?> && !keyCopies.isEmpty()) {
|
||||
@SuppressWarnings("unchecked")
|
||||
var enumMap = (Map<K, V>) LazyCollections.ofLazyMapWithEnumKeys(keyCopies, computingFunction);
|
||||
return enumMap;
|
||||
} else {
|
||||
// A computed map is not Serializable, so we cannot return `Map.of()` if `keys.isEmpty()`
|
||||
return LazyCollections.ofLazyMap(keyCopies, computingFunction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -22,8 +22,11 @@
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package java.util;
|
||||
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
@ -68,6 +71,7 @@ public final class Optional<T> {
|
||||
/**
|
||||
* If non-null, the value; if null, indicates no value is present
|
||||
*/
|
||||
@Stable
|
||||
private final T value;
|
||||
|
||||
/**
|
||||
|
||||
@ -488,7 +488,7 @@ public abstract class ResourceBundle {
|
||||
/**
|
||||
* A Set of the keys contained only in this ResourceBundle.
|
||||
*/
|
||||
private final Supplier<Set<String>> keySet = StableValue.supplier(
|
||||
private final LazyConstant<Set<String>> keySet = LazyConstant.of(
|
||||
new Supplier<>() { public Set<String> get() { return keySet0(); }});
|
||||
|
||||
private Set<String> keySet0() {
|
||||
|
||||
@ -26,14 +26,8 @@
|
||||
package jdk.internal.access;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
public interface JavaUtilCollectionAccess {
|
||||
<E> List<E> listFromTrustedArray(Object[] array);
|
||||
<E> List<E> listFromTrustedArrayNullsAllowed(Object[] array);
|
||||
<E> List<E> stableList(int size, IntFunction<? extends E> mapper);
|
||||
<K, V> Map<K, V> stableMap(Set<K> keys, Function<? super K, ? extends V> mapper);
|
||||
}
|
||||
|
||||
@ -38,6 +38,8 @@ import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
@ -57,7 +59,7 @@ public final class CaptureStateUtil {
|
||||
// of combinators needed to form an adapted method handle.
|
||||
// The function is lazily computed.
|
||||
//
|
||||
private static final Function<SegmentExtractorKey, MethodHandle> SEGMENT_EXTRACTION_HANDLE_CACHE;
|
||||
private static final Map<SegmentExtractorKey, MethodHandle> SEGMENT_EXTRACTION_HANDLE_CACHE;
|
||||
|
||||
static {
|
||||
final Set<SegmentExtractorKey> inputs = new HashSet<>();
|
||||
@ -77,7 +79,7 @@ public final class CaptureStateUtil {
|
||||
}
|
||||
};
|
||||
|
||||
SEGMENT_EXTRACTION_HANDLE_CACHE = StableValue.function(inputs, segmentExtractionHandle);
|
||||
SEGMENT_EXTRACTION_HANDLE_CACHE = Map.ofLazy(inputs, segmentExtractionHandle);
|
||||
}
|
||||
|
||||
// A key that holds both the `returnType` and the `stateName` needed to look up a
|
||||
@ -188,7 +190,10 @@ public final class CaptureStateUtil {
|
||||
final SegmentExtractorKey key = new SegmentExtractorKey(target, stateName);
|
||||
|
||||
// ((int | long), MemorySegment)(int | long)
|
||||
final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.apply(key);
|
||||
final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.get(key);
|
||||
if (segmentExtractor == null) {
|
||||
throw new IllegalArgumentException("Input not allowed: " + key);
|
||||
}
|
||||
|
||||
// Make `target` specific adaptations of the basic handle
|
||||
|
||||
@ -208,7 +213,7 @@ public final class CaptureStateUtil {
|
||||
|
||||
// Use an `Arena` for the first argument instead and extract a segment from it.
|
||||
// (C0=Arena, C1-Cn)(int|long)
|
||||
innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.apply(ALLOCATE));
|
||||
innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.get(ALLOCATE));
|
||||
|
||||
// Add an identity function for the result of the cleanup action.
|
||||
// ((int|long))(int|long)
|
||||
@ -221,7 +226,7 @@ public final class CaptureStateUtil {
|
||||
// cleanup action and invoke `Arena::close` when it is run. The `cleanup` handle
|
||||
// does not have to have all parameters. It can have zero or more.
|
||||
// (Throwable, (int|long), Arena)(int|long)
|
||||
cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.apply(ARENA_CLOSE));
|
||||
cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.get(ARENA_CLOSE));
|
||||
|
||||
// Combine the `innerAdapted` and `cleanup` action into a try/finally block.
|
||||
// (Arena, C1-Cn)(int|long)
|
||||
@ -230,7 +235,7 @@ public final class CaptureStateUtil {
|
||||
// Acquire the arena from the global pool.
|
||||
// With this, we finally arrive at the intended method handle:
|
||||
// (C1-Cn)(int|long)
|
||||
return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.apply(ACQUIRE_ARENA));
|
||||
return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.get(ACQUIRE_ARENA));
|
||||
}
|
||||
|
||||
private static MethodHandle makeSegmentExtractionHandle(SegmentExtractorKey segmentExtractorKey) {
|
||||
@ -260,15 +265,15 @@ public final class CaptureStateUtil {
|
||||
if (segmentExtractorKey.returnType().equals(int.class)) {
|
||||
// (int, MemorySegment)int
|
||||
return MethodHandles.guardWithTest(
|
||||
HANDLES_CACHE.apply(NON_NEGATIVE_INT),
|
||||
HANDLES_CACHE.apply(SUCCESS_INT),
|
||||
HANDLES_CACHE.apply(ERROR_INT).bindTo(intExtractor));
|
||||
HANDLES_CACHE.get(NON_NEGATIVE_INT),
|
||||
HANDLES_CACHE.get(SUCCESS_INT),
|
||||
HANDLES_CACHE.get(ERROR_INT).bindTo(intExtractor));
|
||||
} else {
|
||||
// (long, MemorySegment)long
|
||||
return MethodHandles.guardWithTest(
|
||||
HANDLES_CACHE.apply(NON_NEGATIVE_LONG),
|
||||
HANDLES_CACHE.apply(SUCCESS_LONG),
|
||||
HANDLES_CACHE.apply(ERROR_LONG).bindTo(intExtractor));
|
||||
HANDLES_CACHE.get(NON_NEGATIVE_LONG),
|
||||
HANDLES_CACHE.get(SUCCESS_LONG),
|
||||
HANDLES_CACHE.get(ERROR_LONG).bindTo(intExtractor));
|
||||
}
|
||||
}
|
||||
|
||||
@ -341,8 +346,8 @@ public final class CaptureStateUtil {
|
||||
}
|
||||
};
|
||||
|
||||
private static final IntFunction<MethodHandle> HANDLES_CACHE =
|
||||
StableValue.intFunction(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE);
|
||||
private static final List<MethodHandle> HANDLES_CACHE =
|
||||
List.ofLazy(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE);
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
|
||||
|
||||
@ -39,6 +39,7 @@ import java.util.Formatter;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jdk.internal.access.SharedSecrets;
|
||||
import jdk.internal.util.StaticProperty;
|
||||
@ -114,24 +115,29 @@ public final class JdkConsoleImpl implements JdkConsole {
|
||||
// check if `System.console()` returns a Console instance and use it if available. Otherwise,
|
||||
// it should call this method to obtain a JdkConsoleImpl. This ensures only one Console
|
||||
// instance exists in the Java runtime.
|
||||
private static final StableValue<Optional<JdkConsoleImpl>> INSTANCE = StableValue.of();
|
||||
public static Optional<JdkConsoleImpl> passwordConsole() {
|
||||
return INSTANCE.orElseSet(() -> {
|
||||
// If there's already a proper console, throw an exception
|
||||
if (System.console() != null) {
|
||||
throw new IllegalStateException("Can’t create a dedicated password " +
|
||||
"console since a real console already exists");
|
||||
}
|
||||
private static final LazyConstant<Optional<JdkConsoleImpl>> PASSWORD_CONSOLE = LazyConstant.of(
|
||||
new Supplier<Optional<JdkConsoleImpl>>() {
|
||||
@Override
|
||||
public Optional<JdkConsoleImpl> get() {
|
||||
if (System.console() != null) {
|
||||
throw new IllegalStateException("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<JdkConsoleImpl> passwordConsole() {
|
||||
return PASSWORD_CONSOLE.get();
|
||||
}
|
||||
|
||||
// Dedicated entry for sun.security.util.Password when stdout is redirected.
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -0,0 +1,173 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.vm.annotation.AOTSafeClassInitializer;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The sole implementation of the LazyConstant interface.
|
||||
*
|
||||
* @param <T> type of the constant
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*/
|
||||
@AOTSafeClassInitializer
|
||||
public final class LazyConstantImpl<T> implements LazyConstant<T> {
|
||||
|
||||
// Unsafe allows `LazyConstant` instances to be used early in the boot sequence
|
||||
private static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
// Unsafe offset for access of the `constant` field
|
||||
private static final long CONSTANT_OFFSET =
|
||||
UNSAFE.objectFieldOffset(LazyConstantImpl.class, "constant");
|
||||
|
||||
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
|
||||
// memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`).
|
||||
//
|
||||
// This field is used reflectively via Unsafe using explicit memory semantics.
|
||||
//
|
||||
// | Value | Meaning |
|
||||
// | --------------- | -------------- |
|
||||
// | `null` | Unset |
|
||||
// | `other` | Set to `other` |
|
||||
//
|
||||
@Stable
|
||||
private T constant;
|
||||
|
||||
// Underlying computing function to be used to compute the `constant` field.
|
||||
// The field needs to be `volatile` as a lazy constant can be
|
||||
// created by one thread and computed by another thread.
|
||||
// After the function is successfully invoked, the field is set to
|
||||
// `null` to allow the function to be collected.
|
||||
@Stable
|
||||
private volatile Supplier<? extends T> computingFunction;
|
||||
|
||||
private LazyConstantImpl(Supplier<? extends T> computingFunction) {
|
||||
this.computingFunction = computingFunction;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T get() {
|
||||
final T t = getAcquire();
|
||||
return (t != null) ? t : getSlowPath();
|
||||
}
|
||||
|
||||
private T getSlowPath() {
|
||||
preventReentry();
|
||||
synchronized (this) {
|
||||
T t = getAcquire();
|
||||
if (t == null) {
|
||||
t = computingFunction.get();
|
||||
Objects.requireNonNull(t);
|
||||
setRelease(t);
|
||||
// Allow the underlying supplier to be collected after successful use
|
||||
computingFunction = null;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElse(T other) {
|
||||
final T t = getAcquire();
|
||||
return (t == null) ? other : t;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean isInitialized() {
|
||||
return getAcquire() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + "[" + toStringSuffix() + "]";
|
||||
}
|
||||
|
||||
private String toStringSuffix() {
|
||||
final T t = getAcquire();
|
||||
if (t == this) {
|
||||
return "(this LazyConstant)";
|
||||
} else if (t != null) {
|
||||
return t.toString();
|
||||
}
|
||||
// Volatile read
|
||||
final Supplier<? extends T> cf = computingFunction;
|
||||
// There could be a race here
|
||||
if (cf != null) {
|
||||
return "computing function=" + computingFunction.toString();
|
||||
}
|
||||
// As we know `computingFunction` is `null` via a volatile read, we
|
||||
// can now be sure that this lazy constant is initialized
|
||||
return getAcquire().toString();
|
||||
}
|
||||
|
||||
|
||||
// Discussion on the memory semantics used.
|
||||
// ----------------------------------------
|
||||
// Using acquire/release semantics on the `constant` field is the cheapest way to
|
||||
// establish a happens-before (HB) relation between load and store operations. Every
|
||||
// implementation of a method defined in the interface `LazyConstant` except
|
||||
// `equals()` starts with a load of the `constant` field using acquire semantics.
|
||||
//
|
||||
// If the underlying supplier was guaranteed to always create a new object,
|
||||
// a fence after creation and subsequent plain loads would suffice to ensure
|
||||
// new objects' state are always correctly observed. However, no such restriction is
|
||||
// imposed on the underlying supplier. Hence, the docs state there should be an
|
||||
// HB relation meaning we will have to pay a price (on certain platforms) on every
|
||||
// `get()` operation that is not constant-folded.
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@ForceInline
|
||||
private T getAcquire() {
|
||||
return (T) UNSAFE.getReferenceAcquire(this, CONSTANT_OFFSET);
|
||||
}
|
||||
|
||||
private void setRelease(T newValue) {
|
||||
UNSAFE.putReferenceRelease(this, CONSTANT_OFFSET, newValue);
|
||||
}
|
||||
|
||||
private void preventReentry() {
|
||||
if (Thread.holdsLock(this)) {
|
||||
throw new IllegalStateException("Recursive invocation of a LazyConstant's computing function: " + computingFunction);
|
||||
}
|
||||
}
|
||||
|
||||
// Factory
|
||||
|
||||
public static <T> LazyConstantImpl<T> ofLazy(Supplier<? extends T> computingFunction) {
|
||||
return new LazyConstantImpl<>(computingFunction);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.util.ImmutableBitSetPredicate;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.BitSet;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntPredicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Optimized implementation of a stable Function with enums as keys.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param enumType the class type of the Enum
|
||||
* @param firstOrdinal the lowest ordinal used
|
||||
* @param member an int predicate that can be used to test if an enum is a member
|
||||
* of the valid inputs (as there might be "holes")
|
||||
* @param delegates a delegate array of inputs to StableValue mappings
|
||||
* @param original the original Function
|
||||
* @param <E> the type of the input to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
public record StableEnumFunction<E extends Enum<E>, R>(Class<E> enumType,
|
||||
int firstOrdinal,
|
||||
IntPredicate member,
|
||||
@Stable StableValueImpl<R>[] delegates,
|
||||
Function<? super E, ? extends R> original) implements Function<E, R> {
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(E value) {
|
||||
if (!member.test(value.ordinal())) { // Implicit null-check of value
|
||||
throw new IllegalArgumentException("Input not allowed: " + value);
|
||||
}
|
||||
final int index = value.ordinal() - firstOrdinal;
|
||||
// Since we did the member.test above, we know the index is in bounds
|
||||
return delegates[index].orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(value); }});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Collection<Map.Entry<E, StableValueImpl<R>>> entries = new ArrayList<>(delegates.length);
|
||||
final E[] enumElements = enumType.getEnumConstants();
|
||||
int ordinal = firstOrdinal;
|
||||
for (int i = 0; i < delegates.length; i++, ordinal++) {
|
||||
if (member.test(ordinal)) {
|
||||
entries.add(new AbstractMap.SimpleImmutableEntry<>(enumElements[ordinal], delegates[i]));
|
||||
}
|
||||
}
|
||||
return StableUtil.renderMappings(this, "StableFunction", entries, true);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, E extends Enum<E>, R> Function<T, R> of(Set<? extends T> inputs,
|
||||
Function<? super T, ? extends R> original) {
|
||||
// The input set is not empty
|
||||
final Class<E> enumType = ((E) inputs.iterator().next()).getDeclaringClass();
|
||||
final BitSet bitSet = new BitSet(enumType.getEnumConstants().length);
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
for (T t : inputs) {
|
||||
final int ordinal = ((E) t).ordinal();
|
||||
min = Math.min(min, ordinal);
|
||||
max = Math.max(max, ordinal);
|
||||
bitSet.set(ordinal);
|
||||
}
|
||||
final int size = max - min + 1;
|
||||
final IntPredicate member = ImmutableBitSetPredicate.of(bitSet);
|
||||
return (Function<T, R>) new StableEnumFunction<E, R>(enumType, min, member, StableUtil.array(size), (Function<E, R>) original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
// Note: It would be possible to just use `StableMap::get` with some additional logic
|
||||
// instead of this class but explicitly providing a class like this provides better
|
||||
// debug capability, exception handling, and may provide better performance.
|
||||
/**
|
||||
* Implementation of a stable Function.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param values a delegate map of inputs to StableValue mappings
|
||||
* @param original the original Function
|
||||
* @param <T> the type of the input to the function
|
||||
* @param <R> the type of the result of the function
|
||||
*/
|
||||
public record StableFunction<T, R>(Map<? extends T, StableValueImpl<R>> values,
|
||||
Function<? super T, ? extends R> original) implements Function<T, R> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(T value) {
|
||||
final StableValueImpl<R> stable = values.get(value);
|
||||
if (stable == null) {
|
||||
throw new IllegalArgumentException("Input not allowed: " + value);
|
||||
}
|
||||
return stable.orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(value); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderMappings(this, "StableFunction", values.entrySet(), true);
|
||||
}
|
||||
|
||||
public static <T, R> StableFunction<T, R> of(Set<? extends T> inputs,
|
||||
Function<? super T, ? extends R> original) {
|
||||
return new StableFunction<>(StableUtil.map(inputs), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Implementation of a stable IntFunction.
|
||||
* <p>
|
||||
* For performance reasons (~10%), we are not delegating to a StableList but are using
|
||||
* the more primitive functions in StableValueUtil that are shared with StableList/StableValueImpl.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <R> the return type
|
||||
*/
|
||||
public record StableIntFunction<R>(@Stable StableValueImpl<R>[] delegates,
|
||||
IntFunction<? extends R> original) implements IntFunction<R> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public R apply(int index) {
|
||||
final StableValueImpl<R> delegate;
|
||||
try {
|
||||
delegate = delegates[index];
|
||||
} catch (ArrayIndexOutOfBoundsException ioob) {
|
||||
throw new IllegalArgumentException("Input not allowed: " + index, ioob);
|
||||
}
|
||||
return delegate.orElseSet(new Supplier<R>() {
|
||||
@Override public R get() { return original.apply(index); }});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StableUtil.renderElements(this, "StableIntFunction", delegates);
|
||||
}
|
||||
|
||||
public static <R> StableIntFunction<R> of(int size, IntFunction<? extends R> original) {
|
||||
return new StableIntFunction<>(StableUtil.array(size), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,69 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Implementation of a stable supplier.
|
||||
* <p>
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <T> the return type
|
||||
*/
|
||||
public record StableSupplier<T>(StableValueImpl<T> delegate,
|
||||
Supplier<? extends T> original) implements Supplier<T> {
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T get() {
|
||||
return delegate.orElseSet(original);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj == this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = delegate.wrappedContentsAcquire();
|
||||
return t == this ? "(this StableSupplier)" : StableValueImpl.renderWrapped(t);
|
||||
}
|
||||
|
||||
public static <T> StableSupplier<T> of(Supplier<? extends T> original) {
|
||||
return new StableSupplier<>(StableValueImpl.of(), original);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public final class StableUtil {
|
||||
|
||||
private StableUtil() {}
|
||||
|
||||
public static <R> String renderElements(Object self,
|
||||
String selfName,
|
||||
StableValueImpl<?>[] delegates) {
|
||||
return renderElements(self, selfName, delegates, 0, delegates.length);
|
||||
}
|
||||
|
||||
public static <R> String renderElements(Object self,
|
||||
String selfName,
|
||||
StableValueImpl<?>[] delegates,
|
||||
int offset,
|
||||
int length) {
|
||||
final StringJoiner sj = new StringJoiner(", ", "[", "]");
|
||||
for (int i = 0; i < length; i++) {
|
||||
final Object value = delegates[i + offset].wrappedContentsAcquire();
|
||||
if (value == self) {
|
||||
sj.add("(this " + selfName + ")");
|
||||
} else {
|
||||
sj.add(StableValueImpl.renderWrapped(value));
|
||||
}
|
||||
}
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
public static <K, V> String renderMappings(Object self,
|
||||
String selfName,
|
||||
Iterable<Map.Entry<K, StableValueImpl<V>>> delegates,
|
||||
boolean curly) {
|
||||
final StringJoiner sj = new StringJoiner(", ", curly ? "{" : "[", curly ? "}" : "]");
|
||||
for (var e : delegates) {
|
||||
final Object value = e.getValue().wrappedContentsAcquire();
|
||||
final String valueString;
|
||||
if (value == self) {
|
||||
valueString = "(this " + selfName + ")";
|
||||
} else {
|
||||
valueString = StableValueImpl.renderWrapped(value);
|
||||
}
|
||||
sj.add(e.getKey() + "=" + valueString);
|
||||
}
|
||||
return sj.toString();
|
||||
}
|
||||
|
||||
public static <T> StableValueImpl<T>[] array(int size) {
|
||||
assertSizeNonNegative(size);
|
||||
@SuppressWarnings("unchecked")
|
||||
final var stableValues = (StableValueImpl<T>[]) new StableValueImpl<?>[size];
|
||||
for (int i = 0; i < size; i++) {
|
||||
stableValues[i] = StableValueImpl.of();
|
||||
}
|
||||
return stableValues;
|
||||
}
|
||||
|
||||
public static <K, T> Map<K, StableValueImpl<T>> map(Set<K> keys) {
|
||||
Objects.requireNonNull(keys);
|
||||
@SuppressWarnings("unchecked")
|
||||
final var entries = (Map.Entry<K, StableValueImpl<T>>[]) new Map.Entry<?, ?>[keys.size()];
|
||||
int i = 0;
|
||||
for (K key : keys) {
|
||||
entries[i++] = Map.entry(key, StableValueImpl.of());
|
||||
}
|
||||
return Map.ofEntries(entries);
|
||||
}
|
||||
|
||||
public static void assertSizeNonNegative(int size) {
|
||||
if (size < 0) {
|
||||
throw new IllegalArgumentException("size can not be negative: " + size);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,218 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.internal.lang.stable;
|
||||
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import jdk.internal.vm.annotation.DontInline;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
import jdk.internal.vm.annotation.Stable;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* The implementation of StableValue.
|
||||
*
|
||||
* @implNote This implementation can be used early in the boot sequence as it does not
|
||||
* rely on reflection, MethodHandles, Streams etc.
|
||||
*
|
||||
* @param <T> type of the contents
|
||||
*/
|
||||
public final class StableValueImpl<T> implements StableValue<T> {
|
||||
|
||||
static final String UNSET_LABEL = ".unset";
|
||||
|
||||
// Unsafe allows StableValue to be used early in the boot sequence
|
||||
static final Unsafe UNSAFE = Unsafe.getUnsafe();
|
||||
|
||||
// Unsafe offsets for direct field access
|
||||
|
||||
private static final long CONTENTS_OFFSET =
|
||||
UNSAFE.objectFieldOffset(StableValueImpl.class, "contents");
|
||||
|
||||
// Used to indicate a holder value is `null` (see field `contents` below)
|
||||
private static final Object NULL_SENTINEL = new Object();
|
||||
|
||||
// Generally, fields annotated with `@Stable` are accessed by the JVM using special
|
||||
// memory semantics rules (see `parse.hpp` and `parse(1|2|3).cpp`).
|
||||
//
|
||||
// This field is used directly and reflectively via Unsafe using explicit memory semantics.
|
||||
//
|
||||
// | Value | Meaning |
|
||||
// | -------------- | ------------ |
|
||||
// | null | Unset |
|
||||
// | NULL_SENTINEL | Set(null) |
|
||||
// | other | Set(other) |
|
||||
//
|
||||
@Stable
|
||||
private Object contents;
|
||||
|
||||
// Only allow creation via the factory `StableValueImpl::newInstance`
|
||||
private StableValueImpl() {}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean trySet(T contents) {
|
||||
if (wrappedContentsAcquire() != null) {
|
||||
return false;
|
||||
}
|
||||
// Prevent reentry via an orElseSet(supplier)
|
||||
preventReentry();
|
||||
// Mutual exclusion is required here as `orElseSet` might also
|
||||
// attempt to modify `this.contents`
|
||||
synchronized (this) {
|
||||
return wrapAndSet(contents);
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public void setOrThrow(T contents) {
|
||||
if (!trySet(contents)) {
|
||||
// Neither the set contents nor the provided contents is revealed in the
|
||||
// exception message as it might be sensitive.
|
||||
throw new IllegalStateException("The contents is already set");
|
||||
}
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseThrow() {
|
||||
final Object t = wrappedContentsAcquire();
|
||||
if (t == null) {
|
||||
throw new NoSuchElementException("No contents set");
|
||||
}
|
||||
return unwrap(t);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElse(T other) {
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return (t == null) ? other : unwrap(t);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public boolean isSet() {
|
||||
return wrappedContentsAcquire() != null;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
@Override
|
||||
public T orElseSet(Supplier<? extends T> supplier) {
|
||||
Objects.requireNonNull(supplier);
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return (t == null) ? orElseSetSlowPath(supplier) : unwrap(t);
|
||||
}
|
||||
|
||||
@DontInline
|
||||
private T orElseSetSlowPath(Supplier<? extends T> supplier) {
|
||||
preventReentry();
|
||||
synchronized (this) {
|
||||
final Object t = contents; // Plain semantics suffice here
|
||||
if (t == null) {
|
||||
final T newValue = supplier.get();
|
||||
// The mutex is not reentrant so we know newValue should be returned
|
||||
wrapAndSet(newValue);
|
||||
return newValue;
|
||||
}
|
||||
return unwrap(t);
|
||||
}
|
||||
}
|
||||
|
||||
// The methods equals() and hashCode() should be based on identity (defaults from Object)
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final Object t = wrappedContentsAcquire();
|
||||
return t == this
|
||||
? "(this StableValue)"
|
||||
: renderWrapped(t);
|
||||
}
|
||||
|
||||
// Internal methods shared with other internal classes
|
||||
|
||||
@ForceInline
|
||||
public Object wrappedContentsAcquire() {
|
||||
return UNSAFE.getReferenceAcquire(this, CONTENTS_OFFSET);
|
||||
}
|
||||
|
||||
static String renderWrapped(Object t) {
|
||||
return (t == null) ? UNSET_LABEL : Objects.toString(unwrap(t));
|
||||
}
|
||||
|
||||
// Private methods
|
||||
|
||||
// This method is not annotated with @ForceInline as it is always called
|
||||
// in a slow path.
|
||||
private void preventReentry() {
|
||||
if (Thread.holdsLock(this)) {
|
||||
throw new IllegalStateException("Recursive initialization of a stable value is illegal");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps the provided {@code newValue} and tries to set the contents.
|
||||
* <p>
|
||||
* This method ensures the {@link Stable} field is written to at most once.
|
||||
*
|
||||
* @param newValue to wrap and set
|
||||
* @return if the contents was set
|
||||
*/
|
||||
@ForceInline
|
||||
private boolean wrapAndSet(T newValue) {
|
||||
assert Thread.holdsLock(this);
|
||||
// We know we hold the monitor here so plain semantic is enough
|
||||
if (contents == null) {
|
||||
UNSAFE.putReferenceRelease(this, CONTENTS_OFFSET, wrap(newValue));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Wraps `null` values into a sentinel value
|
||||
@ForceInline
|
||||
private static Object wrap(Object t) {
|
||||
return (t == null) ? NULL_SENTINEL : t;
|
||||
}
|
||||
|
||||
// Unwraps null sentinel values into `null`
|
||||
@SuppressWarnings("unchecked")
|
||||
@ForceInline
|
||||
private static <T> T unwrap(Object t) {
|
||||
return t != NULL_SENTINEL ? (T) t : null;
|
||||
}
|
||||
|
||||
// Factory
|
||||
|
||||
public static <T> StableValueImpl<T> of() {
|
||||
return new StableValueImpl<>();
|
||||
}
|
||||
|
||||
}
|
||||
@ -48,6 +48,8 @@ import java.nio.channels.UnresolvedAddressException;
|
||||
import java.nio.channels.UnsupportedAddressTypeException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Objects;
|
||||
import java.lang.LazyConstant;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import sun.net.ext.ExtendedSocketOptions;
|
||||
import sun.net.util.IPAddressUtil;
|
||||
@ -94,13 +96,14 @@ public class Net {
|
||||
return EXCLUSIVE_BIND;
|
||||
}
|
||||
|
||||
private static final StableValue<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = StableValue.of();
|
||||
private static final LazyConstant<Boolean> SHUTDOWN_WRITE_BEFORE_CLOSE = LazyConstant.of(new Supplier<Boolean>() {
|
||||
@Override public Boolean get() { return shouldShutdownWriteBeforeClose0(); }});
|
||||
|
||||
/**
|
||||
* Tells whether a TCP connection should be shutdown for writing before closing.
|
||||
*/
|
||||
static boolean shouldShutdownWriteBeforeClose() {
|
||||
return SHUTDOWN_WRITE_BEFORE_CLOSE.orElseSet(Net::shouldShutdownWriteBeforeClose0);
|
||||
return SHUTDOWN_WRITE_BEFORE_CLOSE.get();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -92,8 +92,8 @@ public final class BaseLocale {
|
||||
}
|
||||
|
||||
// Interned BaseLocale cache
|
||||
private static final Supplier<ReferencedKeySet<BaseLocale>> CACHE =
|
||||
StableValue.supplier(new Supplier<>() {
|
||||
private static final LazyConstant<ReferencedKeySet<BaseLocale>> CACHE =
|
||||
LazyConstant.of(new Supplier<>() {
|
||||
@Override
|
||||
public ReferencedKeySet<BaseLocale> get() {
|
||||
return ReferencedKeySet.create(true, ReferencedKeySet.concurrentHashMapSupplier());
|
||||
|
||||
@ -50,7 +50,7 @@ public abstract class BreakIteratorResourceBundle extends ResourceBundle {
|
||||
// those keys must be added to NON_DATA_KEYS.
|
||||
private static final Set<String> NON_DATA_KEYS = Set.of("BreakIteratorClasses");
|
||||
|
||||
private final Supplier<Set<String>> keys = StableValue.supplier(
|
||||
private final LazyConstant<Set<String>> keys = LazyConstant.of(
|
||||
new Supplier<>() { public Set<String> get() { return keys0(); }});
|
||||
|
||||
private Set<String> keys0() {
|
||||
|
||||
@ -119,7 +119,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
private final Supplier<Map<String, Object>> lookup = StableValue.supplier(
|
||||
private final LazyConstant<Map<String, Object>> lookup = LazyConstant.of(
|
||||
new Supplier<>() { public Map<String, Object> get() { return lookup0(); }});
|
||||
|
||||
private Map<String, Object> lookup0() {
|
||||
@ -134,7 +134,7 @@ public abstract class OpenListResourceBundle extends ResourceBundle {
|
||||
return temp;
|
||||
}
|
||||
|
||||
private final Supplier<Set<String>> keyset = StableValue.supplier(
|
||||
private final LazyConstant<Set<String>> keyset = LazyConstant.of(
|
||||
new Supplier<>() { public Set<String> get() { return keyset0(); }});
|
||||
|
||||
private Set<String> keyset0() {
|
||||
|
||||
153
test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java
Normal file
153
test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for dependency injection
|
||||
* @enablePreview
|
||||
* @run junit DemoContainerInjectionTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class DemoContainerInjectionTest {
|
||||
|
||||
interface Foo{}
|
||||
interface Bar{};
|
||||
static class FooImpl implements Foo{};
|
||||
static class BarImpl implements Bar{};
|
||||
|
||||
// Provides a type safe way of associating a type to a supplier
|
||||
record Provider<T>(Class<T> type, Supplier<? extends T> supplier){}
|
||||
|
||||
@Test
|
||||
void ComputedComponentsViaLambda() {
|
||||
Container container = ComputedContainer.of(Set.of(Foo.class, Bar.class), t -> switch (t) {
|
||||
case Class<?> c when c.equals(Foo.class) -> new FooImpl();
|
||||
case Class<?> c when c.equals(Bar.class) -> new BarImpl();
|
||||
default -> throw new IllegalArgumentException();
|
||||
});
|
||||
assertContainerPopulated(container);
|
||||
}
|
||||
|
||||
@Test
|
||||
void SettableComponents() {
|
||||
SettableContainer container = SettableScratchContainer.of(Set.of(Foo.class, Bar.class));
|
||||
container.set(Foo.class, new FooImpl());
|
||||
container.set(Bar.class, new BarImpl());
|
||||
assertContainerPopulated(container);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void ProviderComponents() {
|
||||
Container container = ProviderContainer.of(Map.of(
|
||||
Foo.class, FooImpl::new,
|
||||
Bar.class, BarImpl::new));
|
||||
assertContainerPopulated(container);
|
||||
}
|
||||
|
||||
@Test
|
||||
void ProviderTypedComponents() {
|
||||
Container container = providerTypedContainer(Set.of(
|
||||
new Provider<>(Foo.class, FooImpl::new),
|
||||
new Provider<>(Bar.class, BarImpl::new)
|
||||
));
|
||||
assertContainerPopulated(container);
|
||||
}
|
||||
|
||||
private static void assertContainerPopulated(Container container) {
|
||||
assertInstanceOf(FooImpl.class, container.get(Foo.class));
|
||||
assertInstanceOf(BarImpl.class, container.get(Bar.class));
|
||||
}
|
||||
|
||||
interface Container {
|
||||
<T> T get(Class<T> type);
|
||||
}
|
||||
|
||||
interface SettableContainer extends Container {
|
||||
<T> void set(Class<T> type, T implementation);
|
||||
}
|
||||
|
||||
record ComputedContainer(Map<Class<?>, ?> components) implements Container {
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> type) {
|
||||
return type.cast(components.get(type));
|
||||
}
|
||||
|
||||
static Container of(Set<Class<?>> components, Function<Class<?>, ?> mapper) {
|
||||
return new ComputedContainer(Map.ofLazy(components, mapper));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record SettableScratchContainer(Map<Class<?>, Object> scratch, Map<Class<?>, ?> components) implements SettableContainer {
|
||||
|
||||
@Override
|
||||
public <T> void set(Class<T> type, T implementation) {
|
||||
if (scratch.putIfAbsent(type, type.cast(implementation)) != null) {
|
||||
throw new IllegalStateException("Can only set once for " + type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> type) {
|
||||
return type.cast(components.get(type));
|
||||
}
|
||||
|
||||
static SettableContainer of(Set<Class<?>> components) {
|
||||
Map<Class<?>, Object> scratch = new ConcurrentHashMap<>();
|
||||
return new SettableScratchContainer(scratch, Map.ofLazy(components, scratch::get));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
record ProviderContainer(Map<Class<?>, ?> components) implements Container {
|
||||
|
||||
@Override
|
||||
public <T> T get(Class<T> type) {
|
||||
return type.cast(components.get(type));
|
||||
}
|
||||
|
||||
static Container of(Map<Class<?>, Supplier<?>> components) {
|
||||
var map = Map.ofLazy(components.keySet(), t -> components.get(t).get());
|
||||
return new ProviderContainer(map);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static Container providerTypedContainer(Set<Provider<?>> providers) {
|
||||
return ProviderContainer.of(providers.stream()
|
||||
.collect(Collectors.toMap(Provider::type, Provider::supplier)));
|
||||
}
|
||||
|
||||
}
|
||||
83
test/jdk/java/lang/LazyConstant/DemoImperativeTest.java
Normal file
83
test/jdk/java/lang/LazyConstant/DemoImperativeTest.java
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Test of a demo of an imperative stable value based on a lazy constant
|
||||
* @enablePreview
|
||||
* @run junit DemoImperativeTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class DemoImperativeTest {
|
||||
|
||||
interface ImperativeStableValue<T> {
|
||||
T orElse(T other);
|
||||
boolean isSet();
|
||||
boolean trySet(T t);
|
||||
T get();
|
||||
|
||||
static <T> ImperativeStableValue<T> of() {
|
||||
var scratch = new AtomicReference<T>();
|
||||
return new Impl<>(scratch, LazyConstant.of(scratch::get));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private record Impl<T>(AtomicReference<T> scratch,
|
||||
LazyConstant<T> underlying) implements ImperativeStableValue<T> {
|
||||
|
||||
@Override
|
||||
public boolean trySet(T t) {
|
||||
final boolean result = scratch.compareAndSet(null, t);
|
||||
if (result) {
|
||||
// Actually set the value
|
||||
get();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override public T orElse(T other) { return underlying.orElse(other); }
|
||||
@Override public boolean isSet() { return underlying.isInitialized(); }
|
||||
@Override public T get() { return underlying.get(); }
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void basic() {
|
||||
var stableValue = ImperativeStableValue.<Integer>of();
|
||||
assertFalse(stableValue.isSet());
|
||||
assertEquals(13, stableValue.orElse(13));
|
||||
assertTrue(stableValue.trySet(42));
|
||||
assertFalse(stableValue.trySet(13));
|
||||
assertTrue(stableValue.isSet());
|
||||
assertEquals(42, stableValue.get());
|
||||
assertEquals(42, stableValue.orElse(13));
|
||||
}
|
||||
|
||||
}
|
||||
119
test/jdk/java/lang/LazyConstant/DemoMapTest.java
Normal file
119
test/jdk/java/lang/LazyConstant/DemoMapTest.java
Normal file
@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Test of a lazy map application
|
||||
* @enablePreview
|
||||
* @run junit DemoMapTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class DemoMapTest {
|
||||
|
||||
static class OrderController{}
|
||||
|
||||
// NEW:
|
||||
static final Map<String, OrderController> ORDERS
|
||||
= Map.ofLazy(
|
||||
Set.of("Customers", "Internal", "Testing"),
|
||||
_ -> new OrderController()
|
||||
);
|
||||
|
||||
public static OrderController orders() {
|
||||
String groupName = Thread.currentThread().getThreadGroup().getName();
|
||||
return ORDERS.get(groupName);
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderController() throws InterruptedException {
|
||||
Thread t = Thread.ofPlatform()
|
||||
.group(new ThreadGroup("Customers"))
|
||||
.start(() -> {
|
||||
String groupName = Thread.currentThread().getThreadGroup().getName();
|
||||
OrderController orderController = ORDERS.get(groupName);
|
||||
assertNotNull(orderController);
|
||||
});
|
||||
t.join();
|
||||
}
|
||||
|
||||
private static final Map<Integer, String> SERVER_ERROR_PAGES = Map.ofLazy(
|
||||
Set.of(500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511),
|
||||
e -> {
|
||||
try {
|
||||
return Files.readString(Path.of("server_error_" + e + ".html"));
|
||||
} catch (IOException ioe) {
|
||||
throw new RuntimeException(ioe);
|
||||
}
|
||||
});
|
||||
|
||||
private static String htmlServerErrorPage(int errorCode) {
|
||||
return SERVER_ERROR_PAGES.get(errorCode); // Eligible for constant folding
|
||||
}
|
||||
|
||||
@Test
|
||||
void page500() {
|
||||
String page = htmlServerErrorPage(500); // Constant folds
|
||||
assertEquals(DEFAULT_FS_MSG, page);
|
||||
}
|
||||
|
||||
static final String DEFAULT_FS_MSG = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Internal Server Error (500)</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="script.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
There was a general problem with the server, code 500
|
||||
</body>
|
||||
</html>
|
||||
""";
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws IOException {
|
||||
var file = Path.of("server_error_500.html");
|
||||
if (Files.notExists(file)) {
|
||||
Files.createFile(file);
|
||||
Files.writeString(file, DEFAULT_FS_MSG);
|
||||
}
|
||||
assertEquals(DEFAULT_FS_MSG, Files.readString(file));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void cleanUp() throws IOException {
|
||||
var file = Path.of("server_error_500.html");
|
||||
Files.deleteIfExists(file);
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,10 +22,11 @@
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for making sure StableValue publishes values safely
|
||||
* @summary Basic tests for making sure ComputedConstant publishes values safely
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @modules java.base/jdk.internal.lang
|
||||
* @enablePreview
|
||||
* @run junit StableValuesSafePublicationTest
|
||||
* @run junit LazyConstantSafePublicationTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -36,30 +37,22 @@ import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.lang.LazyConstant;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableValuesSafePublicationTest {
|
||||
final class LazyConstantSafePublicationTest {
|
||||
|
||||
private static final int SIZE = 100_000;
|
||||
private static final int THREADS = Runtime.getRuntime().availableProcessors();
|
||||
private static final StableValue<Holder>[] STABLES = stables();
|
||||
|
||||
static StableValue<Holder>[] stables() {
|
||||
@SuppressWarnings("unchecked")
|
||||
StableValue<Holder>[] stables = (StableValue<Holder>[]) new StableValue[SIZE];
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
stables[i] = StableValue.of();
|
||||
}
|
||||
return stables;
|
||||
}
|
||||
|
||||
static final class Holder {
|
||||
// These are non-final fields but should be seen
|
||||
// fully initialized thanks to the HB properties of StableValue.
|
||||
// fully initialized thanks to the HB properties of ComputedConstants.
|
||||
int a, b, c, d, e;
|
||||
|
||||
Holder() {
|
||||
@ -69,17 +62,21 @@ final class StableValuesSafePublicationTest {
|
||||
|
||||
static final class Consumer implements Runnable {
|
||||
|
||||
final LazyConstant<Holder>[] constants;
|
||||
final int[] observations = new int[SIZE];
|
||||
final StableValue<Holder>[] stables = STABLES;
|
||||
int i = 0;
|
||||
|
||||
public Consumer(LazyConstant<Holder>[] constants) {
|
||||
this.constants = constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
for (; i < SIZE; i++) {
|
||||
StableValue<Holder> s = stables[i];
|
||||
LazyConstant<Holder> s = constants[i];
|
||||
Holder h;
|
||||
// Wait until the StableValue has a holder value
|
||||
while ((h = s.orElse(null)) == null) {}
|
||||
// Wait until the ComputedConstant has a holder value
|
||||
while ((h = s.orElse(null)) == null) { Thread.onSpinWait();}
|
||||
int a = h.a;
|
||||
int b = h.b;
|
||||
int c = h.c;
|
||||
@ -92,15 +89,19 @@ final class StableValuesSafePublicationTest {
|
||||
|
||||
static final class Producer implements Runnable {
|
||||
|
||||
final StableValue<Holder>[] stables = STABLES;
|
||||
final LazyConstant<Holder>[] constants;
|
||||
|
||||
public Producer(LazyConstant<Holder>[] constants) {
|
||||
this.constants = constants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StableValue<Holder> s;
|
||||
LazyConstant<Holder> s;
|
||||
long deadlineNs = System.nanoTime();
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
s = stables[i];
|
||||
s.trySet(new Holder());
|
||||
s = constants[i];
|
||||
s.get();
|
||||
deadlineNs += 1000;
|
||||
while (System.nanoTime() < deadlineNs) {
|
||||
Thread.onSpinWait();
|
||||
@ -110,9 +111,10 @@ final class StableValuesSafePublicationTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void main() {
|
||||
void mainTest() {
|
||||
final LazyConstant<Holder>[] constants = constants();
|
||||
List<Consumer> consumers = IntStream.range(0, THREADS)
|
||||
.mapToObj(_ -> new Consumer())
|
||||
.mapToObj(_ -> new Consumer(constants))
|
||||
.toList();
|
||||
|
||||
List<Thread> consumersThreads = IntStream.range(0, THREADS)
|
||||
@ -121,14 +123,14 @@ final class StableValuesSafePublicationTest {
|
||||
.start(consumers.get(i)))
|
||||
.toList();
|
||||
|
||||
Producer producer = new Producer();
|
||||
Producer producer = new Producer(constants);
|
||||
|
||||
Thread producerThread = Thread.ofPlatform()
|
||||
.name("Producer Thread")
|
||||
.start(producer);
|
||||
|
||||
join(consumers, producerThread);
|
||||
join(consumers, consumersThreads.toArray(Thread[]::new));
|
||||
join(constants, consumers, producerThread);
|
||||
join(constants, consumers, consumersThreads.toArray(Thread[]::new));
|
||||
|
||||
int[] histogram = new int[64];
|
||||
for (Consumer consumer : consumers) {
|
||||
@ -146,7 +148,7 @@ final class StableValuesSafePublicationTest {
|
||||
assertEquals(THREADS * SIZE, histogram[63]);
|
||||
}
|
||||
|
||||
static void join(List<Consumer> consumers, Thread... threads) {
|
||||
static void join(final LazyConstant<Holder>[] constants, List<Consumer> consumers, Thread... threads) {
|
||||
try {
|
||||
for (Thread t:threads) {
|
||||
long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(1);
|
||||
@ -163,11 +165,11 @@ final class StableValuesSafePublicationTest {
|
||||
}
|
||||
if (System.nanoTime() > deadline) {
|
||||
long nonNulls = CompletableFuture.supplyAsync(() ->
|
||||
Stream.of(STABLES)
|
||||
Arrays.stream(constants)
|
||||
.map(s -> s.orElse(null))
|
||||
.filter(Objects::nonNull)
|
||||
.count(), Executors.newSingleThreadExecutor()).join();
|
||||
fail("Giving up! Set stables seen by a new thread: " + nonNulls);
|
||||
fail("Giving up! Set lazy constants seen by a new thread: " + nonNulls);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -176,4 +178,13 @@ final class StableValuesSafePublicationTest {
|
||||
}
|
||||
}
|
||||
|
||||
static LazyConstant<Holder>[] constants() {
|
||||
@SuppressWarnings("unchecked")
|
||||
LazyConstant<Holder>[] constants = (LazyConstant<Holder>[]) new LazyConstant[SIZE];
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
constants[i] = LazyConstant.of(Holder::new);
|
||||
}
|
||||
return constants;
|
||||
}
|
||||
|
||||
}
|
||||
236
test/jdk/java/lang/LazyConstant/LazyConstantTest.java
Normal file
236
test/jdk/java/lang/LazyConstant/LazyConstantTest.java
Normal file
@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for the LazyConstant implementation
|
||||
* @enablePreview
|
||||
* @modules java.base/jdk.internal.lang
|
||||
* @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED LazyConstantTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.lang.LazyConstant;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
final class LazyConstantTest {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final Supplier<Integer> SUPPLIER = () -> VALUE;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> LazyConstant.of(null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("factories")
|
||||
void basic(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
|
||||
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER);
|
||||
var lazy = factory.apply(cs);
|
||||
assertFalse(lazy.isInitialized());
|
||||
assertEquals(SUPPLIER.get(), lazy.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(SUPPLIER.get(), lazy.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertTrue(lazy.toString().contains(Integer.toString(SUPPLIER.get())));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("factories")
|
||||
void exception(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
|
||||
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = factory.apply(cs);
|
||||
assertThrows(UnsupportedOperationException.class, lazy::get);
|
||||
assertEquals(1, cs.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, lazy::get);
|
||||
assertEquals(2, cs.cnt());
|
||||
assertTrue(lazy.toString().contains("computing function"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void orElse(LazyConstant<Integer> constant) {
|
||||
assertNull(constant.orElse(null));
|
||||
constant.get();
|
||||
assertEquals(VALUE, constant.orElse(null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void get(LazyConstant<Integer> constant) {
|
||||
assertEquals(VALUE, constant.get());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void isInitialized(LazyConstant<Integer> constant) {
|
||||
assertFalse(constant.isInitialized());
|
||||
constant.get();
|
||||
assertTrue(constant.isInitialized());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void testHashCode(LazyConstant<Integer> constant) {
|
||||
assertEquals(System.identityHashCode(constant), constant.hashCode());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void testEquals(LazyConstant<Integer> c0) {
|
||||
assertNotEquals(null, c0);
|
||||
LazyConstant<Integer> different = LazyConstant.of(SUPPLIER);
|
||||
assertNotEquals(different, c0);
|
||||
assertNotEquals(c0, different);
|
||||
assertNotEquals("a", c0);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void testLazyConstantAsComputingFunction(LazyConstant<Integer> constant) {
|
||||
LazyConstant<Integer> c1 = LazyConstant.of(constant);
|
||||
assertSame(constant, c1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringTest() {
|
||||
Supplier<String> supplier = () -> "str";
|
||||
LazyConstant<String> lazy = LazyConstant.of(supplier);
|
||||
var expectedSubstring = "computing function=" + supplier;
|
||||
assertTrue(lazy.toString().contains(expectedSubstring));
|
||||
lazy.get();
|
||||
assertTrue(lazy.toString().contains("str"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("lazyConstants")
|
||||
void toStringUnset(LazyConstant<Integer> constant) {
|
||||
String unInitializedToString = constant.toString();
|
||||
int suffixEnd = unInitializedToString.indexOf("[");
|
||||
String suffix = unInitializedToString.substring(0, suffixEnd);
|
||||
String expectedUninitialized = suffix+"[computing function=";
|
||||
assertTrue(unInitializedToString.startsWith(expectedUninitialized));
|
||||
constant.get();
|
||||
String expectedInitialized = suffix + "[" + VALUE + "]";
|
||||
assertEquals(expectedInitialized, constant.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringCircular() {
|
||||
AtomicReference<LazyConstant<?>> ref = new AtomicReference<>();
|
||||
LazyConstant<LazyConstant<?>> constant = LazyConstant.of(ref::get);
|
||||
ref.set(constant);
|
||||
constant.get();
|
||||
String toString = assertDoesNotThrow(constant::toString);
|
||||
assertTrue(constant.toString().contains("(this LazyConstant)"), toString);
|
||||
}
|
||||
|
||||
@Test
|
||||
void recursiveCall() {
|
||||
AtomicReference<LazyConstant<Integer>> ref = new AtomicReference<>();
|
||||
LazyConstant<Integer> constant = LazyConstant.of(() -> ref.get().get());
|
||||
LazyConstant<Integer> constant1 = LazyConstant.of(constant);
|
||||
ref.set(constant1);
|
||||
assertThrows(IllegalStateException.class, constant::get);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("factories")
|
||||
void underlying(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
|
||||
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER);
|
||||
var f1 = factory.apply(cs);
|
||||
|
||||
Supplier<?> underlyingBefore = LazyConstantTestUtil.computingFunction(f1);
|
||||
assertSame(cs, underlyingBefore);
|
||||
int v = f1.get();
|
||||
Supplier<?> underlyingAfter = LazyConstantTestUtil.computingFunction(f1);
|
||||
assertNull(underlyingAfter);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("factories")
|
||||
void functionHolderException(Function<Supplier<Integer>, LazyConstant<Integer>> factory) {
|
||||
LazyConstantTestUtil.CountingSupplier<Integer> cs = new LazyConstantTestUtil.CountingSupplier<>(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var f1 = factory.apply(cs);
|
||||
|
||||
Supplier<?> underlyingBefore = LazyConstantTestUtil.computingFunction(f1);
|
||||
assertSame(cs, underlyingBefore);
|
||||
try {
|
||||
int v = f1.get();
|
||||
} catch (UnsupportedOperationException _) {
|
||||
// Expected
|
||||
}
|
||||
Supplier<?> underlyingAfter = LazyConstantTestUtil.computingFunction(f1);
|
||||
assertSame(cs, underlyingAfter);
|
||||
}
|
||||
|
||||
private static Stream<LazyConstant<Integer>> lazyConstants() {
|
||||
return factories()
|
||||
.map(f -> f.apply(() -> VALUE));
|
||||
}
|
||||
|
||||
private static Stream<Function<Supplier<Integer>, LazyConstant<Integer>>> factories() {
|
||||
return Stream.of(
|
||||
supplier("ComputedConstant.of(<lambda>)", LazyConstant::of)
|
||||
);
|
||||
}
|
||||
|
||||
private static Function<Supplier<Integer>, LazyConstant<Integer>> supplier(String name,
|
||||
Function<Supplier<Integer>, LazyConstant<Integer>> underlying) {
|
||||
return new Function<Supplier<Integer>, LazyConstant<Integer>>() {
|
||||
@Override
|
||||
public LazyConstant<Integer> apply(Supplier<Integer> supplier) {
|
||||
return underlying.apply(supplier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
record Lazy<T>(LazyConstant<T> underlying) implements Supplier<T> {
|
||||
@Override
|
||||
public T get() { return underlying.get(); }
|
||||
|
||||
static <T> Lazy<T> of(Supplier<? extends T> computingFunction) {
|
||||
return new Lazy<>(LazyConstant.of(computingFunction));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -21,15 +21,18 @@
|
||||
* questions.
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
final class StableTestUtil {
|
||||
final class LazyConstantTestUtil {
|
||||
|
||||
private StableTestUtil() {}
|
||||
private LazyConstantTestUtil() { }
|
||||
|
||||
public static final String UNINITIALIZED_TAG = ".uninitialized";
|
||||
|
||||
public static final class CountingSupplier<T>
|
||||
extends AbstractCounting<Supplier<T>>
|
||||
@ -117,4 +120,55 @@ final class StableTestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static Object functionHolder(Object o) {
|
||||
try {
|
||||
final Field field = field(o.getClass(), "functionHolder");
|
||||
field.setAccessible(true);
|
||||
return field.get(o);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Object functionHolderFunction(Object o) {
|
||||
try {
|
||||
final Field field = field(o.getClass(), "function");
|
||||
field.setAccessible(true);
|
||||
return field.get(o);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static int functionHolderCounter(Object o) {
|
||||
try {
|
||||
final Field field = field(o.getClass(), "counter");
|
||||
field.setAccessible(true);
|
||||
return (int)field.get(o);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Supplier<?> computingFunction(LazyConstant<?> o) {
|
||||
try {
|
||||
final Field field = field(o.getClass(), "computingFunction");
|
||||
field.setAccessible(true);
|
||||
return (Supplier<?>) field.get(o);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static Field field(Class<?> clazz, String name) {
|
||||
if (clazz.equals(Object.class)) {
|
||||
throw new RuntimeException("No " + name);
|
||||
}
|
||||
try {
|
||||
return clazz.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException e) {
|
||||
return field(clazz.getSuperclass(), name);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -22,23 +22,18 @@
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableList methods
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @summary Basic tests for lazy list methods
|
||||
* @enablePreview
|
||||
* @run junit StableListTest
|
||||
* @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyListTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.Set;
|
||||
@ -54,7 +49,7 @@ import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableListTest {
|
||||
final class LazyListTest {
|
||||
|
||||
private static final int ZERO = 0;
|
||||
private static final int INDEX = 7;
|
||||
@ -63,26 +58,26 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.list(SIZE, null));
|
||||
assertThrows(IllegalArgumentException.class, () -> StableValue.list(-1, IDENTITY));
|
||||
assertThrows(NullPointerException.class, () -> List.ofLazy(SIZE, null));
|
||||
assertThrows(IllegalArgumentException.class, () -> List.ofLazy(-1, IDENTITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
assertFalse(newList().isEmpty());
|
||||
assertTrue(newEmptyList().isEmpty());
|
||||
assertFalse(newLazyList().isEmpty());
|
||||
assertTrue(newEmptyLazyList().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
assertEquals(SIZE, newList().size());
|
||||
assertEquals(ZERO, newEmptyList().size());
|
||||
assertEquals(SIZE, newLazyList().size());
|
||||
assertEquals(ZERO, newEmptyLazyList().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(IDENTITY);
|
||||
var lazy = StableValue.list(SIZE, cif);
|
||||
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<Integer>(IDENTITY);
|
||||
var lazy = List.ofLazy(SIZE, cif);
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(i + 1, cif.cnt());
|
||||
@ -93,10 +88,10 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void getException() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(_ -> {
|
||||
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<Integer>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = StableValue.list(SIZE, cif);
|
||||
var lazy = List.ofLazy(SIZE, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX));
|
||||
@ -105,8 +100,8 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void toArray() {
|
||||
assertArrayEquals(new Object[ZERO], newEmptyList().toArray());
|
||||
assertArrayEquals(newRegularList().toArray(), newList().toArray());
|
||||
assertArrayEquals(new Object[ZERO], newEmptyLazyList().toArray());
|
||||
assertArrayEquals(newRegularList().toArray(), newLazyList().toArray());
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -115,8 +110,8 @@ final class StableListTest {
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
actual[INDEX] = 100 + i;
|
||||
}
|
||||
var list = StableValue.list(INDEX, IDENTITY);
|
||||
assertSame(actual, list.toArray(actual));
|
||||
var lazy = List.ofLazy(INDEX, IDENTITY);
|
||||
assertSame(actual, lazy.toArray(actual));
|
||||
Integer[] expected = IntStream.range(0, SIZE)
|
||||
.mapToObj(i -> i < INDEX ? i : null)
|
||||
.toArray(Integer[]::new);
|
||||
@ -126,7 +121,7 @@ final class StableListTest {
|
||||
@Test
|
||||
void toArrayWithArraySmaller() {
|
||||
Integer[] arr = new Integer[INDEX];
|
||||
Integer[] actual = newList().toArray(arr);
|
||||
Integer[] actual = newLazyList().toArray(arr);
|
||||
assertNotSame(arr, actual);
|
||||
Integer[] expected = newRegularList().toArray(new Integer[0]);
|
||||
assertArrayEquals(expected, actual);
|
||||
@ -135,13 +130,13 @@ final class StableListTest {
|
||||
@Test
|
||||
void toArrayWithGenerator() {
|
||||
Integer[] expected = newRegularList().toArray(Integer[]::new);
|
||||
Integer[] actual = newList().toArray(Integer[]::new);
|
||||
Integer[] actual = newLazyList().toArray(Integer[]::new);
|
||||
assertArrayEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void firstIndex() {
|
||||
var lazy = newList();
|
||||
var lazy = newLazyList();
|
||||
for (int i = INDEX; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.indexOf(i));
|
||||
}
|
||||
@ -150,7 +145,7 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void lastIndex() {
|
||||
var lazy = newList();
|
||||
var lazy = newLazyList();
|
||||
for (int i = INDEX; i < SIZE; i++) {
|
||||
assertEquals(i, lazy.lastIndexOf(i));
|
||||
}
|
||||
@ -159,42 +154,28 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void toStringTest() {
|
||||
assertEquals("[]", newEmptyList().toString());
|
||||
var list = StableValue.list(2, IDENTITY);
|
||||
assertEquals("[.unset, .unset]", list.toString());
|
||||
list.get(0);
|
||||
assertEquals("[0, .unset]", list.toString());
|
||||
list.get(1);
|
||||
assertEquals("[0, 1]", list.toString());
|
||||
assertEquals("[]", newEmptyLazyList().toString());
|
||||
assertEquals("[0, 1]", List.ofLazy(2, IDENTITY).toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeTest() {
|
||||
assertEquals(List.of().hashCode(), newEmptyList().hashCode());
|
||||
assertEquals(newRegularList().hashCode(), newList().hashCode());
|
||||
assertEquals(List.of().hashCode(), newEmptyLazyList().hashCode());
|
||||
assertEquals(newRegularList().hashCode(), newLazyList().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsTest() {
|
||||
assertTrue(newEmptyList().equals(List.of()));
|
||||
assertTrue(List.of().equals(newEmptyList()));
|
||||
assertTrue(newList().equals(newRegularList()));
|
||||
assertTrue(newRegularList().equals(newList()));
|
||||
assertFalse(newList().equals("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsPartialEvaluationTest() {
|
||||
var list = StableValue.list(2, IDENTITY);
|
||||
assertFalse(list.equals(List.of(0)));
|
||||
assertEquals("[0, .unset]", list.toString());
|
||||
assertTrue(list.equals(List.of(0, 1)));
|
||||
assertEquals("[0, 1]", list.toString());
|
||||
assertTrue(newEmptyLazyList().equals(List.of()));
|
||||
assertTrue(List.of().equals(newEmptyLazyList()));
|
||||
assertTrue(newLazyList().equals(newRegularList()));
|
||||
assertTrue(newRegularList().equals(newLazyList()));
|
||||
assertFalse(newLazyList().equals("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorTotal() {
|
||||
var iterator = newList().iterator();
|
||||
var iterator = newLazyList().iterator();
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
assertTrue(iterator.hasNext());
|
||||
assertTrue(iterator.hasNext());
|
||||
@ -209,7 +190,7 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void iteratorPartial() {
|
||||
var iterator = newList().iterator();
|
||||
var iterator = newLazyList().iterator();
|
||||
for (int i = 0; i < INDEX; i++) {
|
||||
assertTrue(iterator.hasNext());
|
||||
assertTrue(iterator.hasNext());
|
||||
@ -225,7 +206,7 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void subList() {
|
||||
var lazy = newList();
|
||||
var lazy = newLazyList();
|
||||
var lazySubList = lazy.subList(1, SIZE);
|
||||
assertInstanceOf(RandomAccess.class, lazySubList);
|
||||
var regularList = newRegularList();
|
||||
@ -235,36 +216,33 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void subList2() {
|
||||
var lazy = newList();
|
||||
var lazy = newLazyList();
|
||||
var lazySubList = lazy.subList(1, SIZE);
|
||||
lazySubList.get(0);
|
||||
var eq = newList();
|
||||
var eq = newLazyList();
|
||||
eq.get(1);
|
||||
assertEquals(eq.toString(), lazy.toString());
|
||||
}
|
||||
|
||||
void assertUnevaluated(List<Integer> subList) {
|
||||
assertEquals(asString(".unset", subList), subList.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void reversed() {
|
||||
var reversed = newList().reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversed);
|
||||
assertEquals(SIZE - 1, reversed.getFirst());
|
||||
assertEquals(0, reversed.getLast());
|
||||
var lazy = newLazyList();
|
||||
var reversedLazy = lazy.reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversedLazy);
|
||||
assertEquals(SIZE - 1, reversedLazy.getFirst());
|
||||
assertEquals(0, reversedLazy.getLast());
|
||||
|
||||
var reversed2 = reversed.reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversed2);
|
||||
assertEquals(0, reversed2.getFirst());
|
||||
assertEquals(SIZE - 1, reversed2.getLast());
|
||||
var reversed2Lazy = reversedLazy.reversed();
|
||||
assertInstanceOf(RandomAccess.class, reversed2Lazy);
|
||||
assertEquals(0, reversed2Lazy.getFirst());
|
||||
assertEquals(SIZE - 1, reversed2Lazy.getLast());
|
||||
// Make sure we get back a non-reversed implementation
|
||||
assertEquals("java.util.ImmutableCollections$StableList", reversed2.getClass().getName());
|
||||
assertEquals(lazy.getClass().getName(), reversed2Lazy.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sublistReversedToString() {
|
||||
var actual = StableValue.list(4, IDENTITY);
|
||||
var actual = List.ofLazy(4, IDENTITY);
|
||||
var expected = List.of(0, 1, 2, 3);
|
||||
for (UnaryOperation op : List.of(
|
||||
new UnaryOperation("subList", l -> l.subList(1, 3)),
|
||||
@ -276,60 +254,17 @@ final class StableListTest {
|
||||
actual.getLast();
|
||||
|
||||
var actualToString = actual.toString();
|
||||
var expectedToString = expected.toString().replace("2", ".unset");
|
||||
var expectedToString = expected.toString();
|
||||
assertEquals(expectedToString, actualToString);
|
||||
}
|
||||
|
||||
// This test makes sure successive view operations retains the property
|
||||
// of being a Stable view.
|
||||
@Test
|
||||
void viewsStable() {
|
||||
viewOperations().forEach(op0 -> {
|
||||
viewOperations().forEach( op1 -> {
|
||||
viewOperations().forEach(op2 -> {
|
||||
var list = newList();
|
||||
var view1 = op0.apply(list);
|
||||
var view2 = op1.apply(view1);
|
||||
var view3 = op2.apply(view2);
|
||||
var className3 = className(view3);
|
||||
var transitions = className(list) + ", " +
|
||||
op0 + " -> " + className(view1) + ", " +
|
||||
op1 + " -> " + className(view2) + ", " +
|
||||
op2 + " -> " + className3;
|
||||
assertTrue(className3.contains("Stable"), transitions);
|
||||
assertUnevaluated(list);
|
||||
assertUnevaluated(view1);
|
||||
assertUnevaluated(view2);
|
||||
assertUnevaluated(view3);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void recursiveCall() {
|
||||
AtomicReference<IntFunction<Integer>> ref = new AtomicReference<>();
|
||||
var lazy = StableValue.list(SIZE, i -> ref.get().apply(i));
|
||||
var lazy = List.ofLazy(SIZE, i -> ref.get().apply(i));
|
||||
ref.set(lazy::get);
|
||||
var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX));
|
||||
assertEquals("Recursive initialization of a stable value is illegal", x.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void indexOfNullInViews() {
|
||||
final int size = 5;
|
||||
final int middle = 2;
|
||||
viewOperations().forEach(op0 -> {
|
||||
viewOperations().forEach( op1 -> {
|
||||
viewOperations().forEach(op2 -> {
|
||||
var list = StableValue.list(size, x -> x == middle ? null : x);;
|
||||
var view1 = op0.apply(list);
|
||||
var view2 = op1.apply(view1);
|
||||
var view3 = op2.apply(view2);
|
||||
assertEquals(middle, view3.indexOf(null));
|
||||
});
|
||||
});
|
||||
});
|
||||
assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage());
|
||||
}
|
||||
|
||||
// Immutability
|
||||
@ -355,7 +290,7 @@ final class StableListTest {
|
||||
}
|
||||
|
||||
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
|
||||
var lazy = newList();
|
||||
var lazy = newLazyList();
|
||||
assertThrows(expectedType, () -> operation.accept(lazy));
|
||||
var sub = lazy.subList(1, SIZE / 2);
|
||||
assertThrows(expectedType, () -> operation.accept(sub));
|
||||
@ -367,14 +302,14 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void serializable() {
|
||||
serializable(newList());
|
||||
serializable(newEmptyList());
|
||||
serializable(newLazyList());
|
||||
serializable(newEmptyLazyList());
|
||||
}
|
||||
|
||||
void serializable(List<Integer> list) {
|
||||
assertFalse(list instanceof Serializable);
|
||||
if (list.size()>INDEX) {
|
||||
assertFalse(newList().subList(1, INDEX) instanceof Serializable);
|
||||
assertFalse(newLazyList().subList(1, INDEX) instanceof Serializable);
|
||||
}
|
||||
assertFalse(list.iterator() instanceof Serializable);
|
||||
assertFalse(list.reversed() instanceof Serializable);
|
||||
@ -383,54 +318,25 @@ final class StableListTest {
|
||||
|
||||
@Test
|
||||
void randomAccess() {
|
||||
assertInstanceOf(RandomAccess.class, newList());
|
||||
assertInstanceOf(RandomAccess.class, newEmptyList());
|
||||
assertInstanceOf(RandomAccess.class, newList().subList(1, INDEX));
|
||||
assertInstanceOf(RandomAccess.class, newLazyList());
|
||||
assertInstanceOf(RandomAccess.class, newEmptyLazyList());
|
||||
assertInstanceOf(RandomAccess.class, newLazyList().subList(1, INDEX));
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinct() {
|
||||
StableValueImpl<Integer>[] array = StableUtil.array(SIZE);
|
||||
assertEquals(SIZE, array.length);
|
||||
// Check, every StableValue is distinct
|
||||
Map<StableValue<Integer>, Boolean> idMap = new IdentityHashMap<>();
|
||||
for (var e: array) {
|
||||
idMap.put(e, true);
|
||||
void functionHolder() {
|
||||
LazyConstantTestUtil.CountingIntFunction<Integer> cif = new LazyConstantTestUtil.CountingIntFunction<>(IDENTITY);
|
||||
List<Integer> f1 = List.ofLazy(SIZE, cif);
|
||||
|
||||
Object holder = LazyConstantTestUtil.functionHolder(f1);
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
assertEquals(SIZE - i, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
int v = f1.get(i);
|
||||
int v2 = f1.get(i);
|
||||
}
|
||||
assertEquals(SIZE, idMap.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void childObjectOpsLazy() {
|
||||
viewOperations().forEach(op0 -> {
|
||||
viewOperations().forEach(op1 -> {
|
||||
viewOperations().forEach(op2 -> {
|
||||
childOperations().forEach(co -> {
|
||||
var list = newList();
|
||||
var view1 = op0.apply(list);
|
||||
var view2 = op1.apply(view1);
|
||||
var view3 = op2.apply(view2);
|
||||
var child = co.apply(view3);
|
||||
var childClassName = className(child);
|
||||
var transitions = className(list) + ", " +
|
||||
op0 + " -> " + className(view1) + ", " +
|
||||
op1 + " -> " + className(view2) + ", " +
|
||||
op2 + " -> " + className(view3) + ", " +
|
||||
co + " -> " + childClassName;
|
||||
|
||||
// None of these operations should trigger evaluation
|
||||
var childToString = child.toString();
|
||||
int childHashCode = child.hashCode();
|
||||
boolean childEqualToNewObj = child.equals(new Object());
|
||||
|
||||
assertUnevaluated(list);
|
||||
assertUnevaluated(view1);
|
||||
assertUnevaluated(view2);
|
||||
assertUnevaluated(view3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
}
|
||||
|
||||
// Support constructs
|
||||
@ -516,25 +422,15 @@ final class StableListTest {
|
||||
);
|
||||
}
|
||||
|
||||
static List<Integer> newList() {
|
||||
return StableValue.list(SIZE, IDENTITY);
|
||||
static List<Integer> newLazyList() {
|
||||
return List.ofLazy(SIZE, IDENTITY);
|
||||
}
|
||||
|
||||
static List<Integer> newEmptyList() {
|
||||
return StableValue.list(ZERO, IDENTITY);
|
||||
static List<Integer> newEmptyLazyList() {
|
||||
return List.ofLazy(ZERO, IDENTITY);
|
||||
}
|
||||
|
||||
static List<Integer> newRegularList() {
|
||||
return IntStream.range(0, SIZE).boxed().toList();
|
||||
}
|
||||
|
||||
static String asString(String first, List<Integer> list) {
|
||||
return "[" + first + ", " + Stream.generate(() -> ".unset")
|
||||
.limit(list.size() - 1)
|
||||
.collect(Collectors.joining(", ")) + "]";
|
||||
}
|
||||
|
||||
static String className(Object o) {
|
||||
return o.getClass().getName();
|
||||
}
|
||||
}
|
||||
573
test/jdk/java/lang/LazyConstant/LazyMapTest.java
Normal file
573
test/jdk/java/lang/LazyConstant/LazyMapTest.java
Normal file
@ -0,0 +1,573 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for lazy map methods
|
||||
* @enablePreview
|
||||
* @run junit/othervm --add-opens java.base/java.util=ALL-UNNAMED LazyMapTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.mapping;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class LazyMapTest {
|
||||
|
||||
enum Value {
|
||||
// Zero is here so that we have enums with ordinals before the first one
|
||||
// actually used in input sets (i.e. ZERO is not in the input set)
|
||||
ZERO(0),
|
||||
ILLEGAL_BEFORE(-1),
|
||||
// Valid values
|
||||
THIRTEEN(13) {
|
||||
@Override
|
||||
public String toString() {
|
||||
// getEnumConstants will be `null` for this enum as it is overridden
|
||||
return super.toString()+" (Overridden)";
|
||||
}
|
||||
},
|
||||
ILLEGAL_BETWEEN(-2),
|
||||
FORTY_TWO(42),
|
||||
// Illegal values (not in the input set)
|
||||
ILLEGAL_AFTER(-3);
|
||||
|
||||
final int intValue;
|
||||
|
||||
Value(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
int asInt() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Function<Value, Integer> MAPPER = Value::asInt;
|
||||
|
||||
private static final Value KEY = Value.FORTY_TWO;
|
||||
private static final Integer VALUE = MAPPER.apply(KEY);
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void factoryInvariants(Set<Value> set) {
|
||||
assertThrows(NullPointerException.class, () -> Map.ofLazy(set, null), set.getClass().getSimpleName());
|
||||
assertThrows(NullPointerException.class, () -> Map.ofLazy(null, MAPPER));
|
||||
Set<Value> setWithNull = new HashSet<>();
|
||||
setWithNull.add(KEY);
|
||||
setWithNull.add(null);
|
||||
assertThrows(NullPointerException.class, () -> Map.ofLazy(setWithNull, MAPPER));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("emptySets")
|
||||
void empty(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
assertTrue(lazy.isEmpty());
|
||||
assertEquals("{}", lazy.toString());
|
||||
assertThrows(NullPointerException.class, () -> lazy.get(null));
|
||||
assertNotEquals(null, lazy);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void size(Set<Value> set) {
|
||||
assertEquals(newRegularMap(set).size(), newLazyMap(set).size());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void get(Set<Value> set) {
|
||||
LazyConstantTestUtil.CountingFunction<Value, Integer> cf = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
|
||||
var lazy = Map.ofLazy(set, cf);
|
||||
int cnt = 1;
|
||||
for (Value v : set) {
|
||||
assertEquals(MAPPER.apply(v), lazy.get(v));
|
||||
assertEquals(cnt, cf.cnt());
|
||||
assertEquals(MAPPER.apply(v), lazy.get(v));
|
||||
assertEquals(cnt++, cf.cnt());
|
||||
}
|
||||
assertNull(lazy.get(Value.ILLEGAL_BETWEEN));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void exception(Set<Value> set) {
|
||||
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = Map.ofLazy(set, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(2, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, lazy::toString);
|
||||
assertEquals(3, cif.cnt());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void containsKey(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
for (Value v : set) {
|
||||
assertTrue(lazy.containsKey(v));
|
||||
}
|
||||
assertFalse(lazy.containsKey(Value.ILLEGAL_BETWEEN));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void containsValue(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
for (Value v : set) {
|
||||
assertTrue(lazy.containsValue(MAPPER.apply(v)));
|
||||
}
|
||||
assertFalse(lazy.containsValue(MAPPER.apply(Value.ILLEGAL_BETWEEN)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void forEach(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var ref = newRegularMap(set);
|
||||
Set<Map.Entry<Value, Integer>> expected = ref.entrySet();
|
||||
Set<Map.Entry<Value, Integer>> actual = new HashSet<>();
|
||||
lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v)));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("emptySets")
|
||||
void toStringTestEmpty(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
assertEquals("{}", lazy.toString());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void toStringTest(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var toString = lazy.toString();
|
||||
assertTrue(toString.startsWith("{"));
|
||||
assertTrue(toString.endsWith("}"));
|
||||
|
||||
// Key order is unspecified
|
||||
for (Value key : set) {
|
||||
toString = lazy.toString();
|
||||
assertTrue(toString.contains(key + "=" + MAPPER.apply(key)), toString);
|
||||
}
|
||||
|
||||
// One between the values
|
||||
assertEquals(set.size() - 1, toString.chars().filter(ch -> ch == ',').count());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void hashCodeTest(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var regular = newRegularMap(set);
|
||||
assertEquals(regular.hashCode(), lazy.hashCode());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void equality(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var regular = newRegularMap(set);
|
||||
assertEquals(regular, lazy);
|
||||
assertEquals(lazy, regular);
|
||||
assertNotEquals("A", lazy);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void circular(Set<Value> set) {
|
||||
final AtomicReference<Map<?, ?>> ref = new AtomicReference<>();
|
||||
Map<Value, Map<?, ?>> lazy = Map.ofLazy(set, _ -> ref.get());
|
||||
ref.set(lazy);
|
||||
lazy.get(KEY);
|
||||
var toString = lazy.toString();
|
||||
assertTrue(toString.contains("FORTY_TWO=(this Map)"), toString);
|
||||
assertDoesNotThrow((() -> lazy.equals(lazy)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void recursiveCall(Set<Value> set) {
|
||||
final AtomicReference<Map<Value, ?>> ref = new AtomicReference<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<Value, Map<Value, Object>> lazy = Map.ofLazy(set, k -> (Map<Value, Object>) ref.get().get(k));
|
||||
ref.set(lazy);
|
||||
var x = assertThrows(IllegalStateException.class, () -> lazy.get(KEY));
|
||||
assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void entrySet(Set<Value> set) {
|
||||
var lazy = newLazyMap(set).entrySet();
|
||||
var regular = newRegularMap(set).entrySet();
|
||||
assertTrue(regular.equals(lazy));
|
||||
assertTrue(lazy.equals(regular));
|
||||
assertTrue(regular.equals(lazy));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void entrySetToString(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var lazyEntrySet = lazy.entrySet();
|
||||
var toString = lazyEntrySet.toString();
|
||||
for (var key : set) {
|
||||
assertTrue(toString.contains(key + "=" + MAPPER.apply(key)));
|
||||
}
|
||||
assertTrue(toString.startsWith("["));
|
||||
assertTrue(toString.endsWith("]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void values(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var lazyValues = lazy.values();
|
||||
// Look at one of the elements
|
||||
var val = lazyValues.stream().iterator().next();
|
||||
assertEquals(lazy.size() - 1, functionCounter(lazy));
|
||||
|
||||
// Mod ops
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazyValues.remove(val));
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazyValues.add(val));
|
||||
assertThrows(UnsupportedOperationException.class, lazyValues::clear);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazyValues.addAll(Set.of(VALUE)));
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazyValues.removeIf(i -> true));
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazyValues.retainAll(Set.of(VALUE)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void valuesToString(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var lazyValues = lazy.values();
|
||||
var toString = lazyValues.toString();
|
||||
|
||||
// Key order is unspecified
|
||||
for (Value key : set) {
|
||||
assertTrue(toString.contains(MAPPER.apply(key).toString()), toString);
|
||||
}
|
||||
assertTrue(toString.startsWith("["), toString);
|
||||
assertTrue(toString.endsWith("]"), toString);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void iteratorNext(Set<Value> set) {
|
||||
Set<Value> encountered = new HashSet<>();
|
||||
var iterator = newLazyMap(set).entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var entry = iterator.next();
|
||||
assertEquals(MAPPER.apply(entry.getKey()), entry.getValue());
|
||||
encountered.add(entry.getKey());
|
||||
}
|
||||
assertEquals(set, encountered);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void iteratorForEachRemaining(Set<Value> set) {
|
||||
Set<Value> encountered = new HashSet<>();
|
||||
var iterator = newLazyMap(set).entrySet().iterator();
|
||||
var entry = iterator.next();
|
||||
assertEquals(MAPPER.apply(entry.getKey()), entry.getValue());
|
||||
encountered.add(entry.getKey());
|
||||
iterator.forEachRemaining(e -> {
|
||||
assertEquals(MAPPER.apply(e.getKey()), e.getValue());
|
||||
encountered.add(e.getKey());
|
||||
});
|
||||
assertEquals(set, encountered);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void lazyEntry(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
var entry = lazy.entrySet().stream()
|
||||
.filter(e -> e.getKey().equals(KEY))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
|
||||
assertEquals(lazy.size(), functionCounter(lazy));
|
||||
var otherDifferent = Map.entry(Value.ZERO, -1);
|
||||
assertNotEquals(entry, otherDifferent);
|
||||
assertEquals(lazy.size(), functionCounter(lazy));
|
||||
var otherEqual = Map.entry(entry.getKey(), entry.getValue());
|
||||
assertEquals(entry, otherEqual);
|
||||
assertEquals(lazy.size() - 1, functionCounter(lazy));
|
||||
assertEquals(entry.hashCode(), otherEqual.hashCode());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void lazyForEachEntry(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
// Only touch the key.
|
||||
lazy.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
|
||||
assertEquals(lazy.size(), functionCounter(lazy)); // No evaluation
|
||||
// Only touch the value.
|
||||
lazy.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
|
||||
assertEquals(0, functionCounter(lazy));
|
||||
}
|
||||
|
||||
// Immutability
|
||||
@ParameterizedTest
|
||||
@MethodSource("unsupportedOperations")
|
||||
void unsupported(Operation operation) {
|
||||
assertThrowsForOperation(UnsupportedOperationException.class, operation);
|
||||
}
|
||||
|
||||
// Method parameter invariant checking
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nullAverseOperations")
|
||||
void nullAverse(Operation operation) {
|
||||
assertThrowsForOperation(NullPointerException.class, operation);
|
||||
}
|
||||
|
||||
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
|
||||
for (Set<Value> set : allSets().toList()) {
|
||||
var lazy = newLazyMap(set);
|
||||
assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementing interfaces
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void serializable(Set<Value> set) {
|
||||
var lazy = newLazyMap(set);
|
||||
assertFalse(lazy instanceof Serializable);
|
||||
assertFalse(lazy.entrySet() instanceof Serializable);
|
||||
assertFalse(lazy.values() instanceof Serializable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullResult() {
|
||||
var lazy = Map.ofLazy(Set.of(0), _ -> null);
|
||||
assertThrows(NullPointerException.class, () -> lazy.getOrDefault(0, 1));;
|
||||
assertTrue(lazy.containsKey(0));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void functionHolder(Set<Value> set) {
|
||||
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
|
||||
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
|
||||
|
||||
Object holder = LazyConstantTestUtil.functionHolder(lazy);
|
||||
|
||||
int i = 0;
|
||||
for (Value key : set) {
|
||||
assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
int v = lazy.get(key);
|
||||
int v2 = lazy.get(key);
|
||||
i++;
|
||||
}
|
||||
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void functionHolderViaEntrySet(Set<Value> set) {
|
||||
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
|
||||
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
|
||||
|
||||
Object holder = LazyConstantTestUtil.functionHolder(lazy);
|
||||
|
||||
int i = 0;
|
||||
for (Map.Entry<Value, Integer> e : lazy.entrySet()) {
|
||||
assertEquals(set.size() - i, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
int v = e.getValue();
|
||||
int v2 = e.getValue();
|
||||
i++;
|
||||
}
|
||||
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void underlyingRefViaEntrySetForEach(Set<Value> set) {
|
||||
LazyConstantTestUtil.CountingFunction<Value, Integer> cif = new LazyConstantTestUtil.CountingFunction<>(MAPPER);
|
||||
Map<Value, Integer> lazy = Map.ofLazy(set, cif);
|
||||
|
||||
Object holder = LazyConstantTestUtil.functionHolder(lazy);
|
||||
|
||||
final AtomicInteger i = new AtomicInteger();
|
||||
lazy.entrySet().forEach(e -> {
|
||||
assertEquals(set.size() - i.get(), LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertSame(cif, LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
Integer val = e.getValue();
|
||||
Integer val2 = e.getValue();
|
||||
i.incrementAndGet();
|
||||
});
|
||||
assertEquals(0, LazyConstantTestUtil.functionHolderCounter(holder));
|
||||
assertNull(LazyConstantTestUtil.functionHolderFunction(holder));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesOptimizedVersion() {
|
||||
Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt);
|
||||
assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName());
|
||||
Map<Value, Integer> emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt);
|
||||
assertFalse(emptyMap.getClass().getName().contains("Enum"), emptyMap.getClass().getName());
|
||||
Map<Value, Integer> regularMap = Map.ofLazy(Set.of(KEY), Value::asInt);
|
||||
assertFalse(regularMap.getClass().getName().contains("Enum"), regularMap.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void overriddenEnum() {
|
||||
final var overridden = Value.THIRTEEN;
|
||||
Map<Value, Integer> enumMap = Map.ofLazy(EnumSet.of(overridden), MAPPER);
|
||||
assertEquals(MAPPER.apply(overridden), enumMap.get(overridden), enumMap.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void enumAliasing() {
|
||||
enum MyEnum {FOO, BAR}
|
||||
enum MySecondEnum{BAZ, QUX}
|
||||
Map<MyEnum, Integer> mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
|
||||
assertEquals(MyEnum.BAR.ordinal(), mapEnum.get(MyEnum.BAR));
|
||||
// Make sure class is checked, not just `ordinal()`
|
||||
assertNull(mapEnum.get(MySecondEnum.QUX));
|
||||
}
|
||||
|
||||
// Support constructs
|
||||
|
||||
record Operation(String name,
|
||||
Consumer<Map<Value, Integer>> consumer) implements Consumer<Map<Value, Integer>> {
|
||||
@java.lang.Override
|
||||
public void accept(Map<Value, Integer> map) { consumer.accept(map); }
|
||||
@java.lang.Override
|
||||
public String toString() { return name; }
|
||||
}
|
||||
|
||||
static Stream<Operation> nullAverseOperations() {
|
||||
return Stream.of(
|
||||
new Operation("forEach", m -> m.forEach(null))
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Operation> unsupportedOperations() {
|
||||
return Stream.of(
|
||||
new Operation("clear", Map::clear),
|
||||
new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)),
|
||||
new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)),
|
||||
new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)),
|
||||
new Operation("merge", m -> m.merge(KEY, VALUE, (a, _) -> a)),
|
||||
new Operation("put", m -> m.put(KEY, 0)),
|
||||
new Operation("putAll", m -> m.putAll(Map.of())),
|
||||
new Operation("remove1", m -> m.remove(KEY)),
|
||||
new Operation("remove2", m -> m.remove(KEY, VALUE)),
|
||||
new Operation("replace2", m -> m.replace(KEY, 1)),
|
||||
new Operation("replace3", m -> m.replace(KEY, VALUE, 1)),
|
||||
new Operation("replaceAll", m -> m.replaceAll((a, _) -> MAPPER.apply(a)))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static Map<Value, Integer> newLazyMap(Set<Value> set) {
|
||||
return Map.ofLazy(set, MAPPER);
|
||||
}
|
||||
static Map<Value, Integer> newRegularMap(Set<Value> set) {
|
||||
return set.stream()
|
||||
.collect(Collectors.toMap(Function.identity(), MAPPER));
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> nonEmptySets() {
|
||||
return Stream.of(
|
||||
Set.of(KEY, Value.THIRTEEN),
|
||||
linkedHashSet(Value.THIRTEEN, KEY),
|
||||
treeSet(KEY, Value.THIRTEEN),
|
||||
EnumSet.of(KEY, Value.THIRTEEN)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> emptySets() {
|
||||
return Stream.of(
|
||||
Set.of(),
|
||||
linkedHashSet(),
|
||||
treeSet(),
|
||||
EnumSet.noneOf(Value.class)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> allSets() {
|
||||
return Stream.concat(
|
||||
nonEmptySets(),
|
||||
emptySets()
|
||||
);
|
||||
}
|
||||
|
||||
static Set<Value> treeSet(Value... values) {
|
||||
return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values);
|
||||
}
|
||||
|
||||
static Set<Value> linkedHashSet(Value... values) {
|
||||
return populate(new LinkedHashSet<>(), values);
|
||||
}
|
||||
|
||||
static Set<Value> populate(Set<Value> set, Value... values) {
|
||||
set.addAll(Arrays.asList(values));
|
||||
return set;
|
||||
}
|
||||
|
||||
private static int functionCounter(Map<?, ?> lazy) {
|
||||
final Object holder = LazyConstantTestUtil.functionHolder(lazy);
|
||||
return LazyConstantTestUtil.functionHolderCounter(holder);
|
||||
}
|
||||
|
||||
}
|
||||
@ -24,14 +24,14 @@
|
||||
/* @test
|
||||
* @summary Basic tests for TrustedFieldType implementations
|
||||
* @modules jdk.unsupported/sun.misc
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @modules java.base/jdk.internal.lang
|
||||
* @modules java.base/jdk.internal.misc
|
||||
* @enablePreview
|
||||
* @run junit/othervm --add-opens java.base/jdk.internal.lang.stable=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest
|
||||
* @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED -Dopens=true TrustedFieldTypeTest
|
||||
* @run junit/othervm -Dopens=false TrustedFieldTypeTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import jdk.internal.lang.LazyConstantImpl;
|
||||
import jdk.internal.misc.Unsafe;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@ -39,84 +39,89 @@ import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InaccessibleObjectException;
|
||||
import java.lang.LazyConstant;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class TrustedFieldTypeTest {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final Supplier<Integer> SUPPLIER = () -> VALUE;
|
||||
|
||||
@Test
|
||||
void varHandle() throws NoSuchFieldException, IllegalAccessException {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
|
||||
StableValue<Integer> originalValue = StableValue.of();
|
||||
LazyConstant<Integer> originalValue = LazyConstant.of(SUPPLIER);
|
||||
@SuppressWarnings("unchecked")
|
||||
StableValue<Integer>[] originalArrayValue = new StableValue[10];
|
||||
LazyConstant<Integer>[] originalArrayValue = new LazyConstant[10];
|
||||
|
||||
final class Holder {
|
||||
private final StableValue<Integer> value = originalValue;
|
||||
private final LazyConstant<Integer> value = originalValue;
|
||||
}
|
||||
final class ArrayHolder {
|
||||
private final StableValue<Integer>[] array = originalArrayValue;
|
||||
private final LazyConstant<Integer>[] array = originalArrayValue;
|
||||
}
|
||||
|
||||
|
||||
VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", StableValue.class);
|
||||
VarHandle valueVarHandle = lookup.findVarHandle(Holder.class, "value", LazyConstant.class);
|
||||
Holder holder = new Holder();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
valueVarHandle.set(holder, StableValue.of())
|
||||
valueVarHandle.set(holder, LazyConstant.of(SUPPLIER))
|
||||
);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
valueVarHandle.compareAndSet(holder, originalValue, StableValue.of())
|
||||
valueVarHandle.compareAndSet(holder, originalValue, LazyConstant.of(SUPPLIER))
|
||||
);
|
||||
|
||||
VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", StableValue[].class);
|
||||
VarHandle arrayVarHandle = lookup.findVarHandle(ArrayHolder.class, "array", LazyConstant[].class);
|
||||
ArrayHolder arrayHolder = new ArrayHolder();
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
arrayVarHandle.set(arrayHolder, new StableValue[1])
|
||||
arrayVarHandle.set(arrayHolder, new LazyConstant[1])
|
||||
);
|
||||
|
||||
assertThrows(UnsupportedOperationException.class, () ->
|
||||
arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new StableValue[1])
|
||||
arrayVarHandle.compareAndSet(arrayHolder, originalArrayValue, new LazyConstant[1])
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateStableValueContentVia_j_i_m_Unsafe() {
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
stableValue.trySet(42);
|
||||
void updateComputedConstantContentVia_j_i_m_Unsafe() {
|
||||
LazyConstant<Integer> lazyConstant = LazyConstant.of(SUPPLIER);
|
||||
lazyConstant.get();
|
||||
jdk.internal.misc.Unsafe unsafe = Unsafe.getUnsafe();
|
||||
|
||||
long offset = unsafe.objectFieldOffset(stableValue.getClass(), "contents");
|
||||
long offset = unsafe.objectFieldOffset(lazyConstant.getClass(), "constant");
|
||||
assertTrue(offset > 0);
|
||||
|
||||
// Unfortunately, it is possible to update the underlying data via jdk.internal.misc.Unsafe
|
||||
Object oldData = unsafe.getAndSetReference(stableValue, offset, 13);
|
||||
assertEquals(42, oldData);
|
||||
assertEquals(13, stableValue.orElseThrow());
|
||||
Object oldData = unsafe.getAndSetReference(lazyConstant, offset, 13);
|
||||
assertEquals(VALUE, oldData);
|
||||
assertEquals(13, lazyConstant.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateStableValueContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException {
|
||||
void updateComputedConstantContentViaSetAccessible() throws NoSuchFieldException, IllegalAccessException {
|
||||
|
||||
if (Boolean.getBoolean("opens")) {
|
||||
// Unfortunately, add-opens allows direct access to the `value` field
|
||||
Field field = StableValueImpl.class.getDeclaredField("contents");
|
||||
Field field = LazyConstantImpl.class.getDeclaredField("constant");
|
||||
field.setAccessible(true);
|
||||
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
stableValue.trySet(42);
|
||||
LazyConstant<Integer> lazyConstant = LazyConstant.of(SUPPLIER);
|
||||
lazyConstant.get();
|
||||
|
||||
Object oldData = field.get(stableValue);
|
||||
assertEquals(42, oldData);
|
||||
Object oldData = field.get(lazyConstant);
|
||||
assertEquals(VALUE, oldData);
|
||||
|
||||
field.set(stableValue, 13);
|
||||
assertEquals(13, stableValue.orElseThrow());
|
||||
field.set(lazyConstant, 13);
|
||||
assertEquals(13, lazyConstant.get());
|
||||
} else {
|
||||
Field field = StableValueImpl.class.getDeclaredField("contents");
|
||||
Field field = LazyConstantImpl.class.getDeclaredField("constant");
|
||||
assertThrows(InaccessibleObjectException.class, ()-> field.setAccessible(true));
|
||||
}
|
||||
}
|
||||
@ -1,250 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableFunction methods
|
||||
* @enablePreview
|
||||
* @run junit StableFunctionTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableFunctionTest {
|
||||
|
||||
enum Value {
|
||||
// Zero is here so that we have enums with ordinals before the first one
|
||||
// actually used in input sets (i.e. ZERO is not in the input set)
|
||||
ZERO(0),
|
||||
ILLEGAL_BEFORE(-1),
|
||||
// Valid values
|
||||
THIRTEEN(13) {
|
||||
@Override
|
||||
public String toString() {
|
||||
// getEnumConstants will be `null` for this enum as it is overridden
|
||||
return super.toString()+" (Overridden)";
|
||||
}
|
||||
},
|
||||
ILLEGAL_BETWEEN(-2),
|
||||
FORTY_TWO(42),
|
||||
// Illegal values (not in the input set)
|
||||
ILLEGAL_AFTER(-3);
|
||||
|
||||
final int intValue;
|
||||
|
||||
Value(int intValue) {
|
||||
this.intValue = intValue;
|
||||
}
|
||||
|
||||
int asInt() {
|
||||
return intValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Function<Value, Integer> MAPPER = Value::asInt;
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void factoryInvariants(Set<Value> inputs) {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(null, MAPPER));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(inputs, null));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void basic(Set<Value> inputs) {
|
||||
basic(inputs, MAPPER);
|
||||
toStringTest(inputs, MAPPER);
|
||||
basic(inputs, _ -> null);
|
||||
toStringTest(inputs, _ -> null);
|
||||
}
|
||||
|
||||
void basic(Set<Value> inputs, Function<Value, Integer> mapper) {
|
||||
StableTestUtil.CountingFunction<Value, Integer> cif = new StableTestUtil.CountingFunction<>(mapper);
|
||||
var cached = StableValue.function(inputs, cif);
|
||||
assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals(mapper.apply(Value.FORTY_TWO), cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
var x0 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BEFORE));
|
||||
assertEquals("Input not allowed: ILLEGAL_BEFORE", x0.getMessage());
|
||||
var x1 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_BETWEEN));
|
||||
assertEquals("Input not allowed: ILLEGAL_BETWEEN", x1.getMessage());
|
||||
var x2 = assertThrows(IllegalArgumentException.class, () -> cached.apply(Value.ILLEGAL_AFTER));
|
||||
assertEquals("Input not allowed: ILLEGAL_AFTER", x2.getMessage());
|
||||
}
|
||||
|
||||
void toStringTest(Set<Value> inputs, Function<Value, Integer> mapper) {
|
||||
var cached = StableValue.function(inputs, mapper);
|
||||
cached.apply(Value.FORTY_TWO);
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.startsWith("{"));
|
||||
// Key order is unspecified
|
||||
assertTrue(toString.contains(Value.THIRTEEN + "=.unset"));
|
||||
assertTrue(toString.contains(Value.FORTY_TWO + "=" + mapper.apply(Value.FORTY_TWO)));
|
||||
assertTrue(toString.endsWith("}"));
|
||||
// One between the values
|
||||
assertEquals(1L, toString.chars().filter(ch -> ch == ',').count());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("emptySets")
|
||||
void empty(Set<Value> inputs) {
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, Value::asInt);
|
||||
Function<Value, Integer> f1 = StableValue.function(inputs, Value::asInt);
|
||||
assertEquals("{}", f0.toString());
|
||||
assertThrows(NullPointerException.class, () -> f0.apply(null));
|
||||
assertNotEquals(f0, f1);
|
||||
assertNotEquals(null, f0);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void exception(Set<Value> inputs) {
|
||||
StableTestUtil.CountingFunction<Value, Integer> cif = new StableTestUtil.CountingFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.function(inputs, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(Value.FORTY_TWO));
|
||||
assertEquals(2, cif.cnt());
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.startsWith("{"));
|
||||
// Key order is unspecified
|
||||
assertTrue(toString.contains(Value.THIRTEEN + "=.unset"));
|
||||
assertTrue(toString.contains(Value.FORTY_TWO + "=.unset"));
|
||||
assertTrue(toString.endsWith("}"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nonEmptySets")
|
||||
void circular(Set<Value> inputs) {
|
||||
final AtomicReference<Function<?, ?>> ref = new AtomicReference<>();
|
||||
Function<Value, Function<?, ?>> cached = StableValue.function(inputs, _ -> ref.get());
|
||||
ref.set(cached);
|
||||
cached.apply(Value.FORTY_TWO);
|
||||
var toString = cached.toString();
|
||||
assertTrue(toString.contains("FORTY_TWO=(this StableFunction)"), toString);
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
assertDoesNotThrow((() -> cached.equals(cached)));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void equality(Set<Value> inputs) {
|
||||
Function<Value, Integer> mapper = Value::asInt;
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, mapper);
|
||||
Function<Value, Integer> f1 = StableValue.function(inputs, mapper);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("allSets")
|
||||
void hashCodeStable(Set<Value> inputs) {
|
||||
Function<Value, Integer> f0 = StableValue.function(inputs, Value::asInt);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
if (!inputs.isEmpty()) {
|
||||
f0.apply(Value.FORTY_TWO);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullKeys() {
|
||||
Set<Value> inputs = new HashSet<>();
|
||||
inputs.add(Value.FORTY_TWO);
|
||||
inputs.add(null);
|
||||
assertThrows(NullPointerException.class, () -> StableValue.function(inputs, MAPPER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void usesOptimizedVersion() {
|
||||
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(Value.FORTY_TWO), Value::asInt);
|
||||
assertEquals("jdk.internal.lang.stable.StableEnumFunction", enumFunction.getClass().getName());
|
||||
Function<Value, Integer> emptyFunction = StableValue.function(Set.of(), Value::asInt);
|
||||
assertEquals("jdk.internal.lang.stable.StableFunction", emptyFunction.getClass().getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void overriddenEnum() {
|
||||
final var overridden = Value.THIRTEEN;
|
||||
Function<Value, Integer> enumFunction = StableValue.function(EnumSet.of(overridden), Value::asInt);
|
||||
assertEquals(MAPPER.apply(overridden), enumFunction.apply(overridden));
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> nonEmptySets() {
|
||||
return Stream.of(
|
||||
Set.of(Value.FORTY_TWO, Value.THIRTEEN),
|
||||
linkedHashSet(Value.THIRTEEN, Value.FORTY_TWO),
|
||||
treeSet(Value.FORTY_TWO, Value.THIRTEEN),
|
||||
EnumSet.of(Value.FORTY_TWO, Value.THIRTEEN)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> emptySets() {
|
||||
return Stream.of(
|
||||
Set.of(),
|
||||
linkedHashSet(),
|
||||
treeSet(),
|
||||
EnumSet.noneOf(Value.class)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Set<Value>> allSets() {
|
||||
return Stream.concat(
|
||||
nonEmptySets(),
|
||||
emptySets()
|
||||
);
|
||||
}
|
||||
|
||||
static Set<Value> treeSet(Value... values) {
|
||||
return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values);
|
||||
}
|
||||
|
||||
static Set<Value> linkedHashSet(Value... values) {
|
||||
return populate(new LinkedHashSet<>(), values);
|
||||
}
|
||||
|
||||
static Set<Value> populate(Set<Value> set, Value... values) {
|
||||
set.addAll(Arrays.asList(values));
|
||||
return set;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableIntFunction methods
|
||||
* @enablePreview
|
||||
* @run junit StableIntFunctionTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableIntFunctionTest {
|
||||
|
||||
private static final int SIZE = 2;
|
||||
private static final IntFunction<Integer> MAPPER = i -> i;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(IllegalArgumentException.class, () -> StableValue.intFunction(-1, MAPPER));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.intFunction(SIZE, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void basic() {
|
||||
basic(MAPPER);
|
||||
basic(i -> null);
|
||||
}
|
||||
|
||||
void basic(IntFunction<Integer> mapper) {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(mapper);
|
||||
var cached = StableValue.intFunction(SIZE, cif);
|
||||
assertEquals("[.unset, .unset]", cached.toString());
|
||||
assertEquals(mapper.apply(1), cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals(mapper.apply(1), cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertEquals("[.unset, " + mapper.apply(1) + "]", cached.toString());
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(SIZE));
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(-1));
|
||||
assertThrows(IllegalArgumentException.class, () -> cached.apply(1_000_000));
|
||||
}
|
||||
|
||||
@Test
|
||||
void exception() {
|
||||
StableTestUtil.CountingIntFunction<Integer> cif = new StableTestUtil.CountingIntFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.intFunction(SIZE, cif);
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(1));
|
||||
assertEquals(1, cif.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> cached.apply(1));
|
||||
assertEquals(2, cif.cnt());
|
||||
assertEquals("[.unset, .unset]", cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void circular() {
|
||||
final AtomicReference<IntFunction<?>> ref = new AtomicReference<>();
|
||||
IntFunction<IntFunction<?>> cached = StableValue.intFunction(SIZE, _ -> ref.get());
|
||||
ref.set(cached);
|
||||
cached.apply(0);
|
||||
String toString = cached.toString();
|
||||
assertEquals("[(this StableIntFunction), .unset]", toString);
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
assertDoesNotThrow((() -> cached.equals(cached)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equality() {
|
||||
IntFunction<Integer> f0 = StableValue.intFunction(8, MAPPER);
|
||||
IntFunction<Integer> f1 = StableValue.intFunction(8, MAPPER);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeStable() {
|
||||
IntFunction<Integer> f0 = StableValue.intFunction(8, MAPPER);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
f0.apply(4);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,388 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableMap methods
|
||||
* @modules java.base/jdk.internal.lang.stable
|
||||
* @enablePreview
|
||||
* @run junit StableMapTest
|
||||
*/
|
||||
|
||||
import jdk.internal.lang.stable.StableUtil;
|
||||
import jdk.internal.lang.stable.StableValueImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableMapTest {
|
||||
|
||||
private static final int NOT_PRESENT = 147;
|
||||
private static final int KEY = 7;
|
||||
private static final Set<Integer> KEYS = Set.of(0, KEY, 13);
|
||||
private static final Set<Integer> EMPTY = Set.of();
|
||||
private static final Function<Integer, Integer> IDENTITY = Function.identity();
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(KEYS, null));
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(null, IDENTITY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmpty() {
|
||||
assertFalse(newMap().isEmpty());
|
||||
assertTrue(newEmptyMap().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void size() {
|
||||
assertEquals(KEYS.size(), newMap().size());
|
||||
assertEquals(EMPTY.size(), newEmptyMap().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void get() {
|
||||
StableTestUtil.CountingFunction<Integer, Integer> cf = new StableTestUtil.CountingFunction<>(IDENTITY);
|
||||
var lazy = StableValue.map(KEYS, cf);
|
||||
int cnt = 1;
|
||||
for (int i : KEYS) {
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(cnt, cf.cnt());
|
||||
assertEquals(i, lazy.get(i));
|
||||
assertEquals(cnt++, cf.cnt());
|
||||
}
|
||||
assertNull(lazy.get(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getException() {
|
||||
StableTestUtil.CountingFunction<Integer, Integer> cf = new StableTestUtil.CountingFunction<>(_ -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var lazy = StableValue.map(KEYS, cf);
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(1, cf.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY));
|
||||
assertEquals(2, cf.cnt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsKey() {
|
||||
var lazy = newMap();
|
||||
for (int i : KEYS) {
|
||||
assertTrue(lazy.containsKey(i));
|
||||
}
|
||||
assertFalse(lazy.containsKey(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void containsValue() {
|
||||
var lazy = newMap();
|
||||
for (int i : KEYS) {
|
||||
assertTrue(lazy.containsValue(i));
|
||||
}
|
||||
assertFalse(lazy.containsValue(NOT_PRESENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void forEach() {
|
||||
var lazy = newMap();
|
||||
Set<Map.Entry<Integer, Integer>> expected = KEYS.stream()
|
||||
.map(i -> new AbstractMap.SimpleImmutableEntry<>(i , i))
|
||||
.collect(Collectors.toSet());
|
||||
Set<Map.Entry<Integer, Integer>> actual = new HashSet<>();
|
||||
lazy.forEach((k, v) -> actual.add(new AbstractMap.SimpleImmutableEntry<>(k , v)));
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringTest() {
|
||||
assertEquals("{}", newEmptyMap().toString());
|
||||
var map = StableValue.map(Set.of(KEY), IDENTITY);
|
||||
assertEquals("{" + KEY + "=.unset}", map.toString());
|
||||
map.get(KEY);
|
||||
assertEquals("{" + KEY + "=" + KEY + "}", map.toString());
|
||||
String actual = newMap().toString();
|
||||
assertTrue(actual.startsWith("{"));
|
||||
for (int key : KEYS) {
|
||||
assertTrue(actual.contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(actual.endsWith("}"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeTest() {
|
||||
assertEquals(Map.of().hashCode(), newEmptyMap().hashCode());
|
||||
assertEquals(newRegularMap().hashCode(), newMap().hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsTest() {
|
||||
assertTrue(newEmptyMap().equals(Map.of()));
|
||||
assertTrue(Map.of().equals(newEmptyMap()));
|
||||
assertTrue(newMap().equals(newRegularMap()));
|
||||
assertTrue(newRegularMap().equals(newMap()));
|
||||
assertFalse(newMap().equals("A"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySet() {
|
||||
var regular = newRegularMap().entrySet();
|
||||
var actual = newMap().entrySet();
|
||||
assertTrue(regular.equals(actual));
|
||||
assertTrue(actual.equals(regular));
|
||||
assertTrue(regular.equals(actual));
|
||||
}
|
||||
|
||||
@Test
|
||||
void entrySetToString() {
|
||||
var map = newMap();
|
||||
var entrySet = map.entrySet();
|
||||
var toString = entrySet.toString();
|
||||
for (var key : KEYS) {
|
||||
assertTrue(toString.contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(toString.startsWith("["));
|
||||
assertTrue(toString.endsWith("]"));
|
||||
|
||||
map.get(KEY);
|
||||
for (var key : KEYS) {
|
||||
if (key.equals(KEY)) {
|
||||
continue;
|
||||
}
|
||||
assertTrue(entrySet.toString().contains(key + "=.unset"));
|
||||
}
|
||||
assertTrue(entrySet.toString().contains(KEY + "=" + KEY));
|
||||
}
|
||||
|
||||
@Test
|
||||
void values() {
|
||||
var map = newMap();
|
||||
var values = map.values();
|
||||
// Look at one of the elements
|
||||
var val = values.stream().iterator().next();
|
||||
var toString = map.toString();
|
||||
for (var key : KEYS) {
|
||||
if (key.equals(val)) {
|
||||
assertTrue(toString.contains(key + "=" + key));
|
||||
} else {
|
||||
assertTrue(toString.contains(key + "=.unset"));
|
||||
}
|
||||
}
|
||||
|
||||
// Mod ops
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.remove(KEY));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.add(KEY));
|
||||
assertThrows(UnsupportedOperationException.class, values::clear);
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.addAll(Set.of(1)));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.removeIf(i -> true));
|
||||
assertThrows(UnsupportedOperationException.class, () -> values.retainAll(Set.of(KEY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valuesToString() {
|
||||
var map = newMap();
|
||||
var values = map.values();
|
||||
assertEquals("[.unset, .unset, .unset]", values.toString());
|
||||
map.get(KEY);
|
||||
var afterGet = values.toString();
|
||||
assertTrue(afterGet.contains(Integer.toString(KEY)), afterGet);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorNext() {
|
||||
Set<Integer> encountered = new HashSet<>();
|
||||
var iterator = newMap().entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
var entry = iterator.next();
|
||||
assertEquals(entry.getKey(), entry.getValue());
|
||||
encountered.add(entry.getValue());
|
||||
}
|
||||
assertEquals(KEYS, encountered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void iteratorForEachRemaining() {
|
||||
Set<Integer> encountered = new HashSet<>();
|
||||
var iterator = newMap().entrySet().iterator();
|
||||
var entry = iterator.next();
|
||||
assertEquals(entry.getKey(), entry.getValue());
|
||||
encountered.add(entry.getValue());
|
||||
iterator.forEachRemaining(e -> {
|
||||
assertEquals(e.getKey(), e.getValue());
|
||||
encountered.add(e.getValue());
|
||||
});
|
||||
assertEquals(KEYS, encountered);
|
||||
}
|
||||
|
||||
@Test
|
||||
void stableEntry() {
|
||||
var map = newMap();
|
||||
var entry = map.entrySet().stream()
|
||||
.filter(e -> e.getKey().equals(KEY))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
|
||||
assertEquals(KEY + "=.unset", entry.toString());
|
||||
var otherDifferent = Map.entry(-1, -1);
|
||||
assertNotEquals(entry, otherDifferent);
|
||||
assertEquals(KEY + "=.unset", entry.toString());
|
||||
var otherEqual = Map.entry(KEY, KEY);
|
||||
assertEquals(entry, otherEqual);
|
||||
assertEquals(KEY + "=" + KEY, entry.toString());
|
||||
assertEquals(entry.hashCode(), otherEqual.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stableForEachEntry() {
|
||||
var map = newMap();
|
||||
// Only touch the key.
|
||||
map.entrySet().iterator().forEachRemaining(Map.Entry::getKey);
|
||||
map.entrySet().iterator()
|
||||
.forEachRemaining(e -> assertTrue(e.toString().contains(".unset")));
|
||||
// Only touch the value.
|
||||
map.entrySet().iterator().forEachRemaining(Map.Entry::getValue);
|
||||
map.entrySet().iterator()
|
||||
.forEachRemaining(e -> assertFalse(e.toString().contains(".unset")));
|
||||
}
|
||||
|
||||
// Immutability
|
||||
@ParameterizedTest
|
||||
@MethodSource("unsupportedOperations")
|
||||
void unsupported(Operation operation) {
|
||||
assertThrowsForOperation(UnsupportedOperationException.class, operation);
|
||||
}
|
||||
|
||||
// Method parameter invariant checking
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("nullAverseOperations")
|
||||
void nullAverse(Operation operation) {
|
||||
assertThrowsForOperation(NullPointerException.class, operation);
|
||||
}
|
||||
|
||||
static <T extends Throwable> void assertThrowsForOperation(Class<T> expectedType, Operation operation) {
|
||||
var lazy = newMap();
|
||||
assertThrows(expectedType, () -> operation.accept(lazy));
|
||||
}
|
||||
|
||||
// Implementing interfaces
|
||||
|
||||
@Test
|
||||
void serializable() {
|
||||
serializable(newMap());
|
||||
serializable(newEmptyMap());
|
||||
}
|
||||
|
||||
void serializable(Map<Integer, Integer> map) {
|
||||
assertFalse(map instanceof Serializable);
|
||||
assertFalse(map.entrySet() instanceof Serializable);
|
||||
assertFalse(map.keySet() instanceof Serializable);
|
||||
assertFalse(map.values() instanceof Serializable);
|
||||
}
|
||||
|
||||
@Test
|
||||
void distinct() {
|
||||
Map<Integer, StableValueImpl<Integer>> map = StableUtil.map(Set.of(1, 2, 3));
|
||||
assertEquals(3, map.size());
|
||||
// Check, every StableValue is distinct
|
||||
Map<StableValue<Integer>, Boolean> idMap = new IdentityHashMap<>();
|
||||
map.forEach((k, v) -> idMap.put(v, true));
|
||||
assertEquals(3, idMap.size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullResult() {
|
||||
var map = StableValue.map(Set.of(0), _ -> null);
|
||||
assertNull(map.getOrDefault(0, 1));;
|
||||
assertTrue(map.containsKey(0));
|
||||
assertNull(map.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullKeys() {
|
||||
Set<Integer> inputs = new HashSet<>();
|
||||
inputs.add(0);
|
||||
inputs.add(null);
|
||||
assertThrows(NullPointerException.class, () -> StableValue.map(inputs, IDENTITY));
|
||||
}
|
||||
|
||||
// Support constructs
|
||||
|
||||
record Operation(String name,
|
||||
Consumer<Map<Integer, Integer>> consumer) implements Consumer<Map<Integer, Integer>> {
|
||||
@java.lang.Override
|
||||
public void accept(Map<Integer, Integer> map) { consumer.accept(map); }
|
||||
@java.lang.Override
|
||||
public String toString() { return name; }
|
||||
}
|
||||
|
||||
static Stream<Operation> nullAverseOperations() {
|
||||
return Stream.of(
|
||||
new Operation("forEach", m -> m.forEach(null))
|
||||
);
|
||||
}
|
||||
|
||||
static Stream<Operation> unsupportedOperations() {
|
||||
return Stream.of(
|
||||
new Operation("clear", Map::clear),
|
||||
new Operation("compute", m -> m.compute(KEY, (_, _) -> 1)),
|
||||
new Operation("computeIfAbsent", m -> m.computeIfAbsent(KEY, _ -> 1)),
|
||||
new Operation("computeIfPresent", m -> m.computeIfPresent(KEY, (_, _) -> 1)),
|
||||
new Operation("merge", m -> m.merge(KEY, KEY, (a, _) -> a)),
|
||||
new Operation("put", m -> m.put(0, 0)),
|
||||
new Operation("putAll", m -> m.putAll(Map.of())),
|
||||
new Operation("remove1", m -> m.remove(KEY)),
|
||||
new Operation("remove2", m -> m.remove(KEY, KEY)),
|
||||
new Operation("replace2", m -> m.replace(KEY, 1)),
|
||||
new Operation("replace3", m -> m.replace(KEY, KEY, 1)),
|
||||
new Operation("replaceAll", m -> m.replaceAll((a, _) -> a))
|
||||
);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newMap() {
|
||||
return StableValue.map(KEYS, IDENTITY);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newEmptyMap() {
|
||||
return StableValue.map(EMPTY, IDENTITY);
|
||||
}
|
||||
|
||||
static Map<Integer, Integer> newRegularMap() {
|
||||
return KEYS.stream().collect(Collectors.toMap(IDENTITY, IDENTITY));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableSupplier methods
|
||||
* @enablePreview
|
||||
* @run junit StableSupplierTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class StableSupplierTest {
|
||||
|
||||
private static final Supplier<Integer> SUPPLIER = () -> 42;
|
||||
|
||||
@Test
|
||||
void factoryInvariants() {
|
||||
assertThrows(NullPointerException.class, () -> StableValue.supplier(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void basic() {
|
||||
basic(SUPPLIER);
|
||||
basic(() -> null);
|
||||
}
|
||||
|
||||
void basic(Supplier<Integer> supplier) {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(supplier);
|
||||
var cached = StableValue.supplier(cs);
|
||||
assertEquals(".unset", cached.toString());
|
||||
assertEquals(supplier.get(), cached.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(supplier.get(), cached.get());
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(Objects.toString(supplier.get()), cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void exception() {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
});
|
||||
var cached = StableValue.supplier(cs);
|
||||
assertThrows(UnsupportedOperationException.class, cached::get);
|
||||
assertEquals(1, cs.cnt());
|
||||
assertThrows(UnsupportedOperationException.class, cached::get);
|
||||
assertEquals(2, cs.cnt());
|
||||
assertEquals(".unset", cached.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void circular() {
|
||||
final AtomicReference<Supplier<?>> ref = new AtomicReference<>();
|
||||
Supplier<Supplier<?>> cached = StableValue.supplier(ref::get);
|
||||
ref.set(cached);
|
||||
cached.get();
|
||||
String toString = cached.toString();
|
||||
assertTrue(toString.startsWith("(this StableSupplier)"));
|
||||
assertDoesNotThrow(cached::hashCode);
|
||||
}
|
||||
|
||||
@Test
|
||||
void equality() {
|
||||
Supplier<Integer> f0 = StableValue.supplier(SUPPLIER);
|
||||
Supplier<Integer> f1 = StableValue.supplier(SUPPLIER);
|
||||
// No function is equal to another function
|
||||
assertNotEquals(f0, f1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void hashCodeStable() {
|
||||
Supplier<Integer> f0 = StableValue.supplier(SUPPLIER);
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
f0.get();
|
||||
assertEquals(System.identityHashCode(f0), f0.hashCode());
|
||||
}
|
||||
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,389 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
/* @test
|
||||
* @summary Basic tests for StableValue implementations
|
||||
* @enablePreview
|
||||
* @run junit StableValueTest
|
||||
*/
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.concurrent.locks.LockSupport;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
final class StableValueTest {
|
||||
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 13;
|
||||
|
||||
@Test
|
||||
void trySet() {
|
||||
trySet(VALUE);
|
||||
trySet(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preSet() {
|
||||
StableValue<Integer> stable = StableValue.of(VALUE);
|
||||
assertTrue(stable.isSet());
|
||||
assertEquals(VALUE, stable.orElseThrow());
|
||||
assertEquals(VALUE, stable.orElse(VALUE2));
|
||||
assertEquals(VALUE, stable.orElseSet(() -> VALUE2));
|
||||
assertFalse(stable.trySet(VALUE2));
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
|
||||
assertEquals(
|
||||
"The contents is already set",
|
||||
e.getMessage());
|
||||
}
|
||||
|
||||
void trySet(Integer initial) {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(initial));
|
||||
assertFalse(stable.trySet(null));
|
||||
assertFalse(stable.trySet(VALUE));
|
||||
assertFalse(stable.trySet(VALUE2));
|
||||
assertEquals(initial, stable.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setOrThrowValue() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
stable.setOrThrow(VALUE);
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(VALUE2));
|
||||
assertEquals("The contents is already set", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void setOrThrowNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
stable.setOrThrow(null);
|
||||
var e = assertThrows(IllegalStateException.class, () -> stable.setOrThrow(null));
|
||||
assertEquals("The contents is already set", e.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElse() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertEquals(VALUE, stable.orElse(VALUE));
|
||||
assertNull(stable.orElse(null));
|
||||
stable.trySet(VALUE);
|
||||
assertEquals(VALUE, stable.orElse(VALUE2));
|
||||
}
|
||||
|
||||
@Test
|
||||
void orElseThrow() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
var e = assertThrows(NoSuchElementException.class, stable::orElseThrow);
|
||||
assertEquals("No contents set", e.getMessage());
|
||||
stable.trySet(VALUE);
|
||||
assertEquals(VALUE, stable.orElseThrow());
|
||||
}
|
||||
|
||||
@Test
|
||||
void isSet() {
|
||||
isSet(VALUE);
|
||||
isSet(null);
|
||||
}
|
||||
|
||||
void isSet(Integer initial) {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertFalse(stable.isSet());
|
||||
stable.trySet(initial);
|
||||
assertTrue(stable.isSet());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOrElseSetSupplier() {
|
||||
StableTestUtil.CountingSupplier<Integer> cs = new StableTestUtil.CountingSupplier<>(() -> VALUE);
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertThrows(NullPointerException.class, () -> stable.orElseSet(null));
|
||||
assertEquals(VALUE, stable.orElseSet(cs));
|
||||
assertEquals(1, cs.cnt());
|
||||
assertEquals(VALUE, stable.orElseSet(cs));
|
||||
assertEquals(1, cs.cnt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testHashCode() {
|
||||
StableValue<Integer> stableValue = StableValue.of();
|
||||
// Should be Object::hashCode
|
||||
assertEquals(System.identityHashCode(stableValue), stableValue.hashCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEquals() {
|
||||
StableValue<Integer> s0 = StableValue.of();
|
||||
assertNotEquals(null, s0);
|
||||
StableValue<Integer> s1 = StableValue.of();
|
||||
assertNotEquals(s0, s1); // Identity based
|
||||
s0.setOrThrow(42);
|
||||
s1.setOrThrow(42);
|
||||
assertNotEquals(s0, s1);
|
||||
assertNotEquals("a", s0);
|
||||
StableValue<Integer> null0 = StableValue.of();
|
||||
StableValue<Integer> null1 = StableValue.of();
|
||||
null0.setOrThrow(null);
|
||||
null1.setOrThrow(null);
|
||||
assertNotEquals(null0, null1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringUnset() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertEquals(".unset", stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(null));
|
||||
assertEquals("null", stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringNonNull() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
assertTrue(stable.trySet(VALUE));
|
||||
assertEquals(Objects.toString(VALUE), stable.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringCircular() {
|
||||
StableValue<StableValue<?>> stable = StableValue.of();
|
||||
stable.trySet(stable);
|
||||
String toString = assertDoesNotThrow(stable::toString);
|
||||
assertEquals("(this StableValue)", toString);
|
||||
assertDoesNotThrow(stable::hashCode);
|
||||
assertDoesNotThrow((() -> stable.equals(stable)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void recursiveCall() {
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
AtomicReference<StableValue<Integer>> ref = new AtomicReference<>(stable);
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
stable.orElseSet(() -> {
|
||||
ref.get().trySet(1);
|
||||
return 1;
|
||||
})
|
||||
);
|
||||
assertThrows(IllegalStateException.class, () ->
|
||||
stable.orElseSet(() -> {
|
||||
ref.get().orElseSet(() -> 1);
|
||||
return 1;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void intFunctionExample() {
|
||||
final class SqrtUtil {
|
||||
|
||||
private SqrtUtil() {}
|
||||
|
||||
private static final int CACHED_SIZE = 10;
|
||||
|
||||
private static final IntFunction<Double> SQRT =
|
||||
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
StableValue.intFunction(CACHED_SIZE, StrictMath::sqrt);
|
||||
|
||||
public static double sqrt(int a) {
|
||||
return SQRT.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
double sqrt9 = SqrtUtil.sqrt(9); // May eventually constant fold to 3.0 at runtime
|
||||
|
||||
assertEquals(3, sqrt9);
|
||||
assertThrows(IllegalArgumentException.class, () -> SqrtUtil.sqrt(16));
|
||||
}
|
||||
|
||||
@Test
|
||||
void intFunctionExample2() {
|
||||
final class PowerOf2Util {
|
||||
|
||||
private PowerOf2Util() {}
|
||||
|
||||
private static final int SIZE = 6;
|
||||
private static final IntFunction<Integer> ORIGINAL_POWER_OF_TWO =
|
||||
v -> 1 << v;
|
||||
|
||||
private static final IntFunction<Integer> POWER_OF_TWO =
|
||||
// @link substring="intFunction" target="#intFunction(int,IntFunction)" :
|
||||
StableValue.intFunction(SIZE, ORIGINAL_POWER_OF_TWO);
|
||||
|
||||
public static int powerOfTwo(int a) {
|
||||
return POWER_OF_TWO.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
int pwr4 = PowerOf2Util.powerOfTwo(4); // May eventually constant fold to 16 at runtime
|
||||
|
||||
assertEquals(16, pwr4);
|
||||
assertEquals(1, PowerOf2Util.powerOfTwo(0));
|
||||
assertEquals(8, PowerOf2Util.powerOfTwo(3));
|
||||
assertEquals(32, PowerOf2Util.powerOfTwo(5));
|
||||
assertThrows(IllegalArgumentException.class, () -> PowerOf2Util.powerOfTwo(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionExample() {
|
||||
|
||||
class Log2Util {
|
||||
|
||||
private Log2Util() {}
|
||||
|
||||
private static final Set<Integer> CACHED_KEYS =
|
||||
Set.of(1, 2, 4, 8, 16, 32);
|
||||
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
|
||||
i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
|
||||
private static final Function<Integer, Integer> LOG2_CACHED =
|
||||
// @link substring="function" target="#function(Set,Function)" :
|
||||
StableValue.function(CACHED_KEYS, LOG2_ORIGINAL);
|
||||
|
||||
public static double log2(int a) {
|
||||
if (CACHED_KEYS.contains(a)) {
|
||||
return LOG2_CACHED.apply(a);
|
||||
} else {
|
||||
return LOG2_ORIGINAL.apply(a);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double log16 = Log2Util.log2(16); // May eventually constant fold to 4.0 at runtime
|
||||
double log256 = Log2Util.log2(256); // Will not constant fold
|
||||
|
||||
assertEquals(4, log16);
|
||||
assertEquals(8, log256);
|
||||
}
|
||||
|
||||
@Test
|
||||
void functionExample2() {
|
||||
|
||||
class Log2Util {
|
||||
|
||||
private Log2Util() {}
|
||||
|
||||
private static final Set<Integer> KEYS =
|
||||
Set.of(1, 2, 4, 8);
|
||||
private static final UnaryOperator<Integer> LOG2_ORIGINAL =
|
||||
i -> 31 - Integer.numberOfLeadingZeros(i);
|
||||
|
||||
private static final Function<Integer, Integer> LOG2 =
|
||||
// @link substring="function" target="#function(Set,Function)" :
|
||||
StableValue.function(KEYS, LOG2_ORIGINAL);
|
||||
|
||||
public static double log2(int a) {
|
||||
return LOG2.apply(a);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
double log16 = Log2Util.log2(8); // May eventually constant fold to 3.0 at runtime
|
||||
|
||||
assertEquals(3, log16);
|
||||
assertThrows(IllegalArgumentException.class, () -> Log2Util.log2(3));
|
||||
}
|
||||
|
||||
private static final BiPredicate<StableValue<Integer>, Integer> TRY_SET = StableValue::trySet;
|
||||
private static final BiPredicate<StableValue<Integer>, Integer> SET_OR_THROW = (s, i) -> {
|
||||
try {
|
||||
s.setOrThrow(i);
|
||||
return true;
|
||||
} catch (IllegalStateException e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
@Test
|
||||
void raceTrySet() {
|
||||
race(TRY_SET);
|
||||
}
|
||||
|
||||
@Test
|
||||
void raceSetOrThrow() {
|
||||
race(SET_OR_THROW);
|
||||
}
|
||||
|
||||
@Test
|
||||
void raceMixed() {
|
||||
race((s, i) -> switch (i % 2) {
|
||||
case 0 -> TRY_SET.test(s, i);
|
||||
case 1 -> SET_OR_THROW.test(s, i);
|
||||
default -> fail("should not reach here");
|
||||
});
|
||||
}
|
||||
|
||||
void race(BiPredicate<StableValue<Integer>, Integer> winnerPredicate) {
|
||||
int noThreads = 10;
|
||||
CountDownLatch starter = new CountDownLatch(noThreads);
|
||||
StableValue<Integer> stable = StableValue.of();
|
||||
Map<Integer, Boolean> winners = new ConcurrentHashMap<>();
|
||||
List<Thread> threads = IntStream.range(0, noThreads).mapToObj(i -> new Thread(() -> {
|
||||
try {
|
||||
// Ready ...
|
||||
starter.countDown();
|
||||
// ... set ...
|
||||
starter.await();
|
||||
// Here we go!
|
||||
winners.put(i, winnerPredicate.test(stable, i));
|
||||
} catch (Throwable t) {
|
||||
fail(t);
|
||||
}
|
||||
}))
|
||||
.toList();
|
||||
threads.forEach(Thread::start);
|
||||
threads.forEach(StableValueTest::join);
|
||||
// There can only be one winner
|
||||
assertEquals(1, winners.values().stream().filter(b -> b).count());
|
||||
}
|
||||
|
||||
private static void join(Thread thread) {
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -220,15 +220,15 @@ public class MOAT {
|
||||
// Immutable List
|
||||
testEmptyList(List.of());
|
||||
testEmptyList(List.of().subList(0,0));
|
||||
testEmptyList(StableValue.list(0, i -> i));
|
||||
testEmptyList(StableValue.list(3, i -> i).subList(0, 0));
|
||||
testEmptyList(List.ofLazy(0, i -> i));
|
||||
testEmptyList(List.ofLazy(3, i -> i).subList(0, 0));
|
||||
testListMutatorsAlwaysThrow(List.of());
|
||||
testListMutatorsAlwaysThrow(List.<Integer>of().subList(0,0));
|
||||
testListMutatorsAlwaysThrow(StableValue.list(0, i -> i));
|
||||
testListMutatorsAlwaysThrow(List.ofLazy(0, i -> i));
|
||||
testEmptyListMutatorsAlwaysThrow(List.of());
|
||||
testEmptyListMutatorsAlwaysThrow(List.<Integer>of().subList(0,0));
|
||||
testEmptyListMutatorsAlwaysThrow(StableValue.list(0, i -> i));
|
||||
testEmptyListMutatorsAlwaysThrow(StableValue.list(3, i -> i).subList(0, 0));
|
||||
testEmptyListMutatorsAlwaysThrow(List.ofLazy(0, i -> i));
|
||||
testEmptyListMutatorsAlwaysThrow(List.ofLazy(3, i -> i).subList(0, 0));
|
||||
for (List<Integer> list : Arrays.asList(
|
||||
List.<Integer>of(),
|
||||
List.of(1),
|
||||
@ -251,9 +251,9 @@ public class MOAT {
|
||||
Stream.of(1, null).toList(),
|
||||
Stream.of(1, null, 3).toList(),
|
||||
Stream.of(1, null, 3, 4).toList(),
|
||||
StableValue.list(0, i -> i),
|
||||
StableValue.list(3, i -> i),
|
||||
StableValue.list(10, i -> i))) {
|
||||
List.ofLazy(0, i -> i),
|
||||
List.ofLazy(3, i -> i),
|
||||
List.ofLazy(10, i -> i))) {
|
||||
testCollection(list);
|
||||
testImmutableList(list);
|
||||
testListMutatorsAlwaysThrow(list);
|
||||
@ -365,9 +365,9 @@ public class MOAT {
|
||||
testEmptyMap(Map.of());
|
||||
testMapMutatorsAlwaysThrow(Map.of());
|
||||
testEmptyMapMutatorsAlwaysThrow(Map.of());
|
||||
testEmptyMap(StableValue.map(Set.of(), k -> k));
|
||||
testMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k));
|
||||
testEmptyMapMutatorsAlwaysThrow(StableValue.map(Set.of(), k -> k));
|
||||
testEmptyMap(Map.ofLazy(Set.of(), k -> k));
|
||||
testMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k));
|
||||
testEmptyMapMutatorsAlwaysThrow(Map.ofLazy(Set.of(), k -> k));
|
||||
for (Map<Integer,Integer> map : Arrays.asList(
|
||||
Map.<Integer,Integer>of(),
|
||||
Map.of(1, 101),
|
||||
@ -381,9 +381,9 @@ public class MOAT {
|
||||
Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909),
|
||||
Map.of(1, 101, 2, 202, 3, 303, 4, 404, 5, 505, 6, 606, 7, 707, 8, 808, 9, 909, 10, 1010),
|
||||
Map.ofEntries(ea),
|
||||
StableValue.map(Set.<Integer>of(), k -> k),
|
||||
StableValue.map(Set.of(1), k -> k),
|
||||
StableValue.map(Set.of(1, 2, 3), k -> k))) {
|
||||
Map.ofLazy(Set.<Integer>of(), k -> k),
|
||||
Map.ofLazy(Set.of(1), k -> k),
|
||||
Map.ofLazy(Set.of(1, 2, 3), k -> k))) {
|
||||
testMap(map);
|
||||
testImmutableMap(map);
|
||||
testMapMutatorsAlwaysThrow(map);
|
||||
|
||||
@ -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()");
|
||||
|
||||
@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring stable list performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -52,16 +52,16 @@ import java.util.function.IntFunction;
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(100)
|
||||
public class StableIntFunctionBenchmark {
|
||||
public class StableListBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final IntFunction<Integer> IDENTITY = i -> i;
|
||||
|
||||
private static final List<Integer> LIST = StableValue.list(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY);
|
||||
private static final List<Integer> LIST = List.ofLazy(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = LIST::get;
|
||||
|
||||
private final List<Integer> list = StableValue.list(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
|
||||
private final List<Integer> list = List.ofLazy(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = list::get;
|
||||
|
||||
@Benchmark
|
||||
public int list() {
|
||||
@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring stable list performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -50,16 +50,16 @@ import java.util.function.IntFunction;
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
public class StableIntFunctionSingleBenchmark {
|
||||
public class StableListSingleBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final IntFunction<Integer> IDENTITY = i -> i;
|
||||
|
||||
private static final List<Integer> STABLE = StableValue.list(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = StableValue.intFunction(SIZE, IDENTITY);
|
||||
private static final List<Integer> STABLE = List.ofLazy(SIZE, IDENTITY);
|
||||
private static final IntFunction<Integer> INT_FUNCTION = STABLE::get;
|
||||
|
||||
private final List<Integer> stable = StableValue.list(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = StableValue.intFunction(SIZE, IDENTITY);
|
||||
private final List<Integer> stable = List.ofLazy(SIZE, IDENTITY);
|
||||
private final IntFunction<Integer> intFunction = stable::get;
|
||||
|
||||
@Benchmark
|
||||
public int list() {
|
||||
@ -43,7 +43,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring stable map performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -55,16 +55,16 @@ import java.util.stream.IntStream;
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
@OperationsPerInvocation(100)
|
||||
public class StableFunctionBenchmark {
|
||||
public class StableMapBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final Set<Integer> SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet());
|
||||
|
||||
private static final Map<Integer, Integer> MAP = StableValue.map(SET, Function.identity());
|
||||
private static final Function<Integer, Integer> FUNCTION = StableValue.function(SET, Function.identity());
|
||||
private static final Map<Integer, Integer> MAP = Map.ofLazy(SET, Function.identity());
|
||||
private static final Function<Integer, Integer> FUNCTION = MAP::get;
|
||||
|
||||
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
|
||||
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
|
||||
private final Map<Integer, Integer> map = Map.ofLazy(SET, Function.identity());
|
||||
private final Function<Integer, Integer> function = map::get;
|
||||
|
||||
@Benchmark
|
||||
public int map() {
|
||||
@ -85,7 +85,7 @@ public class StableFunctionBenchmark {
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSMap() {
|
||||
public int staticMap() {
|
||||
int sum = 0;
|
||||
for (int i = 0; i < SIZE; i++) {
|
||||
sum += MAP.get(i);
|
||||
@ -28,14 +28,15 @@ import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Fork;
|
||||
import org.openjdk.jmh.annotations.Measurement;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.annotations.OperationsPerInvocation;
|
||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
||||
import org.openjdk.jmh.annotations.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.EnumSet;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
@ -43,7 +44,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring lazy map performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -54,16 +55,17 @@ import java.util.stream.IntStream;
|
||||
"--enable-preview"
|
||||
})
|
||||
@Threads(Threads.MAX) // Benchmark under contention
|
||||
public class StableFunctionSingleBenchmark {
|
||||
public class StableMapSingleBenchmark {
|
||||
|
||||
private static final int SIZE = 100;
|
||||
private static final Set<Integer> SET = IntStream.range(0, SIZE).boxed().collect(Collectors.toSet());
|
||||
|
||||
private static final Map<Integer, Integer> MAP = StableValue.map(SET, Function.identity());
|
||||
private static final Function<Integer, Integer> FUNCTION = StableValue.function(SET, Function.identity());
|
||||
private static final Map<Integer, Integer> MAP = Map.ofLazy(SET, Function.identity());
|
||||
private static final Map<MyEnum, Integer> MAP_ENUM = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
|
||||
private static final Map<MyEnum, Optional<Integer>> MAP_ENUM_OPTIONAL = Map.ofLazy(EnumSet.allOf(MyEnum.class), e -> Optional.of(e.ordinal()));
|
||||
|
||||
private final Map<Integer, Integer> map = StableValue.map(SET, Function.identity());
|
||||
private final Function<Integer, Integer> function = StableValue.function(SET, Function.identity());
|
||||
private final Map<Integer, Integer> map = Map.ofLazy(SET, Function.identity());
|
||||
private final Map<MyEnum, Integer> mapEnum = Map.ofLazy(EnumSet.allOf(MyEnum.class), MyEnum::ordinal);
|
||||
|
||||
@Benchmark
|
||||
public int map() {
|
||||
@ -71,18 +73,25 @@ public class StableFunctionSingleBenchmark {
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int function() {
|
||||
return function.apply(1);
|
||||
public int mapEnum() {
|
||||
return mapEnum.get(MyEnum.BAR);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSMap() {
|
||||
public int staticMap() {
|
||||
return MAP.get(1);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticIntFunction() {
|
||||
return FUNCTION.apply(1);
|
||||
public int staticMapEnum() {
|
||||
return MAP_ENUM.get(MyEnum.BAR);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticMapEnumOptional() {
|
||||
return MAP_ENUM_OPTIONAL.get(MyEnum.BAR).orElseThrow();
|
||||
}
|
||||
|
||||
private enum MyEnum {FOO, BAR}
|
||||
|
||||
}
|
||||
@ -35,22 +35,18 @@ import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.classfile.CodeBuilder;
|
||||
import java.lang.classfile.constantpool.ConstantPoolBuilder;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.lang.constant.ConstantDescs.*;
|
||||
import java.lang.LazyConstant;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring lazy value performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -64,17 +60,15 @@ import static java.lang.constant.ConstantDescs.*;
|
||||
public class StableMethodHandleBenchmark {
|
||||
|
||||
private static final MethodHandle FINAL_MH = identityHandle();
|
||||
private static final StableValue<MethodHandle> STABLE_MH;
|
||||
private static final LazyConstant<MethodHandle> STABLE_MH = LazyConstant.of(StableMethodHandleBenchmark::identityHandle);
|
||||
|
||||
private static /* intentionally not final */ MethodHandle mh = identityHandle();
|
||||
private static final Dcl<MethodHandle> DCL = new Dcl<>(StableMethodHandleBenchmark::identityHandle);
|
||||
private static final AtomicReference<MethodHandle> ATOMIC_REFERENCE = new AtomicReference<>(identityHandle());
|
||||
private static final Map<String, MethodHandle> MAP = new ConcurrentHashMap<>();
|
||||
private static final Map<String, MethodHandle> STABLE_MAP = StableValue.map(Set.of("identityHandle"), _ -> identityHandle());
|
||||
private static final Map<String, MethodHandle> STABLE_MAP = Map.ofLazy(Set.of("identityHandle"), _ -> identityHandle());
|
||||
|
||||
static {
|
||||
STABLE_MH = StableValue.of();
|
||||
STABLE_MH.setOrThrow(identityHandle());
|
||||
MAP.put("identityHandle", identityHandle());
|
||||
}
|
||||
|
||||
@ -110,7 +104,7 @@ public class StableMethodHandleBenchmark {
|
||||
|
||||
@Benchmark
|
||||
public int stableMh() throws Throwable {
|
||||
return (int) STABLE_MH.orElseThrow().invokeExact(1);
|
||||
return (int) STABLE_MH.get().invokeExact(1);
|
||||
}
|
||||
|
||||
static MethodHandle identityHandle() {
|
||||
|
||||
@ -36,10 +36,10 @@ import org.openjdk.jmh.annotations.Threads;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.lang.LazyConstant;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring lazy constant performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -56,39 +56,24 @@ public class StableSupplierBenchmark {
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 23;
|
||||
|
||||
private static final StableValue<Integer> STABLE = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> STABLE2 = init(StableValue.of(), VALUE2);
|
||||
private static final Supplier<Integer> SUPPLIER = StableValue.supplier(() -> VALUE);
|
||||
private static final Supplier<Integer> SUPPLIER2 = StableValue.supplier(() -> VALUE);
|
||||
private static final LazyConstant<Integer> STABLE = init(VALUE);
|
||||
private static final LazyConstant<Integer> STABLE2 = init(VALUE2);
|
||||
|
||||
private final StableValue<Integer> stable = init(StableValue.of(), VALUE);
|
||||
private final StableValue<Integer> stable2 = init(StableValue.of(), VALUE2);
|
||||
private final Supplier<Integer> supplier = StableValue.supplier(() -> VALUE);
|
||||
private final Supplier<Integer> supplier2 = StableValue.supplier(() -> VALUE2);
|
||||
private final LazyConstant<Integer> stable = init(VALUE);
|
||||
private final LazyConstant<Integer> stable2 = init(VALUE2);
|
||||
|
||||
@Benchmark
|
||||
public int stable() {
|
||||
return stable.orElseThrow() + stable2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int supplier() {
|
||||
return supplier.get() + supplier2.get();
|
||||
return stable.get() + stable2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticStable() {
|
||||
return STABLE.orElseThrow() + STABLE2.orElseThrow();
|
||||
return STABLE.get() + STABLE2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticSupplier() {
|
||||
return SUPPLIER.get() + SUPPLIER2.get();
|
||||
}
|
||||
|
||||
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
|
||||
m.trySet(value);
|
||||
return m;
|
||||
private static LazyConstant<Integer> init(Integer value) {
|
||||
return LazyConstant.of(() -> value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -25,12 +25,14 @@ package org.openjdk.bench.java.lang.stable;
|
||||
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.lang.LazyConstant;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Benchmark measuring StableValue performance
|
||||
* Benchmark measuring lazy constant performance
|
||||
*/
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@ -47,10 +49,10 @@ public class StableValueBenchmark {
|
||||
private static final int VALUE = 42;
|
||||
private static final int VALUE2 = 23;
|
||||
|
||||
private static final StableValue<Integer> STABLE = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> STABLE2 = init(StableValue.of(), VALUE2);
|
||||
private static final StableValue<Integer> DCL = init(StableValue.of(), VALUE);
|
||||
private static final StableValue<Integer> DCL2 = init(StableValue.of(), VALUE2);
|
||||
private static final LazyConstant<Integer> STABLE = init(VALUE);
|
||||
private static final LazyConstant<Integer> STABLE2 = init(VALUE2);
|
||||
private static final Supplier<Integer> DCL = new Dcl<>(() -> VALUE);
|
||||
private static final Supplier<Integer> DCL2 = new Dcl<>(() -> VALUE2);
|
||||
private static final AtomicReference<Integer> ATOMIC = new AtomicReference<>(VALUE);
|
||||
private static final AtomicReference<Integer> ATOMIC2 = new AtomicReference<>(VALUE2);
|
||||
private static final Holder HOLDER = new Holder(VALUE);
|
||||
@ -58,10 +60,13 @@ public class StableValueBenchmark {
|
||||
private static final RecordHolder RECORD_HOLDER = new RecordHolder(VALUE);
|
||||
private static final RecordHolder RECORD_HOLDER2 = new RecordHolder(VALUE2);
|
||||
|
||||
private final StableValue<Integer> stable = init(StableValue.of(), VALUE);
|
||||
private final StableValue<Integer> stable2 = init(StableValue.of(), VALUE2);
|
||||
private final StableValue<Integer> stableNull = StableValue.of();
|
||||
private final StableValue<Integer> stableNull2 = StableValue.of();
|
||||
private static final LazyConstant<Optional<Integer>> OPTIONAL_42 = LazyConstant.of(() -> Optional.of(42));
|
||||
private static final LazyConstant<Optional<Integer>> OPTIONAL_42_2 = LazyConstant.of(() -> Optional.of(42));
|
||||
private static final LazyConstant<Optional<Integer>> OPTIONAL_EMPTY = LazyConstant.of(Optional::empty);
|
||||
private static final LazyConstant<Optional<Integer>> OPTIONAL_EMPTY2 = LazyConstant.of(Optional::empty);
|
||||
|
||||
private final LazyConstant<Integer> stable = init(VALUE);
|
||||
private final LazyConstant<Integer> stable2 = init(VALUE2);
|
||||
private final Supplier<Integer> dcl = new Dcl<>(() -> VALUE);
|
||||
private final Supplier<Integer> dcl2 = new Dcl<>(() -> VALUE2);
|
||||
private final AtomicReference<Integer> atomic = new AtomicReference<>(VALUE);
|
||||
@ -69,13 +74,6 @@ public class StableValueBenchmark {
|
||||
private final Supplier<Integer> supplier = () -> VALUE;
|
||||
private final Supplier<Integer> supplier2 = () -> VALUE2;
|
||||
|
||||
|
||||
@Setup
|
||||
public void setup() {
|
||||
stableNull.trySet(null);
|
||||
stableNull2.trySet(null);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int atomic() {
|
||||
return atomic.get() + atomic2.get();
|
||||
@ -88,12 +86,7 @@ public class StableValueBenchmark {
|
||||
|
||||
@Benchmark
|
||||
public int stable() {
|
||||
return stable.orElseThrow() + stable2.orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int stableNull() {
|
||||
return (stableNull.orElseThrow() == null ? VALUE : VALUE2) + (stableNull2.orElseThrow() == null ? VALUE : VALUE2);
|
||||
return stable.get() + stable2.get();
|
||||
}
|
||||
|
||||
// Reference case
|
||||
@ -109,7 +102,7 @@ public class StableValueBenchmark {
|
||||
|
||||
@Benchmark
|
||||
public int staticDcl() {
|
||||
return DCL.orElseThrow() + DCL2.orElseThrow();
|
||||
return DCL.get() + DCL2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@ -117,6 +110,16 @@ public class StableValueBenchmark {
|
||||
return HOLDER.get() + HOLDER2.get();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticOptional42() {
|
||||
return OPTIONAL_42.get().orElseThrow() + OPTIONAL_42_2.get().orElseThrow();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public boolean staticOptionalEmpty() {
|
||||
return OPTIONAL_EMPTY.get().isEmpty() ^ OPTIONAL_EMPTY2.get().isEmpty();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int staticRecordHolder() {
|
||||
return RECORD_HOLDER.get() + RECORD_HOLDER2.get();
|
||||
@ -124,50 +127,46 @@ public class StableValueBenchmark {
|
||||
|
||||
@Benchmark
|
||||
public int staticStable() {
|
||||
return STABLE.orElseThrow() + STABLE2.orElseThrow();
|
||||
return STABLE.get() + STABLE2.get();
|
||||
}
|
||||
|
||||
|
||||
private static StableValue<Integer> init(StableValue<Integer> m, Integer value) {
|
||||
m.trySet(value);
|
||||
return m;
|
||||
private static LazyConstant<Integer> init(Integer value) {
|
||||
return LazyConstant.of(() -> value);
|
||||
}
|
||||
|
||||
private static final class Holder {
|
||||
|
||||
private final StableValue<Integer> delegate = StableValue.of();
|
||||
private final LazyConstant<Integer> delegate;
|
||||
|
||||
Holder(int value) {
|
||||
delegate.setOrThrow(value);
|
||||
delegate = LazyConstant.of(() -> value);
|
||||
}
|
||||
|
||||
int get() {
|
||||
return delegate.orElseThrow();
|
||||
return delegate.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private record RecordHolder(StableValue<Integer> delegate) {
|
||||
private record RecordHolder(LazyConstant<Integer> delegate) {
|
||||
|
||||
RecordHolder(int value) {
|
||||
this(StableValue.of());
|
||||
delegate.setOrThrow(value);
|
||||
this(LazyConstant.of(() -> value));
|
||||
}
|
||||
|
||||
int get() {
|
||||
return delegate.orElseThrow();
|
||||
return delegate.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Handles null values
|
||||
public static class Dcl<V> implements Supplier<V> {
|
||||
|
||||
private final Supplier<V> supplier;
|
||||
|
||||
private volatile V value;
|
||||
private boolean bound;
|
||||
|
||||
public Dcl(Supplier<V> supplier) {
|
||||
this.supplier = supplier;
|
||||
@ -177,15 +176,10 @@ public class StableValueBenchmark {
|
||||
public V get() {
|
||||
V v = value;
|
||||
if (v == null) {
|
||||
if (!bound) {
|
||||
synchronized (this) {
|
||||
v = value;
|
||||
if (v == null) {
|
||||
if (!bound) {
|
||||
value = v = supplier.get();
|
||||
bound = true;
|
||||
}
|
||||
}
|
||||
synchronized (this) {
|
||||
v = value;
|
||||
if (v == null) {
|
||||
value = v = supplier.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,6 @@ import java.lang.invoke.VarHandle;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.lang.foreign.MemoryLayout.PathElement.groupElement;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
@ -75,8 +74,8 @@ public class VarHandleHolderBenchmark {
|
||||
private static final VarHandle VH_X = VAR_HANDLE_FUNCTION.apply("x");
|
||||
private static final VarHandle VH_Y = VAR_HANDLE_FUNCTION.apply("y");
|
||||
|
||||
private static final Supplier<VarHandle> SV_X = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("x"));
|
||||
private static final Supplier<VarHandle> SV_Y = StableValue.supplier(() -> VAR_HANDLE_FUNCTION.apply("y"));
|
||||
private static final LazyConstant<VarHandle> SV_X = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("x"));
|
||||
private static final LazyConstant<VarHandle> SV_Y = LazyConstant.of(() -> VAR_HANDLE_FUNCTION.apply("y"));
|
||||
|
||||
private static final Map<String, VarHandle> U_MAP = Map.of(
|
||||
"x", VH_X,
|
||||
@ -86,13 +85,11 @@ public class VarHandleHolderBenchmark {
|
||||
"x", LAYOUT.varHandle(groupElement("x")),
|
||||
"y", LAYOUT.varHandle(groupElement("y")));
|
||||
|
||||
private static final Map<String, VarHandle> S_MAP = StableValue.map(
|
||||
private static final Map<String, VarHandle> S_MAP = Map.ofLazy(
|
||||
Set.of("x", "y"),
|
||||
VAR_HANDLE_FUNCTION);
|
||||
|
||||
private static final Function<String, VarHandle> S_FUN = StableValue.function(
|
||||
Set.of("x", "y"),
|
||||
VAR_HANDLE_FUNCTION);
|
||||
private static final Function<String, VarHandle> S_FUN = S_MAP::get;
|
||||
|
||||
private static final MemorySegment confined;
|
||||
static {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user