001// Generated by delombok at Sat Feb 25 17:58:34 UTC 2023 002package io.cui.tools.io; 003 004import static io.cui.tools.string.MoreStrings.nullToEmpty; 005import java.io.BufferedInputStream; 006import java.io.BufferedReader; 007import java.io.ByteArrayInputStream; 008import java.io.ByteArrayOutputStream; 009import java.io.EOFException; 010import java.io.IOException; 011import java.io.InputStream; 012import java.io.InputStreamReader; 013import java.io.OutputStream; 014import java.io.OutputStreamWriter; 015import java.io.Reader; 016import java.io.StringWriter; 017import java.io.Writer; 018import java.nio.charset.Charset; 019import java.nio.charset.StandardCharsets; 020import io.cui.tools.base.Preconditions; 021 022/** 023 * Provides a number of utilities in context {@link InputStream} and {@link OutputStream}s. The 024 * content is inspired / copied from org.apache.commons.io.IOUtils 025 * 026 * @author Oliver Wolff 027 */ 028public final class IOStreams { 029 private static final String ACTUAL = " actual: "; 030 private static final String SKIP_COUNT_MUST_BE_NON_NEGATIVE_ACTUAL = "Skip count must be non-negative, actual: "; 031 /** 032 * Represents the end-of-file (or stream). 033 */ 034 public static final int EOF = -1; 035 /** 036 * The default buffer size ({@value}) to use in copy methods. 037 */ 038 public static final int DEFAULT_BUFFER_SIZE = 1024 * 4; 039 /** 040 * The default buffer size to use for the skip() methods. 041 */ 042 private static final int SKIP_BUFFER_SIZE = 2048; 043 // Allocated in the relevant skip method if necessary. 044 /* 045 * These buffers are static and are shared between threads. 046 * This is possible because the buffers are write-only - the contents are never read. 047 * 048 * N.B. there is no need to synchronize when creating these because: 049 * - we don't care if the buffer is created multiple times (the data is ignored) 050 * - we always use the same size buffer, so if it is recreated it will still be OK 051 * (if the buffer size were variable, we would need to sync. to ensure some other thread 052 * did not create a smaller one) 053 */ 054 private static char[] skipCharBuffer; 055 private static byte[] skipByteBuffer; 056 057 /** 058 * Compares the contents of two Streams to determine if they are equal or 059 * not. 060 * <p> 061 * This method buffers the input internally using 062 * <code>BufferedInputStream</code> if they are not already buffered. 063 * 064 * @param input1 the first stream 065 * @param input2 the second stream 066 * @return true if the content of the streams are equal or they both don't 067 * exist, false otherwise 068 * @throws NullPointerException if either input is null 069 * @throws IOException if an I/O error occurs 070 */ 071 public static boolean contentEquals(InputStream input1, InputStream input2) throws IOException { 072 if (input1 == input2) { 073 return true; 074 } 075 if (!(input1 instanceof BufferedInputStream)) { 076 input1 = new BufferedInputStream(input1); 077 } 078 if (!(input2 instanceof BufferedInputStream)) { 079 input2 = new BufferedInputStream(input2); 080 } 081 var ch = input1.read(); 082 while (EOF != ch) { 083 final var ch2 = input2.read(); 084 if (ch != ch2) { 085 return false; 086 } 087 ch = input1.read(); 088 } 089 final var ch2 = input2.read(); 090 return ch2 == EOF; 091 } 092 093 /** 094 * Compares the contents of two Readers to determine if they are equal or 095 * not. 096 * <p> 097 * This method buffers the input internally using 098 * <code>BufferedReader</code> if they are not already buffered. 099 * 100 * @param input1 the first reader 101 * @param input2 the second reader 102 * @return true if the content of the readers are equal or they both don't 103 * exist, false otherwise 104 * @throws NullPointerException if either input is null 105 * @throws IOException if an I/O error occurs 106 */ 107 public static boolean contentEquals(Reader input1, Reader input2) throws IOException { 108 if (input1 == input2) { 109 return true; 110 } 111 input1 = toBufferedReader(input1); 112 input2 = toBufferedReader(input2); 113 var ch = input1.read(); 114 while (EOF != ch) { 115 final var ch2 = input2.read(); 116 if (ch != ch2) { 117 return false; 118 } 119 ch = input1.read(); 120 } 121 final var ch2 = input2.read(); 122 return ch2 == EOF; 123 } 124 125 /** 126 * Wraps an {@link ByteArrayInputStream} around a given {@link String} assuming 127 * {@link StandardCharsets#UTF_8} 128 * 129 * @param input to be wrapped, may be null or empty 130 * @return the created {@link InputStream} 131 */ 132 public static InputStream toInputStream(String input) { 133 return new ByteArrayInputStream(nullToEmpty(input).getBytes(StandardCharsets.UTF_8)); 134 } 135 136 /** 137 * Gets the contents of an <code>InputStream</code> as a String 138 * using the specified character encoding. 139 * <p> 140 * This method buffers the input internally, so there is no need to use a 141 * <code>BufferedInputStream</code>. 142 * </p> 143 * 144 * @param input the <code>InputStream</code> to read from, using UTF-8 encoding. 145 * @return the requested String 146 * @throws NullPointerException if the input is null 147 * @throws IOException if an I/O error occurs 148 */ 149 public static String toString(final InputStream input) throws IOException { 150 return toString(input, StandardCharsets.UTF_8); 151 } 152 153 /** 154 * Gets the contents of an <code>InputStream</code> as a String 155 * using the specified character encoding. 156 * <p> 157 * This method buffers the input internally, so there is no need to use a 158 * <code>BufferedInputStream</code>. 159 * </p> 160 * 161 * @param input the <code>InputStream</code> to read from 162 * @param encoding the encoding to use, null means platform default 163 * @return the requested String 164 * @throws IllegalArgumentException if the input is null 165 * @throws IOException if an I/O error occurs 166 */ 167 public static String toString(final InputStream input, final Charset encoding) throws IOException { 168 Preconditions.checkArgument(null != input, "InputStream must not be null"); 169 try (var sw = new StringWriter()) { 170 copy(input, sw, encoding); 171 return sw.toString(); 172 } 173 } 174 175 /** 176 * Returns the given reader if it is a {@link BufferedReader}, otherwise creates a 177 * BufferedReader from the given reader. 178 * 179 * @param reader the reader to wrap or return (not null) 180 * @return the given reader or a new {@link BufferedReader} for the given reader 181 * @throws NullPointerException if the input parameter is null 182 */ 183 public static BufferedReader toBufferedReader(final Reader reader) { 184 return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); 185 } 186 187 // copy from InputStream 188 // ----------------------------------------------------------------------- 189 /** 190 * Copies bytes from an <code>InputStream</code> to an 191 * <code>OutputStream</code>. 192 * <p> 193 * This method buffers the input internally, so there is no need to use a 194 * <code>BufferedInputStream</code>. 195 * <p> 196 * Large streams (over 2GB) will return a bytes copied value of 197 * <code>-1</code> after the copy has completed since the correct 198 * number of bytes cannot be returned as an int. For large streams 199 * use the <code>copyLarge(InputStream, OutputStream)</code> method. 200 * 201 * @param input the <code>InputStream</code> to read from 202 * @param output the <code>OutputStream</code> to write to 203 * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE 204 * @throws NullPointerException if the input or output is null 205 * @throws IOException if an I/O error occurs 206 */ 207 public static int copy(final InputStream input, final OutputStream output) throws IOException { 208 final var count = copyLarge(input, output); 209 if (count > Integer.MAX_VALUE) { 210 return -1; 211 } 212 return (int) count; 213 } 214 215 /** 216 * Copies bytes from an <code>InputStream</code> to an <code>OutputStream</code> using an 217 * internal buffer of the 218 * given size. 219 * <p> 220 * This method buffers the input internally, so there is no need to use a 221 * <code>BufferedInputStream</code>. 222 * <p> 223 * 224 * @param input the <code>InputStream</code> to read from 225 * @param output the <code>OutputStream</code> to write to 226 * @param bufferSize the bufferSize used to copy from the input to the output 227 * @return the number of bytes copied 228 * @throws NullPointerException if the input or output is null 229 * @throws IOException if an I/O error occurs 230 */ 231 public static long copy(final InputStream input, final OutputStream output, final int bufferSize) throws IOException { 232 return copyLarge(input, output, new byte[bufferSize]); 233 } 234 235 /** 236 * Copies bytes from a large (over 2GB) <code>InputStream</code> to an 237 * <code>OutputStream</code>. 238 * <p> 239 * This method buffers the input internally, so there is no need to use a 240 * <code>BufferedInputStream</code>. 241 * <p> 242 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. 243 * 244 * @param input the <code>InputStream</code> to read from 245 * @param output the <code>OutputStream</code> to write to 246 * @return the number of bytes copied 247 * @throws NullPointerException if the input or output is null 248 * @throws IOException if an I/O error occurs 249 */ 250 public static long copyLarge(final InputStream input, final OutputStream output) throws IOException { 251 return copy(input, output, DEFAULT_BUFFER_SIZE); 252 } 253 254 /** 255 * Copies bytes from a large (over 2GB) <code>InputStream</code> to an 256 * <code>OutputStream</code>. 257 * <p> 258 * This method uses the provided buffer, so there is no need to use a 259 * <code>BufferedInputStream</code>. 260 * <p> 261 * 262 * @param input the <code>InputStream</code> to read from 263 * @param output the <code>OutputStream</code> to write to 264 * @param buffer the buffer to use for the copy 265 * @return the number of bytes copied 266 * @throws NullPointerException if the input or output is null 267 * @throws IOException if an I/O error occurs 268 */ 269 public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer) throws IOException { 270 var count = 0L; 271 int n; 272 while (EOF != (n = input.read(buffer))) { 273 output.write(buffer, 0, n); 274 count += n; 275 } 276 return count; 277 } 278 279 /** 280 * Copies some or all bytes from a large (over 2GB) <code>InputStream</code> to an 281 * <code>OutputStream</code>, optionally skipping input bytes. 282 * <p> 283 * This method buffers the input internally, so there is no need to use a 284 * <code>BufferedInputStream</code>. 285 * </p> 286 * <p> 287 * Note that the implementation uses {@link #skip(InputStream, long)}. 288 * This means that the method may be considerably less efficient than using the actual skip 289 * implementation, 290 * this is done to guarantee that the correct number of characters are skipped. 291 * </p> 292 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. 293 * 294 * @param input the <code>InputStream</code> to read from 295 * @param output the <code>OutputStream</code> to write to 296 * @param inputOffset number of bytes to skip from input before copying 297 * -ve values are ignored 298 * @param length number of bytes to copy. -ve means all 299 * @return the number of bytes copied 300 * @throws NullPointerException if the input or output is null 301 * @throws IOException if an I/O error occurs 302 */ 303 public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length) throws IOException { 304 return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); 305 } 306 307 /** 308 * Copies some or all bytes from a large (over 2GB) <code>InputStream</code> to an 309 * <code>OutputStream</code>, optionally skipping input bytes. 310 * <p> 311 * This method uses the provided buffer, so there is no need to use a 312 * <code>BufferedInputStream</code>. 313 * </p> 314 * <p> 315 * Note that the implementation uses {@link #skip(InputStream, long)}. 316 * This means that the method may be considerably less efficient than using the actual skip 317 * implementation, 318 * this is done to guarantee that the correct number of characters are skipped. 319 * </p> 320 * 321 * @param input the <code>InputStream</code> to read from 322 * @param output the <code>OutputStream</code> to write to 323 * @param inputOffset number of bytes to skip from input before copying 324 * -ve values are ignored 325 * @param length number of bytes to copy. -ve means all 326 * @param buffer the buffer to use for the copy 327 * @return the number of bytes copied 328 * @throws NullPointerException if the input or output is null 329 * @throws IOException if an I/O error occurs 330 */ 331 public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset, final long length, final byte[] buffer) throws IOException { 332 if (inputOffset > 0) { 333 skipFully(input, inputOffset); 334 } 335 if (length == 0) { 336 return 0; 337 } 338 final var bufferLength = buffer.length; 339 var bytesToRead = bufferLength; 340 if (length > 0 && length < bufferLength) { 341 bytesToRead = (int) length; 342 } 343 int read; 344 var totalRead = 0L; 345 while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { 346 output.write(buffer, 0, read); 347 totalRead += read; 348 if (length > 0) { 349 // only adjust length if not reading to the end 350 // Note the cast must work because buffer.length is an integer 351 bytesToRead = (int) Math.min(length - totalRead, bufferLength); 352 } 353 } 354 return totalRead; 355 } 356 357 /** 358 * Copies bytes from an <code>InputStream</code> to chars on a 359 * <code>Writer</code> using the specified character encoding. 360 * <p> 361 * This method buffers the input internally, so there is no need to use a 362 * <code>BufferedInputStream</code>. 363 * <p> 364 * This method uses {@link InputStreamReader}. 365 * 366 * @param input the <code>InputStream</code> to read from 367 * @param output the <code>Writer</code> to write to 368 * @param inputEncoding the encoding to use for the input stream, null means platform default 369 * @throws NullPointerException if the input or output is null 370 * @throws IOException if an I/O error occurs 371 */ 372 public static void copy(final InputStream input, final Writer output, final Charset inputEncoding) throws IOException { 373 final var in = new InputStreamReader(input, toCharset(inputEncoding)); 374 copy(in, output); 375 } 376 377 /** 378 * Returns the given Charset or the default Charset if the given Charset is null. 379 * 380 * @param charset 381 * A charset or null. 382 * @return the given Charset or the default Charset if the given Charset is null 383 */ 384 public static Charset toCharset(final Charset charset) { 385 return charset == null ? Charset.defaultCharset() : charset; 386 } 387 388 /** 389 * Copies bytes from an <code>InputStream</code> to chars on a 390 * <code>Writer</code> using the specified character encoding. 391 * <p> 392 * This method buffers the input internally, so there is no need to use a 393 * <code>BufferedInputStream</code>. 394 * <p> 395 * Character encoding names can be found at 396 * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. 397 * <p> 398 * This method uses {@link InputStreamReader}. 399 * 400 * @param input the <code>InputStream</code> to read from 401 * @param output the <code>Writer</code> to write to 402 * @param inputEncoding the encoding to use for the InputStream, null means platform default 403 * @throws NullPointerException if the input or output is null 404 * @throws IOException if an I/O error occurs 405 * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io 406 * .UnsupportedEncodingException} in version 2.2 if the 407 * encoding is not supported. 408 */ 409 public static void copy(final InputStream input, final Writer output, final String inputEncoding) throws IOException { 410 copy(input, output, toCharset(inputEncoding)); 411 } 412 413 /** 414 * Returns a Charset for the named charset. If the name is null, return the default Charset. 415 * 416 * @param charset 417 * The name of the requested charset, may be null. 418 * @return a Charset for the named charset 419 * @throws java.nio.charset.UnsupportedCharsetException 420 * If the named charset is unavailable 421 */ 422 public static Charset toCharset(final String charset) { 423 return charset == null ? Charset.defaultCharset() : Charset.forName(charset); 424 } 425 426 // copy from Reader 427 // ----------------------------------------------------------------------- 428 /** 429 * Copies chars from a <code>Reader</code> to a <code>Writer</code>. 430 * <p> 431 * This method buffers the input internally, so there is no need to use a 432 * <code>BufferedReader</code>. 433 * <p> 434 * Large streams (over 2GB) will return a chars copied value of 435 * <code>-1</code> after the copy has completed since the correct 436 * number of chars cannot be returned as an int. For large streams 437 * use the <code>copyLarge(Reader, Writer)</code> method. 438 * 439 * @param input the <code>Reader</code> to read from 440 * @param output the <code>Writer</code> to write to 441 * @return the number of characters copied, or -1 if > Integer.MAX_VALUE 442 * @throws NullPointerException if the input or output is null 443 * @throws IOException if an I/O error occurs 444 */ 445 public static int copy(final Reader input, final Writer output) throws IOException { 446 final var count = copyLarge(input, output); 447 if (count > Integer.MAX_VALUE) { 448 return -1; 449 } 450 return (int) count; 451 } 452 453 /** 454 * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>. 455 * <p> 456 * This method buffers the input internally, so there is no need to use a 457 * <code>BufferedReader</code>. 458 * <p> 459 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. 460 * 461 * @param input the <code>Reader</code> to read from 462 * @param output the <code>Writer</code> to write to 463 * @return the number of characters copied 464 * @throws NullPointerException if the input or output is null 465 * @throws IOException if an I/O error occurs 466 */ 467 public static long copyLarge(final Reader input, final Writer output) throws IOException { 468 return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); 469 } 470 471 /** 472 * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>. 473 * <p> 474 * This method uses the provided buffer, so there is no need to use a 475 * <code>BufferedReader</code>. 476 * <p> 477 * 478 * @param input the <code>Reader</code> to read from 479 * @param output the <code>Writer</code> to write to 480 * @param buffer the buffer to be used for the copy 481 * @return the number of characters copied 482 * @throws NullPointerException if the input or output is null 483 * @throws IOException if an I/O error occurs 484 */ 485 public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException { 486 var count = 0L; 487 int n; 488 while (EOF != (n = input.read(buffer))) { 489 output.write(buffer, 0, n); 490 count += n; 491 } 492 return count; 493 } 494 495 /** 496 * Copies some or all chars from a large (over 2GB) <code>InputStream</code> to an 497 * <code>OutputStream</code>, optionally skipping input chars. 498 * <p> 499 * This method buffers the input internally, so there is no need to use a 500 * <code>BufferedReader</code>. 501 * <p> 502 * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. 503 * 504 * @param input the <code>Reader</code> to read from 505 * @param output the <code>Writer</code> to write to 506 * @param inputOffset number of chars to skip from input before copying 507 * -ve values are ignored 508 * @param length number of chars to copy. -ve means all 509 * @return the number of chars copied 510 * @throws NullPointerException if the input or output is null 511 * @throws IOException if an I/O error occurs 512 */ 513 public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length) throws IOException { 514 return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); 515 } 516 517 /** 518 * Copies some or all chars from a large (over 2GB) <code>InputStream</code> to an 519 * <code>OutputStream</code>, optionally skipping input chars. 520 * <p> 521 * This method uses the provided buffer, so there is no need to use a 522 * <code>BufferedReader</code>. 523 * <p> 524 * 525 * @param input the <code>Reader</code> to read from 526 * @param output the <code>Writer</code> to write to 527 * @param inputOffset number of chars to skip from input before copying 528 * -ve values are ignored 529 * @param length number of chars to copy. -ve means all 530 * @param buffer the buffer to be used for the copy 531 * @return the number of chars copied 532 * @throws NullPointerException if the input or output is null 533 * @throws IOException if an I/O error occurs 534 */ 535 public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length, final char[] buffer) throws IOException { 536 if (inputOffset > 0) { 537 skipFully(input, inputOffset); 538 } 539 if (length == 0) { 540 return 0; 541 } 542 var bytesToRead = buffer.length; 543 if (length > 0 && length < buffer.length) { 544 bytesToRead = (int) length; 545 } 546 int read; 547 var totalRead = 0L; 548 while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { 549 output.write(buffer, 0, read); 550 totalRead += read; 551 if (length > 0) { 552 // only adjust length if not reading to the end 553 // Note the cast must work because buffer.length is an integer 554 bytesToRead = (int) Math.min(length - totalRead, buffer.length); 555 } 556 } 557 return totalRead; 558 } 559 560 /** 561 * Copies chars from a <code>Reader</code> to bytes on an 562 * <code>OutputStream</code> using the specified character encoding, and 563 * calling flush. 564 * <p> 565 * This method buffers the input internally, so there is no need to use a 566 * <code>BufferedReader</code>. 567 * </p> 568 * <p> 569 * Due to the implementation of OutputStreamWriter, this method performs a 570 * flush. 571 * </p> 572 * <p> 573 * This method uses {@link OutputStreamWriter}. 574 * </p> 575 * 576 * @param input the <code>Reader</code> to read from 577 * @param output the <code>OutputStream</code> to write to 578 * @param outputEncoding the encoding to use for the OutputStream, null means platform default 579 * @throws NullPointerException if the input or output is null 580 * @throws IOException if an I/O error occurs 581 */ 582 public static void copy(final Reader input, final OutputStream output, final Charset outputEncoding) throws IOException { 583 final var out = new OutputStreamWriter(output, toCharset(outputEncoding)); 584 copy(input, out); 585 // Unless anyone is planning on rewriting OutputStreamWriter, 586 // we have to flush here. 587 out.flush(); 588 } 589 590 /** 591 * Copies chars from a <code>Reader</code> to bytes on an 592 * <code>OutputStream</code> using the specified character encoding, and 593 * calling flush. 594 * <p> 595 * This method buffers the input internally, so there is no need to use a 596 * <code>BufferedReader</code>. 597 * <p> 598 * Character encoding names can be found at 599 * <a href="http://www.iana.org/assignments/character-sets">IANA</a>. 600 * <p> 601 * Due to the implementation of OutputStreamWriter, this method performs a 602 * flush. 603 * <p> 604 * This method uses {@link OutputStreamWriter}. 605 * 606 * @param input the <code>Reader</code> to read from 607 * @param output the <code>OutputStream</code> to write to 608 * @param outputEncoding the encoding to use for the OutputStream, null means platform default 609 * @throws NullPointerException if the input or output is null 610 * @throws IOException if an I/O error occurs 611 * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io 612 * .UnsupportedEncodingException} in version 2.2 if the 613 * encoding is not supported. 614 */ 615 public static void copy(final Reader input, final OutputStream output, final String outputEncoding) throws IOException { 616 copy(input, output, toCharset(outputEncoding)); 617 } 618 619 /** 620 * Skips bytes from an input byte stream. 621 * This implementation guarantees that it will read as many bytes 622 * as possible before giving up; this may not always be the case for 623 * skip() implementations in subclasses of {@link InputStream}. 624 * <p> 625 * Note that the implementation uses {@link InputStream#read(byte[], int, int)} rather 626 * than delegating to {@link InputStream#skip(long)}. 627 * This means that the method may be considerably less efficient than using the actual skip 628 * implementation, 629 * this is done to guarantee that the correct number of bytes are skipped. 630 * </p> 631 * 632 * @param input byte stream to skip 633 * @param toSkip number of bytes to skip. 634 * @return number of bytes actually skipped. 635 * @throws IOException if there is a problem reading the file 636 * @throws IllegalArgumentException if toSkip is negative 637 * @see InputStream#skip(long) 638 * @see <a href="https://issues.apache.org/jira/browse/IO-203">IO-203 - Add skipFully() method 639 * for InputStreams</a> 640 */ 641 public static long skip(final InputStream input, final long toSkip) throws IOException { 642 if (toSkip < 0) { 643 throw new IllegalArgumentException(SKIP_COUNT_MUST_BE_NON_NEGATIVE_ACTUAL + toSkip); 644 } 645 /* 646 * N.B. no need to synchronize this because: - we don't care if the buffer is created 647 * multiple times (the data 648 * is ignored) - we always use the same size buffer, so if it is recreated it will still 649 * be OK (if the buffer 650 * size were variable, we would need to synch. to ensure some other thread did not create a 651 * smaller one) 652 */ 653 if (skipByteBuffer == null) { 654 skipByteBuffer = new byte[SKIP_BUFFER_SIZE]; 655 } 656 var remain = toSkip; 657 while (remain > 0) { 658 // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than 659 // delegating to skip() 660 final long n = input.read(skipByteBuffer, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); 661 if (n < 0) { 662 // EOF 663 break; 664 } 665 remain -= n; 666 } 667 return toSkip - remain; 668 } 669 670 /** 671 * Skips characters from an input character stream. 672 * This implementation guarantees that it will read as many characters 673 * as possible before giving up; this may not always be the case for 674 * skip() implementations in subclasses of {@link Reader}. 675 * <p> 676 * Note that the implementation uses {@link Reader#read(char[], int, int)} rather 677 * than delegating to {@link Reader#skip(long)}. 678 * This means that the method may be considerably less efficient than using the actual skip 679 * implementation, 680 * this is done to guarantee that the correct number of characters are skipped. 681 * </p> 682 * 683 * @param input character stream to skip 684 * @param toSkip number of characters to skip. 685 * @return number of characters actually skipped. 686 * @throws IOException if there is a problem reading the file 687 * @throws IllegalArgumentException if toSkip is negative 688 * @see Reader#skip(long) 689 * @see <a href="https://issues.apache.org/jira/browse/IO-203">IO-203 - Add skipFully() method 690 * for InputStreams</a> 691 */ 692 public static long skip(final Reader input, final long toSkip) throws IOException { 693 if (toSkip < 0) { 694 throw new IllegalArgumentException(SKIP_COUNT_MUST_BE_NON_NEGATIVE_ACTUAL + toSkip); 695 } 696 /* 697 * N.B. no need to synchronize this because: - we don't care if the buffer is created 698 * multiple times (the data 699 * is ignored) - we always use the same size buffer, so if it is recreated it will still 700 * be OK (if the buffer 701 * size were variable, we would need to sync. to ensure some other thread did not create a 702 * smaller one) 703 */ 704 if (skipCharBuffer == null) { 705 skipCharBuffer = new char[SKIP_BUFFER_SIZE]; 706 } 707 var remain = toSkip; 708 while (remain > 0) { 709 // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than 710 // delegating to skip() 711 final long n = input.read(skipCharBuffer, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); 712 if (n < 0) { 713 // EOF 714 break; 715 } 716 remain -= n; 717 } 718 return toSkip - remain; 719 } 720 721 /** 722 * Skips the requested number of bytes or fail if there are not enough left. 723 * <p> 724 * This allows for the possibility that {@link InputStream#skip(long)} may 725 * not skip as many bytes as requested (most likely because of reaching EOF). 726 * <p> 727 * Note that the implementation uses {@link #skip(InputStream, long)}. 728 * This means that the method may be considerably less efficient than using the actual skip 729 * implementation, 730 * this is done to guarantee that the correct number of characters are skipped. 731 * </p> 732 * 733 * @param input stream to skip 734 * @param toSkip the number of bytes to skip 735 * @throws IOException if there is a problem reading the file 736 * @throws IllegalArgumentException if toSkip is negative 737 * @throws EOFException if the number of bytes skipped was incorrect 738 * @see InputStream#skip(long) 739 */ 740 public static void skipFully(final InputStream input, final long toSkip) throws IOException { 741 if (toSkip < 0) { 742 throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); 743 } 744 final var skipped = skip(input, toSkip); 745 if (skipped != toSkip) { 746 throw new EOFException("Bytes to skip: " + toSkip + ACTUAL + skipped); 747 } 748 } 749 750 /** 751 * Skips the requested number of characters or fail if there are not enough left. 752 * <p> 753 * This allows for the possibility that {@link Reader#skip(long)} may 754 * not skip as many characters as requested (most likely because of reaching EOF). 755 * <p> 756 * Note that the implementation uses {@link #skip(Reader, long)}. 757 * This means that the method may be considerably less efficient than using the actual skip 758 * implementation, 759 * this is done to guarantee that the correct number of characters are skipped. 760 * </p> 761 * 762 * @param input stream to skip 763 * @param toSkip the number of characters to skip 764 * @throws IOException if there is a problem reading the file 765 * @throws IllegalArgumentException if toSkip is negative 766 * @throws EOFException if the number of characters skipped was incorrect 767 * @see Reader#skip(long) 768 */ 769 public static void skipFully(final Reader input, final long toSkip) throws IOException { 770 final var skipped = skip(input, toSkip); 771 if (skipped != toSkip) { 772 throw new EOFException("Chars to skip: " + toSkip + ACTUAL + skipped); 773 } 774 } 775 776 // InputStream to byte array 777 /** 778 * Gets the contents of an <code>InputStream</code> as a <code>byte[]</code>. 779 * <p> 780 * This method buffers the input internally, so there is no need to use a 781 * <code>BufferedInputStream</code>. 782 * 783 * @param input the <code>InputStream</code> to read from 784 * @return the requested byte array 785 * @throws NullPointerException if the input is null 786 * @throws IOException if an I/O error occurs 787 */ 788 public static byte[] toByteArray(final InputStream input) throws IOException { 789 try (var output = new ByteArrayOutputStream()) { 790 copy(input, output); 791 return output.toByteArray(); 792 } 793 } 794 795 @java.lang.SuppressWarnings("all") 796 @lombok.Generated 797 private IOStreams() { 798 throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated"); 799 } 800}