JDKRandomBridge.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.rng.simple;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.util.Random;
import org.apache.commons.rng.RestorableUniformRandomProvider;
import org.apache.commons.rng.core.RandomProviderDefaultState;
/**
* Subclass of {@link Random} that {@link #next(int) delegates} to a
* {@link RestorableUniformRandomProvider} instance but will otherwise rely
* on the base class for generating all the random types.
*
* <p>Legacy applications coded against the JDK's API could use this subclass
* of {@link Random} in order to replace its linear congruential generator
* by any {@link RandomSource}.</p>
*
* <p>Caveat: Use of this class is <em>not</em> recommended for new applications.
* In particular, there is no guarantee that the serialized form of this class
* will be compatible across (even <em>minor</em>) releases of the library.</p>
*
* @since 1.0
*/
public final class JDKRandomBridge extends Random {
/** Serializable version identifier. */
private static final long serialVersionUID = 20161107L;
/** Source. */
private final RandomSource source;
/** Delegate. */
private transient RestorableUniformRandomProvider delegate;
/** Workaround JDK's "Random" bug: https://bugs.openjdk.java.net/browse/JDK-8154225. */
private final transient boolean isInitialized;
/**
* Creates a new instance.
*
* @param source Source of randomness.
* @param seed Seed. Can be {@code null}.
*/
public JDKRandomBridge(RandomSource source,
Object seed) {
this.source = source;
delegate = source.create(seed);
isInitialized = true;
}
/** {@inheritDoc} */
@Override
public synchronized void setSeed(long seed) {
if (isInitialized) {
delegate = source.create(seed);
// Force the clearing of the "haveNextNextGaussian" flag
// (cf. Javadoc of the base class); the value passed here
// is irrelevant (since it will not be used).
super.setSeed(0L);
}
}
/**
* Delegates the generation of 32 random bits to the
* {@code RandomSource} argument provided at
* {@link #JDKRandomBridge(RandomSource,Object) construction}.
* The returned value is such that if the source of randomness is
* {@link RandomSource#JDK}, all the generated values will be identical
* to those produced by the same sequence of calls on a {@link Random}
* instance initialized with the same seed.
*
* @param n Number of random bits which the requested value must contain.
* @return the value represented by the {@code n} high-order bits of a
* pseudo-random 32-bits integer.
*/
@Override
protected int next(int n) {
synchronized (this) {
return delegate.nextInt() >>> (32 - n);
}
}
/**
* Serialization method.
*
* @param output Output stream.
* @throws IOException if an error occurs.
*/
private void writeObject(ObjectOutputStream output)
throws IOException {
synchronized (this) {
// Write non-transient fields.
output.defaultWriteObject();
// Save current state and size.
// Avoid the use of ObjectOutputStream.writeObject(Object) to save the state.
// This allows deserialization to avoid security issues in using readObject().
final byte[] state = ((RandomProviderDefaultState) delegate.saveState()).getState();
final int size = state.length;
output.writeInt(size);
output.write(state);
}
}
/**
* Deserialization method.
*
* @param input Input stream.
* @throws IOException if an error occurs.
* @throws ClassNotFoundException if an error occurs.
*/
private void readObject(ObjectInputStream input)
throws IOException,
ClassNotFoundException {
// Read non-transient fields.
input.defaultReadObject();
// Recreate the "delegate" from serialized info.
delegate = source.create();
// And restore its state.
// Avoid the use of input.readObject() to deserialize by manually reading the byte[].
// Note: ObjectInputStream.readObject() will execute the readObject() method of the named
// class in the stream which may contain potentially malicious code.
final int size = input.readInt();
final byte[] state = new byte[size];
input.readFully(state);
delegate.restoreState(new RandomProviderDefaultState(state));
}
}