X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2Fstreams%2FBufferedInputStream.java;h=babd2ce88ec929883713af1895a556d6c4178ad9;hb=b440ca6a9a8d7cc3044e93ff91202fd448362cc6;hp=397f6fcf9b22f414631cddb899ab38a3b38f3fa1;hpb=f04d5e49e91832e122617fbbaa5cdb053459a7e7;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/streams/BufferedInputStream.java b/src/be/nikiroo/utils/streams/BufferedInputStream.java index 397f6fc..babd2ce 100644 --- a/src/be/nikiroo/utils/streams/BufferedInputStream.java +++ b/src/be/nikiroo/utils/streams/BufferedInputStream.java @@ -2,7 +2,12 @@ package be.nikiroo.utils.streams; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; +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. @@ -14,29 +19,36 @@ import java.util.Arrays; * @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 buffer, here) marker. */ + /** An End-Of-File (or {@link InputStream}, here) marker. */ protected boolean eof; private boolean closed; private InputStream in; private int openCounter; - // special use, prefetched next buffer - private byte[] buffer2; - private int pos2; - private int len2; - private byte[] originalBuffer; + /** array + offset of pushed-back buffers */ + private List> backBuffers; private long bytesRead; /** - * Create a new {@link BufferedInputStream} that wraps the given + * Create a new {@link BufferedInputStream2} that wraps the given * {@link InputStream}. * * @param in @@ -45,15 +57,15 @@ public class BufferedInputStream extends InputStream { public BufferedInputStream(InputStream in) { this.in = in; - this.buffer = new byte[4096]; - this.originalBuffer = this.buffer; + this.buffer = new byte[BUFFER_SIZE]; this.start = 0; this.stop = 0; + this.backBuffers = new ArrayList>(); } /** - * Create a new {@link BufferedInputStream} that wraps the given bytes array - * as a data source. + * Create a new {@link BufferedInputStream2} that wraps the given bytes + * array as a data source. * * @param in * the array to wrap, cannot be NULL @@ -63,8 +75,8 @@ public class BufferedInputStream extends InputStream { } /** - * Create a new {@link BufferedInputStream} that wraps the given bytes array - * as a data source. + * Create a new {@link BufferedInputStream2} that wraps the given bytes + * array as a data source. * * @param in * the array to wrap, cannot be NULL @@ -89,15 +101,15 @@ public class BufferedInputStream extends InputStream { this.in = null; this.buffer = in; - this.originalBuffer = this.buffer; this.start = offset; this.stop = length; + this.backBuffers = new ArrayList>(); } /** - * Return this very same {@link BufferedInputStream}, but keep a counter of + * Return this very same {@link BufferedInputStream2}, but keep a counter of * how many streams were open this way. When calling - * {@link BufferedInputStream#close()}, decrease this counter if it is not + * {@link BufferedInputStream2#close()}, decrease this counter if it is not * already zero instead of actually closing the stream. *

