diff --git a/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java
index 11d716b68..422ad75b3 100644
--- a/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java
+++ b/epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java
@@ -102,7 +102,7 @@ public class EpubWriter {
try {
resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref()));
InputStream inputStream = resource.getInputStream();
- IOUtil.copy(inputStream, resultStream);
+ IOUtil.copy(inputStream, resultStream,IOUtil.DEFAULT_BUFFER_SIZE);
inputStream.close();
} catch (Exception e) {
Log.e(TAG,e.getMessage(), e);
diff --git a/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java b/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java
index e75d91f70..1c605846c 100644
--- a/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java
+++ b/epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java
@@ -1,12 +1,24 @@
package me.ag2s.epublib.util;
import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.charset.Charset;
+
+import me.ag2s.epublib.util.commons.io.IOConsumer;
/**
* Most of the functions herein are re-implementations of the ones in
@@ -23,8 +35,22 @@ public class IOUtil {
*/
public static final int EOF = -1;
- public static final int IO_COPY_BUFFER_SIZE = 1024 * 8;
- public static final int DEFAULT_BUFFER_SIZE = 8192;
+
+ public static final int DEFAULT_BUFFER_SIZE = 1024*8;
+ private static final byte[] SKIP_BYTE_BUFFER = new byte[DEFAULT_BUFFER_SIZE];
+
+ // Allocated in the relevant skip method if necessary.
+ /*
+ * These buffers are static and are shared between threads.
+ * This is possible because the buffers are write-only - the contents are never read.
+ *
+ * N.B. there is no need to synchronize when creating these because:
+ * - we don't care if the buffer is created multiple times (the data is ignored)
+ * - we always use the same size buffer, so if it it is recreated it will still be OK
+ * (if the buffer size were variable, we would need to synch. to ensure some other thread
+ * did not create a smaller one)
+ */
+ private static char[] SKIP_CHAR_BUFFER;
/**
* Gets the contents of the Reader as a byte[], with the given character encoding.
@@ -51,7 +77,7 @@ public class IOUtil {
*/
public static byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
- copy(in, result);
+ copy(in, result,DEFAULT_BUFFER_SIZE);
result.flush();
return result.toByteArray();
}
@@ -79,7 +105,7 @@ public class IOUtil {
result = new ByteArrayOutputStream();
}
- copy(in, result);
+ copy(in, result,DEFAULT_BUFFER_SIZE);
result.flush();
return result.toByteArray();
} catch (OutOfMemoryError error) {
@@ -112,45 +138,695 @@ public class IOUtil {
}
}
+//
/**
- * Copies the contents of the InputStream to the OutputStream.
+ * Copies bytes from an InputStream
to an OutputStream
using an internal buffer of the
+ * given size.
+ *
+ * This method buffers the input internally, so there is no need to use a BufferedInputStream
.
+ *
InputStream
to read from
+ * @param output the OutputStream
to write to
+ * @param bufferSize the bufferSize used to copy from the input to the output
+ * @return the number of bytes copied. or {@code 0} if {@code input is null}.
+ * @throws NullPointerException if the output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.5
+ */
+ public static long copy(final InputStream input, final OutputStream output, final int bufferSize)
+ throws IOException {
+ return copyLarge(input, output, new byte[bufferSize]);
+ }
+
+ /**
+ * Copies bytes from an InputStream
to chars on a
+ * Writer
using the default character encoding of the platform.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * This method uses {@link InputStreamReader}.
+ *
+ * @param input the InputStream
to read from
+ * @param output the Writer
to write to
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.1
+ * @deprecated 2.5 use {@link #copy(InputStream, Writer, Charset)} instead
+ */
+ @Deprecated
+ public static void copy(final InputStream input, final Writer output)
+ throws IOException {
+ copy(input, output, Charset.defaultCharset());
+ }
+
+ /**
+ * Copies bytes from an InputStream
to chars on a
+ * Writer
using the specified character encoding.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * This method uses {@link InputStreamReader}.
+ *
+ * @param input the InputStream
to read from
+ * @param output the Writer
to write to
+ * @param inputCharset the charset to use for the input stream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static void copy(final InputStream input, final Writer output, final Charset inputCharset)
+ throws IOException {
+ final InputStreamReader in = new InputStreamReader(input, inputCharset.name());
+ copy(in, output);
+ }
+
+ /**
+ * Copies bytes from an InputStream
to chars on a
+ * Writer
using the specified character encoding.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * Character encoding names can be found at + * IANA. + *
+ * This method uses {@link InputStreamReader}.
+ *
+ * @param input the InputStream
to read from
+ * @param output the Writer
to write to
+ * @param inputCharsetName the name of the requested charset for the InputStream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
+ * .UnsupportedEncodingException} in version 2.2 if the
+ * encoding is not supported.
+ * @since 1.1
*/
- public static int copy(InputStream in, OutputStream out)
+ public static void copy(final InputStream input, final Writer output, final String inputCharsetName)
throws IOException {
- byte[] buffer = new byte[IO_COPY_BUFFER_SIZE];
- int readSize;
- int result = 0;
- while ((readSize = in.read(buffer)) >= 0) {
- out.write(buffer, 0, readSize);
- result = calcNewNrReadSize(readSize, result);
+ copy(input, output,Charset.forName(inputCharsetName));
+ }
+
+ /**
+ * Copies chars from a Reader
to a Appendable
.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * Large streams (over 2GB) will return a chars copied value of
+ * -1
after the copy has completed since the correct
+ * number of chars cannot be returned as an int. For large streams
+ * use the copyLarge(Reader, Writer)
method.
+ *
+ * @param input the Reader
to read from
+ * @param output the Appendable
to write to
+ * @return the number of characters copied, or -1 if > Integer.MAX_VALUE
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.7
+ */
+ public static long copy(final Reader input, final Appendable output) throws IOException {
+ return copy(input, output, CharBuffer.allocate(DEFAULT_BUFFER_SIZE));
+ }
+
+ /**
+ * Copies chars from a Reader
to an Appendable
.
+ *
+ * This method uses the provided buffer, so there is no need to use a
+ * BufferedReader
.
+ *
Reader
to read from
+ * @param output the Appendable
to write to
+ * @param buffer the buffer to be used for the copy
+ * @return the number of characters copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.7
+ */
+ public static long copy(final Reader input, final Appendable output, final CharBuffer buffer) throws IOException {
+ long count = 0;
+ int n;
+ while (EOF != (n = input.read(buffer))) {
+ buffer.flip();
+ output.append(buffer, 0, n);
+ count += n;
}
+ return count;
+ }
+
+ /**
+ * Copies chars from a Reader
to bytes on an
+ * OutputStream
using the default character encoding of the
+ * platform, and calling flush.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *
+ * This method uses {@link OutputStreamWriter}.
+ *
+ * @param input the Reader
to read from
+ * @param output the OutputStream
to write to
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.1
+ * @deprecated 2.5 use {@link #copy(Reader, OutputStream, Charset)} instead
+ */
+ @Deprecated
+ public static void copy(final Reader input, final OutputStream output)
+ throws IOException {
+ copy(input, output, Charset.defaultCharset());
+ }
+
+ /**
+ * Copies chars from a Reader
to bytes on an
+ * OutputStream
using the specified character encoding, and
+ * calling flush.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *
+ *+ * This method uses {@link OutputStreamWriter}. + *
+ * + * @param input theReader
to read from
+ * @param output the OutputStream
to write to
+ * @param outputCharset the charset to use for the OutputStream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.3
+ */
+ public static void copy(final Reader input, final OutputStream output, final Charset outputCharset)
+ throws IOException {
+ final OutputStreamWriter out = new OutputStreamWriter(output, outputCharset.name());
+ copy(input, out);
+ // XXX Unless anyone is planning on rewriting OutputStreamWriter,
+ // we have to flush here.
out.flush();
- return result;
}
/**
- * Copies the contents of the Reader to the Writer.
+ * Copies chars from a Reader
to bytes on an
+ * OutputStream
using the specified character encoding, and
+ * calling flush.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * Character encoding names can be found at + * IANA. + *
+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *
+ * This method uses {@link OutputStreamWriter}.
*
- * @param in f
- * @param out f
- * @return the nr of characters read, or -1 if the amount > Integer.MAX_VALUE
- * @throws IOException f
+ * @param input the Reader
to read from
+ * @param output the OutputStream
to write to
+ * @param outputCharsetName the name of the requested charset for the OutputStream, null means platform default
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io
+ * .UnsupportedEncodingException} in version 2.2 if the
+ * encoding is not supported.
+ * @since 1.1
*/
- public static int copy(Reader in, Writer out) throws IOException {
- char[] buffer = new char[IO_COPY_BUFFER_SIZE];
- int readSize;
- int result = 0;
- while ((readSize = in.read(buffer)) >= 0) {
- out.write(buffer, 0, readSize);
- result = calcNewNrReadSize(readSize, result);
+ public static void copy(final Reader input, final OutputStream output, final String outputCharsetName)
+ throws IOException {
+ copy(input, output, Charset.forName(outputCharsetName));
+ }
+
+ /**
+ * Copies chars from a Reader
to a Writer
.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * Large streams (over 2GB) will return a chars copied value of
+ * -1
after the copy has completed since the correct
+ * number of chars cannot be returned as an int. For large streams
+ * use the copyLarge(Reader, Writer)
method.
+ *
+ * @param input the Reader
to read from
+ * @param output the Writer
to write to
+ * @return the number of characters copied, or -1 if > Integer.MAX_VALUE
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.1
+ */
+ public static int copy(final Reader input, final Writer output) throws IOException {
+ final long count = copyLarge(input, output);
+ if (count > Integer.MAX_VALUE) {
+ return -1;
+ }
+ return (int) count;
+ }
+
+ /**
+ * Copies bytes from a large (over 2GB) InputStream
to an
+ * OutputStream
.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + *
+ * + * @param input theInputStream
to read from
+ * @param output the OutputStream
to write to
+ * @return the number of bytes copied. or {@code 0} if {@code input is null}.
+ * @throws NullPointerException if the output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.3
+ */
+ public static long copyLarge(final InputStream input, final OutputStream output)
+ throws IOException {
+ return copy(input, output, DEFAULT_BUFFER_SIZE);
+ }
+
+ /**
+ * Copies bytes from a large (over 2GB) InputStream
to an
+ * OutputStream
.
+ *
+ * This method uses the provided buffer, so there is no need to use a
+ * BufferedInputStream
.
+ *
InputStream
to read from
+ * @param output the OutputStream
to write to
+ * @param buffer the buffer to use for the copy
+ * @return the number of bytes copied. or {@code 0} if {@code input is null}.
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)
+ throws IOException {
+ long count = 0;
+ if (input != null) {
+ int n;
+ while (EOF != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * Copies some or all bytes from a large (over 2GB) InputStream
to an
+ * OutputStream
, optionally skipping input bytes.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * Note that the implementation uses {@link #skip(InputStream, long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of characters are skipped. + *
+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input theInputStream
to read from
+ * @param output the OutputStream
to write to
+ * @param inputOffset : number of bytes to skip from input before copying
+ * -ve values are ignored
+ * @param length : number of bytes to copy. -ve means all
+ * @return the number of bytes copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset,
+ final long length) throws IOException {
+ return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]);
+ }
+
+ /**
+ * Copies some or all bytes from a large (over 2GB) InputStream
to an
+ * OutputStream
, optionally skipping input bytes.
+ *
+ * This method uses the provided buffer, so there is no need to use a
+ * BufferedInputStream
.
+ *
+ * Note that the implementation uses {@link #skip(InputStream, long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of characters are skipped. + *
+ * + * @param input theInputStream
to read from
+ * @param output the OutputStream
to write to
+ * @param inputOffset : number of bytes to skip from input before copying
+ * -ve values are ignored
+ * @param length : number of bytes to copy. -ve means all
+ * @param buffer the buffer to use for the copy
+ * @return the number of bytes copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final InputStream input, final OutputStream output,
+ final long inputOffset, final long length, final byte[] buffer) throws IOException {
+ if (inputOffset > 0) {
+ skipFully(input, inputOffset);
+ }
+ if (length == 0) {
+ return 0;
+ }
+ final int bufferLength = buffer.length;
+ int bytesToRead = bufferLength;
+ if (length > 0 && length < bufferLength) {
+ bytesToRead = (int) length;
+ }
+ int read;
+ long totalRead = 0;
+ while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {
+ output.write(buffer, 0, read);
+ totalRead += read;
+ if (length > 0) { // only adjust length if not reading to the end
+ // Note the cast must work because buffer.length is an integer
+ bytesToRead = (int) Math.min(length - totalRead, bufferLength);
+ }
+ }
+ return totalRead;
+ }
+
+ /**
+ * Copies chars from a large (over 2GB) Reader
to a Writer
.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @param input the Reader
to read from
+ * @param output the Writer
to write to
+ * @return the number of characters copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.3
+ */
+ public static long copyLarge(final Reader input, final Writer output) throws IOException {
+ return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);
+ }
+
+ /**
+ * Copies chars from a large (over 2GB) Reader
to a Writer
.
+ *
+ * This method uses the provided buffer, so there is no need to use a
+ * BufferedReader
.
+ *
+ *
+ * @param input the Reader
to read from
+ * @param output the Writer
to write to
+ * @param buffer the buffer to be used for the copy
+ * @return the number of characters copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException {
+ long count = 0;
+ int n;
+ while (EOF != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ count += n;
+ }
+ return count;
+ }
+
+ /**
+ * Copies some or all chars from a large (over 2GB) InputStream
to an
+ * OutputStream
, optionally skipping input chars.
+ *
+ * This method buffers the input internally, so there is no need to use a
+ * BufferedReader
.
+ *
+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
+ *
+ * @param input the Reader
to read from
+ * @param output the Writer
to write to
+ * @param inputOffset : number of chars to skip from input before copying
+ * -ve values are ignored
+ * @param length : number of chars to copy. -ve means all
+ * @return the number of chars copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length)
+ throws IOException {
+ return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]);
+ }
+
+ /**
+ * Copies some or all chars from a large (over 2GB) InputStream
to an
+ * OutputStream
, optionally skipping input chars.
+ *
+ * This method uses the provided buffer, so there is no need to use a
+ * BufferedReader
.
+ *
+ *
+ * @param input the Reader
to read from
+ * @param output the Writer
to write to
+ * @param inputOffset : number of chars to skip from input before copying
+ * -ve values are ignored
+ * @param length : number of chars to copy. -ve means all
+ * @param buffer the buffer to be used for the copy
+ * @return the number of chars copied
+ * @throws NullPointerException if the input or output is null
+ * @throws IOException if an I/O error occurs
+ * @since 2.2
+ */
+ public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length,
+ final char[] buffer)
+ throws IOException {
+ if (inputOffset > 0) {
+ skipFully(input, inputOffset);
+ }
+ if (length == 0) {
+ return 0;
+ }
+ int bytesToRead = buffer.length;
+ if (length > 0 && length < buffer.length) {
+ bytesToRead = (int) length;
+ }
+ int read;
+ long totalRead = 0;
+ while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {
+ output.write(buffer, 0, read);
+ totalRead += read;
+ if (length > 0) { // only adjust length if not reading to the end
+ // Note the cast must work because buffer.length is an integer
+ bytesToRead = (int) Math.min(length - totalRead, buffer.length);
+ }
+ }
+ return totalRead;
+ }
+
+ /**
+ * Skips bytes from an input byte stream.
+ * This implementation guarantees that it will read as many bytes
+ * as possible before giving up; this may not always be the case for
+ * skip() implementations in subclasses of {@link InputStream}.
+ *
+ * Note that the implementation uses {@link InputStream#read(byte[], int, int)} rather + * than delegating to {@link InputStream#skip(long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of bytes are skipped. + *
+ * + * @param input byte stream to skip + * @param toSkip number of bytes to skip. + * @return number of bytes actually skipped. + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see InputStream#skip(long) + * @see IO-203 - Add skipFully() method for InputStreams + * @since 2.0 + */ + public static long skip(final InputStream input, final long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize access to SKIP_BYTE_BUFFER: - we don't care if the buffer is created multiple + * times (the data is ignored) - we always use the same size buffer, so if it it is recreated it will still be + * OK (if the buffer size were variable, we would need to synch. to ensure some other thread did not create a + * smaller one) + */ + long remain = toSkip; + while (remain > 0) { + // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip() + final long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BYTE_BUFFER.length)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skips bytes from a ReadableByteChannel. + * This implementation guarantees that it will read as many bytes + * as possible before giving up. + * + * @param input ReadableByteChannel to skip + * @param toSkip number of bytes to skip. + * @return number of bytes actually skipped. + * @throws IOException if there is a problem reading the ReadableByteChannel + * @throws IllegalArgumentException if toSkip is negative + * @since 2.5 + */ + public static long skip(final ReadableByteChannel input, final long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + final ByteBuffer skipByteBuffer = ByteBuffer.allocate((int) Math.min(toSkip, SKIP_BYTE_BUFFER.length)); + long remain = toSkip; + while (remain > 0) { + skipByteBuffer.position(0); + skipByteBuffer.limit((int) Math.min(remain, SKIP_BYTE_BUFFER.length)); + final int n = input.read(skipByteBuffer); + if (n == EOF) { + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skips characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * skip() implementations in subclasses of {@link Reader}. + *+ * Note that the implementation uses {@link Reader#read(char[], int, int)} rather + * than delegating to {@link Reader#skip(long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of characters are skipped. + *
+ * + * @param input character stream to skip + * @param toSkip number of characters to skip. + * @return number of characters actually skipped. + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see Reader#skip(long) + * @see IO-203 - Add skipFully() method for InputStreams + * @since 2.0 + */ + public static long skip(final Reader input, final long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_CHAR_BUFFER == null) { + SKIP_CHAR_BUFFER = new char[SKIP_BYTE_BUFFER.length]; + } + long remain = toSkip; + while (remain > 0) { + // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip() + final long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BYTE_BUFFER.length)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skips the requested number of bytes or fail if there are not enough left. + *+ * This allows for the possibility that {@link InputStream#skip(long)} may + * not skip as many bytes as requested (most likely because of reaching EOF). + *
+ * Note that the implementation uses {@link #skip(InputStream, long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of characters are skipped. + *
+ * + * @param input stream to skip + * @param toSkip the number of bytes to skip + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws EOFException if the number of bytes skipped was incorrect + * @see InputStream#skip(long) + * @since 2.0 + */ + public static void skipFully(final InputStream input, final long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); + } + final long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); + } + } + + /** + * Skips the requested number of bytes or fail if there are not enough left. + * + * @param input ReadableByteChannel to skip + * @param toSkip the number of bytes to skip + * @throws IOException if there is a problem reading the ReadableByteChannel + * @throws IllegalArgumentException if toSkip is negative + * @throws EOFException if the number of bytes skipped was incorrect + * @since 2.5 + */ + public static void skipFully(final ReadableByteChannel input, final long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); + } + final long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); + } + } + + /** + * Skips the requested number of characters or fail if there are not enough left. + *+ * This allows for the possibility that {@link Reader#skip(long)} may + * not skip as many characters as requested (most likely because of reaching EOF). + *
+ * Note that the implementation uses {@link #skip(Reader, long)}. + * This means that the method may be considerably less efficient than using the actual skip implementation, + * this is done to guarantee that the correct number of characters are skipped. + *
+ * + * @param input stream to skip + * @param toSkip the number of characters to skip + * @throws IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws EOFException if the number of characters skipped was incorrect + * @see Reader#skip(long) + * @since 2.0 + */ + public static void skipFully(final Reader input, final long toSkip) throws IOException { + final long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); } - out.flush(); - return result; } /** * Returns the length of the given array in a null-safe manner. @@ -195,6 +871,65 @@ public class IOUtil { public static int length(final Object[] array) { return array == null ? 0 : array.length; } + /** + * Closes the given {@link Closeable} as a null-safe operation. + * + * @param closeable The resource to close, may be null. + * @throws IOException if an I/O error occurs. + * @since 2.7 + */ + public static void close(final Closeable closeable) throws IOException { + if (closeable != null) { + closeable.close(); + } + } + + /** + * Closes the given {@link Closeable} as a null-safe operation. + * + * @param closeables The resource(s) to close, may be null. + * @throws IOException if an I/O error occurs. + * @since 2.8.0 + */ + public static void close(final Closeable... closeables) throws IOException { + if (closeables != null) { + for (final Closeable closeable : closeables) { + close(closeable); + } + } + } + + /** + * Closes the given {@link Closeable} as a null-safe operation. + * + * @param closeable The resource to close, may be null. + * @param consumer Consume the IOException thrown by {@link Closeable#close()}. + * @throws IOException if an I/O error occurs. + * @since 2.7 + */ + public static void close(final Closeable closeable, final IOConsumer* See the protected methods for ways in which a subclass can easily decorate * a stream with custom pre-, post- or error processing functionality. - * - * @author Stephen Colebourne - * @version $Id: ProxyInputStream.java 934041 2010-04-14 17:37:24Z jukka $ + *
*/ public abstract class ProxyInputStream extends FilterInputStream { /** * Constructs a new ProxyInputStream. * - * @param proxy the InputStream to delegate to + * @param proxy the InputStream to delegate to */ - public ProxyInputStream(InputStream proxy) { + public ProxyInputStream(final InputStream proxy) { super(proxy); // the proxy is stored in a protected superclass variable named 'in' } /** * Invokes the delegate'sread()
method.
+ *
* @return the byte read or -1 if the end of stream
* @throws IOException if an I/O error occurs
*/
@@ -57,36 +63,38 @@ public abstract class ProxyInputStream extends FilterInputStream {
public int read() throws IOException {
try {
beforeRead(1);
- int b = in.read();
- afterRead(b != -1 ? 1 : -1);
+ final int b = in.read();
+ afterRead(b != EOF ? 1 : EOF);
return b;
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
- return -1;
+ return EOF;
}
}
/**
* Invokes the delegate's read(byte[])
method.
+ *
* @param bts the buffer to read the bytes into
- * @return the number of bytes read or -1 if the end of stream
+ * @return the number of bytes read or EOF if the end of stream
* @throws IOException if an I/O error occurs
*/
@Override
- public int read(byte[] bts) throws IOException {
+ public int read(final byte[] bts) throws IOException {
try {
- beforeRead(bts != null ? bts.length : 0);
- int n = in.read(bts);
+ beforeRead(IOUtil.length(bts));
+ final int n = in.read(bts);
afterRead(n);
return n;
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
- return -1;
+ return EOF;
}
}
/**
* Invokes the delegate's read(byte[], int, int)
method.
+ *
* @param bts the buffer to read the bytes into
* @param off The start offset
* @param len The number of bytes to read
@@ -94,29 +102,30 @@ public abstract class ProxyInputStream extends FilterInputStream {
* @throws IOException if an I/O error occurs
*/
@Override
- public int read(byte[] bts, int off, int len) throws IOException {
+ public int read(final byte[] bts, final int off, final int len) throws IOException {
try {
beforeRead(len);
- int n = in.read(bts, off, len);
+ final int n = in.read(bts, off, len);
afterRead(n);
return n;
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
- return -1;
+ return EOF;
}
}
/**
* Invokes the delegate's skip(long)
method.
+ *
* @param ln the number of bytes to skip
* @return the actual number of bytes skipped
* @throws IOException if an I/O error occurs
*/
@Override
- public long skip(long ln) throws IOException {
+ public long skip(final long ln) throws IOException {
try {
return in.skip(ln);
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
return 0;
}
@@ -124,6 +133,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
/**
* Invokes the delegate's available()
method.
+ *
* @return the number of available bytes
* @throws IOException if an I/O error occurs
*/
@@ -131,7 +141,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
public int available() throws IOException {
try {
return super.available();
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
return 0;
}
@@ -139,41 +149,41 @@ public abstract class ProxyInputStream extends FilterInputStream {
/**
* Invokes the delegate's close()
method.
+ *
* @throws IOException if an I/O error occurs
*/
@Override
public void close() throws IOException {
- try {
- in.close();
- } catch (IOException e) {
- handleIOException(e);
- }
+ IOUtil.close(in, this::handleIOException);
}
/**
* Invokes the delegate's mark(int)
method.
+ *
* @param readlimit read ahead limit
*/
@Override
- public synchronized void mark(int readlimit) {
+ public synchronized void mark(final int readlimit) {
in.mark(readlimit);
}
/**
* Invokes the delegate's reset()
method.
+ *
* @throws IOException if an I/O error occurs
*/
@Override
public synchronized void reset() throws IOException {
try {
in.reset();
- } catch (IOException e) {
+ } catch (final IOException e) {
handleIOException(e);
}
}
/**
* Invokes the delegate's markSupported()
method.
+ *
* @return true if mark is supported, otherwise false
*/
@Override
@@ -195,11 +205,13 @@ public abstract class ProxyInputStream extends FilterInputStream {
* {@link #reset()}. You need to explicitly override those methods if
* you want to add pre-processing steps also to them.
*
- * @since Commons IO 2.0
* @param n number of bytes that the caller asked to be read
+ * @since 2.0
*/
@SuppressWarnings("unused")
- protected void beforeRead(int n) {
+
+ protected void beforeRead(final int n) {
+ // no-op
}
/**
@@ -215,23 +227,25 @@ public abstract class ProxyInputStream extends FilterInputStream {
* {@link #reset()}. You need to explicitly override those methods if
* you want to add post-processing steps also to them.
*
- * @since Commons IO 2.0
* @param n number of bytes read, or -1 if the end of stream was reached
+ * @since 2.0
*/
@SuppressWarnings("unused")
- protected void afterRead(int n) {
+ protected void afterRead(final int n) {
+ // no-op
}
/**
* Handle any IOExceptions thrown.
* * This method provides a point to implement custom exception - * handling. The default behaviour is to re-throw the exception. + * handling. The default behavior is to re-throw the exception. + * * @param e The IOException thrown * @throws IOException if an I/O error occurs - * @since Commons IO 2.0 + * @since 2.0 */ - protected void handleIOException(IOException e) throws IOException { + protected void handleIOException(final IOException e) throws IOException { throw e; }