1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ex.photo.util; 18 19 import android.util.Log; 20 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.util.Arrays; 24 25 /** 26 * Wrapper for {@link InputStream} that allows you to read bytes from it like a byte[]. An 27 * internal buffer is kept as small as possible to avoid large unnecessary allocations. 28 * 29 * <p/> 30 * Care must be taken so that the internal buffer is kept small. The best practice is to 31 * precalculate the maximum buffer size that you will need. For example, 32 * say you have a loop that reads bytes from index <code>0</code> to <code>10</code>, 33 * skips to index <code>N</code>, reads from index <code>N</code> to <code>N+10</code>, etc. Then 34 * you know that the internal buffer can have a maximum size of <code>10</code>, 35 * and you should set the <code>bufferSize</code> parameter to <code>10</code> in the constructor. 36 * 37 * <p/> 38 * Use {@link #advanceTo(int)} to declare that you will not need to access lesser indexes. This 39 * helps to keep the internal buffer small. In the above example, after reading bytes from index 40 * <code>0</code> to <code>10</code>, you should call <code>advanceTo(N)</code> so that internal 41 * buffer becomes filled with bytes from index <code>N</code> to <code>N+10</code>. 42 * 43 * <p/> 44 * If you know that you are reading bytes from a <strong>strictly</strong> increasing or equal 45 * index, then you should set the <code>autoAdvance</code> parameter to <code>true</code> in the 46 * constructor. For complicated access patterns, or when you prefer to control the internal 47 * buffer yourself, set <code>autoAdvance</code> to <code>false</code>. When 48 * <code>autoAdvance</code> is enabled, every time an index is beyond the buffer length, 49 * the buffer will be shifted forward such that the index requested becomes the first element in 50 * the buffer. 51 * 52 * <p/> 53 * All public methods with parameter <code>index</code> are absolute indexed. The index is from 54 * the beginning of the wrapped input stream. 55 */ 56 public class InputStreamBuffer { 57 58 private static final boolean DEBUG = false; 59 private static final int DEBUG_MAX_BUFFER_SIZE = 80; 60 private static final String TAG = "InputStreamBuffer"; 61 62 private InputStream mInputStream; 63 private byte[] mBuffer; 64 private boolean mAutoAdvance; 65 /** Byte count the buffer is offset by. */ 66 private int mOffset = 0; 67 /** Number of bytes filled in the buffer. */ 68 private int mFilled = 0; 69 70 /** 71 * Construct a new wrapper for an InputStream. 72 * 73 * <p/> 74 * If <code>autoAdvance</code> is true, behavior is undefined if you call {@link #get(int)} 75 * or {@link #has(int)} with an index N, then some arbitrary time later call {@link #get(int)} 76 * or {@link #has(int)} with an index M < N. The wrapper may return the right value, 77 * if the buffer happens to still contain index M, but more likely it will throw an 78 * {@link IllegalStateException}. 79 * 80 * <p/> 81 * If <code>autoAdvance</code> is false, you must be diligent and call {@link #advanceTo(int)} 82 * at the appropriate times to ensure that the internal buffer is not unnecessarily resized 83 * and reallocated. 84 * 85 * @param inputStream The input stream to wrap. The input stream will not be closed by the 86 * wrapper. 87 * @param bufferSize The initial size for the internal buffer. The buffer size should be 88 * carefully chosen to avoid resizing and reallocating the internal buffer. 89 * The internal buffer size used will be the least power of two greater 90 * than this parameter. 91 * @param autoAdvance Determines the behavior when you need to read an index that is beyond 92 * the internal buffer size. If true, the internal buffer will shift so 93 * that the requested index becomes the first element. If false, 94 * the internal buffer size will grow to the smallest power of 2 which is 95 * greater than the requested index. 96 */ InputStreamBuffer(final InputStream inputStream, int bufferSize, final boolean autoAdvance)97 public InputStreamBuffer(final InputStream inputStream, int bufferSize, 98 final boolean autoAdvance) { 99 mInputStream = inputStream; 100 if (bufferSize <= 0) { 101 throw new IllegalArgumentException( 102 String.format("Buffer size %d must be positive.", bufferSize)); 103 } 104 bufferSize = leastPowerOf2(bufferSize); 105 mBuffer = new byte[bufferSize]; 106 mAutoAdvance = autoAdvance; 107 } 108 109 /** 110 * Attempt to get byte at the requested index from the wrapped input stream. If the internal 111 * buffer contains the requested index, return immediately. If the index is less than the 112 * head of the buffer, or the index is greater or equal to the size of the wrapped input stream, 113 * a runtime exception is thrown. 114 * 115 * <p/> 116 * If the index is not in the internal buffer, but it can be requested from the input stream, 117 * {@link #fill(int)} will be called first, and the byte at the index returned. 118 * 119 * <p/> 120 * You should always call {@link #has(int)} with the same index, unless you are sure that no 121 * exceptions will be thrown as described above. 122 * 123 * <p/> 124 * Consider calling {@link #advanceTo(int)} if you know that you will never request a lesser 125 * index in the future. 126 * @param index The requested index. 127 * @return The byte at that index. 128 */ get(final int index)129 public byte get(final int index) throws IllegalStateException, IndexOutOfBoundsException { 130 Trace.beginSection("get"); 131 if (has(index)) { 132 final int i = index - mOffset; 133 Trace.endSection(); 134 return mBuffer[i]; 135 } else { 136 Trace.endSection(); 137 throw new IndexOutOfBoundsException( 138 String.format("Index %d beyond length.", index)); 139 } 140 } 141 142 /** 143 * Attempt to return whether the requested index is within the size of the wrapped input 144 * stream. One side effect is {@link #fill(int)} will be called. 145 * 146 * <p/> 147 * If this method returns true, it is guaranteed that {@link #get(int)} with the same index 148 * will not fail. That means that if the requested index is within the size of the wrapped 149 * input stream, but the index is less than the head of the internal buffer, 150 * a runtime exception is thrown. 151 * 152 * <p/> 153 * See {@link #get(int)} for caveats. A lot of the same warnings about exceptions and 154 * <code>advanceTo()</code> apply. 155 * @param index The requested index. 156 * @return True if requested index is within the size of the wrapped input stream. False if 157 * the index is beyond the size. 158 */ has(final int index)159 public boolean has(final int index) throws IllegalStateException, IndexOutOfBoundsException { 160 Trace.beginSection("has"); 161 if (index < mOffset) { 162 Trace.endSection(); 163 throw new IllegalStateException( 164 String.format("Index %d is before buffer %d", index, mOffset)); 165 } 166 167 final int i = index - mOffset; 168 169 // Requested index not in internal buffer. 170 if (i >= mFilled || i >= mBuffer.length) { 171 Trace.endSection(); 172 return fill(index); 173 } 174 175 Trace.endSection(); 176 return true; 177 } 178 179 /** 180 * Attempts to advance the head of the buffer to the requested index. If the index is less 181 * than the head of the buffer, the internal state will not be changed. 182 * 183 * <p/> 184 * Advancing does not fill the internal buffer. The next {@link #get(int)} or 185 * {@link #has(int)} call will fill the buffer. 186 */ advanceTo(final int index)187 public void advanceTo(final int index) throws IllegalStateException, IndexOutOfBoundsException { 188 Trace.beginSection("advance to"); 189 final int i = index - mOffset; 190 if (i <= 0) { 191 // noop 192 Trace.endSection(); 193 return; 194 } else if (i < mFilled) { 195 // Shift elements starting at i to position 0. 196 shiftToBeginning(i); 197 mOffset = index; 198 mFilled = mFilled - i; 199 } else if (mInputStream != null) { 200 // Burn some bytes from the input stream to match the new index. 201 int burn = i - mFilled; 202 boolean empty = false; 203 int fails = 0; 204 try { 205 while (burn > 0) { 206 final long burned = mInputStream.skip(burn); 207 if (burned <= 0) { 208 fails++; 209 } else { 210 burn -= burned; 211 } 212 213 if (fails >= 5) { 214 empty = true; 215 break; 216 } 217 } 218 } catch (IOException ignored) { 219 empty = true; 220 } 221 222 if (empty) { 223 //Mark input stream as consumed. 224 mInputStream = null; 225 } 226 227 mOffset = index - burn; 228 mFilled = 0; 229 } else { 230 // Advancing beyond the input stream. 231 mOffset = index; 232 mFilled = 0; 233 } 234 235 if (Log.isLoggable(TAG, Log.DEBUG)) { 236 Log.d(TAG, String.format("advanceTo %d buffer: %s", i, this)); 237 } 238 Trace.endSection(); 239 } 240 241 /** 242 * Attempt to fill the internal buffer fully. The buffer will be modified such that the 243 * requested index will always be in the buffer. If the index is less 244 * than the head of the buffer, a runtime exception is thrown. 245 * 246 * <p/> 247 * If the requested index is already in bounds of the buffer, then the buffer will just be 248 * filled. 249 * 250 * <p/> 251 * Otherwise, if <code>autoAdvance</code> was set to true in the constructor, 252 * {@link #advanceTo(int)} will be called with the requested index, 253 * and then the buffer filled. If <code>autoAdvance</code> was set to false, 254 * we allocate a single larger buffer of a least multiple-of-two size that can contain the 255 * requested index. The elements in the old buffer are copied over to the head of the new 256 * buffer. Then the entire buffer is filled. 257 * @param index The requested index. 258 * @return True if the byte at the requested index has been filled. False if the wrapped 259 * input stream ends before we reach the index. 260 */ fill(final int index)261 private boolean fill(final int index) { 262 Trace.beginSection("fill"); 263 if (index < mOffset) { 264 Trace.endSection(); 265 throw new IllegalStateException( 266 String.format("Index %d is before buffer %d", index, mOffset)); 267 } 268 269 int i = index - mOffset; 270 // Can't fill buffer anymore if input stream is consumed. 271 if (mInputStream == null) { 272 Trace.endSection(); 273 return false; 274 } 275 276 // Increase buffer size if necessary. 277 int length = i + 1; 278 if (length > mBuffer.length) { 279 if (mAutoAdvance) { 280 advanceTo(index); 281 i = index - mOffset; 282 } else { 283 length = leastPowerOf2(length); 284 Log.w(TAG, String.format( 285 "Increasing buffer length from %d to %d. Bad buffer size chosen, " 286 + "or advanceTo() not called.", 287 mBuffer.length, length)); 288 mBuffer = Arrays.copyOf(mBuffer, length); 289 } 290 } 291 292 // Read from input stream to fill buffer. 293 int read = -1; 294 try { 295 read = mInputStream.read(mBuffer, mFilled, mBuffer.length - mFilled); 296 } catch (IOException ignored) { 297 } 298 299 if (read != -1) { 300 mFilled = mFilled + read; 301 } else { 302 // Mark input stream as consumed. 303 mInputStream = null; 304 } 305 306 if (Log.isLoggable(TAG, Log.DEBUG)) { 307 Log.d(TAG, String.format("fill %d buffer: %s", i, this)); 308 } 309 310 Trace.endSection(); 311 return i < mFilled; 312 } 313 314 /** 315 * Modify the internal buffer so that all the bytes are shifted towards the head by 316 * <code>i</code>. In other words, the byte at index <code>i</code> will now be at index 317 * <code>0</code>. Bytes from a lesser index are tossed. 318 * @param i How much to shift left. 319 */ shiftToBeginning(final int i)320 private void shiftToBeginning(final int i) { 321 if (i >= mBuffer.length) { 322 throw new IndexOutOfBoundsException( 323 String.format("Index %d out of bounds. Length %d", i, mBuffer.length)); 324 } 325 for (int j = 0; j + i < mFilled; j++) { 326 mBuffer[j] = mBuffer[j + i]; 327 } 328 } 329 330 @Override toString()331 public String toString() { 332 if (DEBUG) { 333 return toDebugString(); 334 } 335 return String.format("+%d+%d [%d]", mOffset, mBuffer.length, mFilled); 336 } 337 toDebugString()338 public String toDebugString() { 339 Trace.beginSection("to debug string"); 340 final StringBuilder sb = new StringBuilder(); 341 sb.append("+").append(mOffset); 342 sb.append("+").append(mBuffer.length); 343 sb.append(" ["); 344 for (int i = 0; i < mBuffer.length && i < DEBUG_MAX_BUFFER_SIZE; i++) { 345 if (i > 0) { 346 sb.append(","); 347 } 348 if (i < mFilled) { 349 sb.append(String.format("%02X", mBuffer[i])); 350 } else { 351 sb.append("__"); 352 } 353 } 354 if (mInputStream != null) { 355 sb.append("..."); 356 } 357 sb.append("]"); 358 359 Trace.endSection(); 360 return sb.toString(); 361 } 362 363 /** 364 * Calculate the least power of two greater than or equal to the input. 365 */ leastPowerOf2(int n)366 private static int leastPowerOf2(int n) { 367 n--; 368 n |= n >> 1; 369 n |= n >> 2; 370 n |= n >> 4; 371 n |= n >> 8; 372 n |= n >> 16; 373 n++; 374 return n; 375 } 376 } 377