* You are now responsible for it — you must close it. @@ -116,6 +128,50 @@ public class BufferedInputStream extends InputStream { 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. @@ -132,14 +188,17 @@ public class BufferedInputStream extends InputStream { * in case of I/O error or if the size of the search term is * greater than the internal buffer */ - public boolean startsWiths(String search) throws IOException { - return startsWith(search.getBytes("UTF-8")); + 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. * @@ -153,40 +212,27 @@ public class BufferedInputStream extends InputStream { * greater than the internal buffer */ public boolean startsWith(byte[] search) throws IOException { - if (search.length > originalBuffer.length) { - throw new IOException( - "This stream does not support searching for more than " - + buffer.length + " bytes"); - } - checkClose(); - if (available() < search.length) { + while (consolidatePushBack(search.length) < search.length) { preRead(); - } - - if (available() >= search.length) { - // Easy path - return StreamUtils.startsWith(search, buffer, start, stop); - } else if (!eof) { - // Harder path - if (buffer2 == null && buffer.length == originalBuffer.length) { - buffer2 = Arrays.copyOf(buffer, buffer.length * 2); - - pos2 = buffer.length; - len2 = read(in, buffer2, pos2, buffer.length); - if (len2 > 0) { - bytesRead += len2; - } - - // Note: here, len/len2 = INDEX of last good byte - len2 += pos2; + if (start >= stop) { + // Not enough data left to start with that + return false; } - return StreamUtils.startsWith(search, buffer2, pos2, len2); + byte[] newBuffer = new byte[stop - start]; + System.arraycopy(buffer, start, newBuffer, 0, stop - start); + pushback(newBuffer, 0); + start = stop; } - return false; + Entry bb = backBuffers.get(backBuffers.size() - 1); + byte[] bbBuffer = bb.getKey(); + int bbOffset = bb.getValue(); + + return StreamUtils.startsWith(search, bbBuffer, bbOffset, + bbBuffer.length); } /** @@ -199,8 +245,7 @@ public class BufferedInputStream extends InputStream { } /** - * Check if this stream is totally spent (no more data to read or to - * process). + * Check if this stream is spent (no more data to read or to process). * * @return TRUE if it is * @@ -216,6 +261,24 @@ public class BufferedInputStream extends InputStream { return !hasMoreData(); } + /** + * Read the whole {@link InputStream} until the end and return the number of + * bytes read. + * + * @return the number of bytes read + * + * @throws IOException + * in case of I/O error + */ + public long end() throws IOException { + long skipped = 0; + while (hasMoreData()) { + skipped += skip(buffer.length); + } + + return skipped; + } + @Override public int read() throws IOException { checkClose(); @@ -245,6 +308,28 @@ public class BufferedInputStream extends InputStream { return 0; } + // Read from the pushed-back buffers if any + if (!backBuffers.isEmpty()) { + int read = 0; + + Entry bb = backBuffers + .remove(backBuffers.size() - 1); + byte[] bbBuffer = bb.getKey(); + int bbOffset = bb.getValue(); + int bbSize = bbBuffer.length - bbOffset; + + if (bbSize > blen) { + read = blen; + System.arraycopy(bbBuffer, bbOffset, b, boff, read); + pushback(bbBuffer, bbOffset + read); + } else { + read = bbSize; + System.arraycopy(bbBuffer, bbOffset, b, boff, read); + } + + return read; + } + int done = 0; while (hasMoreData() && done < blen) { preRead(); @@ -268,6 +353,23 @@ public class BufferedInputStream extends InputStream { } long skipped = 0; + while (!backBuffers.isEmpty() && n > 0) { + Entry bb = backBuffers + .remove(backBuffers.size() - 1); + byte[] bbBuffer = bb.getKey(); + int bbOffset = bb.getValue(); + int bbSize = bbBuffer.length - bbOffset; + + int localSkip = 0; + localSkip = (int) Math.min(n, bbSize); + + n -= localSkip; + bbSize -= localSkip; + + if (bbSize > 0) { + pushback(bbBuffer, bbOffset + localSkip); + } + } while (hasMoreData() && n > 0) { preRead(); @@ -286,7 +388,12 @@ public class BufferedInputStream extends InputStream { return 0; } - return Math.max(0, stop - start); + int avail = 0; + for (Entry entry : backBuffers) { + avail += entry.getKey().length - entry.getValue(); + } + + return avail + Math.max(0, stop - start); } /** @@ -295,11 +402,11 @@ public class BufferedInputStream extends InputStream { *

* Including the under-laying {@link InputStream}. *

- * Note: if you called the {@link BufferedInputStream#open()} method + * 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 BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. + * closed when you have called {@link BufferedInputStream2#close()} once + * more than {@link BufferedInputStream2#open()}. * * @exception IOException * in case of I/O error @@ -319,11 +426,11 @@ public class BufferedInputStream extends InputStream { * You can call this method multiple times, it will not cause an * {@link IOException} for subsequent calls. *

- * Note: if you called the {@link BufferedInputStream#open()} method + * 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 BufferedInputStream#close()} once more - * than {@link BufferedInputStream#open()}. + * closed when you have called {@link BufferedInputStream2#close()} once + * more than {@link BufferedInputStream2#open()}. * * @param includingSubStream * also close the under-laying stream @@ -345,6 +452,52 @@ public class BufferedInputStream extends InputStream { } } + /** + * 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 entry : backBuffers) { + bbIndex++; + bbUpToSize += entry.getKey().length - entry.getValue(); + + if (size >= 0 && bbUpToSize >= size) { + break; + } + } + + // Index 0 means "the last buffer is already big enough" + if (bbIndex > 0) { + byte[] consolidatedBuffer = new byte[bbUpToSize]; + int consolidatedPos = 0; + for (int i = 0; i <= bbIndex; i++) { + Entry bb = backBuffers + .remove(backBuffers.size() - 1); + byte[] bbBuffer = bb.getKey(); + int bbOffset = bb.getValue(); + int bbSize = bbBuffer.length - bbOffset; + System.arraycopy(bbBuffer, bbOffset, consolidatedBuffer, + consolidatedPos, bbSize); + } + + pushback(consolidatedBuffer, 0); + } + + return bbUpToSize; + } + /** * Check if we still have some data in the buffer and, if not, fetch some. * @@ -358,21 +511,9 @@ public class BufferedInputStream extends InputStream { boolean hasRead = false; if (in != null && !eof && start >= stop) { start = 0; - if (buffer2 != null) { - buffer = buffer2; - start = pos2; - stop = len2; - - buffer2 = null; - pos2 = 0; - len2 = 0; - } else { - buffer = originalBuffer; - - stop = read(in, buffer, 0, buffer.length); - if (stop > 0) { - bytesRead += stop; - } + stop = read(in, buffer, 0, buffer.length); + if (stop > 0) { + bytesRead += stop; } hasRead = true; @@ -386,12 +527,25 @@ public class BufferedInputStream extends InputStream { } /** - * Read the under-laying stream into the local buffer. + * Push back some data that will be read again at the next read call. + * + * @param buffer + * the buffer to push back + * @param offset + * the offset at which to start reading in the buffer + */ + protected void pushback(byte[] buffer, int offset) { + backBuffers.add( + new AbstractMap.SimpleEntry(buffer, offset)); + } + + /** + * Read the under-laying stream into the given local buffer. * * @param in * the under-laying {@link InputStream} * @param buffer - * the buffer we use in this {@link BufferedInputStream} + * the buffer we use in this {@link BufferedInputStream2} * @param off * the offset * @param len @@ -418,7 +572,7 @@ public class BufferedInputStream extends InputStream { return false; } - return (start < stop) || !eof; + return !backBuffers.isEmpty() || (start < stop) || !eof; } /**