View Javadoc
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 }