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 * @return TRUE if it is
230 public boolean eof() {
231 return closed
|| (len
< 0 && !hasMoreData());
235 public int read() throws IOException
{
243 return buffer
[pos
++];
247 public int read(byte[] b
) throws IOException
{
248 return read(b
, 0, b
.length
);
252 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
256 throw new NullPointerException();
257 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
258 throw new IndexOutOfBoundsException();
259 } else if (blen
== 0) {
264 while (hasMoreData() && done
< blen
) {
267 for (int i
= pos
; i
< blen
&& i
< len
; i
++) {
268 b
[boff
+ done
] = buffer
[i
];
275 return done
> 0 ? done
: -1;
279 public long skip(long n
) throws IOException
{
285 while (hasMoreData() && n
> 0) {
288 long inBuffer
= Math
.min(n
, available());
298 public int available() {
303 return Math
.max(0, len
- pos
);
307 * Closes this stream and releases any system resources associated with the
310 * Including the under-laying {@link InputStream}.
312 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
313 * prior to this one, it will just decrease the internal count of how many
314 * open streams it held and do nothing else. The stream will actually be
315 * closed when you have called {@link NextableInputStream#close()} once more
316 * than {@link NextableInputStream#open()}.
318 * @exception IOException
319 * in case of I/O error
322 public synchronized void close() throws IOException
{
327 * Closes this stream and releases any system resources associated with the
330 * Including the under-laying {@link InputStream} if
331 * <tt>incudingSubStream</tt> is true.
333 * You can call this method multiple times, it will not cause an
334 * {@link IOException} for subsequent calls.
336 * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
337 * prior to this one, it will just decrease the internal count of how many
338 * open streams it held and do nothing else. The stream will actually be
339 * closed when you have called {@link NextableInputStream#close()} once more
340 * than {@link NextableInputStream#open()}.
342 * @exception IOException
343 * in case of I/O error
345 public synchronized void close(boolean includingSubStream
)
348 if (openCounter
> 0) {
352 if (includingSubStream
&& in
!= null) {
360 * Check if we still have some data in the buffer and, if not, fetch some.
362 * @return TRUE if we fetched some data, FALSE if there are still some in
365 * @throws IOException
366 * in case of I/O error
368 private boolean preRead() throws IOException
{
369 boolean hasRead
= false;
370 if (!eof
&& in
!= null && pos
>= len
&& !stopped
) {
372 if (buffer2
!= null) {
381 buffer
= originalBuffer
;
382 len
= in
.read(buffer
);
400 * We have more data available in the buffer or we can fetch more.
402 * @return TRUE if it is the case, FALSE if not
404 private boolean hasMoreData() {
405 return !closed
&& started
&& !(eof
&& pos
>= len
);
409 * Check that the buffer didn't overshot to the next item, and fix
410 * {@link NextableInputStream#len} if needed.
412 * If {@link NextableInputStream#len} is fixed,
413 * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
417 * we changed the buffer, we need to clear some information in
418 * the {@link NextableInputStreamStep}
420 private void checkBuffer(boolean newBuffer
) {
421 if (step
!= null && len
> 0) {
426 int stopAt
= step
.stop(buffer
, pos
, len
);
436 * The implementation of {@link NextableInputStream#next()} and
437 * {@link NextableInputStream#nextAll()}.
440 * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
441 * {@link NextableInputStream#next()}
443 * @return TRUE if we unblocked the next sub-stream, FALSE if not
445 * @throws IOException
446 * in case of I/O error or if the stream is closed
448 private boolean next(boolean all
) throws IOException
{
452 // First call before being allowed to read
462 if (step
!= null && !hasMoreData() && stopped
) {
463 len
= step
.getResumeLen();
464 pos
+= step
.getResumeSkip();
475 // consider that if EOF, there is no next
476 return hasMoreData();
483 * Check that the stream was not closed, and throw an {@link IOException} if
486 * @throws IOException
489 private void checkClose() throws IOException
{
491 throw new IOException(
492 "This NextableInputStream was closed, you cannot use it anymore.");
496 // buffer must be > search
497 static private boolean startsWith(byte[] search
, byte[] buffer
,
500 for (int i
= 0; i
< search
.length
; i
++) {
501 if (search
[i
] != buffer
[offset
+ i
]) {