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