X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Futils%2FNextableInputStream.java;h=f221cbbda9aa2ce9ad0eff7f215c725cbc886ab0;hb=a26188d393b11040b8ee8476338a73bfadabffb6;hp=6f3afc2b261fe439f65c79dbce20602be3bfdd53;hpb=4098af704dfa22ce4a60003940753c28030374fa;p=nikiroo-utils.git diff --git a/src/be/nikiroo/utils/NextableInputStream.java b/src/be/nikiroo/utils/NextableInputStream.java index 6f3afc2..f221cbb 100644 --- a/src/be/nikiroo/utils/NextableInputStream.java +++ b/src/be/nikiroo/utils/NextableInputStream.java @@ -2,118 +2,234 @@ package be.nikiroo.utils; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -public class NextableInputStream extends InputStream { - private List steps = new ArrayList(); - private NextableInputStreamStep step = null; - - private InputStream in; - private boolean eof; - private int pos = 0; - private int len = 0; - private byte[] buffer = new byte[4096]; - - public NextableInputStream(InputStream in) { - this.in = in; +/** + * This {@link InputStream} can be separated into sub-streams (you can process + * it as a normal {@link InputStream} but, when it is spent, you can call + * {@link NextableInputStream#next()} on it to unlock new data). + *

+ * The separation in sub-streams is done via {@link NextableInputStreamStep}. + * + * @author niki + */ +public class NextableInputStream extends BufferedInputStream { + private NextableInputStreamStep step; + private boolean started; + private boolean stopped; + + /** + * Create a new {@link NextableInputStream} that wraps the given + * {@link InputStream}. + * + * @param in + * the {@link InputStream} to wrap + * @param step + * how to separate it into sub-streams (can be NULL, but in that + * case it will behave as a normal {@link InputStream}) + */ + public NextableInputStream(InputStream in, NextableInputStreamStep step) { + super(in); + this.step = step; } - public void addStep(NextableInputStreamStep step) { - steps.add(step); + /** + * Create a new {@link NextableInputStream} that wraps the given bytes array + * as a data source. + * + * @param in + * the array to wrap, cannot be NULL + * @param step + * how to separate it into sub-streams (can be NULL, but in that + * case it will behave as a normal {@link InputStream}) + */ + public NextableInputStream(byte[] in, NextableInputStreamStep step) { + this(in, step, 0, in.length); } - public boolean next() { - if (!hasMoreData() && step != null) { - len = step.getResumeLen(); - pos += step.getSkip(); - eof = false; - step = null; - - checkNexts(false); - - return true; - } - - return false; + /** + * Create a new {@link NextableInputStream} that wraps the given bytes array + * as a data source. + * + * @param in + * the array to wrap, cannot be NULL + * @param step + * how to separate it into sub-streams (can be NULL, but in that + * case it will behave as a normal {@link InputStream}) + * @param offset + * the offset to start the reading at + * @param length + * the number of bytes to take into account in the array, + * starting from the offset + * + * @throws NullPointerException + * if the array is NULL + * @throws IndexOutOfBoundsException + * if the offset and length do not correspond to the given array + */ + public NextableInputStream(byte[] in, NextableInputStreamStep step, + int offset, int length) { + super(in, offset, length); + this.step = step; + checkBuffer(true); } - @Override - public int read() throws IOException { - preRead(); - if (eof) { - return -1; - } + /** + * Unblock the processing of the next sub-stream. + *

+ * It can only be called when the "current" stream is spent (i.e., you must + * first process the stream until it is spent). + *

+ * We consider that when the under-laying {@link InputStream} is also spent, + * we cannot have a next sub-stream (it will thus return FALSE). + *

+ * {@link IOException}s can happen when we have no data available in the + * buffer; in that case, we fetch more data to know if we can have a next + * sub-stream or not. + * + * @return TRUE if we unblocked the next sub-stream, FALSE if not + * + * @throws IOException + * in case of I/O error or if the stream is closed + */ + public boolean next() throws IOException { + return next(false); + } - return buffer[pos++]; + /** + * Unblock the next sub-stream as would have done + * {@link NextableInputStream#next()}, but disable the sub-stream systems. + *

+ * That is, the next stream, if any, will be the last one and will not be + * subject to the {@link NextableInputStreamStep}. + * + * @return TRUE if we unblocked the next sub-stream, FALSE if not + * + * @throws IOException + * in case of I/O error or if the stream is closed + */ + public boolean nextAll() throws IOException { + return next(true); } + /** + * Check if this stream is totally spent (no more data to read or to + * process). + *

+ * Note: an empty stream that is still not started will return FALSE, as we + * don't know yet if it is empty. + * + * @return TRUE if it is + */ @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); + public boolean eof() { + return super.eof(); } - + + /** + * Check if we still have some data in the buffer and, if not, fetch some. + * + * @return TRUE if we fetched some data, FALSE if there are still some in + * the buffer + * + * @throws IOException + * in case of I/O error + */ @Override - public int read(byte[] b, int boff, int blen) throws IOException { - if (b == null) { - throw new NullPointerException(); - } else if (boff < 0 || blen < 0 || blen > b.length - boff) { - throw new IndexOutOfBoundsException(); - } else if (blen == 0) { - return 0; + protected boolean preRead() throws IOException { + if (!stopped) { + boolean bufferChanged = super.preRead(); + checkBuffer(true); + return bufferChanged; } - int done = 0; - while (hasMoreData() && done < blen) { - preRead(); - if (hasMoreData()) { - for (int i = pos; i < blen && i < len; i++) { - b[boff + done] = buffer[i]; - pos++; - done++; - } - } + if (start >= stop) { + eof = true; } - return done > 0 ? done : -1; + return false; } + /** + * We have more data available in the buffer or we can fetch more. + * + * @return TRUE if it is the case, FALSE if not + */ @Override - public int available() throws IOException { - return Math.max(0, len - pos); + protected boolean hasMoreData() { + return started && super.hasMoreData(); } - private void preRead() throws IOException { - if (!eof && in != null && pos >= len && step == null) { - pos = 0; - len = in.read(buffer); - checkNexts(true); - } + /** + * Check that the buffer didn't overshot to the next item, and fix + * {@link NextableInputStream#stop} if needed. + *

+ * If {@link NextableInputStream#stop} is fixed, + * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped} + * are set to TRUE. + * + * @param newBuffer + * we changed the buffer, we need to clear some information in + * the {@link NextableInputStreamStep} + */ + private void checkBuffer(boolean newBuffer) { + if (step != null && stop > 0) { + if (newBuffer) { + step.clearBuffer(); + } - if (pos >= len) { - eof = true; + int stopAt = step.stop(buffer, start, stop); + if (stopAt >= 0) { + stop = stopAt; + eof = true; + stopped = true; + } } } - private boolean hasMoreData() { - return !(eof && pos >= len); - } + /** + * The implementation of {@link NextableInputStream#next()} and + * {@link NextableInputStream#nextAll()}. + * + * @param all + * TRUE for {@link NextableInputStream#nextAll()}, FALSE for + * {@link NextableInputStream#next()} + * + * @return TRUE if we unblocked the next sub-stream, FALSE if not + * + * @throws IOException + * in case of I/O error or if the stream is closed + */ + private boolean next(boolean all) throws IOException { + checkClose(); + + if (!started) { + // First call before being allowed to read + started = true; + + if (all) { + step = null; + } + + return true; + } - private void checkNexts(boolean newBuffer) { - if (!eof) { - for (NextableInputStreamStep step : steps) { - if (newBuffer) { - step.clearBuffer(); - } - - int stopAt = step.stop(buffer, pos, len); - if (stopAt >= 0) { - this.step = step; - len = stopAt; - eof = true; - break; - } + if (step != null && !hasMoreData() && stopped) { + stop = step.getResumeLen(); + start += step.getResumeSkip(); + eof = false; + + if (all) { + step = null; + } + + if (!preRead()) { + checkBuffer(false); } + + // consider that if EOF, there is no next + return hasMoreData(); } + + return false; } }