package be.nikiroo.utils.streams; import java.io.IOException; import java.io.InputStream; import java.util.AbstractMap; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import be.nikiroo.utils.StringUtils; /** * A simple {@link InputStream} that is buffered with a bytes array. *
* It is mostly intended to be used as a base class to create new * {@link InputStream}s with special operation modes, and to give some default * methods. * * @author niki */ public class BufferedInputStream extends InputStream { /** * The size of the internal buffer (can be different if you pass your own * buffer, of course, and can also expand to search for longer "startsWith" * data). *
* Note that special "push-back" buffers can also be created during the life
* of this stream.
*/
static private final int BUFFER_SIZE = 4096;
/** The current position in the buffer. */
protected int start;
/** The index of the last usable position of the buffer. */
protected int stop;
/** The buffer itself. */
protected byte[] buffer;
/** An End-Of-File (or {@link InputStream}, here) marker. */
protected boolean eof;
private boolean closed;
private InputStream in;
private int openCounter;
/** array + offset of pushed-back buffers */
private List
* You are now responsible for it — you must close it.
*
* This method allows you to use a wrapping stream around this one and still
* close the wrapping stream.
*
* @return the same stream, but you are now responsible for closing it
*
* @throws IOException
* in case of I/O error or if the stream is closed
*/
public synchronized InputStream open() throws IOException {
checkClose();
openCounter++;
return this;
}
/**
* Check if the current content (until eof) is equal to the given search
* term.
*
* Note: the search term size must be smaller or equal the internal
* buffer size.
*
* @param search
* the term to search for
*
* @return TRUE if the content that will be read starts with it
*
* @throws IOException
* in case of I/O error or if the size of the search term is
* greater than the internal buffer
*/
public boolean is(String search) throws IOException {
return is(StringUtils.getBytes(search));
}
/**
* Check if the current content (until eof) is equal to the given search
* term.
*
* Note: the search term size must be smaller or equal the internal
* buffer size.
*
* @param search
* the term to search for
*
* @return TRUE if the content that will be read starts with it
*
* @throws IOException
* in case of I/O error or if the size of the search term is
* greater than the internal buffer
*/
public boolean is(byte[] search) throws IOException {
if (startsWith(search)) {
return available() == search.length;
}
return false;
}
/**
* Check if the current content (what will be read next) starts with the
* given search term.
*
* Note: the search term size must be smaller or equal the internal
* buffer size.
*
* @param search
* the term to search for
*
* @return TRUE if the content that will be read starts with it
*
* @throws IOException
* in case of I/O error or if the size of the search term is
* greater than the internal buffer
*/
public boolean startsWith(String search) throws IOException {
return startsWith(StringUtils.getBytes(search));
}
/**
* Check if the current content (what will be read next) starts with the
* given search term.
*
* An empty string will always return true (unless the stream is closed,
* which would throw an {@link IOException}).
*
* Note: the search term size must be smaller or equal the internal
* buffer size.
*
* @param search
* the term to search for
*
* @return TRUE if the content that will be read starts with it
*
* @throws IOException
* in case of I/O error or if the size of the search term is
* greater than the internal buffer
*/
public boolean startsWith(byte[] search) throws IOException {
checkClose();
while (consolidatePushBack(search.length) < search.length) {
preRead();
if (start >= stop) {
// Not enough data left to start with that
return false;
}
byte[] newBuffer = new byte[stop - start];
System.arraycopy(buffer, start, newBuffer, 0, stop - start);
pushback(newBuffer, 0);
start = stop;
}
Entry
* Including the under-laying {@link InputStream}.
*
* Note: if you called the {@link BufferedInputStream2#open()} method
* prior to this one, it will just decrease the internal count of how many
* open streams it held and do nothing else. The stream will actually be
* closed when you have called {@link BufferedInputStream2#close()} once
* more than {@link BufferedInputStream2#open()}.
*
* @exception IOException
* in case of I/O error
*/
@Override
public synchronized void close() throws IOException {
close(true);
}
/**
* Closes this stream and releases any system resources associated with the
* stream.
*
* Including the under-laying {@link InputStream} if
* incudingSubStream is true.
*
* You can call this method multiple times, it will not cause an
* {@link IOException} for subsequent calls.
*
* Note: if you called the {@link BufferedInputStream2#open()} method
* prior to this one, it will just decrease the internal count of how many
* open streams it held and do nothing else. The stream will actually be
* closed when you have called {@link BufferedInputStream2#close()} once
* more than {@link BufferedInputStream2#open()}.
*
* @param includingSubStream
* also close the under-laying stream
*
* @exception IOException
* in case of I/O error
*/
public synchronized void close(boolean includingSubStream)
throws IOException {
if (!closed) {
if (openCounter > 0) {
openCounter--;
} else {
closed = true;
if (includingSubStream && in != null) {
in.close();
}
}
}
}
/**
* Consolidate the push-back buffers so the last one is at least the given
* size, if possible.
*
* If there is not enough data in the push-back buffers, they will all be
* consolidated.
*
* @param size
* the minimum size of the consolidated buffer, or -1 to force
* the consolidation of all push-back buffers
*
* @return the size of the last, consolidated buffer; can be less than the
* requested size if not enough data
*/
protected int consolidatePushBack(int size) {
int bbIndex = -1;
int bbUpToSize = 0;
for (Entry