1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.rng.examples.stress;
18
19 import org.apache.commons.rng.UniformRandomProvider;
20 import org.apache.commons.rng.core.source64.RandomLongSource;
21 import org.apache.commons.rng.simple.RandomSource;
22
23 import picocli.CommandLine.Command;
24 import picocli.CommandLine.Mixin;
25 import picocli.CommandLine.Option;
26 import picocli.CommandLine.Parameters;
27
28 import java.io.BufferedWriter;
29 import java.io.File;
30 import java.io.FilterOutputStream;
31 import java.io.IOException;
32 import java.io.OutputStream;
33 import java.io.OutputStreamWriter;
34 import java.io.Writer;
35 import java.nio.ByteOrder;
36 import java.nio.charset.StandardCharsets;
37 import java.nio.file.Files;
38 import java.util.ArrayList;
39 import java.util.Formatter;
40 import java.util.List;
41 import java.util.concurrent.Callable;
42
43
44
45
46
47
48 @Command(name = "output",
49 description = {"Output data from a named random data generator."})
50 class OutputCommand implements Callable<Void> {
51
52 private static final String NEW_LINE = System.lineSeparator();
53
54 private static final char LEFT_SQUARE_BRACKET = '[';
55
56 private static final char RIGHT_SQUARE_BRACKET = ']';
57
58
59 private static final String[] BIT_REP = {
60 "0000", "0001", "0010", "0011",
61 "0100", "0101", "0110", "0111",
62 "1000", "1001", "1010", "1011",
63 "1100", "1101", "1110", "1111",
64 };
65
66
67 @Mixin
68 private StandardOptions reusableOptions;
69
70
71 @Parameters(index = "0",
72 description = "The random source.")
73 private RandomSource randomSource;
74
75
76 @Parameters(index = "1..*",
77 description = "The arguments to pass to the constructor.",
78 paramLabel = "<argument>")
79 private List<String> arguments = new ArrayList<>();
80
81
82 @Option(names = {"-o", "--out"},
83 description = "The output file (default: stdout).")
84 private File fileOutput;
85
86
87 @Option(names = {"-f", "--format"},
88 description = {"Output format (default: ${DEFAULT-VALUE}).",
89 "Valid values: ${COMPLETION-CANDIDATES}."})
90 private OutputCommand.OutputFormat outputFormat = OutputFormat.DIEHARDER;
91
92
93 @Option(names = {"-s", "--seed"},
94 description = {"The 64-bit number random seed (default: auto)."})
95 private Long seed;
96
97
98 @Option(names = {"-x", "--hex-seed"},
99 description = {"The hex-encoded random seed.",
100 "Seed conversion for multi-byte primitives use little-endian format.",
101 "Over-rides the --seed parameter."})
102 private String byteSeed;
103
104
105 @Option(names = {"-n", "--count"},
106 description = {"The count of numbers to output.",
107 "Use negative for an unlimited stream."})
108 private long count = 10;
109
110
111 @Option(names = {"--buffer-size"},
112 description = {"Byte-buffer size for binary data (default: ${DEFAULT-VALUE}).",
113 "When outputing binary data the count parameter controls the " +
114 "number of buffers written."})
115 private int bufferSize = 8192;
116
117
118 @Option(names = {"-b", "--byte-order"},
119 description = {"Byte-order of the output data (default: ${DEFAULT-VALUE}).",
120 "Uses the Java default of big-endian. This may not match the platform byte-order.",
121 "Valid values: BIG_ENDIAN, LITTLE_ENDIAN."})
122 private ByteOrder byteOrder = ByteOrder.BIG_ENDIAN;
123
124
125 @Option(names = {"-r", "--reverse-bits"},
126 description = {"Reverse the bits in the data (default: ${DEFAULT-VALUE})."})
127 private boolean reverseBits;
128
129
130 @Option(names = {"--high-bits"},
131 description = {"Use the upper 32-bits from the 64-bit long output.",
132 "Takes precedent over --low-bits."})
133 private boolean longHighBits;
134
135
136 @Option(names = {"--low-bits"},
137 description = {"Use the lower 32-bits from the 64-bit long output."})
138 private boolean longLowBits;
139
140
141 @Option(names = {"--raw64"},
142 description = {"Use 64-bit output (default is 32-bit).",
143 "This is ignored if not a native 64-bit generator.",
144 "In 32-bit mode the output uses the upper then lower bits of 64-bit " +
145 "generators sequentially."})
146 private boolean raw64;
147
148
149
150
151 enum OutputFormat {
152
153 BINARY,
154
155 DIEHARDER,
156
157 BITS,
158 }
159
160
161
162
163 @Override
164 public Void call() {
165 LogUtils.setLogLevel(reusableOptions.logLevel);
166 final Object objectSeed = createSeed();
167 UniformRandomProvider rng = createRNG(objectSeed);
168
169
170
171 if (longHighBits) {
172 rng = RNGUtils.createLongUpperBitsIntProvider(rng);
173 } else if (longLowBits) {
174 rng = RNGUtils.createLongLowerBitsIntProvider(rng);
175 }
176 if (reverseBits) {
177 rng = RNGUtils.createReverseBitsProvider(rng);
178 }
179
180
181
182
183
184 if (outputFormat != OutputFormat.BINARY) {
185 rng = toOutputFormat(rng);
186 }
187
188 try (OutputStream out = createOutputStream()) {
189 switch (outputFormat) {
190 case BINARY:
191 writeBinaryData(rng, out);
192 break;
193 case DIEHARDER:
194 writeDieharder(rng, out);
195 break;
196 case BITS:
197 writeBitData(rng, out);
198 break;
199 default:
200 throw new ApplicationException("Unknown output format: " + outputFormat);
201 }
202 } catch (IOException ex) {
203 throw new ApplicationException("IO error: " + ex.getMessage(), ex);
204 }
205 return null;
206 }
207
208
209
210
211
212
213 private Object createSeed() {
214 if (byteSeed != null) {
215 try {
216 return Hex.decodeHex(byteSeed);
217 } catch (IllegalArgumentException ex) {
218 throw new ApplicationException("Invalid hex seed: " + ex.getMessage(), ex);
219 }
220 }
221 if (seed != null) {
222 return seed;
223 }
224
225 return null;
226 }
227
228
229
230
231
232
233 private String createSeedString() {
234 if (byteSeed != null) {
235 return byteSeed;
236 }
237 if (seed != null) {
238 return seed.toString();
239 }
240 return "auto";
241 }
242
243
244
245
246
247
248
249
250 private UniformRandomProvider createRNG(Object objectSeed) {
251 if (randomSource == null) {
252 throw new ApplicationException("Random source is null");
253 }
254 final ArrayList<Object> data = new ArrayList<>();
255
256
257 stripArrayFormatting(arguments);
258
259 for (final String argument : arguments) {
260 data.add(RNGUtils.parseArgument(argument));
261 }
262 try {
263 return RandomSource.create(randomSource, objectSeed, data.toArray());
264 } catch (IllegalStateException | IllegalArgumentException ex) {
265 throw new ApplicationException("Failed to create RNG: " + randomSource + ". " + ex.getMessage(), ex);
266 }
267 }
268
269
270
271
272
273
274
275
276
277 private static void stripArrayFormatting(List<String> arguments) {
278 final int size = arguments.size();
279 if (size > 1) {
280
281 final String first = arguments.get(0);
282 if (first.charAt(0) == LEFT_SQUARE_BRACKET) {
283 arguments.set(0, first.substring(1));
284 }
285 final String last = arguments.get(size - 1);
286 if (last.charAt(last.length() - 1) == RIGHT_SQUARE_BRACKET) {
287 arguments.set(size - 1, last.substring(0, last.length() - 1));
288 }
289 }
290 for (int i = 0; i < size; i++) {
291 final String argument = arguments.get(i);
292 if (argument.endsWith(",")) {
293 arguments.set(i, argument.substring(0, argument.length() - 1));
294 }
295 }
296 }
297
298
299
300
301
302
303
304
305
306 private UniformRandomProvider toOutputFormat(UniformRandomProvider rng) {
307 UniformRandomProvider convertedRng = rng;
308 if (rng instanceof RandomLongSource && !raw64) {
309
310 convertedRng = RNGUtils.createIntProvider(rng);
311 }
312 if (byteOrder == ByteOrder.LITTLE_ENDIAN) {
313 convertedRng = RNGUtils.createReverseBytesProvider(convertedRng);
314 }
315 return convertedRng;
316 }
317
318
319
320
321
322
323 private OutputStream createOutputStream() {
324 if (fileOutput != null) {
325 try {
326 Files.newOutputStream(fileOutput.toPath());
327 } catch (IOException ex) {
328 throw new ApplicationException("Failed to create output: " + fileOutput, ex);
329 }
330 }
331 return new FilterOutputStream(System.out) {
332 @Override
333 public void close() {
334
335 }
336 };
337 }
338
339
340
341
342
343
344
345
346 private static void checkCount(long count,
347 OutputFormat format) {
348 if (count <= 0) {
349 throw new ApplicationException(format + " format requires a positive count: " + count);
350 }
351 }
352
353
354
355
356
357
358
359
360
361 private void writeDieharder(final UniformRandomProvider rng,
362 final OutputStream out) throws IOException {
363 checkCount(count, OutputFormat.DIEHARDER);
364
365
366
367
368
369
370
371
372
373 try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
374 writeHeaderLine(output);
375 output.write("# generator ");
376 output.write(rng.toString());
377 output.write(" seed = ");
378 output.write(createSeedString());
379 output.write(NEW_LINE);
380 writeHeaderLine(output);
381 output.write("type: d");
382 output.write(NEW_LINE);
383 output.write("count: ");
384 output.write(Long.toString(count));
385 output.write(NEW_LINE);
386 output.write("numbit: 32");
387 output.write(NEW_LINE);
388 for (long c = 0; c < count; c++) {
389
390 final String text = Long.toString(rng.nextInt() & 0xffffffffL);
391
392 for (int i = 10 - text.length(); i > 0; i--) {
393 output.write(' ');
394 }
395 output.write(text);
396 output.write(NEW_LINE);
397 }
398 }
399 }
400
401
402
403
404
405
406
407 private static void writeHeaderLine(Writer output) throws IOException {
408 output.write("#==================================================================");
409 output.write(NEW_LINE);
410 }
411
412
413
414
415
416
417
418
419 private void writeBinaryData(final UniformRandomProvider rng,
420 final OutputStream out) throws IOException {
421
422
423 final long limit = (count < 1) ? Long.MAX_VALUE : count;
424 try (RngDataOutput data = RNGUtils.createDataOutput(rng, raw64, out, bufferSize, byteOrder)) {
425 for (long c = 0; c < limit; c++) {
426 data.write(rng);
427 }
428 }
429 }
430
431
432
433
434
435
436
437
438
439 private void writeBitData(final UniformRandomProvider rng,
440 final OutputStream out) throws IOException {
441 checkCount(count, OutputFormat.BITS);
442
443 boolean asLong = rng instanceof RandomLongSource;
444
445 try (BufferedWriter output = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8))) {
446 for (long c = 0; c < count; c++) {
447 if (asLong) {
448 writeLong(output, rng.nextLong());
449 } else {
450 writeInt(output, rng.nextInt());
451 }
452 }
453 }
454 }
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469 @SuppressWarnings("resource")
470 static void writeLong(Writer out,
471 long value) throws IOException {
472
473
474 writeByte(out, (int)(value >>> 56) & 0xff);
475 out.write(' ');
476 writeByte(out, (int)(value >>> 48) & 0xff);
477 out.write(' ');
478 writeByte(out, (int)(value >>> 40) & 0xff);
479 out.write(' ');
480 writeByte(out, (int)(value >>> 32) & 0xff);
481 out.write(' ');
482 writeByte(out, (int)(value >>> 24) & 0xff);
483 out.write(' ');
484 writeByte(out, (int)(value >>> 16) & 0xff);
485 out.write(' ');
486 writeByte(out, (int)(value >>> 8) & 0xff);
487 out.write(' ');
488 writeByte(out, (int)(value >>> 0) & 0xff);
489
490
491 new Formatter(out).format(" %20s %20d%n", Long.toUnsignedString(value), value);
492 }
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507 @SuppressWarnings("resource")
508 static void writeInt(Writer out,
509 int value) throws IOException {
510
511
512 writeByte(out, (value >>> 24) & 0xff);
513 out.write(' ');
514 writeByte(out, (value >>> 16) & 0xff);
515 out.write(' ');
516 writeByte(out, (value >>> 8) & 0xff);
517 out.write(' ');
518 writeByte(out, (value >>> 0) & 0xff);
519
520
521 new Formatter(out).format(" %10d %11d%n", value & 0xffffffffL, value);
522 }
523
524
525
526
527
528
529
530
531
532
533
534
535
536 private static void writeByte(Writer out,
537 int value) throws IOException {
538
539
540 out.write(BIT_REP[value >>> 4]);
541 out.write(BIT_REP[value & 0x0F]);
542 }
543 }