1 package be
.nikiroo
.utils
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.util
.Arrays
;
8 * This {@link InputStream} can be separated into sub-streams (you can process
9 * it as a normal {@link InputStream} but, when it is spent, you can call
10 * {@link NextableInputStream#next()} on it to unlock new data).
12 * The separation in sub-streams is done via {@link NextableInputStreamStep}.
16 public class NextableInputStream
extends InputStream
{
17 private NextableInputStreamStep step
;
18 private boolean started
;
19 private boolean stopped
;
20 private boolean closed
;
22 private InputStream in
;
23 private int openCounter
;
27 private byte[] buffer
;
29 // special use, prefetched next buffer
30 private byte[] buffer2
;
33 private byte[] originalBuffer
;
35 private long bytesRead
;
38 * Create a new {@link NextableInputStream} that wraps the given
39 * {@link InputStream}.
42 * the {@link InputStream} to wrap
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(InputStream in
, NextableInputStreamStep step
) {
51 this.buffer
= new byte[4096];
52 this.originalBuffer
= this.buffer
;
58 * Create a new {@link NextableInputStream} that wraps the given bytes array
62 * the array to wrap, cannot be NULL
64 * how to separate it into sub-streams (can be NULL, but in that
65 * case it will behave as a normal {@link InputStream})
67 public NextableInputStream(byte[] in
, NextableInputStreamStep step
) {
68 this(in
, step
, 0, in
.length
);
72 * Create a new {@link NextableInputStream} that wraps the given bytes array
76 * the array to wrap, cannot be NULL
78 * how to separate it into sub-streams (can be NULL, but in that
79 * case it will behave as a normal {@link InputStream})
81 * the offset to start the reading at
83 * the number of bytes to take into account in the array,
84 * starting from the offset
86 * @throws NullPointerException
87 * if the array is NULL
88 * @throws IndexOutOfBoundsException
89 * if the offset and length do not correspond to the given array
91 public NextableInputStream(byte[] in
, NextableInputStreamStep step
,
92 int offset
, int length
) {
94 throw new NullPointerException();
95 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
96 throw new IndexOutOfBoundsException();
103 this.originalBuffer
= this.buffer
;
111 * Return this very same {@link NextableInputStream}, but keep a counter of
112 * how many streams were open this way. When calling
113 * {@link NextableInputStream#close()}, decrease this counter if it is not
114 * already zero instead of actually closing the stream.
116 * You are now responsible for it — you <b>must</b> close it.
118 * This method allows you to use a wrapping stream around this one and still
119 * close the wrapping stream.
121 * @return the same stream, but you are now responsible for closing it
123 * @throws IOException
124 * in case of I/O error or if the stream is closed
126 public synchronized InputStream
open() throws IOException
{
133 * Unblock the processing of the next sub-stream.
135 * It can only be called when the "current" stream is spent (i.e., you must
136 * first process the stream until it is spent).
138 * We consider that when the under-laying {@link InputStream} is also spent,
139 * we cannot have a next sub-stream (it will thus return FALSE).
141 * {@link IOException}s can happen when we have no data available in the
142 * buffer; in that case, we fetch more data to know if we can have a next
145 * @return TRUE if we unblocked the next sub-stream, FALSE if not
147 * @throws IOException
148 * in case of I/O error or if the stream is closed
150 public boolean next() throws IOException
{
155 * Unblock the next sub-stream as would have done
156 * {@link NextableInputStream#next()}, but disable the sub-stream systems.
158 * That is, the next stream, if any, will be the last one and will not be
159 * subject to the {@link NextableInputStreamStep}.
161 * @return TRUE if we unblocked the next sub-stream, FALSE if not
163 * @throws IOException
164 * in case of I/O error or if the stream is closed
166 public boolean nextAll() throws IOException
{
170 // max is buffer.size !
171 public boolean startsWiths(String search
) throws IOException
{
172 return startsWith(search
.getBytes("UTF-8"));
175 // max is buffer.size !
176 public boolean startsWith(byte[] search
) throws IOException
{
177 if (search
.length
> originalBuffer
.length
) {
178 throw new IOException(
179 "This stream does not support searching for more than "
180 + buffer
.length
+ " bytes");
185 if (available() < search
.length
) {
189 if (available() >= search
.length
) {
191 return startsWith(search
, buffer
, pos
);
194 if (buffer2
== null && buffer
.length
== originalBuffer
.length
) {
195 buffer2
= Arrays
.copyOf(buffer
, buffer
.length
* 2);
197 pos2
= buffer
.length
;
198 len2
= in
.read(buffer2
, pos2
, buffer
.length
);
203 // Note: here, len/len2 = INDEX of last good byte
207 if (available() + (len2
- pos2
) >= search
.length
) {
208 return startsWith(search
, buffer2
, pos2
);
216 * The number of bytes read from the under-laying {@link InputStream}.
218 * @return the number of bytes
220 public long getBytesRead() {
225 * Check if this stream is totally spent (no more data to read or to
228 * Note: an empty stream that is still not started will return FALSE, as we
229 * don't know yet if it is empty.
231 * @return TRUE if it is
233 public boolean eof() {
234 return closed
|| (len
< 0 && !hasMoreData());
238 public int read() throws IOException
{
246 return buffer
[pos
++];
250 public int read(byte[] b
) throws IOException
{
251 return read(b
, 0, b
.length
);
255 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
259 throw new NullPointerException();
260 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
261 throw new IndexOutOfBoundsException();
262 } else if (blen
== 0) {
267 while (hasMoreData() && done
< blen
) {
270 int now
= Math
.min(blen
, len
) - pos
;
272 System
.arraycopy(buffer
, pos
, b
, boff
, now
);
279 return done
> 0 ? done
: -1;
283 public long skip(long n
) throws IOException
{
289 while (hasMoreData() && n
> 0) {
292 long inBuffer
= Math
.min(n
, available());
302 public int available() {
307 return Math
.max(0, len
- pos
);
311 * Closes this stream and releases any system resources associated with the
314 * Including the under-laying {@link InputStream}.
316 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
317 * prior to this one, it will just decrease the internal count of how many
318 * open streams it held and do nothing else. The stream will actually be
319 * closed when you have called {@link NextableInputStream#close()} once more
320 * than {@link NextableInputStream#open()}.
322 * @exception IOException
323 * in case of I/O error
326 public synchronized void close() throws IOException
{
331 * Closes this stream and releases any system resources associated with the
334 * Including the under-laying {@link InputStream} if
335 * <tt>incudingSubStream</tt> is true.
337 * You can call this method multiple times, it will not cause an
338 * {@link IOException} for subsequent calls.
340 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
341 * prior to this one, it will just decrease the internal count of how many
342 * open streams it held and do nothing else. The stream will actually be
343 * closed when you have called {@link NextableInputStream#close()} once more
344 * than {@link NextableInputStream#open()}.
346 * @exception IOException
347 * in case of I/O error
349 public synchronized void close(boolean includingSubStream
)
352 if (openCounter
> 0) {
356 if (includingSubStream
&& in
!= null) {
364 * Check if we still have some data in the buffer and, if not, fetch some.
366 * @return TRUE if we fetched some data, FALSE if there are still some in
369 * @throws IOException
370 * in case of I/O error
372 private boolean preRead() throws IOException
{
373 boolean hasRead
= false;
374 if (!eof
&& in
!= null && pos
>= len
&& !stopped
) {
376 if (buffer2
!= null) {
385 buffer
= originalBuffer
;
386 len
= in
.read(buffer
);
404 * We have more data available in the buffer or we can fetch more.
406 * @return TRUE if it is the case, FALSE if not
408 private boolean hasMoreData() {
409 return !closed
&& started
&& !(eof
&& pos
>= len
);
413 * Check that the buffer didn't overshot to the next item, and fix
414 * {@link NextableInputStream#len} if needed.
416 * If {@link NextableInputStream#len} is fixed,
417 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
421 * we changed the buffer, we need to clear some information in
422 * the {@link NextableInputStreamStep}
424 private void checkBuffer(boolean newBuffer
) {
425 if (step
!= null && len
> 0) {
430 int stopAt
= step
.stop(buffer
, pos
, len
);
440 * The implementation of {@link NextableInputStream#next()} and
441 * {@link NextableInputStream#nextAll()}.
444 * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
445 * {@link NextableInputStream#next()}
447 * @return TRUE if we unblocked the next sub-stream, FALSE if not
449 * @throws IOException
450 * in case of I/O error or if the stream is closed
452 private boolean next(boolean all
) throws IOException
{
456 // First call before being allowed to read
466 if (step
!= null && !hasMoreData() && stopped
) {
467 len
= step
.getResumeLen();
468 pos
+= step
.getResumeSkip();
479 // consider that if EOF, there is no next
480 return hasMoreData();
487 * Check that the stream was not closed, and throw an {@link IOException} if
490 * @throws IOException
493 private void checkClose() throws IOException
{
495 throw new IOException(
496 "This NextableInputStream was closed, you cannot use it anymore.");
500 // buffer must be > search
501 static private boolean startsWith(byte[] search
, byte[] buffer
, int offset
) {
503 for (int i
= 0; i
< search
.length
; i
++) {
504 if (search
[i
] != buffer
[offset
+ i
]) {