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 }