• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.bumptech.glide.load.resource.bitmap;
2 
3 /*
4  *  Licensed to the Apache Software Foundation (ASF) under one or more
5  *  contributor license agreements.  See the NOTICE file distributed with
6  *  this work for additional information regarding copyright ownership.
7  *  The ASF licenses this file to You under the Apache License, Version 2.0
8  *  (the "License"); you may not use this file except in compliance with
9  *  the License.  You may obtain a copy of the License at
10  *
11  *     http://www.apache.org/licenses/LICENSE-2.0
12  *
13  *  Unless required by applicable law or agreed to in writing, software
14  *  distributed under the License is distributed on an "AS IS" BASIS,
15  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  *  See the License for the specific language governing permissions and
17  *  limitations under the License.
18  */
19 
20 import android.util.Log;
21 
22 import java.io.FilterInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 
26 /**
27  * Wraps an existing {@link InputStream} and <em>buffers</em> the input.
28  * Expensive interaction with the underlying input stream is minimized, since
29  * most (smaller) requests can be satisfied by accessing the buffer alone. The
30  * drawback is that some extra space is required to hold the buffer and that
31  * copying takes place when filling that buffer, but this is usually outweighed
32  * by the performance benefits.
33  *
34  * <p>A typical application pattern for the class looks like this:</p>
35  *
36  * <pre>
37  * BufferedInputStream buf = new BufferedInputStream(new FileInputStream("file.java"));
38  * </pre>
39  */
40 public class RecyclableBufferedInputStream extends FilterInputStream {
41     private static final String TAG = "BufferedIs";
42 
43     /**
44      * The buffer containing the current bytes read from the target InputStream.
45      */
46     private volatile byte[] buf;
47 
48     /**
49      * The total number of bytes inside the byte array {@code buf}.
50      */
51     private int count;
52 
53     /**
54      * The current limit, which when passed, invalidates the current mark.
55      */
56     private int marklimit;
57 
58     /**
59      * The currently marked position. -1 indicates no mark has been set or the
60      * mark has been invalidated.
61      */
62     private int markpos = -1;
63 
64     /**
65      * The current position within the byte array {@code buf}.
66      */
67     private int pos;
68 
RecyclableBufferedInputStream(InputStream in, byte[] buffer)69     public RecyclableBufferedInputStream(InputStream in, byte[] buffer) {
70         super(in);
71         if (buffer == null || buffer.length == 0) {
72             throw new IllegalArgumentException("buffer is null or empty");
73         }
74         buf = buffer;
75     }
76 
77     /**
78      * Returns an estimated number of bytes that can be read or skipped without blocking for more
79      * input. This method returns the number of bytes available in the buffer
80      * plus those available in the source stream, but see {@link InputStream#available} for
81      * important caveats.
82      *
83      * @return the estimated number of bytes available
84      * @throws IOException if this stream is closed or an error occurs
85      */
86     @Override
available()87     public synchronized int available() throws IOException {
88         // in could be invalidated by close().
89         InputStream localIn = in;
90         if (buf == null || localIn == null) {
91             throw streamClosed();
92         }
93         return count - pos + localIn.available();
94     }
95 
streamClosed()96     private static IOException streamClosed() throws IOException {
97         throw new IOException("BufferedInputStream is closed");
98     }
99 
100     /**
101      * Reduces the mark limit to match the current buffer length to prevent the buffer from
102      * continuing to increase in size.
103      *
104      * <p>Subsequent calls to {@link #mark(int)} will be obeyed and may cause the buffer size
105      * to increase.
106      */
fixMarkLimit()107     public synchronized void fixMarkLimit() {
108         marklimit = buf.length;
109     }
110 
111     /**
112      * Closes this stream. The source stream is closed and any resources
113      * associated with it are released.
114      *
115      * @throws IOException
116      *             if an error occurs while closing this stream.
117      */
118     @Override
close()119     public void close() throws IOException {
120         buf = null;
121         InputStream localIn = in;
122         in = null;
123         if (localIn != null) {
124             localIn.close();
125         }
126     }
127 
fillbuf(InputStream localIn, byte[] localBuf)128     private int fillbuf(InputStream localIn, byte[] localBuf)
129             throws IOException {
130         if (markpos == -1 || pos - markpos >= marklimit) {
131             // Mark position not set or exceeded readlimit
132             int result = localIn.read(localBuf);
133             if (result > 0) {
134                 markpos = -1;
135                 pos = 0;
136                 count = result;
137             }
138             return result;
139         }
140         // Added count == localBuf.length so that we do not immediately double the buffer size before reading any data
141         // when marklimit > localBuf.length. Instead, we will double the buffer size only after reading the initial
142         // localBuf worth of data without finding what we're looking for in the stream. This allows us to set a
143         // relatively small initial buffer size and a large marklimit for safety without causing an allocation each time
144         // read is called.
145         if (markpos == 0 && marklimit > localBuf.length && count == localBuf.length) {
146             // Increase buffer size to accommodate the readlimit
147             int newLength = localBuf.length * 2;
148             if (newLength > marklimit) {
149                 newLength = marklimit;
150             }
151             if (Log.isLoggable(TAG, Log.DEBUG)) {
152                 Log.d(TAG, "allocate buffer of length: " + newLength);
153             }
154             byte[] newbuf = new byte[newLength];
155             System.arraycopy(localBuf, 0, newbuf, 0, localBuf.length);
156             // Reassign buf, which will invalidate any local references
157             // FIXME: what if buf was null?
158             localBuf = buf = newbuf;
159         } else if (markpos > 0) {
160             System.arraycopy(localBuf, markpos, localBuf, 0, localBuf.length
161                     - markpos);
162         }
163         // Set the new position and mark position
164         pos -= markpos;
165         count = markpos = 0;
166         int bytesread = localIn.read(localBuf, pos, localBuf.length - pos);
167         count = bytesread <= 0 ? pos : pos + bytesread;
168         return bytesread;
169     }
170 
171     /**
172      * Sets a mark position in this stream. The parameter {@code readlimit}
173      * indicates how many bytes can be read before a mark is invalidated.
174      * Calling {@link #reset()} will reposition the stream back to the marked
175      * position if {@code readlimit} has not been surpassed. The underlying
176      * buffer may be increased in size to allow {@code readlimit} number of
177      * bytes to be supported.
178      *
179      * @param readlimit
180      *            the number of bytes that can be read before the mark is
181      *            invalidated.
182      * @see #reset()
183      */
184     @Override
mark(int readlimit)185     public synchronized void mark(int readlimit) {
186         // This is stupid, but BitmapFactory.decodeStream calls mark(1024)
187         // which is too small for a substantial portion of images. This
188         // change (using Math.max) ensures that we don't overwrite readlimit
189         // with a smaller value
190         marklimit = Math.max(marklimit, readlimit);
191         markpos = pos;
192     }
193 
194     /**
195      * Indicates whether {@code BufferedInputStream} supports the {@link #mark(int)}
196      * and {@link #reset()} methods.
197      *
198      * @return {@code true} for BufferedInputStreams.
199      * @see #mark(int)
200      * @see #reset()
201      */
202     @Override
markSupported()203     public boolean markSupported() {
204         return true;
205     }
206 
207     /**
208      * Reads a single byte from this stream and returns it as an integer in the
209      * range from 0 to 255. Returns -1 if the end of the source string has been
210      * reached. If the internal buffer does not contain any available bytes then
211      * it is filled from the source stream and the first byte is returned.
212      *
213      * @return the byte read or -1 if the end of the source stream has been
214      *         reached.
215      * @throws IOException
216      *             if this stream is closed or another IOException occurs.
217      */
218     @Override
read()219     public synchronized int read() throws IOException {
220         // Use local refs since buf and in may be invalidated by an
221         // unsynchronized close()
222         byte[] localBuf = buf;
223         InputStream localIn = in;
224         if (localBuf == null || localIn == null) {
225             throw streamClosed();
226         }
227 
228         // Are there buffered bytes available?
229         if (pos >= count && fillbuf(localIn, localBuf) == -1) {
230             // no, fill buffer
231             return -1;
232         }
233         // localBuf may have been invalidated by fillbuf
234         if (localBuf != buf) {
235             localBuf = buf;
236             if (localBuf == null) {
237                 throw streamClosed();
238             }
239         }
240 
241         // Did filling the buffer fail with -1 (EOF)?
242         if (count - pos > 0) {
243             return localBuf[pos++] & 0xFF;
244         }
245         return -1;
246     }
247 
248     /**
249      * Reads at most {@code byteCount} bytes from this stream and stores them in
250      * byte array {@code buffer} starting at offset {@code offset}. Returns the
251      * number of bytes actually read or -1 if no bytes were read and the end of
252      * the stream was encountered. If all the buffered bytes have been used, a
253      * mark has not been set and the requested number of bytes is larger than
254      * the receiver's buffer size, this implementation bypasses the buffer and
255      * simply places the results directly into {@code buffer}.
256      *
257      * @param buffer
258      *            the byte array in which to store the bytes read.
259      * @return the number of bytes actually read or -1 if end of stream.
260      * @throws IndexOutOfBoundsException
261      *             if {@code offset < 0} or {@code byteCount < 0}, or if
262      *             {@code offset + byteCount} is greater than the size of
263      *             {@code buffer}.
264      * @throws IOException
265      *             if the stream is already closed or another IOException
266      *             occurs.
267      */
268     @Override
read(byte[] buffer, int offset, int byteCount)269     public synchronized int read(byte[] buffer, int offset, int byteCount) throws IOException {
270         // Use local ref since buf may be invalidated by an unsynchronized close()
271         byte[] localBuf = buf;
272         if (localBuf == null) {
273             throw streamClosed();
274         }
275         //Arrays.checkOffsetAndCount(buffer.length, offset, byteCount);
276         if (byteCount == 0) {
277             return 0;
278         }
279         InputStream localIn = in;
280         if (localIn == null) {
281             throw streamClosed();
282         }
283 
284         int required;
285         if (pos < count) {
286             // There are bytes available in the buffer.
287             int copylength = count - pos >= byteCount ? byteCount : count - pos;
288             System.arraycopy(localBuf, pos, buffer, offset, copylength);
289             pos += copylength;
290             if (copylength == byteCount || localIn.available() == 0) {
291                 return copylength;
292             }
293             offset += copylength;
294             required = byteCount - copylength;
295         } else {
296             required = byteCount;
297         }
298 
299         while (true) {
300             int read;
301             // If we're not marked and the required size is greater than the buffer,
302             // simply read the bytes directly bypassing the buffer.
303             if (markpos == -1 && required >= localBuf.length) {
304                 read = localIn.read(buffer, offset, required);
305                 if (read == -1) {
306                     return required == byteCount ? -1 : byteCount - required;
307                 }
308             } else {
309                 if (fillbuf(localIn, localBuf) == -1) {
310                     return required == byteCount ? -1 : byteCount - required;
311                 }
312                 // localBuf may have been invalidated by fillbuf
313                 if (localBuf != buf) {
314                     localBuf = buf;
315                     if (localBuf == null) {
316                         throw streamClosed();
317                     }
318                 }
319 
320                 read = count - pos >= required ? required : count - pos;
321                 System.arraycopy(localBuf, pos, buffer, offset, read);
322                 pos += read;
323             }
324             required -= read;
325             if (required == 0) {
326                 return byteCount;
327             }
328             if (localIn.available() == 0) {
329                 return byteCount - required;
330             }
331             offset += read;
332         }
333     }
334 
335     /**
336      * Resets this stream to the last marked location.
337      *
338      * @throws IOException
339      *             if this stream is closed, no mark has been set or the mark is
340      *             no longer valid because more than {@code readlimit} bytes
341      *             have been read since setting the mark.
342      * @see #mark(int)
343      */
344     @Override
reset()345     public synchronized void reset() throws IOException {
346         if (buf == null) {
347             throw new IOException("Stream is closed");
348         }
349         if (-1 == markpos) {
350             throw new InvalidMarkException("Mark has been invalidated");
351         }
352         pos = markpos;
353     }
354 
355     /**
356      * Skips {@code byteCount} bytes in this stream. Subsequent calls to
357      * {@link #read} will not return these bytes unless {@link #reset} is
358      * used.
359      *
360      * @param byteCount
361      *            the number of bytes to skip. {@link #skip} does nothing and
362      *            returns 0 if {@code byteCount} is less than zero.
363      * @return the number of bytes actually skipped.
364      * @throws IOException
365      *             if this stream is closed or another IOException occurs.
366      */
367     @Override
skip(long byteCount)368     public synchronized long skip(long byteCount) throws IOException {
369         // Use local refs since buf and in may be invalidated by an unsynchronized close()
370         byte[] localBuf = buf;
371         InputStream localIn = in;
372         if (localBuf == null) {
373             throw streamClosed();
374         }
375         if (byteCount < 1) {
376             return 0;
377         }
378         if (localIn == null) {
379             throw streamClosed();
380         }
381 
382         if (count - pos >= byteCount) {
383             pos += byteCount;
384             return byteCount;
385         }
386         long read = count - pos;
387         pos = count;
388 
389         if (markpos != -1 && byteCount <= marklimit) {
390             if (fillbuf(localIn, localBuf) == -1) {
391                 return read;
392             }
393             if (count - pos >= byteCount - read) {
394                 pos += byteCount - read;
395                 return byteCount;
396             }
397             // Couldn't get all the bytes, skip what we read.
398             read = read + count - pos;
399             pos = count;
400             return read;
401         }
402         return read + localIn.skip(byteCount - read);
403     }
404 
405     /**
406      * An exception thrown when a mark can no longer be obeyed because the underlying buffer size is smaller than the
407      * amount of data read after the mark position.
408      */
409     public static class InvalidMarkException extends RuntimeException {
410         private static final long serialVersionUID = -4338378848813561757L;
411 
InvalidMarkException(String detailMessage)412         public InvalidMarkException(String detailMessage) {
413             super(detailMessage);
414         }
415     }
416 }
417