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
;
44 private byte[] singleByteReader
= new byte[1];
46 /** array + offset of pushed-back buffers */
47 private List
<Entry
<byte[], Integer
>> backBuffers
;
49 private long bytesRead
;
52 * Create a new {@link BufferedInputStream} that wraps the given
53 * {@link InputStream}.
56 * the {@link InputStream} to wrap
58 public BufferedInputStream(InputStream in
) {
61 this.buffer
= new byte[BUFFER_SIZE
];
64 this.backBuffers
= new ArrayList
<Entry
<byte[], Integer
>>();
68 * Create a new {@link BufferedInputStream} that wraps the given bytes array
72 * the array to wrap, cannot be NULL
74 public BufferedInputStream(byte[] in
) {
75 this(in
, 0, in
.length
);
79 * Create a new {@link BufferedInputStream} that wraps the given bytes array
83 * the array to wrap, cannot be NULL
85 * the offset to start the reading at
87 * the number of bytes to take into account in the array,
88 * starting from the offset
90 * @throws NullPointerException
91 * if the array is NULL
92 * @throws IndexOutOfBoundsException
93 * if the offset and length do not correspond to the given array
95 public BufferedInputStream(byte[] in
, int offset
, int length
) {
97 throw new NullPointerException();
98 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
99 throw new IndexOutOfBoundsException();
107 this.backBuffers
= new ArrayList
<Entry
<byte[], Integer
>>();
111 * Return this very same {@link BufferedInputStream}, but keep a counter of
112 * how many streams were open this way. When calling
113 * {@link BufferedInputStream#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 * Check if the current content (until eof) is equal to the given search
136 * Note: the search term size <b>must</b> be smaller or equal the internal
140 * the term to search for
142 * @return TRUE if the content that will be read starts with it
144 * @throws IOException
145 * in case of I/O error or if the size of the search term is
146 * greater than the internal buffer
148 public boolean is(String search
) throws IOException
{
149 return is(StringUtils
.getBytes(search
));
153 * Check if the current content (until eof) is equal to the given search
156 * Note: the search term size <b>must</b> be smaller or equal the internal
160 * the term to search for
162 * @return TRUE if the content that will be read starts with it
164 * @throws IOException
165 * in case of I/O error or if the size of the search term is
166 * greater than the internal buffer
168 public boolean is(byte[] search
) throws IOException
{
169 if (startsWith(search
)) {
170 return available() == search
.length
;
177 * Check if the current content (what will be read next) starts with the
180 * Note: the search term size <b>must</b> be smaller or equal the internal
184 * the term to search for
186 * @return TRUE if the content that will be read starts with it
188 * @throws IOException
189 * in case of I/O error or if the size of the search term is
190 * greater than the internal buffer
192 public boolean startsWith(String search
) throws IOException
{
193 return startsWith(StringUtils
.getBytes(search
));
197 * Check if the current content (what will be read next) starts with the
200 * An empty string will always return true (unless the stream is closed,
201 * which would throw an {@link IOException}).
203 * Note: the search term size <b>must</b> be smaller or equal the internal
207 * the term to search for
209 * @return TRUE if the content that will be read starts with it
211 * @throws IOException
212 * in case of I/O error or if the size of the search term is
213 * greater than the internal buffer
215 public boolean startsWith(byte[] search
) throws IOException
{
218 while (consolidatePushBack(search
.length
) < search
.length
) {
221 // Not enough data left to start with that
225 byte[] newBuffer
= new byte[stop
- start
];
226 System
.arraycopy(buffer
, start
, newBuffer
, 0, stop
- start
);
227 pushback(newBuffer
, 0);
231 Entry
<byte[], Integer
> bb
= backBuffers
.get(backBuffers
.size() - 1);
232 byte[] bbBuffer
= bb
.getKey();
233 int bbOffset
= bb
.getValue();
235 return StreamUtils
.startsWith(search
, bbBuffer
, bbOffset
,
240 * The number of bytes read from the under-laying {@link InputStream}.
242 * @return the number of bytes
244 public long getBytesRead() {
249 * Check if this stream is spent (no more data to read or to process).
251 * @return TRUE if it is
253 * @throws IOException
254 * in case of I/O error
256 public boolean eof() throws IOException
{
262 return !hasMoreData();
266 * Read the whole {@link InputStream} until the end and return the number of
269 * @return the number of bytes read
271 * @throws IOException
272 * in case of I/O error
274 public long end() throws IOException
{
276 while (hasMoreData()) {
277 skipped
+= skip(buffer
.length
);
284 public int read() throws IOException
{
285 if (read(singleByteReader
) < 0) {
289 return singleByteReader
[0];
293 public int read(byte[] b
) throws IOException
{
294 return read(b
, 0, b
.length
);
298 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
302 throw new NullPointerException();
303 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
304 throw new IndexOutOfBoundsException();
305 } else if (blen
== 0) {
309 // Read from the pushed-back buffers if any
310 if (backBuffers
.isEmpty()) {
311 preRead(); // an implementation could pushback in preRead()
314 if (!backBuffers
.isEmpty()) {
317 Entry
<byte[], Integer
> bb
= backBuffers
318 .remove(backBuffers
.size() - 1);
319 byte[] bbBuffer
= bb
.getKey();
320 int bbOffset
= bb
.getValue();
321 int bbSize
= bbBuffer
.length
- bbOffset
;
325 System
.arraycopy(bbBuffer
, bbOffset
, b
, boff
, read
);
326 pushback(bbBuffer
, bbOffset
+ read
);
329 System
.arraycopy(bbBuffer
, bbOffset
, b
, boff
, read
);
336 while (hasMoreData() && done
< blen
) {
339 int now
= Math
.min(blen
- done
, stop
- start
);
341 System
.arraycopy(buffer
, start
, b
, boff
+ done
, now
);
348 return done
> 0 ? done
: -1;
352 public long skip(long n
) throws IOException
{
358 while (!backBuffers
.isEmpty() && n
> 0) {
359 Entry
<byte[], Integer
> bb
= backBuffers
360 .remove(backBuffers
.size() - 1);
361 byte[] bbBuffer
= bb
.getKey();
362 int bbOffset
= bb
.getValue();
363 int bbSize
= bbBuffer
.length
- bbOffset
;
366 localSkip
= (int) Math
.min(n
, bbSize
);
372 pushback(bbBuffer
, bbOffset
+ localSkip
);
375 while (hasMoreData() && n
> 0) {
378 long inBuffer
= Math
.min(n
, available());
388 public int available() {
394 for (Entry
<byte[], Integer
> entry
: backBuffers
) {
395 avail
+= entry
.getKey().length
- entry
.getValue();
398 return avail
+ Math
.max(0, stop
- start
);
402 * Closes this stream and releases any system resources associated with the
405 * Including the under-laying {@link InputStream}.
407 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
408 * prior to this one, it will just decrease the internal count of how many
409 * open streams it held and do nothing else. The stream will actually be
410 * closed when you have called {@link BufferedInputStream#close()} once more
411 * than {@link BufferedInputStream#open()}.
413 * @exception IOException
414 * in case of I/O error
417 public synchronized void close() throws IOException
{
422 * Closes this stream and releases any system resources associated with the
425 * Including the under-laying {@link InputStream} if
426 * <tt>incudingSubStream</tt> is true.
428 * You can call this method multiple times, it will not cause an
429 * {@link IOException} for subsequent calls.
431 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
432 * prior to this one, it will just decrease the internal count of how many
433 * open streams it held and do nothing else. The stream will actually be
434 * closed when you have called {@link BufferedInputStream#close()} once more
435 * than {@link BufferedInputStream#open()}.
437 * @param includingSubStream
438 * also close the under-laying stream
440 * @exception IOException
441 * in case of I/O error
443 public synchronized void close(boolean includingSubStream
)
446 if (openCounter
> 0) {
450 if (includingSubStream
&& in
!= null) {
458 * Consolidate the push-back buffers so the last one is at least the given
461 * If there is not enough data in the push-back buffers, they will all be
465 * the minimum size of the consolidated buffer, or -1 to force
466 * the consolidation of all push-back buffers
468 * @return the size of the last, consolidated buffer; can be less than the
469 * requested size if not enough data
471 protected int consolidatePushBack(int size
) {
474 for (Entry
<byte[], Integer
> entry
: backBuffers
) {
476 bbUpToSize
+= entry
.getKey().length
- entry
.getValue();
478 if (size
>= 0 && bbUpToSize
>= size
) {
483 // Index 0 means "the last buffer is already big enough"
485 byte[] consolidatedBuffer
= new byte[bbUpToSize
];
486 int consolidatedPos
= 0;
487 for (int i
= 0; i
<= bbIndex
; i
++) {
488 Entry
<byte[], Integer
> bb
= backBuffers
489 .remove(backBuffers
.size() - 1);
490 byte[] bbBuffer
= bb
.getKey();
491 int bbOffset
= bb
.getValue();
492 int bbSize
= bbBuffer
.length
- bbOffset
;
493 System
.arraycopy(bbBuffer
, bbOffset
, consolidatedBuffer
,
494 consolidatedPos
, bbSize
);
497 pushback(consolidatedBuffer
, 0);
504 * Check if we still have some data in the buffer and, if not, fetch some.
506 * @return TRUE if we fetched some data, FALSE if there are still some in
509 * @throws IOException
510 * in case of I/O error
512 protected boolean preRead() throws IOException
{
513 boolean hasRead
= false;
514 if (in
!= null && !eof
&& start
>= stop
) {
516 stop
= read(in
, buffer
);
532 * Push back some data that will be read again at the next read call.
535 * the buffer to push back
537 * the offset at which to start reading in the buffer
539 protected void pushback(byte[] buffer
, int offset
) {
541 new AbstractMap
.SimpleEntry
<byte[], Integer
>(buffer
, offset
));
545 * Push back some data that will be read again at the next read call.
548 * the buffer to push back
550 * the offset at which to start reading in the buffer
554 protected void pushback(byte[] buffer
, int offset
, int len
) {
555 // TODO: not efficient!
556 if (buffer
.length
!= len
) {
557 byte[] lenNotSupportedYet
= new byte[len
];
558 System
.arraycopy(buffer
, offset
, lenNotSupportedYet
, 0, len
);
559 buffer
= lenNotSupportedYet
;
563 pushback(buffer
, offset
);
567 * Read the under-laying stream into the given local buffer.
570 * the under-laying {@link InputStream}
572 * the buffer we use in this {@link BufferedInputStream}
574 * @return the number of bytes read
576 * @throws IOException
577 * in case of I/O error
579 protected int read(InputStream in
, byte[] buffer
) throws IOException
{
580 return in
.read(buffer
, 0, buffer
.length
);
584 * We have more data available in the buffer <b>or</b> we can, maybe, fetch
587 * @return TRUE if it is the case, FALSE if not
589 protected boolean hasMoreData() {
594 return !backBuffers
.isEmpty() || (start
< stop
) || !eof
;
598 * Check that the stream was not closed, and throw an {@link IOException} if
601 * @throws IOException
604 protected void checkClose() throws IOException
{
606 throw new IOException(
607 "This BufferedInputStream was closed, you cannot use it anymore.");