1 package be
.nikiroo
.utils
.streams
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.io
.UnsupportedEncodingException
;
6 import java
.util
.Arrays
;
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).
13 * The separation in sub-streams is done via {@link NextableInputStreamStep}.
17 public class NextableInputStream
extends BufferedInputStream
{
18 private NextableInputStreamStep step
;
19 private boolean started
;
20 private boolean stopped
;
23 * Create a new {@link NextableInputStream} that wraps the given
24 * {@link InputStream}.
27 * the {@link InputStream} to wrap
29 * how to separate it into sub-streams (can be NULL, but in that
30 * case it will behave as a normal {@link InputStream})
32 public NextableInputStream(InputStream in
, NextableInputStreamStep step
) {
38 * Create a new {@link NextableInputStream} that wraps the given bytes array
42 * the array to wrap, cannot be NULL
44 * how to separate it into sub-streams (can be NULL, but in that
45 * case it will behave as a normal {@link InputStream})
47 public NextableInputStream(byte[] in
, NextableInputStreamStep step
) {
48 this(in
, step
, 0, in
.length
);
52 * Create a new {@link NextableInputStream} that wraps the given bytes array
56 * the array to wrap, cannot be NULL
58 * how to separate it into sub-streams (can be NULL, but in that
59 * case it will behave as a normal {@link InputStream})
61 * the offset to start the reading at
63 * the number of bytes to take into account in the array,
64 * starting from the offset
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
71 public NextableInputStream(byte[] in
, NextableInputStreamStep step
,
72 int offset
, int length
) {
73 super(in
, offset
, length
);
79 * Unblock the processing of the next sub-stream.
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).
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
88 * This is can be a blocking call when data need to be fetched.
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)
94 * in case of I/O error or if the stream is closed
96 public boolean next() throws IOException
{
101 * Unblock the next sub-stream as would have done
102 * {@link NextableInputStream#next()}, but disable the sub-stream systems.
104 * That is, the next stream, if any, will be the last one and will not be
105 * subject to the {@link NextableInputStreamStep}.
107 * This is can be a blocking call when data need to be fetched.
109 * @return TRUE if we unblocked the next sub-stream, FALSE if not
111 * @throws IOException
112 * in case of I/O error or if the stream is closed
114 public boolean nextAll() throws IOException
{
119 * Check if this stream is totally spent (no more data to read or to
122 * Note: when the stream is divided into sub-streams, each sub-stream will
123 * report its own eof when spent.
125 * @return TRUE if it is
127 * @throws IOException
128 * in case of I/O error
131 public boolean eof() throws IOException
{
136 * Check if we still have some data in the buffer and, if not, fetch some.
138 * @return TRUE if we fetched some data, FALSE if there are still some in
141 * @throws IOException
142 * in case of I/O error
145 protected boolean preRead() throws IOException
{
147 boolean bufferChanged
= super.preRead();
148 checkBuffer(bufferChanged
);
149 return bufferChanged
;
160 protected boolean hasMoreData() {
161 return started
&& super.hasMoreData();
165 * Check that the buffer didn't overshot to the next item, and fix
166 * {@link NextableInputStream#stop} if needed.
168 * If {@link NextableInputStream#stop} is fixed,
169 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
173 * we changed the buffer, we need to clear some information in
174 * the {@link NextableInputStreamStep}
176 private void checkBuffer(boolean newBuffer
) {
177 if (step
!= null && stop
>= 0) {
182 int stopAt
= step
.stop(buffer
, start
, stop
, eof
);
192 * The implementation of {@link NextableInputStream#next()} and
193 * {@link NextableInputStream#nextAll()}.
195 * This is can be a blocking call when data need to be fetched.
198 * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
199 * {@link NextableInputStream#next()}
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)
204 * @throws IOException
205 * in case of I/O error or if the stream is closed
207 private boolean next(boolean all
) throws IOException
{
211 // First call before being allowed to read
221 // If started, must be stopped and no more data to continue
222 // i.e., sub-stream must be spent
223 if (!stopped
|| hasMoreData()) {
228 stop
= step
.getResumeLen();
229 start
+= step
.getResumeSkip();
230 eof
= step
.getResumeEof();
244 // // consider that if EOF, there is no next
245 // if (start >= stop) {
246 // // Make sure, block if necessary
249 // return hasMoreData();
256 * Display a DEBUG {@link String} representation of this object.
258 * Do <b>not</b> use for release code.
261 public String
toString() {
265 data
= new String(Arrays
.copyOfRange(buffer
, 0, stop
), "UTF-8");
266 } catch (UnsupportedEncodingException e
) {
268 if (data
.length() > 200) {
269 data
= data
.substring(0, 197) + "...";
272 String rep
= String
.format(
273 "Nextable %s: %d -> %d [eof: %s] [more data: %s]: %s",
274 (stopped ?
"stopped" : "running"), start
, stop
, "" + eof
, ""
275 + hasMoreData(), data
);