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