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
18package java.io;
19
20import java.util.Arrays;
21
22/**
23 * Wraps an existing {@link InputStream} and <em>buffers</em> the input.
24 * Expensive interaction with the underlying input stream is minimized, since
25 * most (smaller) requests can be satisfied by accessing the buffer alone. The
26 * drawback is that some extra space is required to hold the buffer and that
27 * copying takes place when filling that buffer, but this is usually outweighed
28 * by the performance benefits.
29 *
30 * <p/>A typical application pattern for the class looks like this:<p/>
31 *
32 * <pre>
33 * BufferedInputStream buf = new BufferedInputStream(new FileInputStream(&quot;file.java&quot;));
34 * </pre>
35 *
36 * @see BufferedOutputStream
37 */
38public class BufferedInputStream extends FilterInputStream {
39    /**
40     * The default buffer size if it is not specified.
41     *
42     * @hide
43     */
44    public static final int DEFAULT_BUFFER_SIZE = 8192;
45
46    /**
47     * The buffer containing the current bytes read from the target InputStream.
48     */
49    protected volatile byte[] buf;
50
51    /**
52     * The total number of bytes inside the byte array {@code buf}.
53     */
54    protected int count;
55
56    /**
57     * The current limit, which when passed, invalidates the current mark.
58     */
59    protected int marklimit;
60
61    /**
62     * The currently marked position. -1 indicates no mark has been set or the
63     * mark has been invalidated.
64     */
65    protected int markpos = -1;
66
67    /**
68     * The current position within the byte array {@code buf}.
69     */
70    protected int pos;
71
72    /**
73     * Constructs a new {@code BufferedInputStream}, providing {@code in} with a buffer
74     * of 8192 bytes.
75     *
76     * <p><strong>Warning:</strong> passing a null source creates a closed
77     * {@code BufferedInputStream}. All read operations on such a stream will
78     * fail with an IOException.
79     *
80     * @param in the {@code InputStream} the buffer reads from.
81     */
82    public BufferedInputStream(InputStream in) {
83        this(in, DEFAULT_BUFFER_SIZE);
84    }
85
86    /**
87     * Constructs a new {@code BufferedInputStream}, providing {@code in} with {@code size} bytes
88     * of buffer.
89     *
90     * <p><strong>Warning:</strong> passing a null source creates a closed
91     * {@code BufferedInputStream}. All read operations on such a stream will
92     * fail with an IOException.
93     *
94     * @param in the {@code InputStream} the buffer reads from.
95     * @param size the size of buffer in bytes.
96     * @throws IllegalArgumentException if {@code size <= 0}.
97     */
98    public BufferedInputStream(InputStream in, int size) {
99        super(in);
100        if (size <= 0) {
101            throw new IllegalArgumentException("size <= 0");
102        }
103        buf = new byte[size];
104    }
105
106    /**
107     * Returns an estimated number of bytes that can be read or skipped without blocking for more
108     * input. This method returns the number of bytes available in the buffer
109     * plus those available in the source stream, but see {@link InputStream#available} for
110     * important caveats.
111     *
112     * @return the estimated number of bytes available
113     * @throws IOException if this stream is closed or an error occurs
114     */
115    @Override
116    public synchronized int available() throws IOException {
117        InputStream localIn = in; // 'in' could be invalidated by close()
118        if (buf == null || localIn == null) {
119            throw streamClosed();
120        }
121        return count - pos + localIn.available();
122    }
123
124    private IOException streamClosed() throws IOException {
125        throw new IOException("BufferedInputStream is closed");
126    }
127
128    /**
129     * Closes this stream. The source stream is closed and any resources
130     * associated with it are released.
131     *
132     * @throws IOException
133     *             if an error occurs while closing this stream.
134     */
135    @Override
136    public void close() throws IOException {
137        buf = null;
138        InputStream localIn = in;
139        in = null;
140        if (localIn != null) {
141            localIn.close();
142        }
143    }
144
145    private int fillbuf(InputStream localIn, byte[] localBuf)
146            throws IOException {
147        if (markpos == -1 || (pos - markpos >= marklimit)) {
148            /* Mark position not set or exceeded readlimit */
149            int result = localIn.read(localBuf);
150            if (result > 0) {
151                markpos = -1;
152                pos = 0;
153                count = result == -1 ? 0 : result;
154            }
155            return result;
156        }
157        if (markpos == 0 && marklimit > localBuf.length) {
158            /* Increase buffer size to accommodate the readlimit */
159            int newLength = localBuf.length * 2;
160            if (newLength > marklimit) {
161                newLength = marklimit;
162            }
163            byte[] newbuf = new byte[newLength];
164            System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
165            // Reassign buf, which will invalidate any local references
166            // FIXME: what if buf was null?
167            localBuf = buf = newbuf;
168        } else if (markpos > 0) {
169            System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length
170                    - markpos);
171        }
172        /* Set the new position and mark position */
173        pos -= markpos;
174        count = markpos = 0;
175        int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);
176        count = bytesread <= 0 ? pos : pos + bytesread;
177        return bytesread;
178    }
179
180    /**
181     * Sets a mark position in this stream. The parameter {@code readlimit}
182     * indicates how many bytes can be read before a mark is invalidated.
183     * Calling {@code reset()} will reposition the stream back to the marked
184     * position if {@code readlimit} has not been surpassed. The underlying
185     * buffer may be increased in size to allow {@code readlimit} number of
186     * bytes to be supported.
187     *
188     * @param readlimit
189     *            the number of bytes that can be read before the mark is
190     *            invalidated.
191     * @see #reset()
192     */
193    @Override
194    public synchronized void mark(int readlimit) {
195        marklimit = readlimit;
196        markpos = pos;
197    }
198
199    /**
200     * Indicates whether {@code BufferedInputStream} supports the {@code mark()}
201     * and {@code reset()} methods.
202     *
203     * @return {@code true} for BufferedInputStreams.
204     * @see #mark(int)
205     * @see #reset()
206     */
207    @Override
208    public boolean markSupported() {
209        return true;
210    }
211
212    /**
213     * Reads a single byte from this stream and returns it as an integer in the
214     * range from 0 to 255. Returns -1 if the end of the source string has been
215     * reached. If the internal buffer does not contain any available bytes then
216     * it is filled from the source stream and the first byte is returned.
217     *
218     * @return the byte read or -1 if the end of the source stream has been
219     *         reached.
220     * @throws IOException
221     *             if this stream is closed or another IOException occurs.
222     */
223    @Override
224    public synchronized int read() throws IOException {
225        // Use local refs since buf and in may be invalidated by an
226        // unsynchronized close()
227        byte[] localBuf = buf;
228        InputStream localIn = in;
229        if (localBuf == null || localIn == null) {
230            throw streamClosed();
231        }
232
233        /* Are there buffered bytes available? */
234        if (pos >= count && fillbuf(localIn, localBuf) == -1) {
235            return -1; /* no, fill buffer */
236        }
237        // localBuf may have been invalidated by fillbuf
238        if (localBuf != buf) {
239            localBuf = buf;
240            if (localBuf == null) {
241                throw streamClosed();
242            }
243        }
244
245        /* Did filling the buffer fail with -1 (EOF)? */
246        if (count - pos > 0) {
247            return localBuf[pos++] & 0xFF;
248        }
249        return -1;
250    }
251
252    @Override public synchronized int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
253        // Use local ref since buf may be invalidated by an unsynchronized
254        // close()
255        byte[] localBuf = buf;
256        if (localBuf == null) {
257            throw streamClosed();
258        }
259        Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
260        if (byteCount == 0) {
261            return 0;
262        }
263        InputStream localIn = in;
264        if (localIn == null) {
265            throw streamClosed();
266        }
267
268        int required;
269        if (pos < count) {
270            /* There are bytes available in the buffer. */
271            int copylength = count - pos >= byteCount ? byteCount : count - pos;
272            System.arraycopy(localBuf, pos, buffer, byteOffset, copylength);
273            pos += copylength;
274            if (copylength == byteCount || localIn.available() == 0) {
275                return copylength;
276            }
277            byteOffset += copylength;
278            required = byteCount - copylength;
279        } else {
280            required = byteCount;
281        }
282
283        while (true) {
284            int read;
285            /*
286             * If we're not marked and the required size is greater than the
287             * buffer, simply read the bytes directly bypassing the buffer.
288             */
289            if (markpos == -1 && required >= localBuf.length) {
290                read = localIn.read(buffer, byteOffset, required);
291                if (read == -1) {
292                    return required == byteCount ? -1 : byteCount - required;
293                }
294            } else {
295                if (fillbuf(localIn, localBuf) == -1) {
296                    return required == byteCount ? -1 : byteCount - required;
297                }
298                // localBuf may have been invalidated by fillbuf
299                if (localBuf != buf) {
300                    localBuf = buf;
301                    if (localBuf == null) {
302                        throw streamClosed();
303                    }
304                }
305
306                read = count - pos >= required ? required : count - pos;
307                System.arraycopy(localBuf, pos, buffer, byteOffset, read);
308                pos += read;
309            }
310            required -= read;
311            if (required == 0) {
312                return byteCount;
313            }
314            if (localIn.available() == 0) {
315                return byteCount - required;
316            }
317            byteOffset += read;
318        }
319    }
320
321    /**
322     * Resets this stream to the last marked location.
323     *
324     * @throws IOException
325     *             if this stream is closed, no mark has been set or the mark is
326     *             no longer valid because more than {@code readlimit} bytes
327     *             have been read since setting the mark.
328     * @see #mark(int)
329     */
330    @Override
331    public synchronized void reset() throws IOException {
332        if (buf == null) {
333            throw new IOException("Stream is closed");
334        }
335        if (markpos == -1) {
336            throw new IOException("Mark has been invalidated.");
337        }
338        pos = markpos;
339    }
340
341    /**
342     * Skips {@code byteCount} bytes in this stream. Subsequent calls to
343     * {@code read} will not return these bytes unless {@code reset} is
344     * used.
345     *
346     * @param byteCount
347     *            the number of bytes to skip. {@code skip} does nothing and
348     *            returns 0 if {@code byteCount} is less than zero.
349     * @return the number of bytes actually skipped.
350     * @throws IOException
351     *             if this stream is closed or another IOException occurs.
352     */
353    @Override
354    public synchronized long skip(long byteCount) throws IOException {
355        // Use local refs since buf and in may be invalidated by an
356        // unsynchronized close()
357        byte[] localBuf = buf;
358        InputStream localIn = in;
359        if (localBuf == null) {
360            throw streamClosed();
361        }
362        if (byteCount < 1) {
363            return 0;
364        }
365        if (localIn == null) {
366            throw streamClosed();
367        }
368
369        if (count - pos >= byteCount) {
370            pos += byteCount;
371            return byteCount;
372        }
373        long read = count - pos;
374        pos = count;
375
376        if (markpos != -1) {
377            if (byteCount <= marklimit) {
378                if (fillbuf(localIn, localBuf) == -1) {
379                    return read;
380                }
381                if (count - pos >= byteCount - read) {
382                    pos += byteCount - read;
383                    return byteCount;
384                }
385                // Couldn't get all the bytes, skip what we read
386                read += (count - pos);
387                pos = count;
388                return read;
389            }
390        }
391        return read + localIn.skip(byteCount - read);
392    }
393}
394