Commit | Line | Data |
---|---|---|
2e7584da NR |
1 | package be.nikiroo.utils; |
2 | ||
3 | import java.io.IOException; | |
4 | import 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 | */ | |
33895a7b | 15 | public class NextableInputStream extends BufferedInputStream { |
63b46ca9 | 16 | private NextableInputStreamStep step; |
d6f9bd9f | 17 | private boolean started; |
63b46ca9 | 18 | private boolean stopped; |
2e7584da | 19 | |
63b46ca9 NR |
20 | /** |
21 | * Create a new {@link NextableInputStream} that wraps the given | |
22 | * {@link InputStream}. | |
23 | * | |
24 | * @param in | |
25 | * the {@link InputStream} to wrap | |
26 | * @param step | |
27 | * how to separate it into sub-streams (can be NULL, but in that | |
28 | * case it will behave as a normal {@link InputStream}) | |
29 | */ | |
30 | public NextableInputStream(InputStream in, NextableInputStreamStep step) { | |
33895a7b | 31 | super(in); |
63b46ca9 | 32 | this.step = step; |
d6f9bd9f NR |
33 | } |
34 | ||
35 | /** | |
36 | * Create a new {@link NextableInputStream} that wraps the given bytes array | |
37 | * as a data source. | |
38 | * | |
39 | * @param in | |
40 | * the array to wrap, cannot be NULL | |
41 | * @param step | |
42 | * how to separate it into sub-streams (can be NULL, but in that | |
43 | * case it will behave as a normal {@link InputStream}) | |
44 | */ | |
45 | public NextableInputStream(byte[] in, NextableInputStreamStep step) { | |
46 | this(in, step, 0, in.length); | |
47 | } | |
48 | ||
49 | /** | |
50 | * Create a new {@link NextableInputStream} that wraps the given bytes array | |
51 | * as a data source. | |
52 | * | |
53 | * @param in | |
54 | * the array to wrap, cannot be NULL | |
55 | * @param step | |
56 | * how to separate it into sub-streams (can be NULL, but in that | |
57 | * case it will behave as a normal {@link InputStream}) | |
58 | * @param offset | |
59 | * the offset to start the reading at | |
60 | * @param length | |
61 | * the number of bytes to take into account in the array, | |
62 | * starting from the offset | |
63 | * | |
64 | * @throws NullPointerException | |
65 | * if the array is NULL | |
66 | * @throws IndexOutOfBoundsException | |
67 | * if the offset and length do not correspond to the given array | |
68 | */ | |
69 | public NextableInputStream(byte[] in, NextableInputStreamStep step, | |
70 | int offset, int length) { | |
33895a7b | 71 | super(in, offset, length); |
d6f9bd9f | 72 | this.step = step; |
d6f9bd9f NR |
73 | checkBuffer(true); |
74 | } | |
75 | ||
63b46ca9 NR |
76 | /** |
77 | * Unblock the processing of the next sub-stream. | |
78 | * <p> | |
79 | * It can only be called when the "current" stream is spent (i.e., you must | |
80 | * first process the stream until it is spent). | |
81 | * <p> | |
82 | * We consider that when the under-laying {@link InputStream} is also spent, | |
83 | * we cannot have a next sub-stream (it will thus return FALSE). | |
84 | * <p> | |
85 | * {@link IOException}s can happen when we have no data available in the | |
86 | * buffer; in that case, we fetch more data to know if we can have a next | |
87 | * sub-stream or not. | |
88 | * | |
89 | * @return TRUE if we unblocked the next sub-stream, FALSE if not | |
90 | * | |
91 | * @throws IOException | |
d6f9bd9f | 92 | * in case of I/O error or if the stream is closed |
63b46ca9 NR |
93 | */ |
94 | public boolean next() throws IOException { | |
d6f9bd9f NR |
95 | return next(false); |
96 | } | |
4098af70 | 97 | |
d6f9bd9f NR |
98 | /** |
99 | * Unblock the next sub-stream as would have done | |
100 | * {@link NextableInputStream#next()}, but disable the sub-stream systems. | |
101 | * <p> | |
102 | * That is, the next stream, if any, will be the last one and will not be | |
103 | * subject to the {@link NextableInputStreamStep}. | |
104 | * | |
105 | * @return TRUE if we unblocked the next sub-stream, FALSE if not | |
106 | * | |
107 | * @throws IOException | |
108 | * in case of I/O error or if the stream is closed | |
109 | */ | |
110 | public boolean nextAll() throws IOException { | |
111 | return next(true); | |
112 | } | |
63b46ca9 | 113 | |
63b46ca9 NR |
114 | /** |
115 | * Check if we still have some data in the buffer and, if not, fetch some. | |
116 | * | |
117 | * @return TRUE if we fetched some data, FALSE if there are still some in | |
118 | * the buffer | |
119 | * | |
120 | * @throws IOException | |
121 | * in case of I/O error | |
122 | */ | |
33895a7b NR |
123 | @Override |
124 | protected boolean preRead() throws IOException { | |
125 | if (!stopped) { | |
126 | boolean bufferChanged = super.preRead(); | |
63b46ca9 | 127 | checkBuffer(true); |
33895a7b | 128 | return bufferChanged; |
2e7584da NR |
129 | } |
130 | ||
131 | if (pos >= len) { | |
132 | eof = true; | |
133 | } | |
63b46ca9 | 134 | |
33895a7b | 135 | return false; |
2e7584da | 136 | } |
4098af70 | 137 | |
63b46ca9 NR |
138 | /** |
139 | * We have more data available in the buffer or we can fetch more. | |
140 | * | |
141 | * @return TRUE if it is the case, FALSE if not | |
142 | */ | |
33895a7b NR |
143 | @Override |
144 | protected boolean hasMoreData() { | |
145 | return started && super.hasMoreData(); | |
4098af70 N |
146 | } |
147 | ||
63b46ca9 NR |
148 | /** |
149 | * Check that the buffer didn't overshot to the next item, and fix | |
150 | * {@link NextableInputStream#len} if needed. | |
151 | * <p> | |
152 | * If {@link NextableInputStream#len} is fixed, | |
153 | * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped} | |
154 | * are set to TRUE. | |
155 | * | |
156 | * @param newBuffer | |
157 | * we changed the buffer, we need to clear some information in | |
158 | * the {@link NextableInputStreamStep} | |
159 | */ | |
160 | private void checkBuffer(boolean newBuffer) { | |
d6f9bd9f | 161 | if (step != null && len > 0) { |
63b46ca9 NR |
162 | if (newBuffer) { |
163 | step.clearBuffer(); | |
164 | } | |
4098af70 | 165 | |
63b46ca9 NR |
166 | int stopAt = step.stop(buffer, pos, len); |
167 | if (stopAt >= 0) { | |
168 | len = stopAt; | |
169 | eof = true; | |
170 | stopped = true; | |
4098af70 N |
171 | } |
172 | } | |
173 | } | |
d6f9bd9f NR |
174 | |
175 | /** | |
176 | * The implementation of {@link NextableInputStream#next()} and | |
177 | * {@link NextableInputStream#nextAll()}. | |
178 | * | |
179 | * @param all | |
180 | * TRUE for {@link NextableInputStream#nextAll()}, FALSE for | |
181 | * {@link NextableInputStream#next()} | |
182 | * | |
183 | * @return TRUE if we unblocked the next sub-stream, FALSE if not | |
184 | * | |
185 | * @throws IOException | |
186 | * in case of I/O error or if the stream is closed | |
187 | */ | |
188 | private boolean next(boolean all) throws IOException { | |
189 | checkClose(); | |
190 | ||
191 | if (!started) { | |
192 | // First call before being allowed to read | |
193 | started = true; | |
194 | ||
195 | if (all) { | |
196 | step = null; | |
197 | } | |
198 | ||
199 | return true; | |
200 | } | |
201 | ||
202 | if (step != null && !hasMoreData() && stopped) { | |
203 | len = step.getResumeLen(); | |
204 | pos += step.getResumeSkip(); | |
205 | eof = false; | |
206 | ||
207 | if (all) { | |
208 | step = null; | |
209 | } | |
210 | ||
211 | if (!preRead()) { | |
212 | checkBuffer(false); | |
213 | } | |
214 | ||
215 | // consider that if EOF, there is no next | |
216 | return hasMoreData(); | |
217 | } | |
218 | ||
219 | return false; | |
220 | } | |
2e7584da | 221 | } |