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 * We consider that when the under-laying {@link InputStream} is also spent,
85 * we cannot have a next sub-stream (it will thus return FALSE).
87 * {@link IOException}s can happen when we have no data available in the
88 * buffer; in that case, we fetch more data to know if we can have a next
91 * This is can be a blocking call when data need to be fetched.
93 * @return TRUE if we unblocked the next sub-stream, FALSE if not
96 * in case of I/O error or if the stream is closed
98 public boolean next() throws IOException
{
103 * Unblock the next sub-stream as would have done
104 * {@link NextableInputStream#next()}, but disable the sub-stream systems.
106 * That is, the next stream, if any, will be the last one and will not be
107 * subject to the {@link NextableInputStreamStep}.
109 * This is can be a blocking call when data need to be fetched.
111 * @return TRUE if we unblocked the next sub-stream, FALSE if not
113 * @throws IOException
114 * in case of I/O error or if the stream is closed
116 public boolean nextAll() throws IOException
{
121 * Check if this stream is totally spent (no more data to read or to
124 * Note: when the stream is divided into sub-streams, each sub-stream will
125 * report it is eof when emptied.
127 * @return TRUE if it is
129 * @throws IOException
130 * in case of I/O error
133 public boolean eof() throws IOException
{
138 * Check if we still have some data in the buffer and, if not, fetch some.
140 * @return TRUE if we fetched some data, FALSE if there are still some in
143 * @throws IOException
144 * in case of I/O error
147 protected boolean preRead() throws IOException
{
149 boolean bufferChanged
= super.preRead();
150 checkBuffer(bufferChanged
);
151 return bufferChanged
;
162 * We have more data available in the buffer or we can fetch more.
164 * @return TRUE if it is the case, FALSE if not
167 protected boolean hasMoreData() {
168 return started
&& super.hasMoreData();
172 * Check that the buffer didn't overshot to the next item, and fix
173 * {@link NextableInputStream#stop} if needed.
175 * If {@link NextableInputStream#stop} is fixed,
176 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
180 * we changed the buffer, we need to clear some information in
181 * the {@link NextableInputStreamStep}
183 private void checkBuffer(boolean newBuffer
) {
184 if (step
!= null && stop
>= 0) {
189 int stopAt
= step
.stop(buffer
, start
, stop
, eof
);
199 * The implementation of {@link NextableInputStream#next()} and
200 * {@link NextableInputStream#nextAll()}.
202 * This is can be a blocking call when data need to be fetched.
205 * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
206 * {@link NextableInputStream#next()}
208 * @return TRUE if we unblocked the next sub-stream, FALSE if not
210 * @throws IOException
211 * in case of I/O error or if the stream is closed
213 private boolean next(boolean all
) throws IOException
{
217 // First call before being allowed to read
225 if (step
!= null && !hasMoreData() && stopped
) {
226 stop
= step
.getResumeLen();
227 start
+= step
.getResumeSkip();
228 eof
= step
.getResumeEof();
238 // consider that if EOF, there is no next
240 // Make sure, block if necessary
243 return hasMoreData();
249 public String
DEBUG() {
253 data
= new String(Arrays
.copyOfRange(buffer
, 0, stop
), "UTF-8");
254 } catch (UnsupportedEncodingException e
) {
256 if (data
.length() > 50) {
257 data
= data
.substring(0, 47) + "...";
260 String rep
= String
.format(
261 "Nextable %s: %d -> %d [eof: %s] [more data: %s]: %s",
262 (stopped ?
"stopped" : "running"), start
, stop
, "" + eof
, ""
263 + hasMoreData(), data
);