blob: 2ed63f2455d54b675ea5970a644eb43bcff7acd0 [file] [log] [blame]
/*
* Copyright (c) 2021, 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.random;
import java.lang.reflect.Constructor;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Objects;
import java.util.function.Function;
import java.util.Map;
import java.util.random.RandomGenerator.ArbitrarilyJumpableGenerator;
import java.util.random.RandomGenerator.JumpableGenerator;
import java.util.random.RandomGenerator.LeapableGenerator;
import java.util.random.RandomGenerator.SplittableGenerator;
import java.util.random.RandomGenerator.StreamableGenerator;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.internal.util.random.RandomSupport.RandomGeneratorProperties;
/**
* This is a factory class for generating multiple random number generators
* of a specific <a href="package-summary.html#algorithms">algorithm</a>.
* {@link RandomGeneratorFactory} also provides
* methods for selecting random number generator algorithms.
*
* A specific {@link RandomGeneratorFactory} can be located by using the
* {@link RandomGeneratorFactory#of(String)} method, where the argument string
* is the name of the <a href="package-summary.html#algorithms">algorithm</a>
* required. The method
* {@link RandomGeneratorFactory#all()} produces a non-empty {@link Stream} of all available
* {@link RandomGeneratorFactory RandomGeneratorFactorys} that can be searched
* to locate a {@link RandomGeneratorFactory} suitable to the task.
*
* There are three methods for constructing a RandomGenerator instance,
* depending on the type of initial seed required.
* {@link RandomGeneratorFactory#create(long)} is used for long
* seed construction,
* {@link RandomGeneratorFactory#create(byte[])} is used for byte[]
* seed construction, and
* {@link RandomGeneratorFactory#create()} is used for random seed
* construction. Example;
*
* <pre>{@code
* RandomGeneratorFactory<RandomGenerator> factory = RandomGeneratorFactory.of("Random");
*
* for (int i = 0; i < 10; i++) {
* new Thread(() -> {
* RandomGenerator random = factory.create(100L);
* System.out.println(random.nextDouble());
* }).start();
* }
* }</pre>
*
* RandomGeneratorFactory also provides methods describing the attributes (or properties)
* of a generator and can be used to select random number generator
* <a href="package-summary.html#algorithms">algorithms</a>.
* These methods are typically used in
* conjunction with {@link RandomGeneratorFactory#all()}. In this example, the code
* locates the {@link RandomGeneratorFactory} that produces
* {@link RandomGenerator RandomGenerators}
* with the highest number of state bits.
*
* <pre>{@code
* RandomGeneratorFactory<RandomGenerator> best = RandomGeneratorFactory.all()
* .sorted(Comparator.comparingInt(RandomGenerator::stateBits).reversed())
* .findFirst()
* .orElse(RandomGeneratorFactory.of("Random"));
* System.out.println(best.name() + " in " + best.group() + " was selected");
*
* RandomGenerator rng = best.create();
* System.out.println(rng.nextLong());
* }</pre>
*
* @since 17
*
* @see java.util.random
*
*/
public final class RandomGeneratorFactory<T extends RandomGenerator> {
/**
* Instance provider class of random number algorithm.
*/
private final Provider<? extends RandomGenerator> provider;
/**
* Provider RandomGeneratorProperties annotation.
*/
private volatile RandomGeneratorProperties properties;
/**
* Default provider constructor.
*/
private volatile Constructor<T> ctor;
/**
* Provider constructor with long seed.
*/
private Constructor<T> ctorLong;
/**
* Provider constructor with byte[] seed.
*/
private Constructor<T> ctorBytes;
private static class FactoryMapHolder {
static final Map<String, Provider<? extends RandomGenerator>> FACTORY_MAP = createFactoryMap();
/**
* Returns the factory map, lazily constructing map on first use.
*
* @return Map of RandomGeneratorFactory classes.
*/
private static Map<String, Provider<? extends RandomGenerator>> createFactoryMap() {
return ServiceLoader
.load(RandomGenerator.class)
.stream()
.filter(p -> !p.type().isInterface())
.collect(Collectors.toMap(p -> p.type().getSimpleName(), Function.identity()));
}
}
/**
* Private constructor.
*
* @param provider Provider class to wrap.
*/
private RandomGeneratorFactory(Provider<? extends RandomGenerator> provider) {
this.provider = provider;
}
/**
* Returns the factory map, lazily constructing map on first call.
*
* @return Map of RandomGeneratorFactory classes.
*/
private static Map<String, Provider<? extends RandomGenerator>> getFactoryMap() {
return FactoryMapHolder.FACTORY_MAP;
}
/**
* Return the annotation for the specified provider.
*
* @return RandomGeneratorProperties annotation for the specified provider.
*/
private RandomGeneratorProperties getProperties() {
if (properties == null) {
synchronized (provider) {
if (properties == null) {
properties = provider.type().getDeclaredAnnotation(RandomGeneratorProperties.class);
Objects.requireNonNull(properties, provider.type() + " missing annotation");
}
}
}
return properties;
}
/**
* Return true if the provider is a subclass of the category.
*
* @param category Interface category, sub-interface of {@link RandomGenerator}.
*
* @return true if the provider is a subclass of the category.
*/
private boolean isSubclass(Class<? extends RandomGenerator> category) {
return isSubclass(category, provider);
}
/**
* Return true if the provider is a subclass of the category.
*
* @param category Interface category, sub-interface of {@link RandomGenerator}.
* @param provider Provider that is being filtered.
*
* @return true if the provider is a subclass of the category.
*/
private static boolean isSubclass(Class<? extends RandomGenerator> category,
Provider<? extends RandomGenerator> provider) {
return provider != null && category.isAssignableFrom(provider.type());
}
/**
* Returns the provider matching name and category.
*
* @param name Name of RandomGenerator
* @param category Interface category, sub-interface of {@link RandomGenerator}.
*
* @return A provider matching name and category.
*
* @throws IllegalArgumentException if provider is not a subclass of category.
*/
private static Provider<? extends RandomGenerator> findProvider(String name,
Class<? extends RandomGenerator> category)
throws IllegalArgumentException {
Map<String, Provider<? extends RandomGenerator>> fm = getFactoryMap();
Provider<? extends RandomGenerator> provider = fm.get(name);
if (provider == null) {
throw new IllegalArgumentException("No implementation of the random number generator algorithm \"" +
name +
"\" is available");
} else if (!isSubclass(category, provider)) {
throw new IllegalArgumentException("The random number generator algorithm \"" +
name +
"\" is not implemented with the interface \"" +
category.getSimpleName() +
"\"");
}
return provider;
}
/**
* Returns a {@link RandomGenerator} that utilizes the {@code name}
* <a href="package-summary.html#algorithms">algorithm</a>.
*
* @param name Name of random number algorithm to use
* @param category Sub-interface of {@link RandomGenerator} to type check
* @param <T> Sub-interface of {@link RandomGenerator} to produce
*
* @return An instance of {@link RandomGenerator}
*
* @throws IllegalArgumentException when either the name or category is null
*/
static <T extends RandomGenerator> T of(String name, Class<T> category)
throws IllegalArgumentException {
@SuppressWarnings("unchecked")
T uncheckedRandomGenerator = (T)findProvider(name, category).get();
return uncheckedRandomGenerator;
}
/**
* Returns a {@link RandomGeneratorFactory} that will produce instances
* of {@link RandomGenerator} that utilizes the named algorithm.
*
* @param name Name of random number algorithm to use
* @param category Sub-interface of {@link RandomGenerator} to type check
* @param <T> Sub-interface of {@link RandomGenerator} to produce
*
* @return Factory of {@link RandomGenerator}
*
* @throws IllegalArgumentException when either the name or category is null
*/
static <T extends RandomGenerator> RandomGeneratorFactory<T> factoryOf(String name, Class<T> category)
throws IllegalArgumentException {
Provider<? extends RandomGenerator> uncheckedProvider = findProvider(name, category);
return new RandomGeneratorFactory<>(uncheckedProvider);
}
/**
* Fetch the required constructors for class of random number algorithm.
*
* @param randomGeneratorClass class of random number algorithm (provider)
*/
private void getConstructors(Class<? extends RandomGenerator> randomGeneratorClass) {
if (ctor == null) {
synchronized (provider) {
if (ctor == null) {
PrivilegedExceptionAction<Constructor<?>[]> ctorAction = randomGeneratorClass::getConstructors;
try {
@SuppressWarnings("removal")
Constructor<?>[] ctors = AccessController.doPrivileged(ctorAction);
Constructor<T> tmpCtor = null;
Constructor<T> tmpCtorLong = null;
Constructor<T> tmpCtorBytes = null;
for (Constructor<?> ctorGeneric : ctors) {
@SuppressWarnings("unchecked")
Constructor<T> ctorSpecific = (Constructor<T>) ctorGeneric;
final Class<?>[] parameterTypes = ctorSpecific.getParameterTypes();
if (parameterTypes.length == 0) {
tmpCtor = ctorSpecific;
} else if (parameterTypes.length == 1) {
Class<?> argType = parameterTypes[0];
if (argType == long.class) {
tmpCtorLong = ctorSpecific;
} else if (argType == byte[].class) {
tmpCtorBytes = ctorSpecific;
}
}
}
if (tmpCtor == null) {
throw new IllegalStateException("Random algorithm " + name() + " is missing a default constructor");
}
// Store specialized constructors first, guarded by ctor
ctorBytes = tmpCtorBytes;
ctorLong = tmpCtorLong;
ctor = tmpCtor;
} catch (PrivilegedActionException ex) {
// Do nothing
}
}
}
}
}
/**
* Ensure all the required constructors are fetched.
*/
private void ensureConstructors() {
getConstructors(provider.type());
}
/**
* Returns a {@link RandomGeneratorFactory} that can produce instances of
* {@link RandomGenerator} that utilize the {@code name}
* <a href="package-summary.html#algorithms">algorithm</a>.
*
* @implSpec Availability is determined by RandomGeneratorFactory using the
* service provider API to locate implementations of the RandomGenerator interface.
*
* @param name Name of random number generator
* <a href="package-summary.html#algorithms">algorithm</a>
* @param <T> Sub-interface of {@link RandomGenerator} to produce
*
* @return {@link RandomGeneratorFactory} of {@link RandomGenerator}
*
* @throws NullPointerException if name is null
* @throws IllegalArgumentException if the named algorithm is not found
*/
public static <T extends RandomGenerator> RandomGeneratorFactory<T> of(String name) {
Objects.requireNonNull(name);
@SuppressWarnings("unchecked")
RandomGeneratorFactory<T> factory =
(RandomGeneratorFactory<T>)factoryOf(name, RandomGenerator.class);
return factory;
}
/**
* Returns a {@link RandomGeneratorFactory} meeting the minimal requirement
* of having an algorithm whose state bits are greater than or equal 64.
*
* @implSpec Since algorithms will improve over time, there is no
* guarantee that this method will return the same algorithm over time.
*
* @return a {@link RandomGeneratorFactory}
*/
public static RandomGeneratorFactory<RandomGenerator> getDefault() {
return factoryOf("L32X64MixRandom", RandomGenerator.class);
}
/**
* Returns a non-empty stream of available {@link RandomGeneratorFactory RandomGeneratorFactory(s)}.
*
* RandomGenerators that are marked as deprecated or are not properly configured are not included in the result.
*
* @implSpec Availability is determined by RandomGeneratorFactory using the service provider API
* to locate implementations of the RandomGenerator interface.
*
* @return a non-empty stream of all available {@link RandomGeneratorFactory RandomGeneratorFactory(s)}.
*/
public static Stream<RandomGeneratorFactory<RandomGenerator>> all() {
Map<String, Provider<? extends RandomGenerator>> fm = getFactoryMap();
return fm.values()
.stream()
.filter(p -> !p.type().isAnnotationPresent(Deprecated.class) &&
p.type().isAnnotationPresent(RandomGeneratorProperties.class))
.map(RandomGeneratorFactory::new);
}
/**
* Return the name of the <a href="package-summary.html#algorithms">algorithm</a>
* used by the random number generator.
*
* @return Name of the <a href="package-summary.html#algorithms">algorithm</a>.
*/
public String name() {
return provider.type().getSimpleName();
}
/**
* Return the group name of the <a href="package-summary.html#algorithms">algorithm</a>
* used by the random number generator.
*
* @return Group name of the <a href="package-summary.html#algorithms">algorithm</a>.
*/
public String group() {
return getProperties().group();
}
/**
* Returns number of bits used by the <a href="package-summary.html#algorithms">algorithm</a>
* to maintain state of seed.
*
* @return number of bits used by the <a href="package-summary.html#algorithms">algorithm</a>
* to maintain state of seed.
*/
public int stateBits() {
RandomGeneratorProperties properties = getProperties();
int i = properties.i();
int k = properties.k();
return i == 0 && k == 0 ? Integer.MAX_VALUE : i + k;
}
/**
* Returns the equidistribution of the <a href="package-summary.html#algorithms">algorithm</a>.
*
* @return the equidistribution of the <a href="package-summary.html#algorithms">algorithm</a>.
*/
public int equidistribution() {
return getProperties().equidistribution();
}
/**
* Return the period of the <a href="package-summary.html#algorithms">algorithm</a>
* used by the random number generator.
* Returns BigInteger.ZERO if period is not determinable.
*
* @return BigInteger period.
*/
public BigInteger period() {
RandomGeneratorProperties properties = getProperties();
int i = properties.i();
int j = properties.j();
int k = properties.k();
if (i == 0 && j == 0 && k == 0) {
return BigInteger.ZERO;
} else {
return BigInteger.ONE.shiftLeft(i).subtract(BigInteger.valueOf(j)).shiftLeft(k);
}
}
/**
* Return true if random generator is computed using an arithmetic
* <a href="package-summary.html#algorithms">algorithm</a>
* and is statistically deterministic.
*
* @return true if random generator is statistical.
*/
public boolean isStatistical() {
return !getProperties().isStochastic();
}
/**
* Return true if random generator is computed using external or entropic
* sources as inputs.
*
* @return true if random generator is stochastic.
*/
public boolean isStochastic() {
return getProperties().isStochastic();
}
/**
* Return true if random generator uses a hardware device (HRNG) to produce
* entropic input.
*
* @return true if random generator is generated by hardware.
*/
public boolean isHardware() {
return getProperties().isHardware();
}
/**
* Return true if random generator can jump an arbitrarily specified distant
* point in the state cycle.
*
* @return true if random generator is arbitrarily jumpable.
*/
public boolean isArbitrarilyJumpable() {
return isSubclass(ArbitrarilyJumpableGenerator.class);
}
/**
* Return true if random generator can jump a specified distant point in
* the state cycle.
*
* @return true if random generator is jumpable.
*/
public boolean isJumpable() {
return isSubclass(JumpableGenerator.class);
}
/**
* Return true if random generator is jumpable and can leap to a very distant
* point in the state cycle.
*
* @return true if random generator is leapable.
*/
public boolean isLeapable() {
return isSubclass(LeapableGenerator.class);
}
/**
* Return true if random generator can be cloned into a separate object with
* the same properties but positioned further in the state cycle.
*
* @return true if random generator is splittable.
*/
public boolean isSplittable() {
return isSubclass(SplittableGenerator.class);
}
/**
* Return true if random generator can be used to create
* {@link java.util.stream.Stream Streams} of random numbers.
*
* @return true if random generator is streamable.
*/
public boolean isStreamable() {
return isSubclass(StreamableGenerator.class);
}
/**
* Return true if the implementation of RandomGenerator (algorithm) has been
* marked for deprecation.
*
* @implNote Random number generator algorithms evolve over time; new
* algorithms will be introduced and old algorithms will
* lose standing. If an older algorithm is deemed unsuitable
* for continued use, it will be marked as deprecated to indicate
* that it may be removed at some point in the future.
*
* @return true if the implementation of RandomGenerator (algorithm) has been
* marked for deprecation
*/
public boolean isDeprecated() {
return provider.type().isAnnotationPresent(Deprecated.class);
}
/**
* Create an instance of {@link RandomGenerator} based on
* <a href="package-summary.html#algorithms">algorithm</a> chosen.
*
* @return new in instance of {@link RandomGenerator}.
*
*/
public T create() {
try {
ensureConstructors();
return ctor.newInstance();
} catch (Exception ex) {
// Should never happen.
throw new IllegalStateException("Random algorithm " + name() + " is missing a default constructor", ex);
}
}
/**
* Create an instance of {@link RandomGenerator} based on
* <a href="package-summary.html#algorithms">algorithm</a> chosen
* providing a starting long seed. If long seed is not supported by an
* algorithm then the no argument form of create is used.
*
* @param seed long random seed value.
*
* @return new in instance of {@link RandomGenerator}.
*/
public T create(long seed) {
try {
ensureConstructors();
return ctorLong.newInstance(seed);
} catch (Exception ex) {
return create();
}
}
/**
* Create an instance of {@link RandomGenerator} based on
* <a href="package-summary.html#algorithms">algorithm</a> chosen
* providing a starting byte[] seed. If byte[] seed is not supported by an
* <a href="package-summary.html#algorithms">algorithm</a> then the no
* argument form of create is used.
*
* @param seed byte array random seed value.
*
* @return new in instance of {@link RandomGenerator}.
*
* @throws NullPointerException if seed is null.
*/
public T create(byte[] seed) {
Objects.requireNonNull(seed, "seed must not be null");
try {
ensureConstructors();
return ctorBytes.newInstance(seed);
} catch (Exception ex) {
return create();
}
}
}