1 package be
.nikiroo
.utils
.streams
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.util
.AbstractMap
;
6 import java
.util
.ArrayList
;
8 import java
.util
.Map
.Entry
;
10 import be
.nikiroo
.utils
.StringUtils
;
13 * A simple {@link InputStream} that is buffered with a bytes array.
15 * It is mostly intended to be used as a base class to create new
16 * {@link InputStream}s with special operation modes, and to give some default
21 public class BufferedInputStream
extends InputStream
{
23 * The size of the internal buffer (can be different if you pass your own
24 * buffer, of course, and can also expand to search for longer "startsWith"
27 * Note that special "push-back" buffers can also be created during the life
30 static private final int BUFFER_SIZE
= 4096;
32 /** The current position in the buffer. */
34 /** The index of the last usable position of the buffer. */
36 /** The buffer itself. */
37 protected byte[] buffer
;
38 /** An End-Of-File (or {@link InputStream}, here) marker. */
39 protected boolean eof
;
41 private boolean closed
;
42 private InputStream in
;
43 private int openCounter
;
45 /** array + offset of pushed-back buffers */
46 private List
<Entry
<byte[], Integer
>> backBuffers
;
48 private long bytesRead
;
51 * Create a new {@link BufferedInputStream2} that wraps the given
52 * {@link InputStream}.
55 * the {@link InputStream} to wrap
57 public BufferedInputStream(InputStream in
) {
60 this.buffer
= new byte[BUFFER_SIZE
];
63 this.backBuffers
= new ArrayList
<Entry
<byte[], Integer
>>();
67 * Create a new {@link BufferedInputStream2} that wraps the given bytes
68 * array as a data source.
71 * the array to wrap, cannot be NULL
73 public BufferedInputStream(byte[] in
) {
74 this(in
, 0, in
.length
);
78 * Create a new {@link BufferedInputStream2} that wraps the given bytes
79 * array as a data source.
82 * the array to wrap, cannot be NULL
84 * the offset to start the reading at
86 * the number of bytes to take into account in the array,
87 * starting from the offset
89 * @throws NullPointerException
90 * if the array is NULL
91 * @throws IndexOutOfBoundsException
92 * if the offset and length do not correspond to the given array
94 public BufferedInputStream(byte[] in
, int offset
, int length
) {
96 throw new NullPointerException();
97 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
98 throw new IndexOutOfBoundsException();
106 this.backBuffers
= new ArrayList
<Entry
<byte[], Integer
>>();
110 * Return this very same {@link BufferedInputStream2}, but keep a counter of
111 * how many streams were open this way. When calling
112 * {@link BufferedInputStream2#close()}, decrease this counter if it is not
113 * already zero instead of actually closing the stream.
115 * You are now responsible for it — you <b>must</b> close it.
117 * This method allows you to use a wrapping stream around this one and still
118 * close the wrapping stream.
120 * @return the same stream, but you are now responsible for closing it
122 * @throws IOException
123 * in case of I/O error or if the stream is closed
125 public synchronized InputStream
open() throws IOException
{
132 * Check if the current content (until eof) is equal to the given search
135 * Note: the search term size <b>must</b> be smaller or equal the internal
139 * the term to search for
141 * @return TRUE if the content that will be read starts with it
143 * @throws IOException
144 * in case of I/O error or if the size of the search term is
145 * greater than the internal buffer
147 public boolean is(String search
) throws IOException
{
148 return is(StringUtils
.getBytes(search
));
152 * Check if the current content (until eof) is equal to the given search
155 * Note: the search term size <b>must</b> be smaller or equal the internal
159 * the term to search for
161 * @return TRUE if the content that will be read starts with it
163 * @throws IOException
164 * in case of I/O error or if the size of the search term is
165 * greater than the internal buffer
167 public boolean is(byte[] search
) throws IOException
{
168 if (startsWith(search
)) {
169 return available() == search
.length
;
176 * Check if the current content (what will be read next) starts with the
179 * Note: the search term size <b>must</b> be smaller or equal the internal
183 * the term to search for
185 * @return TRUE if the content that will be read starts with it
187 * @throws IOException
188 * in case of I/O error or if the size of the search term is
189 * greater than the internal buffer
191 public boolean startsWith(String search
) throws IOException
{
192 return startsWith(StringUtils
.getBytes(search
));
196 * Check if the current content (what will be read next) starts with the
199 * An empty string will always return true (unless the stream is closed,
200 * which would throw an {@link IOException}).
202 * Note: the search term size <b>must</b> be smaller or equal the internal
206 * the term to search for
208 * @return TRUE if the content that will be read starts with it
210 * @throws IOException
211 * in case of I/O error or if the size of the search term is
212 * greater than the internal buffer
214 public boolean startsWith(byte[] search
) throws IOException
{
217 while (consolidatePushBack(search
.length
) < search
.length
) {
220 // Not enough data left to start with that
224 byte[] newBuffer
= new byte[stop
- start
];
225 System
.arraycopy(buffer
, start
, newBuffer
, 0, stop
- start
);
226 pushback(newBuffer
, 0);
230 Entry
<byte[], Integer
> bb
= backBuffers
.get(backBuffers
.size() - 1);
231 byte[] bbBuffer
= bb
.getKey();
232 int bbOffset
= bb
.getValue();
234 return StreamUtils
.startsWith(search
, bbBuffer
, bbOffset
,
239 * The number of bytes read from the under-laying {@link InputStream}.
241 * @return the number of bytes
243 public long getBytesRead() {
248 * Check if this stream is spent (no more data to read or to process).
250 * @return TRUE if it is
252 * @throws IOException
253 * in case of I/O error
255 public boolean eof() throws IOException
{
261 return !hasMoreData();
265 * Read the whole {@link InputStream} until the end and return the number of
268 * @return the number of bytes read
270 * @throws IOException
271 * in case of I/O error
273 public long end() throws IOException
{
275 while (hasMoreData()) {
276 skipped
+= skip(buffer
.length
);
283 public int read() throws IOException
{
291 return buffer
[start
++];
295 public int read(byte[] b
) throws IOException
{
296 return read(b
, 0, b
.length
);
300 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
304 throw new NullPointerException();
305 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
306 throw new IndexOutOfBoundsException();
307 } else if (blen
== 0) {
311 // Read from the pushed-back buffers if any
312 if (!backBuffers
.isEmpty()) {
315 Entry
<byte[], Integer
> bb
= backBuffers
316 .remove(backBuffers
.size() - 1);
317 byte[] bbBuffer
= bb
.getKey();
318 int bbOffset
= bb
.getValue();
319 int bbSize
= bbBuffer
.length
- bbOffset
;
323 System
.arraycopy(bbBuffer
, bbOffset
, b
, boff
, read
);
324 pushback(bbBuffer
, bbOffset
+ read
);
327 System
.arraycopy(bbBuffer
, bbOffset
, b
, boff
, read
);
334 while (hasMoreData() && done
< blen
) {
337 int now
= Math
.min(blen
- done
, stop
- start
);
339 System
.arraycopy(buffer
, start
, b
, boff
+ done
, now
);
346 return done
> 0 ? done
: -1;
350 public long skip(long n
) throws IOException
{
356 while (!backBuffers
.isEmpty() && n
> 0) {
357 Entry
<byte[], Integer
> bb
= backBuffers
358 .remove(backBuffers
.size() - 1);
359 byte[] bbBuffer
= bb
.getKey();
360 int bbOffset
= bb
.getValue();
361 int bbSize
= bbBuffer
.length
- bbOffset
;
364 localSkip
= (int) Math
.min(n
, bbSize
);
370 pushback(bbBuffer
, bbOffset
+ localSkip
);
373 while (hasMoreData() && n
> 0) {
376 long inBuffer
= Math
.min(n
, available());
386 public int available() {
392 for (Entry
<byte[], Integer
> entry
: backBuffers
) {
393 avail
+= entry
.getKey().length
- entry
.getValue();
396 return avail
+ Math
.max(0, stop
- start
);
400 * Closes this stream and releases any system resources associated with the
403 * Including the under-laying {@link InputStream}.
405 * <b>Note:</b> if you called the {@link BufferedInputStream2#open()} method
406 * prior to this one, it will just decrease the internal count of how many
407 * open streams it held and do nothing else. The stream will actually be
408 * closed when you have called {@link BufferedInputStream2#close()} once
409 * more than {@link BufferedInputStream2#open()}.
411 * @exception IOException
412 * in case of I/O error
415 public synchronized void close() throws IOException
{
420 * Closes this stream and releases any system resources associated with the
423 * Including the under-laying {@link InputStream} if
424 * <tt>incudingSubStream</tt> is true.
426 * You can call this method multiple times, it will not cause an
427 * {@link IOException} for subsequent calls.
429 * <b>Note:</b> if you called the {@link BufferedInputStream2#open()} method
430 * prior to this one, it will just decrease the internal count of how many
431 * open streams it held and do nothing else. The stream will actually be
432 * closed when you have called {@link BufferedInputStream2#close()} once
433 * more than {@link BufferedInputStream2#open()}.
435 * @param includingSubStream
436 * also close the under-laying stream
438 * @exception IOException
439 * in case of I/O error
441 public synchronized void close(boolean includingSubStream
)
444 if (openCounter
> 0) {
448 if (includingSubStream
&& in
!= null) {
456 * Consolidate the push-back buffers so the last one is at least the given
459 * If there is not enough data in the push-back buffers, they will all be
463 * the minimum size of the consolidated buffer, or -1 to force
464 * the consolidation of all push-back buffers
466 * @return the size of the last, consolidated buffer; can be less than the
467 * requested size if not enough data
469 protected int consolidatePushBack(int size
) {
472 for (Entry
<byte[], Integer
> entry
: backBuffers
) {
474 bbUpToSize
+= entry
.getKey().length
- entry
.getValue();
476 if (size
>= 0 && bbUpToSize
>= size
) {
481 // Index 0 means "the last buffer is already big enough"
483 byte[] consolidatedBuffer
= new byte[bbUpToSize
];
484 int consolidatedPos
= 0;
485 for (int i
= 0; i
<= bbIndex
; i
++) {
486 Entry
<byte[], Integer
> bb
= backBuffers
487 .remove(backBuffers
.size() - 1);
488 byte[] bbBuffer
= bb
.getKey();
489 int bbOffset
= bb
.getValue();
490 int bbSize
= bbBuffer
.length
- bbOffset
;
491 System
.arraycopy(bbBuffer
, bbOffset
, consolidatedBuffer
,
492 consolidatedPos
, bbSize
);
495 pushback(consolidatedBuffer
, 0);
502 * Check if we still have some data in the buffer and, if not, fetch some.
504 * @return TRUE if we fetched some data, FALSE if there are still some in
507 * @throws IOException
508 * in case of I/O error
510 protected boolean preRead() throws IOException
{
511 boolean hasRead
= false;
512 if (in
!= null && !eof
&& start
>= stop
) {
514 stop
= read(in
, buffer
, 0, buffer
.length
);
530 * Push back some data that will be read again at the next read call.
533 * the buffer to push back
535 * the offset at which to start reading in the buffer
537 protected void pushback(byte[] buffer
, int offset
) {
539 new AbstractMap
.SimpleEntry
<byte[], Integer
>(buffer
, offset
));
543 * Read the under-laying stream into the given local buffer.
546 * the under-laying {@link InputStream}
548 * the buffer we use in this {@link BufferedInputStream2}
552 * the length in bytes
554 * @return the number of bytes read
556 * @throws IOException
557 * in case of I/O error
559 protected int read(InputStream in
, byte[] buffer
, int off
, int len
)
561 return in
.read(buffer
, off
, len
);
565 * We have more data available in the buffer <b>or</b> we can, maybe, fetch
568 * @return TRUE if it is the case, FALSE if not
570 protected boolean hasMoreData() {
575 return !backBuffers
.isEmpty() || (start
< stop
) || !eof
;
579 * Check that the stream was not closed, and throw an {@link IOException} if
582 * @throws IOException
585 protected void checkClose() throws IOException
{
587 throw new IOException(
588 "This BufferedInputStream was closed, you cannot use it anymore.");