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 &gt; 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 &gt; 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}