1
2
3
4
5
6
7
8 package de.netseeker.ejoe.io;
9
10 import java.io.FilterInputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13
14 /***
15 * A <code>BufferedInputStream</code> adds functionality to another input stream-namely, the ability to buffer the
16 * input and to support the <code>mark</code> and <code>reset</code> methods. When the
17 * <code>BufferedInputStream</code> is created, an internal buffer array is created. As bytes from the stream are read
18 * or skipped, the internal buffer is refilled as necessary from the contained input stream, many bytes at a time. The
19 * <code>mark</code> operation remembers a point in the input stream and the <code>reset</code> operation causes all
20 * the bytes read since the most recent <code>mark</code> operation to be reread before new bytes are taken from the
21 * contained input stream.
22 *
23 * @author Arthur van Hoff
24 * @version 1.43, 01/23/03
25 * @since JDK1.0
26 */
27 public class FastBufferedInputStream extends FilterInputStream
28 {
29
30 private static int defaultBufferSize = 2048;
31
32 /***
33 * The internal buffer array where the data is stored. When necessary, it may be replaced by another array of a
34 * different size.
35 */
36 protected byte buf[];
37
38 /***
39 * The index one greater than the index of the last valid byte in the buffer. This value is always in the range
40 * <code>0</code> through <code>buf.length</code>; elements <code>buf[0]</code> through <code>buf[count-1]
41 * </code>contain
42 * buffered input data obtained from the underlying input stream.
43 */
44 protected int count;
45
46 /***
47 * The current position in the buffer. This is the index of the next character to be read from the <code>buf</code>
48 * array.
49 * <p>
50 * This value is always in the range <code>0</code> through <code>count</code>. If it is less than
51 * <code>count</code>, then <code>buf[pos]</code> is the next byte to be supplied as input; if it is equal to
52 * <code>count</code>, then the next <code>read</code> or <code>skip</code> operation will require more bytes
53 * to be read from the contained input stream.
54 *
55 * @see java.io.BufferedInputStream#buf
56 */
57 protected int pos;
58
59 /***
60 * The value of the <code>pos</code> field at the time the last <code>mark</code> method was called.
61 * <p>
62 * This value is always in the range <code>-1</code> through <code>pos</code>. If there is no marked position
63 * in the input stream, this field is <code>-1</code>. If there is a marked position in the input stream, then
64 * <code>buf[markpos]</code> is the first byte to be supplied as input after a <code>reset</code> operation. If
65 * <code>markpos</code> is not <code>-1</code>, then all bytes from positions <code>buf[markpos]</code>
66 * through <code>buf[pos-1]</code> must remain in the buffer array (though they may be moved to another place in
67 * the buffer array, with suitable adjustments to the values of <code>count</code>, <code>pos</code>, and
68 * <code>markpos</code>); they may not be discarded unless and until the difference between <code>pos</code>
69 * and <code>markpos</code> exceeds <code>marklimit</code>.
70 *
71 * @see java.io.BufferedInputStream#mark(int)
72 * @see java.io.BufferedInputStream#pos
73 */
74 protected int markpos = -1;
75
76 /***
77 * The maximum read ahead allowed after a call to the <code>mark</code> method before subsequent calls to the
78 * <code>reset</code> method fail. Whenever the difference between <code>pos</code> and <code>markpos</code>
79 * exceeds <code>marklimit</code>, then the mark may be dropped by setting <code>markpos</code> to
80 * <code>-1</code>.
81 *
82 * @see java.io.BufferedInputStream#mark(int)
83 * @see java.io.BufferedInputStream#reset()
84 */
85 protected int marklimit;
86
87 /***
88 * Check to make sure that this stream has not been closed
89 */
90 private void ensureOpen() throws IOException
91 {
92 if ( in == null ) throw new IOException( "Stream closed" );
93 }
94
95 /***
96 * Creates a <code>FastBufferedInputStream</code> and saves its argument, the input stream <code>in</code>, for
97 * later use. An internal buffer array is created and stored in <code>buf</code>.
98 *
99 * @param in the underlying input stream.
100 */
101 public FastBufferedInputStream(InputStream in)
102 {
103 this( in, defaultBufferSize );
104 }
105
106 /***
107 * Creates a <code>FastBufferedInputStream</code> with the specified buffer size, and saves its argument, the
108 * input stream <code>in</code>, for later use. An internal buffer array of length <code>size</code> is created
109 * and stored in <code>buf</code>.
110 *
111 * @param in the underlying input stream.
112 * @param size the buffer size.
113 * @exception IllegalArgumentException if size <= 0.
114 */
115 public FastBufferedInputStream(InputStream in, int size)
116 {
117 super( in );
118 if ( size <= 0 )
119 {
120 throw new IllegalArgumentException( "Buffer size <= 0" );
121 }
122 buf = new byte[size];
123 }
124
125 /***
126 * Fills the buffer with more data, taking into account shuffling and other tricks for dealing with marks. Assumes
127 * that it is being called by a synchronized method. This method also assumes that all data has already been read
128 * in, hence pos > count.
129 */
130 private void fill() throws IOException
131 {
132 if ( markpos < 0 )
133 pos = 0;
134 else if ( pos >= buf.length )
135 if ( markpos > 0 )
136 {
137 int sz = pos - markpos;
138 System.arraycopy( buf, markpos, buf, 0, sz );
139 pos = sz;
140 markpos = 0;
141 }
142 else if ( buf.length >= marklimit )
143 {
144 markpos = -1;
145 pos = 0;
146 }
147 else
148 {
149 int nsz = pos * 2;
150 if ( nsz > marklimit ) nsz = marklimit;
151 byte nbuf[] = new byte[nsz];
152 System.arraycopy( buf, 0, nbuf, 0, pos );
153 buf = nbuf;
154 }
155 count = pos;
156 int n = in.read( buf, pos, buf.length - pos );
157 if ( n > 0 ) count = n + pos;
158 }
159
160 /***
161 * See the general contract of the <code>read</code> method of <code>InputStream</code>.
162 *
163 * @return the next byte of data, or <code>-1</code> if the end of the stream is reached.
164 * @exception IOException if an I/O error occurs.
165 * @see java.io.FilterInputStream#in
166 */
167 public int read() throws IOException
168 {
169 ensureOpen();
170 if ( pos >= count )
171 {
172 fill();
173 if ( pos >= count ) return -1;
174 }
175 return buf[pos++] & 0xff;
176 }
177
178 /***
179 * Read characters into a portion of an array, reading from the underlying stream at most once if necessary.
180 */
181 private int read1( byte[] b, int off, int len ) throws IOException
182 {
183 int avail = count - pos;
184 if ( avail <= 0 )
185 {
186
187
188
189
190 if ( len >= buf.length && markpos < 0 )
191 {
192 return in.read( b, off, len );
193 }
194 fill();
195 avail = count - pos;
196 if ( avail <= 0 ) return -1;
197 }
198 int cnt = (avail < len) ? avail : len;
199 System.arraycopy( buf, pos, b, off, cnt );
200 pos += cnt;
201 return cnt;
202 }
203
204 /***
205 * Reads bytes from this byte-input stream into the specified byte array, starting at the given offset.
206 * <p>
207 * This method implements the general contract of the corresponding
208 * <code>{@link InputStream#read(byte[], int, int) read}</code> method of the <code>{@link InputStream}</code>
209 * class. As an additional convenience, it attempts to read as many bytes as possible by repeatedly invoking the
210 * <code>read</code> method of the underlying stream. This iterated <code>read</code> continues until one of the
211 * following conditions becomes true:
212 * <ul>
213 * <li> The specified number of bytes have been read,
214 * <li> The <code>read</code> method of the underlying stream returns <code>-1</code>, indicating end-of-file,
215 * or
216 * <li> The <code>available</code> method of the underlying stream returns zero, indicating that further input
217 * requests would block.
218 * </ul>
219 * If the first <code>read</code> on the underlying stream returns <code>-1</code> to indicate end-of-file then
220 * this method returns <code>-1</code>. Otherwise this method returns the number of bytes actually read.
221 * <p>
222 * Subclasses of this class are encouraged, but not required, to attempt to read as many bytes as possible in the
223 * same fashion.
224 *
225 * @param b destination buffer.
226 * @param off offset at which to start storing bytes.
227 * @param len maximum number of bytes to read.
228 * @return the number of bytes read, or <code>-1</code> if the end of the stream has been reached.
229 * @exception IOException if an I/O error occurs.
230 */
231 public int read( byte b[], int off, int len ) throws IOException
232 {
233 ensureOpen();
234 if ( (off | len | (off + len) | (b.length - (off + len))) < 0 )
235 {
236 throw new IndexOutOfBoundsException();
237 }
238 else if ( len == 0 )
239 {
240 return 0;
241 }
242
243 int n = read1( b, off, len );
244 if ( n <= 0 ) return n;
245 while ( (n < len) && (in.available() > 0) )
246 {
247 int n1 = read1( b, off + n, len - n );
248 if ( n1 <= 0 ) break;
249 n += n1;
250 }
251 return n;
252 }
253
254 /***
255 * See the general contract of the <code>skip</code> method of <code>InputStream</code>.
256 *
257 * @param n the number of bytes to be skipped.
258 * @return the actual number of bytes skipped.
259 * @exception IOException if an I/O error occurs.
260 */
261 public long skip( long n ) throws IOException
262 {
263 ensureOpen();
264 if ( n <= 0 )
265 {
266 return 0;
267 }
268 long avail = count - pos;
269
270 if ( avail <= 0 )
271 {
272
273 if ( markpos < 0 ) return in.skip( n );
274
275
276 fill();
277 avail = count - pos;
278 if ( avail <= 0 ) return 0;
279 }
280
281 long skipped = (avail < n) ? avail : n;
282 pos += skipped;
283 return skipped;
284 }
285
286 /***
287 * Returns the number of bytes that can be read from this input stream without blocking.
288 * <p>
289 * The <code>available</code> method of <code>BufferedInputStream</code> returns the sum of the the number of
290 * bytes remaining to be read in the buffer (<code>count - pos</code>) and the result of calling the
291 * <code>available</code> method of the underlying input stream.
292 *
293 * @return the number of bytes that can be read from this input stream without blocking.
294 * @exception IOException if an I/O error occurs.
295 * @see java.io.FilterInputStream#in
296 */
297 public int available() throws IOException
298 {
299 ensureOpen();
300 return (count - pos) + in.available();
301 }
302
303 /***
304 * See the general contract of the <code>mark</code> method of <code>InputStream</code>.
305 *
306 * @param readlimit the maximum limit of bytes that can be read before the mark position becomes invalid.
307 * @see java.io.BufferedInputStream#reset()
308 */
309 public void mark( int readlimit )
310 {
311 marklimit = readlimit;
312 markpos = pos;
313 }
314
315 /***
316 * See the general contract of the <code>reset</code> method of <code>InputStream</code>.
317 * <p>
318 * If <code>markpos</code> is <code>-1</code> (no mark has been set or the mark has been invalidated), an
319 * <code>IOException</code> is thrown. Otherwise, <code>pos</code> is set equal to <code>markpos</code>.
320 *
321 * @exception IOException if this stream has not been marked or if the mark has been invalidated.
322 * @see java.io.BufferedInputStream#mark(int)
323 */
324 public void reset() throws IOException
325 {
326 ensureOpen();
327 if ( markpos < 0 ) throw new IOException( "Resetting to invalid mark" );
328 pos = markpos;
329 }
330
331 /***
332 * Tests if this input stream supports the <code>mark</code> and <code>reset</code> methods. The
333 * <code>markSupported</code> method of <code>BufferedInputStream</code> returns <code>true</code>.
334 *
335 * @return a <code>boolean</code> indicating if this stream type supports the <code>mark</code> and
336 * <code>reset</code> methods.
337 * @see java.io.InputStream#mark(int)
338 * @see java.io.InputStream#reset()
339 */
340 public boolean markSupported()
341 {
342 return true;
343 }
344
345 /***
346 * Closes this input stream and releases any system resources associated with the stream.
347 *
348 * @exception IOException if an I/O error occurs.
349 */
350 public void close() throws IOException
351 {
352 if ( in == null ) return;
353 in.close();
354 in = null;
355 buf = null;
356 }
357 }