Commit | Line | Data |
---|---|---|
8e76f6ab | 1 | package be.nikiroo.utils.streams; |
2e7584da NR |
2 | |
3 | import java.io.IOException; | |
4 | import java.io.InputStream; | |
12784931 NR |
5 | import java.io.UnsupportedEncodingException; |
6 | import java.util.Arrays; | |
2e7584da | 7 | |
63b46ca9 NR |
8 | /** |
9 | * This {@link InputStream} can be separated into sub-streams (you can process | |
10 | * it as a normal {@link InputStream} but, when it is spent, you can call | |
11 | * {@link NextableInputStream#next()} on it to unlock new data). | |
12 | * <p> | |
13 | * The separation in sub-streams is done via {@link NextableInputStreamStep}. | |
14 | * | |
15 | * @author niki | |
16 | */ | |
33895a7b | 17 | public class NextableInputStream extends BufferedInputStream { |
63b46ca9 | 18 | private NextableInputStreamStep step; |
d6f9bd9f | 19 | private boolean started; |
63b46ca9 | 20 | private boolean stopped; |
2e7584da | 21 | |
63b46ca9 NR |
22 | /** |
23 | * Create a new {@link NextableInputStream} that wraps the given | |
24 | * {@link InputStream}. | |
25 | * | |
26 | * @param in | |
27 | * the {@link InputStream} to wrap | |
28 | * @param step | |
29 | * how to separate it into sub-streams (can be NULL, but in that | |
30 | * case it will behave as a normal {@link InputStream}) | |
31 | */ | |
32 | public NextableInputStream(InputStream in, NextableInputStreamStep step) { | |
33895a7b | 33 | super(in); |
63b46ca9 | 34 | this.step = step; |
d6f9bd9f NR |
35 | } |
36 | ||
37 | /** | |
38 | * Create a new {@link NextableInputStream} that wraps the given bytes array | |
39 | * as a data source. | |
40 | * | |
41 | * @param in | |
42 | * the array to wrap, cannot be NULL | |
43 | * @param step | |
44 | * how to separate it into sub-streams (can be NULL, but in that | |
45 | * case it will behave as a normal {@link InputStream}) | |
46 | */ | |
47 | public NextableInputStream(byte[] in, NextableInputStreamStep step) { | |
48 | this(in, step, 0, in.length); | |
49 | } | |
50 | ||
51 | /** | |
52 | * Create a new {@link NextableInputStream} that wraps the given bytes array | |
53 | * as a data source. | |
54 | * | |
55 | * @param in | |
56 | * the array to wrap, cannot be NULL | |
57 | * @param step | |
58 | * how to separate it into sub-streams (can be NULL, but in that | |
59 | * case it will behave as a normal {@link InputStream}) | |
60 | * @param offset | |
61 | * the offset to start the reading at | |
62 | * @param length | |
63 | * the number of bytes to take into account in the array, | |
64 | * starting from the offset | |
65 | * | |
66 | * @throws NullPointerException | |
67 | * if the array is NULL | |
68 | * @throws IndexOutOfBoundsException | |
69 | * if the offset and length do not correspond to the given array | |
70 | */ | |
71 | public NextableInputStream(byte[] in, NextableInputStreamStep step, | |
72 | int offset, int length) { | |
33895a7b | 73 | super(in, offset, length); |
d6f9bd9f | 74 | this.step = step; |
d6f9bd9f NR |
75 | checkBuffer(true); |
76 | } | |
77 | ||
63b46ca9 NR |
78 | /** |
79 | * Unblock the processing of the next sub-stream. | |
80 | * <p> | |
81 | * It can only be called when the "current" stream is spent (i.e., you must | |
82 | * first process the stream until it is spent). | |
83 | * <p> | |
63b46ca9 NR |
84 | * {@link IOException}s can happen when we have no data available in the |
85 | * buffer; in that case, we fetch more data to know if we can have a next | |
86 | * sub-stream or not. | |
177e14c9 NR |
87 | * <p> |
88 | * This is can be a blocking call when data need to be fetched. | |
63b46ca9 | 89 | * |
d2219aa0 NR |
90 | * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e., |
91 | * FALSE when there are no more sub-streams to fetch) | |
63b46ca9 NR |
92 | * |
93 | * @throws IOException | |
d6f9bd9f | 94 | * in case of I/O error or if the stream is closed |
63b46ca9 NR |
95 | */ |
96 | public boolean next() throws IOException { | |
d6f9bd9f NR |
97 | return next(false); |
98 | } | |
4098af70 | 99 | |
d6f9bd9f NR |
100 | /** |
101 | * Unblock the next sub-stream as would have done | |
102 | * {@link NextableInputStream#next()}, but disable the sub-stream systems. | |
103 | * <p> | |
104 | * That is, the next stream, if any, will be the last one and will not be | |
105 | * subject to the {@link NextableInputStreamStep}. | |
177e14c9 NR |
106 | * <p> |
107 | * This is can be a blocking call when data need to be fetched. | |
d6f9bd9f NR |
108 | * |
109 | * @return TRUE if we unblocked the next sub-stream, FALSE if not | |
110 | * | |
111 | * @throws IOException | |
112 | * in case of I/O error or if the stream is closed | |
113 | */ | |
114 | public boolean nextAll() throws IOException { | |
115 | return next(true); | |
116 | } | |
63b46ca9 | 117 | |
eeb2cc17 NR |
118 | /** |
119 | * Check if this stream is totally spent (no more data to read or to | |
120 | * process). | |
121 | * <p> | |
028ff7c2 | 122 | * Note: when the stream is divided into sub-streams, each sub-stream will |
d2219aa0 | 123 | * report its own eof when spent. |
eeb2cc17 NR |
124 | * |
125 | * @return TRUE if it is | |
028ff7c2 NR |
126 | * |
127 | * @throws IOException | |
128 | * in case of I/O error | |
eeb2cc17 NR |
129 | */ |
130 | @Override | |
028ff7c2 | 131 | public boolean eof() throws IOException { |
eeb2cc17 NR |
132 | return super.eof(); |
133 | } | |
028ff7c2 | 134 | |
63b46ca9 NR |
135 | /** |
136 | * Check if we still have some data in the buffer and, if not, fetch some. | |
137 | * | |
138 | * @return TRUE if we fetched some data, FALSE if there are still some in | |
139 | * the buffer | |
140 | * | |
141 | * @throws IOException | |
142 | * in case of I/O error | |
143 | */ | |
33895a7b NR |
144 | @Override |
145 | protected boolean preRead() throws IOException { | |
146 | if (!stopped) { | |
147 | boolean bufferChanged = super.preRead(); | |
028ff7c2 | 148 | checkBuffer(bufferChanged); |
33895a7b | 149 | return bufferChanged; |
2e7584da NR |
150 | } |
151 | ||
a26188d3 | 152 | if (start >= stop) { |
2e7584da NR |
153 | eof = true; |
154 | } | |
63b46ca9 | 155 | |
33895a7b | 156 | return false; |
2e7584da | 157 | } |
4098af70 | 158 | |
33895a7b NR |
159 | @Override |
160 | protected boolean hasMoreData() { | |
161 | return started && super.hasMoreData(); | |
4098af70 N |
162 | } |
163 | ||
63b46ca9 NR |
164 | /** |
165 | * Check that the buffer didn't overshot to the next item, and fix | |
a26188d3 | 166 | * {@link NextableInputStream#stop} if needed. |
63b46ca9 | 167 | * <p> |
a26188d3 | 168 | * If {@link NextableInputStream#stop} is fixed, |
63b46ca9 NR |
169 | * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped} |
170 | * are set to TRUE. | |
171 | * | |
172 | * @param newBuffer | |
173 | * we changed the buffer, we need to clear some information in | |
174 | * the {@link NextableInputStreamStep} | |
175 | */ | |
176 | private void checkBuffer(boolean newBuffer) { | |
028ff7c2 | 177 | if (step != null && stop >= 0) { |
63b46ca9 NR |
178 | if (newBuffer) { |
179 | step.clearBuffer(); | |
180 | } | |
4098af70 | 181 | |
028ff7c2 | 182 | int stopAt = step.stop(buffer, start, stop, eof); |
63b46ca9 | 183 | if (stopAt >= 0) { |
a26188d3 | 184 | stop = stopAt; |
63b46ca9 NR |
185 | eof = true; |
186 | stopped = true; | |
4098af70 N |
187 | } |
188 | } | |
189 | } | |
d6f9bd9f NR |
190 | |
191 | /** | |
192 | * The implementation of {@link NextableInputStream#next()} and | |
193 | * {@link NextableInputStream#nextAll()}. | |
177e14c9 NR |
194 | * <p> |
195 | * This is can be a blocking call when data need to be fetched. | |
d6f9bd9f NR |
196 | * |
197 | * @param all | |
198 | * TRUE for {@link NextableInputStream#nextAll()}, FALSE for | |
199 | * {@link NextableInputStream#next()} | |
200 | * | |
d2219aa0 NR |
201 | * @return TRUE if we unblocked the next sub-stream, FALSE if not (i.e., |
202 | * FALSE when there are no more sub-streams to fetch) | |
d6f9bd9f NR |
203 | * |
204 | * @throws IOException | |
205 | * in case of I/O error or if the stream is closed | |
206 | */ | |
207 | private boolean next(boolean all) throws IOException { | |
208 | checkClose(); | |
209 | ||
210 | if (!started) { | |
211 | // First call before being allowed to read | |
212 | started = true; | |
213 | ||
214 | if (all) { | |
215 | step = null; | |
216 | } | |
d2219aa0 NR |
217 | |
218 | return true; | |
d6f9bd9f NR |
219 | } |
220 | ||
d2219aa0 NR |
221 | // If started, must be stopped an no more data to continue |
222 | // i.e., sub-stream must be spent | |
223 | if (!stopped || hasMoreData()) { | |
224 | return false; | |
225 | } | |
226 | ||
227 | if (step != null) { | |
a26188d3 NR |
228 | stop = step.getResumeLen(); |
229 | start += step.getResumeSkip(); | |
028ff7c2 NR |
230 | eof = step.getResumeEof(); |
231 | stopped = false; | |
d6f9bd9f NR |
232 | |
233 | if (all) { | |
234 | step = null; | |
235 | } | |
236 | ||
028ff7c2 | 237 | checkBuffer(false); |
177e14c9 | 238 | |
d2219aa0 | 239 | return true; |
d6f9bd9f NR |
240 | } |
241 | ||
d2219aa0 NR |
242 | return false; |
243 | ||
244 | // // consider that if EOF, there is no next | |
245 | // if (start >= stop) { | |
246 | // // Make sure, block if necessary | |
247 | // preRead(); | |
248 | // | |
249 | // return hasMoreData(); | |
250 | // } | |
251 | // | |
252 | // return true; | |
177e14c9 NR |
253 | } |
254 | ||
7194ac50 NR |
255 | /** |
256 | * Display a DEBUG {@link String} representation of this object. | |
257 | * <p> | |
258 | * Do <b>not</b> use for release code. | |
259 | */ | |
260 | @Override | |
261 | public String toString() { | |
12784931 NR |
262 | String data = ""; |
263 | if (stop > 0) { | |
264 | try { | |
265 | data = new String(Arrays.copyOfRange(buffer, 0, stop), "UTF-8"); | |
266 | } catch (UnsupportedEncodingException e) { | |
267 | } | |
268 | if (data.length() > 50) { | |
269 | data = data.substring(0, 47) + "..."; | |
270 | } | |
271 | } | |
177e14c9 | 272 | String rep = String.format( |
12784931 | 273 | "Nextable %s: %d -> %d [eof: %s] [more data: %s]: %s", |
177e14c9 | 274 | (stopped ? "stopped" : "running"), start, stop, "" + eof, "" |
12784931 | 275 | + hasMoreData(), data); |
177e14c9 NR |
276 | |
277 | return rep; | |
d6f9bd9f | 278 | } |
2e7584da | 279 | } |