Simple NextableInputStream
[nikiroo-utils.git] / src / be / nikiroo / utils / NextableInputStream.java
CommitLineData
2e7584da
NR
1package be.nikiroo.utils;
2
3import java.io.IOException;
4import java.io.InputStream;
5
63b46ca9
NR
6/**
7 * This {@link InputStream} can be separated into sub-streams (you can process
8 * it as a normal {@link InputStream} but, when it is spent, you can call
9 * {@link NextableInputStream#next()} on it to unlock new data).
10 * <p>
11 * The separation in sub-streams is done via {@link NextableInputStreamStep}.
12 *
13 * @author niki
14 */
2e7584da 15public class NextableInputStream extends InputStream {
63b46ca9
NR
16 private NextableInputStreamStep step;
17 private boolean stopped;
4098af70 18
2e7584da
NR
19 private InputStream in;
20 private boolean eof;
21 private int pos = 0;
22 private int len = 0;
23 private byte[] buffer = new byte[4096];
24
63b46ca9
NR
25 /**
26 * Create a new {@link NextableInputStream} that wraps the given
27 * {@link InputStream}.
28 *
29 * @param in
30 * the {@link InputStream} to wrap
31 * @param step
32 * how to separate it into sub-streams (can be NULL, but in that
33 * case it will behave as a normal {@link InputStream})
34 */
35 public NextableInputStream(InputStream in, NextableInputStreamStep step) {
2e7584da 36 this.in = in;
63b46ca9 37 this.step = step;
2e7584da
NR
38 }
39
63b46ca9
NR
40 /**
41 * Unblock the processing of the next sub-stream.
42 * <p>
43 * It can only be called when the "current" stream is spent (i.e., you must
44 * first process the stream until it is spent).
45 * <p>
46 * We consider that when the under-laying {@link InputStream} is also spent,
47 * we cannot have a next sub-stream (it will thus return FALSE).
48 * <p>
49 * {@link IOException}s can happen when we have no data available in the
50 * buffer; in that case, we fetch more data to know if we can have a next
51 * sub-stream or not.
52 *
53 * @return TRUE if we unblocked the next sub-stream, FALSE if not
54 *
55 * @throws IOException
56 * in case of I/O error
57 */
58 public boolean next() throws IOException {
59 if (!hasMoreData() && stopped) {
4098af70 60 len = step.getResumeLen();
63b46ca9 61 pos += step.getResumeSkip();
4098af70 62 eof = false;
4098af70 63
63b46ca9
NR
64 if (!preRead()) {
65 checkBuffer(false);
66 }
67
68 // consider that if EOF, there is no next
69 return hasMoreData();
4098af70
N
70 }
71
72 return false;
73 }
74
2e7584da
NR
75 @Override
76 public int read() throws IOException {
77 preRead();
78 if (eof) {
79 return -1;
80 }
81
82 return buffer[pos++];
83 }
84
85 @Override
86 public int read(byte[] b) throws IOException {
87 return read(b, 0, b.length);
88 }
89
90 @Override
91 public int read(byte[] b, int boff, int blen) throws IOException {
92 if (b == null) {
93 throw new NullPointerException();
94 } else if (boff < 0 || blen < 0 || blen > b.length - boff) {
95 throw new IndexOutOfBoundsException();
96 } else if (blen == 0) {
97 return 0;
98 }
99
100 int done = 0;
4098af70 101 while (hasMoreData() && done < blen) {
2e7584da 102 preRead();
4098af70
N
103 if (hasMoreData()) {
104 for (int i = pos; i < blen && i < len; i++) {
105 b[boff + done] = buffer[i];
106 pos++;
107 done++;
108 }
2e7584da
NR
109 }
110 }
111
112 return done > 0 ? done : -1;
113 }
114
4098af70
N
115 @Override
116 public int available() throws IOException {
117 return Math.max(0, len - pos);
118 }
119
63b46ca9
NR
120 /**
121 * Check if we still have some data in the buffer and, if not, fetch some.
122 *
123 * @return TRUE if we fetched some data, FALSE if there are still some in
124 * the buffer
125 *
126 * @throws IOException
127 * in case of I/O error
128 */
129 private boolean preRead() throws IOException {
130 boolean hasRead = false;
131 if (!eof && in != null && pos >= len && !stopped) {
2e7584da
NR
132 pos = 0;
133 len = in.read(buffer);
63b46ca9
NR
134 checkBuffer(true);
135 hasRead = true;
2e7584da
NR
136 }
137
138 if (pos >= len) {
139 eof = true;
140 }
63b46ca9
NR
141
142 return hasRead;
2e7584da 143 }
4098af70 144
63b46ca9
NR
145 /**
146 * We have more data available in the buffer or we can fetch more.
147 *
148 * @return TRUE if it is the case, FALSE if not
149 */
4098af70
N
150 private boolean hasMoreData() {
151 return !(eof && pos >= len);
152 }
153
63b46ca9
NR
154 /**
155 * Check that the buffer didn't overshot to the next item, and fix
156 * {@link NextableInputStream#len} if needed.
157 * <p>
158 * If {@link NextableInputStream#len} is fixed,
159 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
160 * are set to TRUE.
161 *
162 * @param newBuffer
163 * we changed the buffer, we need to clear some information in
164 * the {@link NextableInputStreamStep}
165 */
166 private void checkBuffer(boolean newBuffer) {
167 if (step != null) {
168 if (newBuffer) {
169 step.clearBuffer();
170 }
4098af70 171
63b46ca9
NR
172 int stopAt = step.stop(buffer, pos, len);
173 if (stopAt >= 0) {
174 len = stopAt;
175 eof = true;
176 stopped = true;
4098af70
N
177 }
178 }
179 }
2e7584da 180}