RndAdapter.java
/*
* License : The MIT License
* Copyright(c) 2022 Olyutorskii
*/
package io.github.olyutorskii.aletojio.rng;
import io.github.olyutorskii.aletojio.BitPool;
import java.util.Random;
/**
* Random generator exchange adapter.
*
* <p>Designed to conserve as much random source entropy as possible.
*
* <p>Entropy is consumed first from MSB.
*
* <p>512 bits buffer is provided.
*
* <p>TODO: supporting {@literal java.util.random.RandomGenerator} (JDK17 or later)
* and {@literal org.apache.commons.rng.UniformRandomProvider}
*/
@SuppressWarnings("serial")
public class RndAdapter
extends Random
implements RndInt32, RndInt64, RndInt31 {
private final transient RndSource source;
private final transient RndInt31 delRndInt31;
private final transient RndInt32 delRndInt32;
private final transient RndInt64 delRndInt64;
private final transient Random delRandom;
private final transient BitPool bitPool = new BitPool();
private final transient int inUnit;
private final transient boolean initDone;
/**
* Constructor.
*
* @param rndSrc random generator
* @throws NullPointerException null argument
*/
public RndAdapter(RndInt32 rndSrc) throws NullPointerException {
this(rndSrc, null, null, null);
return;
}
/**
* Constructor.
*
* @param rndSrc random generator
* @throws NullPointerException null argument
*/
public RndAdapter(RndInt64 rndSrc) throws NullPointerException {
this(null, rndSrc, null, null);
return;
}
/**
* Constructor.
*
* @param rndSrc random generator
* @throws NullPointerException null argument
*/
public RndAdapter(RndInt31 rndSrc) throws NullPointerException {
this(null, null, rndSrc, null);
return;
}
/**
* Constructor.
*
* @param rndSrc random generator
* @throws NullPointerException null argument
*/
public RndAdapter(Random rndSrc) throws NullPointerException {
this(null, null, null, rndSrc);
return;
}
/**
* Constructor.
*
* <p>Any generator argument must be non-null.
*
* @param rndInt32 random generator
* @param rndInt64 random generator
* @param rndInt31 random generator
* @param rnd random generator
* @throws NullPointerException all argument is null.
*/
private RndAdapter(
RndInt32 rndInt32,
RndInt64 rndInt64,
RndInt31 rndInt31,
Random rnd
) throws NullPointerException {
super();
RndSource src;
int unit;
if (rndInt32 != null) {
src = RndSource.R32;
unit = 32;
} else if (rndInt64 != null) {
src = RndSource.R64;
unit = 64;
} else if (rndInt31 != null) {
src = RndSource.R31;
unit = 31;
} else if (rnd != null) {
src = RndSource.JRND;
unit = 32;
} else {
throw new NullPointerException();
}
this.source = src;
this.inUnit = unit;
this.delRndInt32 = rndInt32;
this.delRndInt64 = rndInt64;
this.delRndInt31 = rndInt31;
this.delRandom = rnd;
this.initDone = true;
assert this.bitPool.capacity() >= Long.SIZE * 2;
return;
}
/**
* Seed change is not supported.
*
* <p>* However, it is allowed only during the initial constructor.
*
* @param seed not supported
* @throws UnsupportedOperationException always
*/
@Override
public void setSeed(long seed) throws UnsupportedOperationException {
if (this.initDone) {
throw new UnsupportedOperationException();
}
return;
}
/**
* If not enough, fill bit-pool.
*
* @param minSz minimum pool size
*/
private void fillPool(int minSz) {
if (this.bitPool.size() >= minSz) return;
while (this.bitPool.remaining() >= this.inUnit) {
int iVal;
long lVal;
switch (this.source) {
case R32:
iVal = this.delRndInt32.nextInt32();
this.bitPool.pushInt(iVal);
break;
case R64:
lVal = this.delRndInt64.nextInt64();
this.bitPool.pushLong(lVal);
break;
case R31:
iVal = this.delRndInt31.nextInt31();
this.bitPool.pushInt(iVal, 31);
break;
case JRND:
iVal = this.delRandom.nextInt();
this.bitPool.pushInt(iVal);
break;
default:
break;
}
}
return;
}
/**
* Return next random number as 1bit boolean.
*
* <p>Unlike {@link java.util.Random#nextBoolean()} implement,
* any entropy from random source is not discarded.
*
* @return random number
*/
public boolean nextBit() {
fillPool(1);
boolean result = this.bitPool.chopBoolean();
return result;
}
/**
* Return next random number as 8bit byte.
*
* <p>Unlike {@link java.util.Random#nextBytes(byte[])} implement,
* any entropy from random source is not discarded.
*
* @return random number
*/
public byte nextByte() {
fillPool(Byte.SIZE);
byte result = this.bitPool.chopByte();
return result;
}
/**
* {@inheritDoc}
*
* <p>Entropy obtained from random source is returned
* without entropy missing.
*
* @return {@inheritDoc}
*/
@Override
public int nextInt31() {
fillPool(31);
int result = this.bitPool.chopInt(31);
return result;
}
/**
* {@inheritDoc}
*
* <p>Entropy obtained from random source is returned
* without entropy missing.
*
* @return {@inheritDoc}
*/
@Override
public int nextInt32() {
fillPool(Integer.SIZE);
int result = this.bitPool.chopInt();
return result;
}
/**
* {@inheritDoc}
*
* <p>Entropy obtained from random source is returned
* without entropy missing.
*
* @return {@inheritDoc}
*/
@Override
public long nextInt64() {
fillPool(Long.SIZE);
long result = this.bitPool.chopLong();
return result;
}
/**
* Generates the next pseudorandom number.
* Subclasses should override this,
* as this is used by {@link java.util.Random} original implementations.
*
* <p>* The original {@link java.util.Random} implementation
* often discards higher or lower bits of this return value.
*
* <p>Entropy obtained from random source is returned
* without entropy missing.
*
* @param bits {@inheritDoc}
* @return {@inheritDoc}
*/
@Override
protected int next(int bits) {
fillPool(bits);
int result = this.bitPool.chopInt(bits);
return result;
}
/**
* Random sources.
*/
private static enum RndSource {
/** {@link RndInt32}. */
R32,
/** {@link RndInt64}. */
R64,
/** {@link RndInt31}. */
R31,
/** {@link java.util.Random}. */
JRND,
}
}