1 package be
.nikiroo
.utils
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
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).
11 * The separation in sub-streams is done via {@link NextableInputStreamStep}.
15 public class NextableInputStream
extends InputStream
{
16 private NextableInputStreamStep step
;
17 private boolean started
;
18 private boolean stopped
;
19 private boolean closed
;
21 private InputStream in
;
22 private int openCounter
;
26 private byte[] buffer
;
28 private long bytesRead
;
31 * Create a new {@link NextableInputStream} that wraps the given
32 * {@link InputStream}.
35 * the {@link InputStream} to wrap
37 * how to separate it into sub-streams (can be NULL, but in that
38 * case it will behave as a normal {@link InputStream})
40 public NextableInputStream(InputStream in
, NextableInputStreamStep step
) {
44 this.buffer
= new byte[4096];
50 * Create a new {@link NextableInputStream} that wraps the given bytes array
54 * the array to wrap, cannot be NULL
56 * how to separate it into sub-streams (can be NULL, but in that
57 * case it will behave as a normal {@link InputStream})
59 public NextableInputStream(byte[] in
, NextableInputStreamStep step
) {
60 this(in
, step
, 0, in
.length
);
64 * Create a new {@link NextableInputStream} that wraps the given bytes array
68 * the array to wrap, cannot be NULL
70 * how to separate it into sub-streams (can be NULL, but in that
71 * case it will behave as a normal {@link InputStream})
73 * the offset to start the reading at
75 * the number of bytes to take into account in the array,
76 * starting from the offset
78 * @throws NullPointerException
79 * if the array is NULL
80 * @throws IndexOutOfBoundsException
81 * if the offset and length do not correspond to the given array
83 public NextableInputStream(byte[] in
, NextableInputStreamStep step
,
84 int offset
, int length
) {
86 throw new NullPointerException();
87 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
88 throw new IndexOutOfBoundsException();
102 * Return this very same {@link NextableInputStream}, but keep a counter of
103 * how many streams were open this way. When calling
104 * {@link NextableInputStream#close()}, decrease this counter if it is not
105 * already zero instead of actually closing the stream.
107 * You are now responsible for it — you <b>must</b> close it.
109 * This method allows you to use a wrapping stream around this one and still
110 * close the wrapping stream.
112 * @return the same stream, but you are now responsible for closing it
114 * @throws IOException
115 * in case of I/O error or if the stream is closed
117 public synchronized InputStream
open() throws IOException
{
124 * Unblock the processing of the next sub-stream.
126 * It can only be called when the "current" stream is spent (i.e., you must
127 * first process the stream until it is spent).
129 * We consider that when the under-laying {@link InputStream} is also spent,
130 * we cannot have a next sub-stream (it will thus return FALSE).
132 * {@link IOException}s can happen when we have no data available in the
133 * buffer; in that case, we fetch more data to know if we can have a next
136 * @return TRUE if we unblocked the next sub-stream, FALSE if not
138 * @throws IOException
139 * in case of I/O error or if the stream is closed
141 public boolean next() throws IOException
{
146 * Unblock the next sub-stream as would have done
147 * {@link NextableInputStream#next()}, but disable the sub-stream systems.
149 * That is, the next stream, if any, will be the last one and will not be
150 * subject to the {@link NextableInputStreamStep}.
152 * @return TRUE if we unblocked the next sub-stream, FALSE if not
154 * @throws IOException
155 * in case of I/O error or if the stream is closed
157 public boolean nextAll() throws IOException
{
161 public boolean startWith() {
166 public boolean startWiths() {
172 * The number of bytes read from the under-laying {@link InputStream}.
174 * @return the number of bytes
176 public long getBytesRead() {
181 * Check if this stream is totally spent (no more data to read or to
184 * @return TRUE if it is
186 public boolean eof() {
187 return closed
|| (len
< 0 && !hasMoreData());
191 public int read() throws IOException
{
199 return buffer
[pos
++];
203 public int read(byte[] b
) throws IOException
{
204 return read(b
, 0, b
.length
);
208 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
212 throw new NullPointerException();
213 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
214 throw new IndexOutOfBoundsException();
215 } else if (blen
== 0) {
220 while (hasMoreData() && done
< blen
) {
223 for (int i
= pos
; i
< blen
&& i
< len
; i
++) {
224 b
[boff
+ done
] = buffer
[i
];
231 return done
> 0 ? done
: -1;
235 public long skip(long n
) throws IOException
{
236 // TODO Auto-generated method stub
237 return super.skip(n
);
241 public int available() {
246 return Math
.max(0, len
- pos
);
250 * Closes this stream and releases any system resources associated with the
253 * Including the under-laying {@link InputStream}.
255 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
256 * prior to this one, it will just decrease the internal count of how many
257 * open streams it held and do nothing else. The stream will actually be
258 * closed when you have called {@link NextableInputStream#close()} once more
259 * than {@link NextableInputStream#open()}.
261 * @exception IOException
262 * in case of I/O error
265 public synchronized void close() throws IOException
{
270 * Closes this stream and releases any system resources associated with the
273 * Including the under-laying {@link InputStream} if
274 * <tt>incudingSubStream</tt> is true.
276 * You can call this method multiple times, it will not cause an
277 * {@link IOException} for subsequent calls.
279 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
280 * prior to this one, it will just decrease the internal count of how many
281 * open streams it held and do nothing else. The stream will actually be
282 * closed when you have called {@link NextableInputStream#close()} once more
283 * than {@link NextableInputStream#open()}.
285 * @exception IOException
286 * in case of I/O error
288 public synchronized void close(boolean includingSubStream
)
291 if (openCounter
> 0) {
295 if (includingSubStream
&& in
!= null) {
303 * Check if we still have some data in the buffer and, if not, fetch some.
305 * @return TRUE if we fetched some data, FALSE if there are still some in
308 * @throws IOException
309 * in case of I/O error
311 private boolean preRead() throws IOException
{
312 boolean hasRead
= false;
313 if (!eof
&& in
!= null && pos
>= len
&& !stopped
) {
315 len
= in
.read(buffer
);
332 * We have more data available in the buffer or we can fetch more.
334 * @return TRUE if it is the case, FALSE if not
336 private boolean hasMoreData() {
337 return !closed
&& started
&& !(eof
&& pos
>= len
);
341 * Check that the buffer didn't overshot to the next item, and fix
342 * {@link NextableInputStream#len} if needed.
344 * If {@link NextableInputStream#len} is fixed,
345 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
349 * we changed the buffer, we need to clear some information in
350 * the {@link NextableInputStreamStep}
352 private void checkBuffer(boolean newBuffer
) {
353 if (step
!= null && len
> 0) {
358 int stopAt
= step
.stop(buffer
, pos
, len
);
368 * The implementation of {@link NextableInputStream#next()} and
369 * {@link NextableInputStream#nextAll()}.
372 * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
373 * {@link NextableInputStream#next()}
375 * @return TRUE if we unblocked the next sub-stream, FALSE if not
377 * @throws IOException
378 * in case of I/O error or if the stream is closed
380 private boolean next(boolean all
) throws IOException
{
384 // First call before being allowed to read
394 if (step
!= null && !hasMoreData() && stopped
) {
395 len
= step
.getResumeLen();
396 pos
+= step
.getResumeSkip();
407 // consider that if EOF, there is no next
408 return hasMoreData();
415 * Check that the stream was not closed, and throw an {@link IOException} if
418 * @throws IOException
421 private void checkClose() throws IOException
{
423 throw new IOException(
424 "This NextableInputStream was closed, you cannot use it anymore.");