1 package be
.nikiroo
.utils
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.util
.Arrays
;
8 * A simple {@link InputStream} that is buffered with a bytes array.
10 * It is mostly intended to be used as a base class to create new
11 * {@link InputStream}s with special operation modes, and to give some default
16 public class BufferedInputStream
extends InputStream
{
17 /** The current position in the buffer. */
19 /** The index of the last usable position of the buffer. */
21 /** The buffer itself. */
22 protected byte[] buffer
;
23 /** An End-Of-File (or buffer, here) marker. */
24 protected boolean eof
;
26 private boolean closed
;
27 private InputStream in
;
28 private int openCounter
;
30 // special use, prefetched next buffer
31 private byte[] buffer2
;
34 private byte[] originalBuffer
;
36 private long bytesRead
;
39 * Create a new {@link BufferedInputStream} that wraps the given
40 * {@link InputStream}.
43 * the {@link InputStream} to wrap
45 public BufferedInputStream(InputStream in
) {
48 this.buffer
= new byte[4096];
49 this.originalBuffer
= this.buffer
;
55 * Create a new {@link BufferedInputStream} that wraps the given bytes array
59 * the array to wrap, cannot be NULL
61 public BufferedInputStream(byte[] in
) {
62 this(in
, 0, in
.length
);
66 * Create a new {@link BufferedInputStream} that wraps the given bytes array
70 * the array to wrap, cannot be NULL
72 * the offset to start the reading at
74 * the number of bytes to take into account in the array,
75 * starting from the offset
77 * @throws NullPointerException
78 * if the array is NULL
79 * @throws IndexOutOfBoundsException
80 * if the offset and length do not correspond to the given array
82 public BufferedInputStream(byte[] in
, int offset
, int length
) {
84 throw new NullPointerException();
85 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
86 throw new IndexOutOfBoundsException();
92 this.originalBuffer
= this.buffer
;
98 * Return this very same {@link BufferedInputStream}, but keep a counter of
99 * how many streams were open this way. When calling
100 * {@link BufferedInputStream#close()}, decrease this counter if it is not
101 * already zero instead of actually closing the stream.
103 * You are now responsible for it — you <b>must</b> close it.
105 * This method allows you to use a wrapping stream around this one and still
106 * close the wrapping stream.
108 * @return the same stream, but you are now responsible for closing it
110 * @throws IOException
111 * in case of I/O error or if the stream is closed
113 public synchronized InputStream
open() throws IOException
{
120 * Check if the current content (what will be read next) starts with the
123 * Note: the search term size <b>must</b> be smaller or equal the internal
127 * the term to search for
129 * @return TRUE if the content that will be read starts with it
131 * @throws IOException
132 * in case of I/O error or if the size of the search term is
133 * greater than the internal buffer
135 public boolean startsWiths(String search
) throws IOException
{
136 return startsWith(search
.getBytes("UTF-8"));
140 * Check if the current content (what will be read next) starts with the
143 * Note: the search term size <b>must</b> be smaller or equal the internal
147 * the term to search for
149 * @return TRUE if the content that will be read starts with it
151 * @throws IOException
152 * in case of I/O error or if the size of the search term is
153 * greater than the internal buffer
155 public boolean startsWith(byte[] search
) throws IOException
{
156 if (search
.length
> originalBuffer
.length
) {
157 throw new IOException(
158 "This stream does not support searching for more than "
159 + buffer
.length
+ " bytes");
164 if (available() < search
.length
) {
168 if (available() >= search
.length
) {
170 return startsWith(search
, buffer
, pos
, len
);
173 if (buffer2
== null && buffer
.length
== originalBuffer
.length
) {
174 buffer2
= Arrays
.copyOf(buffer
, buffer
.length
* 2);
176 pos2
= buffer
.length
;
177 len2
= in
.read(buffer2
, pos2
, buffer
.length
);
182 // Note: here, len/len2 = INDEX of last good byte
186 return startsWith(search
, buffer2
, pos2
, len2
);
193 * The number of bytes read from the under-laying {@link InputStream}.
195 * @return the number of bytes
197 public long getBytesRead() {
202 * Check if this stream is totally spent (no more data to read or to
205 * Note: an empty stream that is still not started will return FALSE, as we
206 * don't know yet if it is empty.
208 * @return TRUE if it is
210 public boolean eof() {
211 return closed
|| (len
< 0 && !hasMoreData());
215 public int read() throws IOException
{
223 return buffer
[pos
++];
227 public int read(byte[] b
) throws IOException
{
228 return read(b
, 0, b
.length
);
232 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
236 throw new NullPointerException();
237 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
238 throw new IndexOutOfBoundsException();
239 } else if (blen
== 0) {
244 while (hasMoreData() && done
< blen
) {
247 int now
= Math
.min(blen
, len
) - pos
;
249 System
.arraycopy(buffer
, pos
, b
, boff
, now
);
256 return done
> 0 ? done
: -1;
260 public long skip(long n
) throws IOException
{
266 while (hasMoreData() && n
> 0) {
269 long inBuffer
= Math
.min(n
, available());
279 public int available() {
284 return Math
.max(0, len
- pos
);
288 * Closes this stream and releases any system resources associated with the
291 * Including the under-laying {@link InputStream}.
293 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
294 * prior to this one, it will just decrease the internal count of how many
295 * open streams it held and do nothing else. The stream will actually be
296 * closed when you have called {@link BufferedInputStream#close()} once more
297 * than {@link BufferedInputStream#open()}.
299 * @exception IOException
300 * in case of I/O error
303 public synchronized void close() throws IOException
{
308 * Closes this stream and releases any system resources associated with the
311 * Including the under-laying {@link InputStream} if
312 * <tt>incudingSubStream</tt> is true.
314 * You can call this method multiple times, it will not cause an
315 * {@link IOException} for subsequent calls.
317 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
318 * prior to this one, it will just decrease the internal count of how many
319 * open streams it held and do nothing else. The stream will actually be
320 * closed when you have called {@link BufferedInputStream#close()} once more
321 * than {@link BufferedInputStream#open()}.
323 * @param includingSubStream
324 * also close the under-laying stream
326 * @exception IOException
327 * in case of I/O error
329 public synchronized void close(boolean includingSubStream
)
332 if (openCounter
> 0) {
336 if (includingSubStream
&& in
!= null) {
344 * Check if we still have some data in the buffer and, if not, fetch some.
346 * @return TRUE if we fetched some data, FALSE if there are still some in
349 * @throws IOException
350 * in case of I/O error
352 protected boolean preRead() throws IOException
{
353 boolean hasRead
= false;
354 if (!eof
&& in
!= null && pos
>= len
) {
356 if (buffer2
!= null) {
365 buffer
= originalBuffer
;
367 len
= read(in
, buffer
);
384 * Read the under-laying stream into the local buffer.
387 * the under-laying {@link InputStream}
389 * the buffer we use in this {@link BufferedInputStream}
391 * @return the number of bytes read
393 * @throws IOException
394 * in case of I/O error
396 protected int read(InputStream in
, byte[] buffer
) throws IOException
{
397 return in
.read(buffer
);
401 * We have more data available in the buffer or we can fetch more.
403 * @return TRUE if it is the case, FALSE if not
405 protected boolean hasMoreData() {
406 return !closed
&& !(eof
&& pos
>= len
);
410 * Check that the stream was not closed, and throw an {@link IOException} if
413 * @throws IOException
416 protected void checkClose() throws IOException
{
418 throw new IOException(
419 "This BufferedInputStream was closed, you cannot use it anymore.");
424 * Check if the buffer starts with the given search term (given as an array,
425 * a start position and a end position).
427 * Note: the parameter <tt>len</tt> is the <b>index</b> of the last
428 * position, <b>not</b> the length.
430 * Note: the search term size <b>must</b> be smaller or equal the internal
434 * the term to search for
436 * the buffer to look into
438 * the offset at which to start the search
440 * the maximum index of the data to check (this is <b>not</b> a
441 * length, but an index)
443 * @return TRUE if the search content is present at the given location and
444 * does not exceed the <tt>len</tt> index
446 static protected boolean startsWith(byte[] search
, byte[] buffer
,
447 int offset
, int len
) {
449 // Check if there even is enough space for it
450 if (search
.length
> (len
- offset
)) {
455 for (int i
= 0; i
< search
.length
; i
++) {
456 if (search
[i
] != buffer
[offset
+ i
]) {