jdoc
[nikiroo-utils.git] / src / be / nikiroo / utils / streams / BufferedInputStream.java
CommitLineData
8e76f6ab 1package be.nikiroo.utils.streams;
33895a7b
NR
2
3import java.io.IOException;
4import java.io.InputStream;
5import java.util.Arrays;
6
f8147a0e
NR
7import be.nikiroo.utils.StringUtils;
8
33895a7b
NR
9/**
10 * A simple {@link InputStream} that is buffered with a bytes array.
11 * <p>
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
14 * methods.
15 *
16 * @author niki
17 */
18public class BufferedInputStream extends InputStream {
7194ac50
NR
19 /**
20 * The size of the internal buffer (can be different if you pass your own
21 * buffer, of course).
22 * <p>
23 * A second buffer of twice the size can sometimes be created as needed for
24 * the {@link BufferedInputStream#startsWith(byte[])} search operation.
25 */
26 static private final int BUFFER_SIZE = 4096;
27
eeaa5ebc 28 /** The current position in the buffer. */
a26188d3 29 protected int start;
eeaa5ebc 30 /** The index of the last usable position of the buffer. */
a26188d3 31 protected int stop;
eeaa5ebc 32 /** The buffer itself. */
33895a7b 33 protected byte[] buffer;
7194ac50 34 /** An End-Of-File (or {@link InputStream}, here) marker. */
33895a7b
NR
35 protected boolean eof;
36
37 private boolean closed;
38 private InputStream in;
39 private int openCounter;
40
41 // special use, prefetched next buffer
42 private byte[] buffer2;
43 private int pos2;
44 private int len2;
45 private byte[] originalBuffer;
46
47 private long bytesRead;
48
49 /**
50 * Create a new {@link BufferedInputStream} that wraps the given
51 * {@link InputStream}.
52 *
53 * @param in
54 * the {@link InputStream} to wrap
55 */
56 public BufferedInputStream(InputStream in) {
57 this.in = in;
58
7194ac50 59 this.buffer = new byte[BUFFER_SIZE];
33895a7b 60 this.originalBuffer = this.buffer;
a26188d3
NR
61 this.start = 0;
62 this.stop = 0;
33895a7b
NR
63 }
64
65 /**
66 * Create a new {@link BufferedInputStream} that wraps the given bytes array
67 * as a data source.
68 *
69 * @param in
70 * the array to wrap, cannot be NULL
71 */
72 public BufferedInputStream(byte[] in) {
73 this(in, 0, in.length);
74 }
75
76 /**
77 * Create a new {@link BufferedInputStream} that wraps the given bytes array
78 * as a data source.
79 *
80 * @param in
81 * the array to wrap, cannot be NULL
82 * @param offset
83 * the offset to start the reading at
84 * @param length
85 * the number of bytes to take into account in the array,
86 * starting from the offset
87 *
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
92 */
93 public BufferedInputStream(byte[] in, int offset, int length) {
94 if (in == null) {
95 throw new NullPointerException();
96 } else if (offset < 0 || length < 0 || length > in.length - offset) {
97 throw new IndexOutOfBoundsException();
98 }
99
100 this.in = null;
101
102 this.buffer = in;
103 this.originalBuffer = this.buffer;
a26188d3
NR
104 this.start = offset;
105 this.stop = length;
33895a7b
NR
106 }
107
108 /**
109 * Return this very same {@link BufferedInputStream}, but keep a counter of
110 * how many streams were open this way. When calling
111 * {@link BufferedInputStream#close()}, decrease this counter if it is not
112 * already zero instead of actually closing the stream.
113 * <p>
114 * You are now responsible for it &mdash; you <b>must</b> close it.
115 * <p>
116 * This method allows you to use a wrapping stream around this one and still
117 * close the wrapping stream.
118 *
119 * @return the same stream, but you are now responsible for closing it
120 *
121 * @throws IOException
122 * in case of I/O error or if the stream is closed
123 */
124 public synchronized InputStream open() throws IOException {
125 checkClose();
126 openCounter++;
127 return this;
128 }
129
eeaa5ebc
NR
130 /**
131 * Check if the current content (what will be read next) starts with the
132 * given search term.
133 * <p>
134 * Note: the search term size <b>must</b> be smaller or equal the internal
135 * buffer size.
136 *
137 * @param search
138 * the term to search for
139 *
140 * @return TRUE if the content that will be read starts with it
141 *
142 * @throws IOException
143 * in case of I/O error or if the size of the search term is
144 * greater than the internal buffer
145 */
33895a7b 146 public boolean startsWiths(String search) throws IOException {
f8147a0e 147 return startsWith(StringUtils.getBytes(search));
33895a7b
NR
148 }
149
eeaa5ebc
NR
150 /**
151 * Check if the current content (what will be read next) starts with the
152 * given search term.
153 * <p>
154 * Note: the search term size <b>must</b> be smaller or equal the internal
155 * buffer size.
156 *
157 * @param search
158 * the term to search for
159 *
160 * @return TRUE if the content that will be read starts with it
161 *
162 * @throws IOException
163 * in case of I/O error or if the size of the search term is
164 * greater than the internal buffer
165 */
33895a7b
NR
166 public boolean startsWith(byte[] search) throws IOException {
167 if (search.length > originalBuffer.length) {
168 throw new IOException(
169 "This stream does not support searching for more than "
170 + buffer.length + " bytes");
171 }
172
173 checkClose();
174
175 if (available() < search.length) {
176 preRead();
177 }
178
179 if (available() >= search.length) {
180 // Easy path
c8ce09c4 181 return StreamUtils.startsWith(search, buffer, start, stop);
33895a7b
NR
182 } else if (!eof) {
183 // Harder path
184 if (buffer2 == null && buffer.length == originalBuffer.length) {
185 buffer2 = Arrays.copyOf(buffer, buffer.length * 2);
186
187 pos2 = buffer.length;
028ff7c2 188 len2 = read(in, buffer2, pos2, buffer.length);
33895a7b
NR
189 if (len2 > 0) {
190 bytesRead += len2;
191 }
192
193 // Note: here, len/len2 = INDEX of last good byte
194 len2 += pos2;
195 }
196
c8ce09c4 197 return StreamUtils.startsWith(search, buffer2, pos2, len2);
33895a7b
NR
198 }
199
200 return false;
201 }
202
203 /**
204 * The number of bytes read from the under-laying {@link InputStream}.
205 *
206 * @return the number of bytes
207 */
208 public long getBytesRead() {
209 return bytesRead;
210 }
211
212 /**
213 * Check if this stream is totally spent (no more data to read or to
214 * process).
33895a7b
NR
215 *
216 * @return TRUE if it is
028ff7c2
NR
217 *
218 * @throws IOException
219 * in case of I/O error
33895a7b 220 */
028ff7c2
NR
221 public boolean eof() throws IOException {
222 if (closed) {
223 return true;
224 }
225
226 preRead();
227 return !hasMoreData();
33895a7b
NR
228 }
229
230 @Override
231 public int read() throws IOException {
232 checkClose();
233
234 preRead();
235 if (eof) {
236 return -1;
237 }
238
a26188d3 239 return buffer[start++];
33895a7b
NR
240 }
241
242 @Override
243 public int read(byte[] b) throws IOException {
244 return read(b, 0, b.length);
245 }
246
247 @Override
248 public int read(byte[] b, int boff, int blen) throws IOException {
249 checkClose();
250
251 if (b == null) {
252 throw new NullPointerException();
253 } else if (boff < 0 || blen < 0 || blen > b.length - boff) {
254 throw new IndexOutOfBoundsException();
255 } else if (blen == 0) {
256 return 0;
257 }
258
259 int done = 0;
260 while (hasMoreData() && done < blen) {
261 preRead();
262 if (hasMoreData()) {
f04d5e49 263 int now = Math.min(blen - done, stop - start);
33895a7b 264 if (now > 0) {
a26188d3
NR
265 System.arraycopy(buffer, start, b, boff + done, now);
266 start += now;
33895a7b
NR
267 done += now;
268 }
269 }
270 }
271
272 return done > 0 ? done : -1;
273 }
274
275 @Override
276 public long skip(long n) throws IOException {
277 if (n <= 0) {
278 return 0;
279 }
280
281 long skipped = 0;
282 while (hasMoreData() && n > 0) {
283 preRead();
284
285 long inBuffer = Math.min(n, available());
a26188d3 286 start += inBuffer;
33895a7b
NR
287 n -= inBuffer;
288 skipped += inBuffer;
289 }
290
291 return skipped;
292 }
293
294 @Override
295 public int available() {
296 if (closed) {
297 return 0;
298 }
299
a26188d3 300 return Math.max(0, stop - start);
33895a7b
NR
301 }
302
303 /**
304 * Closes this stream and releases any system resources associated with the
305 * stream.
306 * <p>
307 * Including the under-laying {@link InputStream}.
308 * <p>
309 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
310 * prior to this one, it will just decrease the internal count of how many
311 * open streams it held and do nothing else. The stream will actually be
312 * closed when you have called {@link BufferedInputStream#close()} once more
313 * than {@link BufferedInputStream#open()}.
314 *
315 * @exception IOException
316 * in case of I/O error
317 */
318 @Override
319 public synchronized void close() throws IOException {
320 close(true);
321 }
322
323 /**
324 * Closes this stream and releases any system resources associated with the
325 * stream.
326 * <p>
327 * Including the under-laying {@link InputStream} if
328 * <tt>incudingSubStream</tt> is true.
329 * <p>
330 * You can call this method multiple times, it will not cause an
331 * {@link IOException} for subsequent calls.
332 * <p>
333 * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
334 * prior to this one, it will just decrease the internal count of how many
335 * open streams it held and do nothing else. The stream will actually be
336 * closed when you have called {@link BufferedInputStream#close()} once more
337 * than {@link BufferedInputStream#open()}.
338 *
eeaa5ebc
NR
339 * @param includingSubStream
340 * also close the under-laying stream
341 *
33895a7b
NR
342 * @exception IOException
343 * in case of I/O error
344 */
345 public synchronized void close(boolean includingSubStream)
346 throws IOException {
347 if (!closed) {
348 if (openCounter > 0) {
349 openCounter--;
350 } else {
351 closed = true;
352 if (includingSubStream && in != null) {
353 in.close();
354 }
355 }
356 }
357 }
358
359 /**
360 * Check if we still have some data in the buffer and, if not, fetch some.
361 *
362 * @return TRUE if we fetched some data, FALSE if there are still some in
363 * the buffer
364 *
365 * @throws IOException
366 * in case of I/O error
367 */
368 protected boolean preRead() throws IOException {
369 boolean hasRead = false;
028ff7c2 370 if (in != null && !eof && start >= stop) {
a26188d3 371 start = 0;
33895a7b
NR
372 if (buffer2 != null) {
373 buffer = buffer2;
a26188d3
NR
374 start = pos2;
375 stop = len2;
33895a7b
NR
376
377 buffer2 = null;
378 pos2 = 0;
379 len2 = 0;
380 } else {
381 buffer = originalBuffer;
382
028ff7c2 383 stop = read(in, buffer, 0, buffer.length);
a26188d3
NR
384 if (stop > 0) {
385 bytesRead += stop;
33895a7b
NR
386 }
387 }
388
389 hasRead = true;
390 }
391
a26188d3 392 if (start >= stop) {
33895a7b
NR
393 eof = true;
394 }
395
396 return hasRead;
397 }
398
399 /**
400 * Read the under-laying stream into the local buffer.
401 *
402 * @param in
403 * the under-laying {@link InputStream}
404 * @param buffer
405 * the buffer we use in this {@link BufferedInputStream}
028ff7c2
NR
406 * @param off
407 * the offset
408 * @param len
409 * the length in bytes
33895a7b
NR
410 *
411 * @return the number of bytes read
412 *
413 * @throws IOException
414 * in case of I/O error
415 */
028ff7c2
NR
416 protected int read(InputStream in, byte[] buffer, int off, int len)
417 throws IOException {
418 return in.read(buffer, off, len);
33895a7b
NR
419 }
420
421 /**
028ff7c2
NR
422 * We have more data available in the buffer <b>or</b> we can, maybe, fetch
423 * more.
33895a7b
NR
424 *
425 * @return TRUE if it is the case, FALSE if not
426 */
427 protected boolean hasMoreData() {
028ff7c2
NR
428 if (closed) {
429 return false;
430 }
431
432 return (start < stop) || !eof;
33895a7b
NR
433 }
434
435 /**
436 * Check that the stream was not closed, and throw an {@link IOException} if
437 * it was.
438 *
439 * @throws IOException
440 * if it was closed
441 */
442 protected void checkClose() throws IOException {
443 if (closed) {
444 throw new IOException(
eeaa5ebc 445 "This BufferedInputStream was closed, you cannot use it anymore.");
33895a7b
NR
446 }
447 }
33895a7b 448}