View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.helyx.basics4me.io;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  
23  /**
24   * <code>BufferedInputStream</code> is a class which takes an input stream and
25   * <em>buffers</em> the input. In this way, costly interaction with the
26   * original input stream can be minimized by reading buffered amounts of data
27   * infrequently. The drawback is that extra space is required to hold the buffer
28   * and that copying takes place when reading that buffer.
29   * 
30   */
31  public class BufferedInputStream extends FilterInputStream {
32      /**
33       * The buffer containing the current bytes read from the target InputStream.
34       */
35      protected byte[] buf;
36  
37      /**
38       * The total number of bytes inside the byte array <code>buf</code>.
39       */
40      protected int count;
41  
42      /**
43       * The current limit, which when passed, invalidates the current mark.
44       */
45      protected int marklimit;
46  
47      /**
48       * The currently marked position. -1 indicates no mark has been set or the
49       * mark has been invalidated.
50       */
51      protected int markpos = -1;
52  
53      /**
54       * The current position within the byte array <code>buf</code>.
55       */
56      protected int pos;
57  
58      private boolean closed = false;
59  
60      /**
61       * Constructs a new <code>BufferedInputStream</code> on the InputStream
62       * <code>in</code>. The default buffer size (8Kb) is allocated and all
63       * reads can now be filtered through this stream.
64       * 
65       * @param in
66       *            the InputStream to buffer reads on.
67       */
68      public BufferedInputStream(InputStream in) {
69          super(in);
70          buf = new byte[8192];
71      }
72  
73      /**
74       * Constructs a new BufferedInputStream on the InputStream <code>in</code>.
75       * The buffer size is specified by the parameter <code>size</code> and all
76       * reads can now be filtered through this BufferedInputStream.
77       * 
78       * @param in
79       *            the InputStream to buffer reads on.
80       * @param size
81       *            the size of buffer to allocate.
82       */
83      public BufferedInputStream(InputStream in, int size) {
84          super(in);
85          if (size <= 0) {
86              // K0058=size must be > 0
87              throw new IllegalArgumentException("size must be > 0"); //$NON-NLS-1$
88          }
89          buf = new byte[size];
90      }
91  
92      /**
93       * Answers an int representing the number of bytes that are available before
94       * this BufferedInputStream will block. This method returns the number of
95       * bytes available in the buffer plus those available in the target stream.
96       * 
97       * @return the number of bytes available before blocking.
98       * 
99       * @throws IOException
100      *             If an error occurs in this stream.
101      */
102     public synchronized int available() throws IOException {
103         if (buf == null) {
104             // K0059=Stream is closed
105             throw new IOException("Stream is closed"); //$NON-NLS-1$
106         }
107         return count - pos + in.available();
108     }
109 
110     /**
111      * Close this BufferedInputStream. This implementation closes the target
112      * stream and releases any resources associated with it.
113      * 
114      * @throws IOException
115      *             If an error occurs attempting to close this stream.
116      */
117     public synchronized void close() throws IOException {
118         if (null != in) {
119             super.close();
120             in = null;
121         }
122         buf = null;
123         closed = true;
124     }
125 
126     private int fillbuf() throws IOException {
127         if (markpos == -1 || (pos - markpos >= marklimit)) {
128             /* Mark position not set or exceeded readlimit */
129             int result = in.read(buf);
130             if (result > 0) {
131                 markpos = -1;
132                 pos = 0;
133                 count = result == -1 ? 0 : result;
134             }
135             return result;
136         }
137         if (markpos == 0 && marklimit > buf.length) {
138             /* Increase buffer size to accomodate the readlimit */
139             int newLength = buf.length * 2;
140             if (newLength > marklimit) {
141                 newLength = marklimit;
142             }
143             byte[] newbuf = new byte[newLength];
144             System.arraycopy(buf, 0, newbuf, 0, buf.length);
145             buf = newbuf;
146         } else if (markpos > 0) {
147             System.arraycopy(buf, markpos, buf, 0, buf.length - markpos);
148         }
149         /* Set the new position and mark position */
150         pos -= markpos;
151         count = markpos = 0;
152         int bytesread = in.read(buf, pos, buf.length - pos);
153         count = bytesread <= 0 ? pos : pos + bytesread;
154         return bytesread;
155     }
156 
157     /**
158      * Set a Mark position in this BufferedInputStream. The parameter
159      * <code>readLimit</code> indicates how many bytes can be read before a
160      * mark is invalidated. Sending reset() will reposition the Stream back to
161      * the marked position provided <code>readLimit</code> has not been
162      * surpassed. The underlying buffer may be increased in size to allow
163      * <code>readlimit</code> number of bytes to be supported.
164      * 
165      * @param readlimit
166      *            the number of bytes to be able to read before invalidating the
167      *            mark.
168      */
169     public synchronized void mark(int readlimit) {
170         marklimit = readlimit;
171         markpos = pos;
172     }
173 
174     /**
175      * Answers a boolean indicating whether or not this BufferedInputStream
176      * supports mark() and reset(). This implementation answers
177      * <code>true</code>.
178      * 
179      * @return <code>true</code> for BufferedInputStreams.
180      */
181     
182     public boolean markSupported() {
183         return true;
184     }
185 
186     /**
187      * Reads a single byte from this BufferedInputStream and returns the result
188      * as an int. The low-order byte is returned or -1 of the end of stream was
189      * encountered. If the underlying buffer does not contain any available
190      * bytes then it is filled and the first byte is returned.
191      * 
192      * @return the byte read or -1 if end of stream.
193      * 
194      * @throws IOException
195      *             If the stream is already closed or another IOException
196      *             occurs.
197      */
198     public synchronized int read() throws IOException {
199         if (in == null) {
200             // K0059=Stream is closed
201             throw new IOException("Stream is closed"); //$NON-NLS-1$
202         }
203 
204         /* Are there buffered bytes available? */
205         if (pos >= count && fillbuf() == -1) {
206             return -1; /* no, fill buffer */
207         }
208 
209         /* Did filling the buffer fail with -1 (EOF)? */
210         if (count - pos > 0) {
211             return buf[pos++] & 0xFF;
212         }
213         return -1;
214     }
215 
216     /**
217      * Reads at most <code>length</code> bytes from this BufferedInputStream
218      * and stores them in byte array <code>buffer</code> starting at offset
219      * <code>offset</code>. Answer the number of bytes actually read or -1 if
220      * no bytes were read and end of stream was encountered. If all the buffered
221      * bytes have been used, a mark has not been set, and the requested number
222      * of bytes is larger than the receiver's buffer size, this implementation
223      * bypasses the buffer and simply places the results directly into
224      * <code>buffer</code>.
225      * 
226      * @param buffer
227      *            the byte array in which to store the read bytes.
228      * @param offset
229      *            the offset in <code>buffer</code> to store the read bytes.
230      * @param length
231      *            the maximum number of bytes to store in <code>buffer</code>.
232      * @return the number of bytes actually read or -1 if end of stream.
233      * 
234      * @throws IOException
235      *             If the stream is already closed or another IOException
236      *             occurs.
237      */
238     public synchronized int read(byte[] buffer, int offset, int length)
239             throws IOException {
240         if (closed) {
241             // K0059=Stream is closed
242             throw new IOException("Stream is closed"); //$NON-NLS-1$
243         }
244         // avoid int overflow
245         if (offset > buffer.length - length || offset < 0 || length < 0) {
246             throw new IndexOutOfBoundsException();
247         }
248         if (length == 0) {
249             return 0;
250         }
251         if (null == buf) {
252             throw new IOException("Stream is closed"); //$NON-NLS-1$
253         }
254 
255         int required;
256         if (pos < count) {
257             /* There are bytes available in the buffer. */
258             int copylength = count - pos >= length ? length : count - pos;
259             System.arraycopy(buf, pos, buffer, offset, copylength);
260             pos += copylength;
261             if (copylength == length || in.available() == 0) {
262                 return copylength;
263             }
264             offset += copylength;
265             required = length - copylength;
266         } else {
267             required = length;
268         }
269 
270         while (true) {
271             int read;
272             /*
273              * If we're not marked and the required size is greater than the
274              * buffer, simply read the bytes directly bypassing the buffer.
275              */
276             if (markpos == -1 && required >= buf.length) {
277                 read = in.read(buffer, offset, required);
278                 if (read == -1) {
279                     return required == length ? -1 : length - required;
280                 }
281             } else {
282                 if (fillbuf() == -1) {
283                     return required == length ? -1 : length - required;
284                 }
285                 read = count - pos >= required ? required : count - pos;
286                 System.arraycopy(buf, pos, buffer, offset, read);
287                 pos += read;
288             }
289             required -= read;
290             if (required == 0) {
291                 return length;
292             }
293             if (in.available() == 0) {
294                 return length - required;
295             }
296             offset += read;
297         }
298     }
299 
300     /**
301      * Reset this BufferedInputStream to the last marked location. If the
302      * <code>readlimit</code> has been passed or no <code>mark</code> has
303      * been set, throw IOException. This implementation resets the target
304      * stream.
305      * 
306      * @throws IOException
307      *             If the stream is already closed or another IOException
308      *             occurs.
309      */
310     public synchronized void reset() throws IOException {
311         if (closed) {
312             // K0059=Stream is closed
313             throw new IOException("Stream is closed"); //$NON-NLS-1$	
314         }
315         if (-1 == markpos) {
316             // K005a=Mark has been invalidated.
317             throw new IOException("Mark has been invalidated"); //$NON-NLS-1$
318         }
319         pos = markpos;
320     }
321 
322     /**
323      * Skips <code>amount</code> number of bytes in this BufferedInputStream.
324      * Subsequent <code>read()</code>'s will not return these bytes unless
325      * <code>reset()</code> is used.
326      * 
327      * @param amount
328      *            the number of bytes to skip.
329      * @return the number of bytes actually skipped.
330      * 
331      * @throws IOException
332      *             If the stream is already closed or another IOException
333      *             occurs.
334      */
335     public synchronized long skip(long amount) throws IOException {
336         if (null == in) {
337             // K0059=Stream is closed
338             throw new IOException("Stream is closed"); //$NON-NLS-1$
339         }
340         if (amount < 1) {
341             return 0;
342         }
343 
344         if (count - pos >= amount) {
345             pos += amount;
346             return amount;
347         }
348         long read = count - pos;
349         pos = count;
350 
351         if (markpos != -1) {
352             if (amount <= marklimit) {
353                 if (fillbuf() == -1) {
354                     return read;
355                 }
356                 if (count - pos >= amount - read) {
357                     pos += amount - read;
358                     return amount;
359                 }
360                 // Couldn't get all the bytes, skip what we read
361                 read += (count - pos);
362                 pos = count;
363                 return read;
364             }
365             markpos = -1;
366         }
367         return read + in.skip(amount - read);
368     }
369 }