View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.rng.examples.stress;
18  
19  import org.apache.commons.rng.UniformRandomProvider;
20  
21  import java.io.Closeable;
22  import java.io.IOException;
23  import java.io.OutputStream;
24  import java.nio.ByteOrder;
25  
26  /**
27   * A specialised data output class that combines the functionality of
28   * {@link java.io.DataOutputStream DataOutputStream} and
29   * {@link java.io.BufferedOutputStream BufferedOutputStream} to write byte data from a RNG
30   * to an OutputStream. Large blocks of byte data are written in a single operation for efficiency.
31   * The byte data endianness can be configured.
32   *
33   * <p>This class is the functional equivalent of:</p>
34   *
35   * <pre>
36   * <code>
37   * OutputStream out = ...
38   * UniformRandomProvider rng = ...
39   * int size = 2048;
40   * DataOutputStream sink = new DataOutputStream(new BufferedOutputStream(out, size * 4));
41   * for (int i = 0; i &lt; size; i++) {
42   *    sink.writeInt(rng.nextInt());
43   * }
44   *
45   * // Replaced with
46   * RngDataOutput output = RngDataOutput.ofInt(out, size, ByteOrder.BIG_ENDIAN);
47   * output.write(rng);
48   * </code>
49   * </pre>
50   *
51   * <p>Use of this class avoids the synchronized write operations in
52   * {@link java.io.BufferedOutputStream BufferedOutputStream}. In particular it avoids the
53   * 4 synchronized write operations to
54   * {@link java.io.BufferedOutputStream#write(int) BufferedOutputStream#write(int)} that
55   * occur for each {@code int} value that is written to
56   * {@link java.io.DataOutputStream#writeInt(int) DataOutputStream#writeInt(int)}.</p>
57   *
58   * <p>This class has adaptors to write the long output from a RNG to two int values.
59   * To match the caching implementation in the core LongProvider class this is tested
60   * to output int values in the same order as an instance of the LongProvider. Currently
61   * this outputs in order: low 32-bits, high 32-bits.
62   */
63  abstract class RngDataOutput implements Closeable {
64      /** The data buffer. */
65      protected final byte[] buffer;
66  
67      /** The underlying output stream. */
68      private final OutputStream out;
69  
70      /**
71       * Write big-endian {@code int} data.
72       * <pre>{@code
73       * 3210  ->  3210
74       * }</pre>
75       */
76      private static class BIntRngDataOutput extends RngDataOutput {
77          /**
78           * @param out Output stream.
79           * @param size Buffer size.
80           */
81          BIntRngDataOutput(OutputStream out, int size) {
82              super(out, size);
83          }
84  
85          @Override
86          public void fillBuffer(UniformRandomProvider rng) {
87              for (int i = 0; i < buffer.length; i += 4) {
88                  writeIntBE(i, rng.nextInt());
89              }
90          }
91      }
92  
93      /**
94       * Write little-endian {@code int} data.
95       * <pre>{@code
96       * 3210  ->  0123
97       * }</pre>
98       */
99      private static class LIntRngDataOutput extends RngDataOutput {
100         /**
101          * @param out Output stream.
102          * @param size Buffer size.
103          */
104         LIntRngDataOutput(OutputStream out, int size) {
105             super(out, size);
106         }
107 
108         @Override
109         public void fillBuffer(UniformRandomProvider rng) {
110             for (int i = 0; i < buffer.length; i += 4) {
111                 writeIntLE(i, rng.nextInt());
112             }
113         }
114     }
115 
116     /**
117      * Write big-endian {@code long} data.
118      * <pre>{@code
119      * 76543210  ->  76543210
120      * }</pre>
121      */
122     private static class BLongRngDataOutput extends RngDataOutput {
123         /**
124          * @param out Output stream.
125          * @param size Buffer size.
126          */
127         BLongRngDataOutput(OutputStream out, int size) {
128             super(out, size);
129         }
130 
131         @Override
132         public void fillBuffer(UniformRandomProvider rng) {
133             for (int i = 0; i < buffer.length; i += 8) {
134                 writeLongBE(i, rng.nextLong());
135             }
136         }
137     }
138 
139     /**
140      * Write little-endian {@code long} data.
141      * <pre>{@code
142      * 76543210  ->  01234567
143      * }</pre>
144      */
145     private static class LLongRngDataOutput extends RngDataOutput {
146         /**
147          * @param out Output stream.
148          * @param size Buffer size.
149          */
150         LLongRngDataOutput(OutputStream out, int size) {
151             super(out, size);
152         }
153 
154         @Override
155         public void fillBuffer(UniformRandomProvider rng) {
156             for (int i = 0; i < buffer.length; i += 8) {
157                 writeLongLE(i, rng.nextLong());
158             }
159         }
160     }
161 
162     /**
163      * Write {@code long} data as two little-endian {@code int} values, high 32-bits then
164      * low 32-bits.
165      * <pre>{@code
166      * 76543210  ->  4567  0123
167      * }</pre>
168      *
169      * <p>This is a specialisation that allows the Java big-endian representation to be split
170      * into two little-endian values in the original order of upper then lower bits. In
171      * comparison the {@link LLongRngDataOutput} will output the same data as:
172      *
173      * <pre>{@code
174      * 76543210  ->  0123  4567
175      * }</pre>
176      */
177     private static class LLongAsIntRngDataOutput extends RngDataOutput {
178         /**
179          * @param out Output stream.
180          * @param size Buffer size.
181          */
182         LLongAsIntRngDataOutput(OutputStream out, int size) {
183             super(out, size);
184         }
185 
186         @Override
187         public void fillBuffer(UniformRandomProvider rng) {
188             for (int i = 0; i < buffer.length; i += 8) {
189                 writeLongAsHighLowIntLE(i, rng.nextLong());
190             }
191         }
192     }
193 
194     /**
195      * Write {@code long} data as two big-endian {@code int} values, low 32-bits then
196      * high 32-bits.
197      * <pre>{@code
198      * 76543210  ->  3210  7654
199      * }</pre>
200      *
201      * <p>This is a specialisation that allows the Java big-endian representation to be split
202      * into two big-endian values in the original order of lower then upper bits. In
203      * comparison the {@link BLongRngDataOutput} will output the same data as:
204      *
205      * <pre>{@code
206      * 76543210  ->  7654  3210
207      * }</pre>
208      */
209     private static class BLongAsLoHiIntRngDataOutput extends RngDataOutput {
210         /**
211          * @param out Output stream.
212          * @param size Buffer size.
213          */
214         BLongAsLoHiIntRngDataOutput(OutputStream out, int size) {
215             super(out, size);
216         }
217 
218         @Override
219         public void fillBuffer(UniformRandomProvider rng) {
220             for (int i = 0; i < buffer.length; i += 8) {
221                 writeLongAsLowHighIntBE(i, rng.nextLong());
222             }
223         }
224     }
225 
226     /**
227      * Create a new instance.
228      *
229      * @param out Output stream.
230      * @param size Buffer size.
231      */
232     RngDataOutput(OutputStream out, int size) {
233         this.out = out;
234         buffer = new byte[size];
235     }
236 
237     /**
238      * Write the configured amount of byte data from the specified RNG to the output.
239      *
240      * @param rng Source of randomness.
241      * @exception IOException if an I/O error occurs.
242      */
243     public void write(UniformRandomProvider rng) throws IOException {
244         fillBuffer(rng);
245         out.write(buffer);
246     }
247 
248     /**
249      * Fill the buffer from the specified RNG.
250      *
251      * @param rng Source of randomness.
252      */
253     public abstract void fillBuffer(UniformRandomProvider rng);
254 
255     /**
256      * Writes an {@code int} to the buffer as four bytes, high byte first (big-endian).
257      *
258      * @param index the index to start writing.
259      * @param value an {@code int} to be written.
260      */
261     final void writeIntBE(int index, int value) {
262         buffer[index    ] = (byte) (value >>> 24);
263         buffer[index + 1] = (byte) (value >>> 16);
264         buffer[index + 2] = (byte) (value >>> 8);
265         buffer[index + 3] = (byte) value;
266     }
267 
268     /**
269      * Writes an {@code int} to the buffer as four bytes, low byte first (little-endian).
270      *
271      * @param index the index to start writing.
272      * @param value an {@code int} to be written.
273      */
274     final void writeIntLE(int index, int value) {
275         buffer[index    ] = (byte) value;
276         buffer[index + 1] = (byte) (value >>> 8);
277         buffer[index + 2] = (byte) (value >>> 16);
278         buffer[index + 3] = (byte) (value >>> 24);
279     }
280 
281     /**
282      * Writes an {@code long} to the buffer as eight bytes, high byte first (big-endian).
283      *
284      * @param index the index to start writing.
285      * @param value an {@code long} to be written.
286      */
287     final void writeLongBE(int index, long value) {
288         buffer[index    ] = (byte) (value >>> 56);
289         buffer[index + 1] = (byte) (value >>> 48);
290         buffer[index + 2] = (byte) (value >>> 40);
291         buffer[index + 3] = (byte) (value >>> 32);
292         buffer[index + 4] = (byte) (value >>> 24);
293         buffer[index + 5] = (byte) (value >>> 16);
294         buffer[index + 6] = (byte) (value >>> 8);
295         buffer[index + 7] = (byte) value;
296     }
297 
298     /**
299      * Writes an {@code long} to the buffer as eight bytes, low byte first (little-endian).
300      *
301      * @param index the index to start writing.
302      * @param value an {@code long} to be written.
303      */
304     final void writeLongLE(int index, long value) {
305         buffer[index    ] = (byte) value;
306         buffer[index + 1] = (byte) (value >>> 8);
307         buffer[index + 2] = (byte) (value >>> 16);
308         buffer[index + 3] = (byte) (value >>> 24);
309         buffer[index + 4] = (byte) (value >>> 32);
310         buffer[index + 5] = (byte) (value >>> 40);
311         buffer[index + 6] = (byte) (value >>> 48);
312         buffer[index + 7] = (byte) (value >>> 56);
313     }
314 
315     /**
316      * Writes an {@code long} to the buffer as two integers of four bytes, each
317      * low byte first (little-endian). The long is written as the high 32-bits,
318      * then the low 32-bits.
319      *
320      * <p>Note: A LowHigh little-endian output is the same as {@link #writeLongLE(int, long)}.
321      *
322      * @param index the index to start writing.
323      * @param value an {@code long} to be written.
324      */
325     final void writeLongAsHighLowIntLE(int index, long value) {
326         // high
327         buffer[index    ] = (byte) (value >>> 32);
328         buffer[index + 1] = (byte) (value >>> 40);
329         buffer[index + 2] = (byte) (value >>> 48);
330         buffer[index + 3] = (byte) (value >>> 56);
331         // low
332         buffer[index + 4] = (byte) value;
333         buffer[index + 5] = (byte) (value >>> 8);
334         buffer[index + 6] = (byte) (value >>> 16);
335         buffer[index + 7] = (byte) (value >>> 24);
336     }
337 
338     /**
339      * Writes an {@code long} to the buffer as two integers of four bytes, each
340      * high byte first (big-endian). The long is written as the low 32-bits,
341      * then the high 32-bits.
342      *
343      * <p>Note: A HighLow big-endian output is the same as {@link #writeLongBE(int, long)}.
344      *
345      * @param index the index to start writing.
346      * @param value an {@code long} to be written.
347      */
348     final void writeLongAsLowHighIntBE(int index, long value) {
349         // low
350         buffer[index    ] = (byte) (value >>> 24);
351         buffer[index + 1] = (byte) (value >>> 16);
352         buffer[index + 2] = (byte) (value >>> 8);
353         buffer[index + 3] = (byte) value;
354         // high
355         buffer[index + 4] = (byte) (value >>> 56);
356         buffer[index + 5] = (byte) (value >>> 48);
357         buffer[index + 6] = (byte) (value >>> 40);
358         buffer[index + 7] = (byte) (value >>> 32);
359     }
360 
361     @Override
362     public void close() throws IOException {
363         try (OutputStream ostream = out) {
364             ostream.flush();
365         }
366     }
367 
368     /**
369      * Create a new instance to write batches of data from
370      * {@link UniformRandomProvider#nextInt()} to the specified output.
371      *
372      * @param out Output stream.
373      * @param size Number of values to write.
374      * @param byteOrder Byte order.
375      * @return the data output
376      */
377     @SuppressWarnings("resource")
378     static RngDataOutput ofInt(OutputStream out, int size, ByteOrder byteOrder) {
379         // Ensure the buffer is positive and a factor of 4
380         final int bytes = Math.max(size * 4, 4);
381         return byteOrder == ByteOrder.LITTLE_ENDIAN ?
382             new LIntRngDataOutput(out, bytes) :
383             new BIntRngDataOutput(out, bytes);
384     }
385 
386     /**
387      * Create a new instance to write batches of data from
388      * {@link UniformRandomProvider#nextLong()} to the specified output.
389      *
390      * @param out Output stream.
391      * @param size Number of values to write.
392      * @param byteOrder Byte order.
393      * @return the data output
394      */
395     @SuppressWarnings("resource")
396     static RngDataOutput ofLong(OutputStream out, int size, ByteOrder byteOrder) {
397         // Ensure the buffer is positive and a factor of 8
398         final int bytes = Math.max(size * 8, 8);
399         return byteOrder == ByteOrder.LITTLE_ENDIAN ?
400             new LLongRngDataOutput(out, bytes) :
401             new BLongRngDataOutput(out, bytes);
402     }
403 
404     /**
405      * Create a new instance to write batches of data from
406      * {@link UniformRandomProvider#nextLong()} to the specified output as two sequential
407      * {@code int} values, high 32-bits then low 32-bits.
408      *
409      * <p>This will output the following bytes:</p>
410      *
411      * <pre>{@code
412      * // Little-endian
413      * 76543210  ->  4567  0123
414      *
415      * // Big-endian
416      * 76543210  ->  7654  3210
417      * }</pre>
418      *
419      * <p>This ensures the output from the generator is the original upper then lower order bits
420      * for each endianess.
421      *
422      * @param out Output stream.
423      * @param size Number of values to write.
424      * @param byteOrder Byte order.
425      * @return the data output
426      */
427     @SuppressWarnings("resource")
428     static RngDataOutput ofLongAsHLInt(OutputStream out, int size, ByteOrder byteOrder) {
429         // Ensure the buffer is positive and a factor of 8
430         final int bytes = Math.max(size * 8, 8);
431         return byteOrder == ByteOrder.LITTLE_ENDIAN ?
432             new LLongAsIntRngDataOutput(out, bytes) :
433             new BLongRngDataOutput(out, bytes);
434     }
435 
436     /**
437      * Create a new instance to write batches of data from
438      * {@link UniformRandomProvider#nextLong()} to the specified output as two sequential
439      * {@code int} values, low 32-bits then high 32-bits.
440      *
441      * <p>This will output the following bytes:</p>
442      *
443      * <pre>{@code
444      * // Little-endian
445      * 76543210  ->  0123  4567
446      *
447      * // Big-endian
448      * 76543210  ->  3210  7654
449      * }</pre>
450      *
451      * <p>This ensures the output from the generator is the original lower then upper order bits
452      * for each endianess.
453      *
454      * @param out Output stream.
455      * @param size Number of values to write.
456      * @param byteOrder Byte order.
457      * @return the data output
458      */
459     @SuppressWarnings("resource")
460     static RngDataOutput ofLongAsLHInt(OutputStream out, int size, ByteOrder byteOrder) {
461         // Ensure the buffer is positive and a factor of 8
462         final int bytes = Math.max(size * 8, 8);
463         return byteOrder == ByteOrder.LITTLE_ENDIAN ?
464             new LLongRngDataOutput(out, bytes) :
465             new BLongAsLoHiIntRngDataOutput(out, bytes);
466     }
467 }