1 package be
.nikiroo
.utils
.streams
;
3 import java
.io
.IOException
;
4 import java
.io
.InputStream
;
5 import java
.util
.Arrays
;
7 import be
.nikiroo
.utils
.StringUtils
;
10 * A simple {@link InputStream} that is buffered with a bytes array.
12 * It is mostly intended to be used as a base class to create new
13 * {@link InputStream}s with special operation modes, and to give some default
18 public class BufferedInputStream
extends InputStream
{
20 * The size of the internal buffer (can be different if you pass your own
23 * A second buffer of twice the size can sometimes be created as needed for
24 * the {@link BufferedInputStream#startsWith(byte[])} search operation.
26 static private final int BUFFER_SIZE
= 4096;
28 /** The current position in the buffer. */
30 /** The index of the last usable position of the buffer. */
32 /** The buffer itself. */
33 protected byte[] buffer
;
34 /** An End-Of-File (or {@link InputStream}, here) marker. */
35 protected boolean eof
;
37 private boolean closed
;
38 private InputStream in
;
39 private int openCounter
;
41 // special use, prefetched next buffer
42 private byte[] buffer2
;
45 private byte[] originalBuffer
;
47 private long bytesRead
;
50 * Create a new {@link BufferedInputStream} that wraps the given
51 * {@link InputStream}.
54 * the {@link InputStream} to wrap
56 public BufferedInputStream(InputStream in
) {
59 this.buffer
= new byte[BUFFER_SIZE
];
60 this.originalBuffer
= this.buffer
;
66 * Create a new {@link BufferedInputStream} that wraps the given bytes array
70 * the array to wrap, cannot be NULL
72 public BufferedInputStream(byte[] in
) {
73 this(in
, 0, in
.length
);
77 * Create a new {@link BufferedInputStream} that wraps the given bytes array
81 * the array to wrap, cannot be NULL
83 * the offset to start the reading at
85 * the number of bytes to take into account in the array,
86 * starting from the offset
88 * @throws NullPointerException
89 * if the array is NULL
90 * @throws IndexOutOfBoundsException
91 * if the offset and length do not correspond to the given array
93 public BufferedInputStream(byte[] in
, int offset
, int length
) {
95 throw new NullPointerException();
96 } else if (offset
< 0 || length
< 0 || length
> in
.length
- offset
) {
97 throw new IndexOutOfBoundsException();
103 this.originalBuffer
= this.buffer
;
109 * The internal buffer size (can be useful to know for search methods).
111 * @return the size of the internal buffer, in bytes.
113 public int getInternalBufferSize() {
114 return originalBuffer
.length
;
118 * Return this very same {@link BufferedInputStream}, but keep a counter of
119 * how many streams were open this way. When calling
120 * {@link BufferedInputStream#close()}, decrease this counter if it is not
121 * already zero instead of actually closing the stream.
123 * You are now responsible for it — you <b>must</b> close it.
125 * This method allows you to use a wrapping stream around this one and still
126 * close the wrapping stream.
128 * @return the same stream, but you are now responsible for closing it
130 * @throws IOException
131 * in case of I/O error or if the stream is closed
133 public synchronized InputStream
open() throws IOException
{
140 * Check if the current content (until eof) is equal to the given search
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 is(String search
) throws IOException
{
156 return is(StringUtils
.getBytes(search
));
160 * Check if the current content (until eof) is equal to the given search
163 * Note: the search term size <b>must</b> be smaller or equal the internal
167 * the term to search for
169 * @return TRUE if the content that will be read starts with it
171 * @throws IOException
172 * in case of I/O error or if the size of the search term is
173 * greater than the internal buffer
175 public boolean is(byte[] search
) throws IOException
{
176 if (startsWith(search
)) {
177 return (stop
- start
) == search
.length
;
184 * Check if the current content (what will be read next) starts with the
187 * Note: the search term size <b>must</b> be smaller or equal the internal
191 * the term to search for
193 * @return TRUE if the content that will be read starts with it
195 * @throws IOException
196 * in case of I/O error or if the size of the search term is
197 * greater than the internal buffer
199 public boolean startsWith(String search
) throws IOException
{
200 return startsWith(StringUtils
.getBytes(search
));
204 * Check if the current content (what will be read next) starts with the
207 * An empty string will always return true (unless the stream is closed,
208 * which would throw an {@link IOException}).
210 * Note: the search term size <b>must</b> be smaller or equal the internal
214 * the term to search for
216 * @return TRUE if the content that will be read starts with it
218 * @throws IOException
219 * in case of I/O error or if the size of the search term is
220 * greater than the internal buffer
222 public boolean startsWith(byte[] search
) throws IOException
{
223 if (search
.length
> originalBuffer
.length
) {
224 throw new IOException(
225 "This stream does not support searching for more than "
226 + buffer
.length
+ " bytes");
231 if (available() < search
.length
) {
235 if (available() >= search
.length
) {
237 return StreamUtils
.startsWith(search
, buffer
, start
, stop
);
238 } else if (in
!= null && !eof
) {
240 if (buffer2
== null && buffer
.length
== originalBuffer
.length
) {
241 buffer2
= Arrays
.copyOf(buffer
, buffer
.length
* 2);
243 pos2
= buffer
.length
;
244 len2
= read(in
, buffer2
, pos2
, buffer
.length
);
249 // Note: here, len/len2 = INDEX of last good byte
253 return StreamUtils
.startsWith(search
, buffer2
, pos2
, len2
);
260 * The number of bytes read from the under-laying {@link InputStream}.
262 * @return the number of bytes
264 public long getBytesRead() {
269 * Check if this stream is spent (no more data to read or to
272 * @return TRUE if it is
274 * @throws IOException
275 * in case of I/O error
277 public boolean eof() throws IOException
{
283 return !hasMoreData();
287 * Read the whole {@link InputStream} until the end and return the number of
290 * @return the number of bytes read
292 * @throws IOException
293 * in case of I/O error
295 public long end() throws IOException
{
297 while (hasMoreData()) {
298 skipped
+= skip(buffer
.length
);
305 public int read() throws IOException
{
313 return buffer
[start
++];
317 public int read(byte[] b
) throws IOException
{
318 return read(b
, 0, b
.length
);
322 public int read(byte[] b
, int boff
, int blen
) throws IOException
{
326 throw new NullPointerException();
327 } else if (boff
< 0 || blen
< 0 || blen
> b
.length
- boff
) {
328 throw new IndexOutOfBoundsException();
329 } else if (blen
== 0) {
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 (hasMoreData() && n
> 0) {
359 long inBuffer
= Math
.min(n
, available());
369 public int available() {
374 return Math
.max(0, stop
- start
);
378 * Closes this stream and releases any system resources associated with the
381 * Including the under-laying {@link InputStream}.
383 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
384 * prior to this one, it will just decrease the internal count of how many
385 * open streams it held and do nothing else. The stream will actually be
386 * closed when you have called {@link BufferedInputStream#close()} once more
387 * than {@link BufferedInputStream#open()}.
389 * @exception IOException
390 * in case of I/O error
393 public synchronized void close() throws IOException
{
398 * Closes this stream and releases any system resources associated with the
401 * Including the under-laying {@link InputStream} if
402 * <tt>incudingSubStream</tt> is true.
404 * You can call this method multiple times, it will not cause an
405 * {@link IOException} for subsequent calls.
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 * @param includingSubStream
414 * also close the under-laying stream
416 * @exception IOException
417 * in case of I/O error
419 public synchronized void close(boolean includingSubStream
)
422 if (openCounter
> 0) {
426 if (includingSubStream
&& in
!= null) {
434 * Check if we still have some data in the buffer and, if not, fetch some.
436 * @return TRUE if we fetched some data, FALSE if there are still some in
439 * @throws IOException
440 * in case of I/O error
442 protected boolean preRead() throws IOException
{
443 boolean hasRead
= false;
444 if (in
!= null && !eof
&& start
>= stop
) {
446 if (buffer2
!= null) {
455 buffer
= originalBuffer
;
457 stop
= read(in
, buffer
, 0, buffer
.length
);
474 * Read the under-laying stream into the local buffer.
477 * the under-laying {@link InputStream}
479 * the buffer we use in this {@link BufferedInputStream}
483 * the length in bytes
485 * @return the number of bytes read
487 * @throws IOException
488 * in case of I/O error
490 protected int read(InputStream in
, byte[] buffer
, int off
, int len
)
492 return in
.read(buffer
, off
, len
);
496 * We have more data available in the buffer <b>or</b> we can, maybe, fetch
499 * @return TRUE if it is the case, FALSE if not
501 protected boolean hasMoreData() {
506 return (start
< stop
) || !eof
;
510 * Check that the stream was not closed, and throw an {@link IOException} if
513 * @throws IOException
516 protected void checkClose() throws IOException
{
518 throw new IOException(
519 "This BufferedInputStream was closed, you cannot use it anymore.");