hm.binkley.xio.XInputStream Maven / Gradle / Ivy
package hm.binkley.xio;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
/**
* {@code XInputStream} is a minimal rewrite of {@link InputStream}.
*
* @author B. K. Oxley (binkley)
*/
public interface XInputStream
extends Closeable {
/**
* MAX_SKIP_BUFFER_SIZE is used to determine the maximum buffer size to use
* when skipping.
*
* @see JInputStream#MAX_SKIP_BUFFER_SIZE
*/
int MAX_SKIP_BUFFER_SIZE = 2048;
/**
* Creates a new JDK {@code InputStream} forwarding all calls to this {@code
* XInputStream}.
*
* @return the new JDK input stream, never missing
*/
@Nonnull
default JInputStream asInputStream() {
return new JInputStream(this);
}
/**
* Reads the next byte of data from the input stream. The value byte is
* returned as an {@code int} in the range {@code 0} to {@code 255}. If no
* byte is available because the end of the stream has been reached, the
* value {@code -1} is returned. This method blocks until input data is
* available, the end of the stream is detected, or an exception is thrown.
*
* @return the next byte of data, or {@code -1} if the end of the stream is
* reached.
*
* @throws IOException if an I/O error occurs.
*/
int read()
throws IOException;
/**
* Reads some number of bytes from the input stream and stores them into the
* buffer array {@code b}. The number of bytes actually read is returned as
* an integer. This method blocks until input data is available, end of
* file is detected, or an exception is thrown.
*
* If the length of {@code b} is zero, then no bytes are read and {@code 0}
* is returned; otherwise, there is an attempt to read at least one byte. If
* no byte is available because the stream is at the end of the file, the
* value {@code -1} is returned; otherwise, at least one byte is read and
* stored into {@code b}.
*
* The first byte read is stored into element {@code b[0]}, the next one
* into {@code b[1]}, and so on. The number of bytes read is, at most, equal
* to the length of {@code b}. Let k be the number of bytes actually
* read; these bytes will be stored in elements {@code b[0]} through {@code
* b[}k{@code -1]}, leaving elements {@code b[}k{@code ]}
* through {@code b[b.length-1]} unaffected.
*
* The {@code read(b)} method for class {@code InputStream}
* has the same effect as:
{@code read(b, 0, b.length) }
*
* @param b the buffer into which the data is read.
*
* @return the total number of bytes read into the buffer, or {@code -1} if
* there is no more data because the end of the stream has been reached.
*
* @throws IOException If the first byte cannot be read for any reason other
* than the end of the file, if the input stream has been closed, or if some
* other I/O error occurs.
* @throws NullPointerException if {@code b} is {@code null}.
* @see XInputStream#read(byte[], int, int)
*/
default int read(@Nonnull final byte[] b)
throws IOException {
return read(b, 0, b.length);
}
/**
* Reads up to {@code len} bytes of data from the input stream into an array
* of bytes. An attempt is made to read as many as {@code len} bytes, but a
* smaller number may be read. The number of bytes actually read is returned
* as an integer.
*
* This method blocks until input data is available, end of file is
* detected, or an exception is thrown.
*
* If {@code len} is zero, then no bytes are read and {@code 0} is returned;
* otherwise, there is an attempt to read at least one byte. If no byte is
* available because the stream is at end of file, the value {@code -1} is
* returned; otherwise, at least one byte is read and stored into {@code
* b}.
*
* The first byte read is stored into element {@code b[off]}, the next one
* into {@code b[off+1]}, and so on. The number of bytes read is, at most,
* equal to {@code len}. Let k be the number of bytes actually read;
* these bytes will be stored in elements {@code b[off]} through {@code
* b[off+}k{@code -1]}, leaving elements {@code b[off+}k{@code
* ]} through {@code b[off+len-1]} unaffected.
*
* In every case, elements {@code b[0]} through {@code b[off]} and elements
* {@code b[off+len]} through {@code b[b.length-1]} are unaffected.
*
* The {@code read(b, off, len)} method for class {@code InputStream} simply
* calls the method {@code read()} repeatedly. If the first such call
* results in an {@code IOException}, that exception is returned from the
* call to the {@code read(b, off, len)} method. If any subsequent call to
* {@code read()} results in a {@code IOException}, the exception is caught
* and treated as if it were end of file; the bytes read up to that point
* are stored into {@code b} and the number of bytes read before the
* exception occurred is returned. The default implementation of this method
* blocks until the requested amount of input data {@code len} has been
* read, end of file is detected, or an exception is thrown. Subclasses are
* encouraged to provide a more efficient implementation of this method.
*
* @param b the buffer into which the data is read.
* @param off the start offset in array {@code b} at which the data is
* written.
* @param len the maximum number of bytes to read.
*
* @return the total number of bytes read into the buffer, or {@code -1} if
* there is no more data because the end of the stream has been reached.
*
* @throws IOException If the first byte cannot be read for any reason other
* than end of file, or if the input stream has been closed, or if some
* other I/O error occurs.
* @throws NullPointerException If {@code b} is {@code null}.
* @throws IndexOutOfBoundsException If {@code off} is negative, {@code len}
* is negative, or {@code len} is greater than {@code b.length - off}
* @see XInputStream#read()
*/
default int read(@Nonnull final byte[] b, final int off, final int len)
throws IOException {
if (0 == len)
return 0;
if (0 > off || 0 > len || len > b.length - off)
throw new IndexOutOfBoundsException();
int c = read();
if (-1 == c)
return -1;
b[off] = (byte) c;
int i = 1;
try {
for (; i < len; i++) {
c = read();
if (-1 == c)
break;
b[off + i] = (byte) c;
}
} catch (final IOException ignored) {
}
return i;
}
/**
* Skips over and discards {@code n} bytes of data from this input stream.
* The {@code skip} method may, for a variety of reasons, end up skipping
* over some smaller number of bytes, possibly {@code 0}. This may result
* from any of a number of conditions; reaching end of file before {@code n}
* bytes have been skipped is only one possibility. The actual number of
* bytes skipped is returned. If {@code n} is negative, the {@code skip}
* method for class {@code InputStream} always returns 0, and no bytes are
* skipped. Subclasses may handle the negative value differently.
*
* The {@code skip} method of this class creates a byte array and then
* repeatedly reads into it until {@code n} bytes have been read or the end
* of the stream has been reached. Subclasses are encouraged to provide a
* more efficient implementation of this method. For instance, the
* implementation may depend on the ability to seek.
*
* @param n the number of bytes to be skipped.
*
* @return the actual number of bytes skipped.
*
* @throws IOException if the stream does not support seek, or if some other
* I/O error occurs.
*/
default long skip(final long n)
throws IOException {
long remaining = n;
if (0 >= n)
return 0;
final int size = (int) Math.min(MAX_SKIP_BUFFER_SIZE, remaining);
final byte[] skipBuffer = new byte[size];
while (0 < remaining) {
final int nr = read(skipBuffer, 0, (int) Math.min(size, remaining));
if (0 > nr)
break;
remaining -= nr;
}
return n - remaining;
}
/**
* Returns an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking by the next invocation of a
* method for this input stream. The next invocation might be the same
* thread or another thread. A single read or skip of this many bytes will
* not block, but may read or skip fewer bytes.
*
* Note that while some implementations of {@code InputStream} will return
* the total number of bytes in the stream, many will not. It is never
* correct to use the return value of this method to allocate a buffer
* intended to hold all data in this stream.
*
* A subclass' implementation of this method may choose to throw an {@link
* IOException} if this input stream has been closed by invoking the {@link
* #close()} method.
*
* The {@code available} method for class {@code InputStream} always returns
* {@code 0}.
*
* This method should be overridden by subclasses.
*
* @return an estimate of the number of bytes that can be read (or skipped
* over) from this input stream without blocking or {@code 0} when it
* reaches the end of the input stream.
*
* @throws IOException if an I/O error occurs.
*/
default int available()
throws IOException {
return 0;
}
/**
* Closes this input stream and releases any system resources associated
* with the stream.
*
* The {@code close} method of {@code InputStream} does nothing.
*
* @throws IOException if an I/O error occurs.
*/
default void close()
throws IOException {
}
class JInputStream
extends InputStream {
private final XInputStream in;
protected JInputStream(@Nonnull final XInputStream in) {
this.in = in;
}
@Override
public int read()
throws IOException {
return in.read();
}
@Override
public int read(@Nonnull final byte[] b)
throws IOException {
return in.read(b);
}
@Override
public int read(@Nonnull final byte[] b, final int off, final int len)
throws IOException {
return in.read(b, off, len);
}
@Override
public long skip(final long n)
throws IOException {
return in.skip(n);
}
@Override
public int available()
throws IOException {
return in.available();
}
@Override
public void close()
throws IOException {
in.close();
}
@Override
public synchronized void mark(final int readlimit) {
if (markSupported())
((XMarkable) in).mark(readlimit);
}
@Override
public synchronized void reset()
throws IOException {
if (markSupported())
((XMarkable) in).reset();
else
throw new IOException("mark/reset not supported");
}
@Override
public boolean markSupported() {
return in instanceof XMarkable;
}
}
}