1 /*
2 * License : The MIT License
3 * Copyright(c) 2022 Olyutorskii
4 */
5
6 package io.github.olyutorskii.aletojio.rng;
7
8 import io.github.olyutorskii.aletojio.BitPool;
9 import java.util.Random;
10
11 /**
12 * Random generator exchange adapter.
13 *
14 * <p>Designed to conserve as much random source entropy as possible.
15 *
16 * <p>Entropy is consumed first from MSB.
17 *
18 * <p>512 bits buffer is provided.
19 *
20 * <p>TODO: supporting {@literal java.util.random.RandomGenerator} (JDK17 or later)
21 * and {@literal org.apache.commons.rng.UniformRandomProvider}
22 */
23 @SuppressWarnings("serial")
24 public class RndAdapter
25 extends Random
26 implements RndInt32, RndInt64, RndInt31 {
27
28 private final transient RndSource source;
29
30 private final transient RndInt31 delRndInt31;
31 private final transient RndInt32 delRndInt32;
32 private final transient RndInt64 delRndInt64;
33 private final transient Random delRandom;
34
35 private final transient BitPool bitPool = new BitPool();
36
37 private final transient int inUnit;
38
39 private final transient boolean initDone;
40
41
42 /**
43 * Constructor.
44 *
45 * @param rndSrc random generator
46 * @throws NullPointerException null argument
47 */
48 public RndAdapter(RndInt32 rndSrc) throws NullPointerException {
49 this(rndSrc, null, null, null);
50 return;
51 }
52
53 /**
54 * Constructor.
55 *
56 * @param rndSrc random generator
57 * @throws NullPointerException null argument
58 */
59 public RndAdapter(RndInt64 rndSrc) throws NullPointerException {
60 this(null, rndSrc, null, null);
61 return;
62 }
63
64 /**
65 * Constructor.
66 *
67 * @param rndSrc random generator
68 * @throws NullPointerException null argument
69 */
70 public RndAdapter(RndInt31 rndSrc) throws NullPointerException {
71 this(null, null, rndSrc, null);
72 return;
73 }
74
75 /**
76 * Constructor.
77 *
78 * @param rndSrc random generator
79 * @throws NullPointerException null argument
80 */
81 public RndAdapter(Random rndSrc) throws NullPointerException {
82 this(null, null, null, rndSrc);
83 return;
84 }
85
86 /**
87 * Constructor.
88 *
89 * <p>Any generator argument must be non-null.
90 *
91 * @param rndInt32 random generator
92 * @param rndInt64 random generator
93 * @param rndInt31 random generator
94 * @param rnd random generator
95 * @throws NullPointerException all argument is null.
96 */
97 private RndAdapter(
98 RndInt32 rndInt32,
99 RndInt64 rndInt64,
100 RndInt31 rndInt31,
101 Random rnd
102 ) throws NullPointerException {
103 super();
104
105 RndSource src;
106 int unit;
107 if (rndInt32 != null) {
108 src = RndSource.R32;
109 unit = 32;
110 } else if (rndInt64 != null) {
111 src = RndSource.R64;
112 unit = 64;
113 } else if (rndInt31 != null) {
114 src = RndSource.R31;
115 unit = 31;
116 } else if (rnd != null) {
117 src = RndSource.JRND;
118 unit = 32;
119 } else {
120 throw new NullPointerException();
121 }
122 this.source = src;
123 this.inUnit = unit;
124
125 this.delRndInt32 = rndInt32;
126 this.delRndInt64 = rndInt64;
127 this.delRndInt31 = rndInt31;
128 this.delRandom = rnd;
129
130 this.initDone = true;
131
132 assert this.bitPool.capacity() >= Long.SIZE * 2;
133
134 return;
135 }
136
137
138 /**
139 * Seed change is not supported.
140 *
141 * <p>* However, it is allowed only during the initial constructor.
142 *
143 * @param seed not supported
144 * @throws UnsupportedOperationException always
145 */
146 @Override
147 public void setSeed(long seed) throws UnsupportedOperationException {
148 if (this.initDone) {
149 throw new UnsupportedOperationException();
150 }
151 return;
152 }
153
154 /**
155 * If not enough, fill bit-pool.
156 *
157 * @param minSz minimum pool size
158 */
159 private void fillPool(int minSz) {
160 if (this.bitPool.size() >= minSz) return;
161
162 while (this.bitPool.remaining() >= this.inUnit) {
163 int iVal;
164 long lVal;
165
166 switch (this.source) {
167 case R32:
168 iVal = this.delRndInt32.nextInt32();
169 this.bitPool.pushInt(iVal);
170 break;
171 case R64:
172 lVal = this.delRndInt64.nextInt64();
173 this.bitPool.pushLong(lVal);
174 break;
175 case R31:
176 iVal = this.delRndInt31.nextInt31();
177 this.bitPool.pushInt(iVal, 31);
178 break;
179 case JRND:
180 iVal = this.delRandom.nextInt();
181 this.bitPool.pushInt(iVal);
182 break;
183 default:
184 break;
185 }
186 }
187
188 return;
189 }
190
191 /**
192 * Return next random number as 1bit boolean.
193 *
194 * <p>Unlike {@link java.util.Random#nextBoolean()} implement,
195 * any entropy from random source is not discarded.
196 *
197 * @return random number
198 */
199 public boolean nextBit() {
200 fillPool(1);
201 boolean result = this.bitPool.chopBoolean();
202 return result;
203 }
204
205 /**
206 * Return next random number as 8bit byte.
207 *
208 * <p>Unlike {@link java.util.Random#nextBytes(byte[])} implement,
209 * any entropy from random source is not discarded.
210 *
211 * @return random number
212 */
213 public byte nextByte() {
214 fillPool(Byte.SIZE);
215 byte result = this.bitPool.chopByte();
216 return result;
217 }
218
219 /**
220 * {@inheritDoc}
221 *
222 * <p>Entropy obtained from random source is returned
223 * without entropy missing.
224 *
225 * @return {@inheritDoc}
226 */
227 @Override
228 public int nextInt31() {
229 fillPool(31);
230 int result = this.bitPool.chopInt(31);
231 return result;
232 }
233
234 /**
235 * {@inheritDoc}
236 *
237 * <p>Entropy obtained from random source is returned
238 * without entropy missing.
239 *
240 * @return {@inheritDoc}
241 */
242 @Override
243 public int nextInt32() {
244 fillPool(Integer.SIZE);
245 int result = this.bitPool.chopInt();
246 return result;
247 }
248
249 /**
250 * {@inheritDoc}
251 *
252 * <p>Entropy obtained from random source is returned
253 * without entropy missing.
254 *
255 * @return {@inheritDoc}
256 */
257 @Override
258 public long nextInt64() {
259 fillPool(Long.SIZE);
260 long result = this.bitPool.chopLong();
261 return result;
262 }
263
264 /**
265 * Generates the next pseudorandom number.
266 * Subclasses should override this,
267 * as this is used by {@link java.util.Random} original implementations.
268 *
269 * <p>* The original {@link java.util.Random} implementation
270 * often discards higher or lower bits of this return value.
271 *
272 * <p>Entropy obtained from random source is returned
273 * without entropy missing.
274 *
275 * @param bits {@inheritDoc}
276 * @return {@inheritDoc}
277 */
278 @Override
279 protected int next(int bits) {
280 fillPool(bits);
281 int result = this.bitPool.chopInt(bits);
282 return result;
283 }
284
285 /**
286 * Random sources.
287 */
288 private static enum RndSource {
289 /** {@link RndInt32}. */
290 R32,
291 /** {@link RndInt64}. */
292 R64,
293 /** {@link RndInt31}. */
294 R31,
295 /** {@link java.util.Random}. */
296 JRND,
297 }
298
299 }