X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=blobdiff_plain;f=streams%2FBufferedInputStream.java;fp=streams%2FBufferedInputStream.java;h=3cad70d2152fc433c5313b8fbec1292f9c3ff9d2;hp=683fa55865aff5ee4b6d442638721372c99e0e02;hb=7665ac5903eff6055e601dc0ad7540032d2f8a14;hpb=90eaf7c6209fc6ad21a9f9a37bc301e6078c930e diff --git a/streams/BufferedInputStream.java b/streams/BufferedInputStream.java index 683fa55..3cad70d 100644 --- a/streams/BufferedInputStream.java +++ b/streams/BufferedInputStream.java @@ -2,7 +2,10 @@ 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; @@ -18,10 +21,11 @@ import be.nikiroo.utils.StringUtils; public class BufferedInputStream extends InputStream { /** * The size of the internal buffer (can be different if you pass your own - * buffer, of course). + * buffer, of course, and can also expand to search for longer "startsWith" + * data). *

- * A second buffer of twice the size can sometimes be created as needed for - * the {@link BufferedInputStream#startsWith(byte[])} search operation. + * Note that special "push-back" buffers can also be created during the life + * of this stream. */ static private final int BUFFER_SIZE = 4096; @@ -37,12 +41,10 @@ public class BufferedInputStream extends InputStream { private boolean closed; private InputStream in; private int openCounter; + private byte[] singleByteReader = new byte[1]; - // 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; @@ -57,9 +59,9 @@ public class BufferedInputStream extends InputStream { this.in = in; this.buffer = new byte[BUFFER_SIZE]; - this.originalBuffer = this.buffer; this.start = 0; this.stop = 0; + this.backBuffers = new ArrayList>(); } /** @@ -100,18 +102,9 @@ public class BufferedInputStream extends InputStream { this.in = null; this.buffer = in; - this.originalBuffer = this.buffer; this.start = offset; this.stop = length; - } - - /** - * The internal buffer size (can be useful to know for search methods). - * - * @return the size of the internal buffer, in bytes. - */ - public int getInternalBufferSize() { - return originalBuffer.length; + this.backBuffers = new ArrayList>(); } /** @@ -174,7 +167,7 @@ public class BufferedInputStream extends InputStream { */ public boolean is(byte[] search) throws IOException { if (startsWith(search)) { - return (stop - start) == search.length; + return available() == search.length; } return false; @@ -220,40 +213,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 (in != null && !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); } /** @@ -266,8 +246,7 @@ public class BufferedInputStream extends InputStream { } /** - * Check if this stream is 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 * @@ -303,14 +282,11 @@ public class BufferedInputStream extends InputStream { @Override public int read() throws IOException { - checkClose(); - - preRead(); - if (eof) { + if (read(singleByteReader) < 0) { return -1; } - return buffer[start++]; + return singleByteReader[0]; } @Override @@ -330,6 +306,32 @@ public class BufferedInputStream extends InputStream { return 0; } + // Read from the pushed-back buffers if any + if (backBuffers.isEmpty()) { + preRead(); // an implementation could pushback in preRead() + } + + 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(); @@ -353,6 +355,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(); @@ -371,7 +390,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); } /** @@ -430,6 +454,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. * @@ -443,21 +513,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); + if (stop > 0) { + bytesRead += stop; } hasRead = true; @@ -471,25 +529,55 @@ 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)); + } + + /** + * 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 + * @param len + * the length to copy + */ + protected void pushback(byte[] buffer, int offset, int len) { + // TODO: not efficient! + if (buffer.length != len) { + byte[] lenNotSupportedYet = new byte[len]; + System.arraycopy(buffer, offset, lenNotSupportedYet, 0, len); + buffer = lenNotSupportedYet; + offset = 0; + } + + pushback(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} - * @param off - * the offset - * @param len - * the length in bytes * * @return the number of bytes read * * @throws IOException * in case of I/O error */ - protected int read(InputStream in, byte[] buffer, int off, int len) - throws IOException { - return in.read(buffer, off, len); + protected int read(InputStream in, byte[] buffer) throws IOException { + return in.read(buffer, 0, buffer.length); } /** @@ -503,7 +591,7 @@ public class BufferedInputStream extends InputStream { return false; } - return (start < stop) || !eof; + return !backBuffers.isEmpty() || (start < stop) || !eof; } /**