pull/952/head
ag2s20150909 4 years ago
parent c78ebfe58a
commit e4513af88e
  1. 2
      epublib/src/main/java/me/ag2s/epublib/epub/EpubWriter.java
  2. 797
      epublib/src/main/java/me/ag2s/epublib/util/IOUtil.java
  3. 1
      epublib/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java
  4. 59
      epublib/src/main/java/me/ag2s/epublib/util/commons/io/IOConsumer.java
  5. 86
      epublib/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java

@ -102,7 +102,7 @@ public class EpubWriter {
try { try {
resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref())); resultStream.putNextEntry(new ZipEntry("OEBPS/" + resource.getHref()));
InputStream inputStream = resource.getInputStream(); InputStream inputStream = resource.getInputStream();
IOUtil.copy(inputStream, resultStream); IOUtil.copy(inputStream, resultStream,IOUtil.DEFAULT_BUFFER_SIZE);
inputStream.close(); inputStream.close();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG,e.getMessage(), e); Log.e(TAG,e.getMessage(), e);

@ -1,12 +1,24 @@
package me.ag2s.epublib.util; package me.ag2s.epublib.util;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader; import java.io.Reader;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; 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 * 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 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. * 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 { public static byte[] toByteArray(InputStream in) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream(); ByteArrayOutputStream result = new ByteArrayOutputStream();
copy(in, result); copy(in, result,DEFAULT_BUFFER_SIZE);
result.flush(); result.flush();
return result.toByteArray(); return result.toByteArray();
} }
@ -79,7 +105,7 @@ public class IOUtil {
result = new ByteArrayOutputStream(); result = new ByteArrayOutputStream();
} }
copy(in, result); copy(in, result,DEFAULT_BUFFER_SIZE);
result.flush(); result.flush();
return result.toByteArray(); return result.toByteArray();
} catch (OutOfMemoryError error) { } catch (OutOfMemoryError error) {
@ -112,45 +138,695 @@ public class IOUtil {
} }
} }
//
/** /**
* Copies the contents of the InputStream to the OutputStream. * Copies bytes from an <code>InputStream</code> to an <code>OutputStream</code> using an internal buffer of the
* given size.
* <p>
* This method buffers the input internally, so there is no need to use a <code>BufferedInputStream</code>.
* </p>
* *
* @param in f * @param input the <code>InputStream</code> to read from
* @param out f * @param output the <code>OutputStream</code> to write to
* @return the nr of bytes read, or -1 if the amount &gt; Integer.MAX_VALUE * @param bufferSize the bufferSize used to copy from the input to the output
* @throws IOException f * @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 <code>InputStream</code> to chars on a
* <code>Writer</code> using the default character encoding of the platform.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* This method uses {@link InputStreamReader}.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>Writer</code> 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 <code>InputStream</code> to chars on a
* <code>Writer</code> using the specified character encoding.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* This method uses {@link InputStreamReader}.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>Writer</code> 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 <code>InputStream</code> to chars on a
* <code>Writer</code> using the specified character encoding.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* Character encoding names can be found at
* <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
* <p>
* This method uses {@link InputStreamReader}.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>Writer</code> 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 { throws IOException {
byte[] buffer = new byte[IO_COPY_BUFFER_SIZE]; copy(input, output,Charset.forName(inputCharsetName));
int readSize; }
int result = 0;
while ((readSize = in.read(buffer)) >= 0) { /**
out.write(buffer, 0, readSize); * Copies chars from a <code>Reader</code> to a <code>Appendable</code>.
result = calcNewNrReadSize(readSize, result); * <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* Large streams (over 2GB) will return a chars copied value of
* <code>-1</code> after the copy has completed since the correct
* number of chars cannot be returned as an int. For large streams
* use the <code>copyLarge(Reader, Writer)</code> method.
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Appendable</code> to write to
* @return the number of characters copied, or -1 if &gt; 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 <code>Reader</code> to an <code>Appendable</code>.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedReader</code>.
* </p>
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Appendable</code> 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 <code>Reader</code> to bytes on an
* <code>OutputStream</code> using the default character encoding of the
* platform, and calling flush.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* Due to the implementation of OutputStreamWriter, this method performs a
* flush.
* <p>
* This method uses {@link OutputStreamWriter}.
*
* @param input the <code>Reader</code> to read from
* @param output the <code>OutputStream</code> 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 <code>Reader</code> to bytes on an
* <code>OutputStream</code> using the specified character encoding, and
* calling flush.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* </p>
* <p>
* Due to the implementation of OutputStreamWriter, this method performs a
* flush.
* </p>
* <p>
* This method uses {@link OutputStreamWriter}.
* </p>
*
* @param input the <code>Reader</code> to read from
* @param output the <code>OutputStream</code> 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(); out.flush();
return result;
} }
/** /**
* Copies the contents of the Reader to the Writer. * Copies chars from a <code>Reader</code> to bytes on an
* <code>OutputStream</code> using the specified character encoding, and
* calling flush.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* Character encoding names can be found at
* <a href="http://www.iana.org/assignments/character-sets">IANA</a>.
* <p>
* Due to the implementation of OutputStreamWriter, this method performs a
* flush.
* <p>
* This method uses {@link OutputStreamWriter}.
* *
* @param in f * @param input the <code>Reader</code> to read from
* @param out f * @param output the <code>OutputStream</code> to write to
* @return the nr of characters read, or -1 if the amount &gt; Integer.MAX_VALUE * @param outputCharsetName the name of the requested charset for the OutputStream, null means platform default
* @throws IOException f * @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 { public static void copy(final Reader input, final OutputStream output, final String outputCharsetName)
char[] buffer = new char[IO_COPY_BUFFER_SIZE]; throws IOException {
int readSize; copy(input, output, Charset.forName(outputCharsetName));
int result = 0; }
while ((readSize = in.read(buffer)) >= 0) {
out.write(buffer, 0, readSize); /**
result = calcNewNrReadSize(readSize, result); * Copies chars from a <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* Large streams (over 2GB) will return a chars copied value of
* <code>-1</code> after the copy has completed since the correct
* number of chars cannot be returned as an int. For large streams
* use the <code>copyLarge(Reader, Writer)</code> method.
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> to write to
* @return the number of characters copied, or -1 if &gt; 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) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* </p>
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
* </p>
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> 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) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedInputStream</code>.
* </p>
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> 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) <code>InputStream</code> to an
* <code>OutputStream</code>, optionally skipping input bytes.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* </p>
* <p>
* 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.
* </p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> 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) <code>InputStream</code> to an
* <code>OutputStream</code>, optionally skipping input bytes.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedInputStream</code>.
* </p>
* <p>
* 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.
* </p>
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> 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) <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> 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) <code>Reader</code> to a <code>Writer</code>.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> 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) <code>InputStream</code> to an
* <code>OutputStream</code>, optionally skipping input chars.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
* The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> 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) <code>InputStream</code> to an
* <code>OutputStream</code>, optionally skipping input chars.
* <p>
* This method uses the provided buffer, so there is no need to use a
* <code>BufferedReader</code>.
* <p>
*
* @param input the <code>Reader</code> to read from
* @param output the <code>Writer</code> 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}.
* <p>
* 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.
* </p>
*
* @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 <a href="https://issues.apache.org/jira/browse/IO-203">IO-203 - Add skipFully() method for InputStreams</a>
* @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}.
* <p>
* 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.
* </p>
*
* @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 <a href="https://issues.apache.org/jira/browse/IO-203">IO-203 - Add skipFully() method for InputStreams</a>
* @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.
* <p>
* This allows for the possibility that {@link InputStream#skip(long)} may
* not skip as many bytes as requested (most likely because of reaching EOF).
* <p>
* 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.
* </p>
*
* @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.
* <p>
* This allows for the possibility that {@link Reader#skip(long)} may
* not skip as many characters as requested (most likely because of reaching EOF).
* <p>
* 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.
* </p>
*
* @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. * 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) { public static int length(final Object[] array) {
return array == null ? 0 : array.length; 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<IOException> consumer) throws IOException {
if (closeable != null) {
try {
closeable.close();
} catch (final IOException e) {
if (consumer != null) {
consumer.accept(e);
}
}
}
}
/**
* Closes a URLConnection.
*
* @param conn the connection to close.
* @since 2.4
*/
public static void close(final URLConnection conn) {
if (conn instanceof HttpURLConnection) {
((HttpURLConnection) conn).disconnect();
}
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static String Stream2String(InputStream inputStream) { public static String Stream2String(InputStream inputStream) {

@ -59,6 +59,7 @@ public class ByteOrderMark implements Serializable {
* @see <a href="http://unicode.org/faq/utf_bom.html#BOM">Byte Order Mark (BOM) FAQ</a> * @see <a href="http://unicode.org/faq/utf_bom.html#BOM">Byte Order Mark (BOM) FAQ</a>
* @since 2.5 * @since 2.5
*/ */
@SuppressWarnings("unused")
public static final char UTF_BOM = '\uFEFF'; public static final char UTF_BOM = '\uFEFF';
private final String charsetName; private final String charsetName;

@ -0,0 +1,59 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package me.ag2s.epublib.util.commons.io;
import java.io.IOException;
import java.util.Objects;
import java.util.function.Consumer;
/**
* Like {@link Consumer} but throws {@link IOException}.
*
* @param <T> the type of the input to the operations.
* @since 2.7
*/
@FunctionalInterface
public interface IOConsumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
* @throws IOException if an I/O error occurs.
*/
void accept(T t) throws IOException;
/**
* Returns a composed {@code IoConsumer} that performs, in sequence, this operation followed by the {@code after}
* operation. If performing either operation throws an exception, it is relayed to the caller of the composed
* operation. If performing this operation throws an exception, the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after}
* operation
* @throws NullPointerException if {@code after} is null
*/
@SuppressWarnings("unused")
default IOConsumer<T> andThen(final IOConsumer<? super T> after) {
Objects.requireNonNull(after);
return (final T t) -> {
accept(t);
after.accept(t);
};
}
}

@ -17,10 +17,16 @@ package me.ag2s.epublib.util.commons.io;
* limitations under the License. * limitations under the License.
*/ */
import java.io.FilterInputStream; import java.io.FilterInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import me.ag2s.epublib.util.IOUtil;
import static me.ag2s.epublib.util.IOUtil.EOF;
/** /**
* A Proxy stream which acts as expected, that is it passes the method * A Proxy stream which acts as expected, that is it passes the method
* calls on to the proxied stream and doesn't change which methods are * calls on to the proxied stream and doesn't change which methods are
@ -29,27 +35,27 @@ import java.io.InputStream;
* It is an alternative base class to FilterInputStream * It is an alternative base class to FilterInputStream
* to increase reusability, because FilterInputStream changes the * to increase reusability, because FilterInputStream changes the
* methods being called, such as read(byte[]) to read(byte[], int, int). * methods being called, such as read(byte[]) to read(byte[], int, int).
* </p>
* <p> * <p>
* See the protected methods for ways in which a subclass can easily decorate * See the protected methods for ways in which a subclass can easily decorate
* a stream with custom pre-, post- or error processing functionality. * a stream with custom pre-, post- or error processing functionality.
* * </p>
* @author Stephen Colebourne
* @version $Id: ProxyInputStream.java 934041 2010-04-14 17:37:24Z jukka $
*/ */
public abstract class ProxyInputStream extends FilterInputStream { public abstract class ProxyInputStream extends FilterInputStream {
/** /**
* Constructs a new ProxyInputStream. * 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); super(proxy);
// the proxy is stored in a protected superclass variable named 'in' // the proxy is stored in a protected superclass variable named 'in'
} }
/** /**
* Invokes the delegate's <code>read()</code> method. * Invokes the delegate's <code>read()</code> method.
*
* @return the byte read or -1 if the end of stream * @return the byte read or -1 if the end of stream
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
@ -57,36 +63,38 @@ public abstract class ProxyInputStream extends FilterInputStream {
public int read() throws IOException { public int read() throws IOException {
try { try {
beforeRead(1); beforeRead(1);
int b = in.read(); final int b = in.read();
afterRead(b != -1 ? 1 : -1); afterRead(b != EOF ? 1 : EOF);
return b; return b;
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
return -1; return EOF;
} }
} }
/** /**
* Invokes the delegate's <code>read(byte[])</code> method. * Invokes the delegate's <code>read(byte[])</code> method.
*
* @param bts the buffer to read the bytes into * @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 * @throws IOException if an I/O error occurs
*/ */
@Override @Override
public int read(byte[] bts) throws IOException { public int read(final byte[] bts) throws IOException {
try { try {
beforeRead(bts != null ? bts.length : 0); beforeRead(IOUtil.length(bts));
int n = in.read(bts); final int n = in.read(bts);
afterRead(n); afterRead(n);
return n; return n;
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
return -1; return EOF;
} }
} }
/** /**
* Invokes the delegate's <code>read(byte[], int, int)</code> method. * Invokes the delegate's <code>read(byte[], int, int)</code> method.
*
* @param bts the buffer to read the bytes into * @param bts the buffer to read the bytes into
* @param off The start offset * @param off The start offset
* @param len The number of bytes to read * @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 * @throws IOException if an I/O error occurs
*/ */
@Override @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 { try {
beforeRead(len); beforeRead(len);
int n = in.read(bts, off, len); final int n = in.read(bts, off, len);
afterRead(n); afterRead(n);
return n; return n;
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
return -1; return EOF;
} }
} }
/** /**
* Invokes the delegate's <code>skip(long)</code> method. * Invokes the delegate's <code>skip(long)</code> method.
*
* @param ln the number of bytes to skip * @param ln the number of bytes to skip
* @return the actual number of bytes skipped * @return the actual number of bytes skipped
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
@Override @Override
public long skip(long ln) throws IOException { public long skip(final long ln) throws IOException {
try { try {
return in.skip(ln); return in.skip(ln);
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
return 0; return 0;
} }
@ -124,6 +133,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
/** /**
* Invokes the delegate's <code>available()</code> method. * Invokes the delegate's <code>available()</code> method.
*
* @return the number of available bytes * @return the number of available bytes
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
@ -131,7 +141,7 @@ public abstract class ProxyInputStream extends FilterInputStream {
public int available() throws IOException { public int available() throws IOException {
try { try {
return super.available(); return super.available();
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
return 0; return 0;
} }
@ -139,41 +149,41 @@ public abstract class ProxyInputStream extends FilterInputStream {
/** /**
* Invokes the delegate's <code>close()</code> method. * Invokes the delegate's <code>close()</code> method.
*
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
@Override @Override
public void close() throws IOException { public void close() throws IOException {
try { IOUtil.close(in, this::handleIOException);
in.close();
} catch (IOException e) {
handleIOException(e);
}
} }
/** /**
* Invokes the delegate's <code>mark(int)</code> method. * Invokes the delegate's <code>mark(int)</code> method.
*
* @param readlimit read ahead limit * @param readlimit read ahead limit
*/ */
@Override @Override
public synchronized void mark(int readlimit) { public synchronized void mark(final int readlimit) {
in.mark(readlimit); in.mark(readlimit);
} }
/** /**
* Invokes the delegate's <code>reset()</code> method. * Invokes the delegate's <code>reset()</code> method.
*
* @throws IOException if an I/O error occurs * @throws IOException if an I/O error occurs
*/ */
@Override @Override
public synchronized void reset() throws IOException { public synchronized void reset() throws IOException {
try { try {
in.reset(); in.reset();
} catch (IOException e) { } catch (final IOException e) {
handleIOException(e); handleIOException(e);
} }
} }
/** /**
* Invokes the delegate's <code>markSupported()</code> method. * Invokes the delegate's <code>markSupported()</code> method.
*
* @return true if mark is supported, otherwise false * @return true if mark is supported, otherwise false
*/ */
@Override @Override
@ -195,11 +205,13 @@ public abstract class ProxyInputStream extends FilterInputStream {
* {@link #reset()}. You need to explicitly override those methods if * {@link #reset()}. You need to explicitly override those methods if
* you want to add pre-processing steps also to them. * 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 * @param n number of bytes that the caller asked to be read
* @since 2.0
*/ */
@SuppressWarnings("unused") @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 * {@link #reset()}. You need to explicitly override those methods if
* you want to add post-processing steps also to them. * 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 * @param n number of bytes read, or -1 if the end of stream was reached
* @since 2.0
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
protected void afterRead(int n) { protected void afterRead(final int n) {
// no-op
} }
/** /**
* Handle any IOExceptions thrown. * Handle any IOExceptions thrown.
* <p> * <p>
* This method provides a point to implement custom exception * 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 * @param e The IOException thrown
* @throws IOException if an I/O error occurs * @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; throw e;
} }

Loading…
Cancel
Save