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