FileDocCategorySizeDatePackage
InputStreamBuffer.javaAPI DocAndroid 5.1 API14619Thu Mar 12 22:22:52 GMT 2015com.android.ex.photo.util

InputStreamBuffer

public class InputStreamBuffer extends Object
Wrapper for {@link InputStream} that allows you to read bytes from it like a byte[]. An internal buffer is kept as small as possible to avoid large unnecessary allocations.

Care must be taken so that the internal buffer is kept small. The best practice is to precalculate the maximum buffer size that you will need. For example, say you have a loop that reads bytes from index 0 to 10, skips to index N, reads from index N to N+10, etc. Then you know that the internal buffer can have a maximum size of 10, and you should set the bufferSize parameter to 10 in the constructor.

Use {@link #advanceTo(int)} to declare that you will not need to access lesser indexes. This helps to keep the internal buffer small. In the above example, after reading bytes from index 0 to 10, you should call advanceTo(N) so that internal buffer becomes filled with bytes from index N to N+10.

If you know that you are reading bytes from a strictly increasing or equal index, then you should set the autoAdvance parameter to true in the constructor. For complicated access patterns, or when you prefer to control the internal buffer yourself, set autoAdvance to false. When autoAdvance is enabled, every time an index is beyond the buffer length, the buffer will be shifted forward such that the index requested becomes the first element in the buffer.

All public methods with parameter index are absolute indexed. The index is from the beginning of the wrapped input stream.

Fields Summary
private static final boolean
DEBUG
private static final int
DEBUG_MAX_BUFFER_SIZE
private static final String
TAG
private InputStream
mInputStream
private byte[]
mBuffer
private boolean
mAutoAdvance
private int
mOffset
Byte count the buffer is offset by.
private int
mFilled
Number of bytes filled in the buffer.
Constructors Summary
public InputStreamBuffer(InputStream inputStream, int bufferSize, boolean autoAdvance)
Construct a new wrapper for an InputStream.

If autoAdvance is true, behavior is undefined if you call {@link #get(int)} or {@link #has(int)} with an index N, then some arbitrary time later call {@link #get(int)} or {@link #has(int)} with an index M < N. The wrapper may return the right value, if the buffer happens to still contain index M, but more likely it will throw an {@link IllegalStateException}.

If autoAdvance is false, you must be diligent and call {@link #advanceTo(int)} at the appropriate times to ensure that the internal buffer is not unnecessarily resized and reallocated.

param
inputStream The input stream to wrap. The input stream will not be closed by the wrapper.
param
bufferSize The initial size for the internal buffer. The buffer size should be carefully chosen to avoid resizing and reallocating the internal buffer. The internal buffer size used will be the least power of two greater than this parameter.
param
autoAdvance Determines the behavior when you need to read an index that is beyond the internal buffer size. If true, the internal buffer will shift so that the requested index becomes the first element. If false, the internal buffer size will grow to the smallest power of 2 which is greater than the requested index.


                                                                                                                                                                                                                                                                                                                                                                                     
         
               
        mInputStream = inputStream;
        if (bufferSize <= 0) {
            throw new IllegalArgumentException(
                    String.format("Buffer size %d must be positive.", bufferSize));
        }
        bufferSize = leastPowerOf2(bufferSize);
        mBuffer = new byte[bufferSize];
        mAutoAdvance = autoAdvance;
    
Methods Summary
public voidadvanceTo(int index)
Attempts to advance the head of the buffer to the requested index. If the index is less than the head of the buffer, the internal state will not be changed.

Advancing does not fill the internal buffer. The next {@link #get(int)} or {@link #has(int)} call will fill the buffer.

        Trace.beginSection("advance to");
        final int i = index - mOffset;
        if (i <= 0) {
            // noop
            Trace.endSection();
            return;
        } else if (i < mFilled) {
            // Shift elements starting at i to position 0.
            shiftToBeginning(i);
            mOffset = index;
            mFilled = mFilled - i;
        } else if (mInputStream != null) {
            // Burn some bytes from the input stream to match the new index.
            int burn = i - mFilled;
            boolean empty = false;
            int fails = 0;
            try {
                while (burn > 0) {
                    final long burned = mInputStream.skip(burn);
                    if (burned <= 0) {
                        fails++;
                    } else {
                        burn -= burned;
                    }

                    if (fails >= 5) {
                        empty = true;
                        break;
                    }
                }
            } catch (IOException ignored) {
                empty = true;
            }

            if (empty) {
                //Mark input stream as consumed.
                mInputStream = null;
            }

            mOffset = index - burn;
            mFilled = 0;
        } else {
            // Advancing beyond the input stream.
            mOffset = index;
            mFilled = 0;
        }

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, String.format("advanceTo %d buffer: %s", i, this));
        }
        Trace.endSection();
    
private booleanfill(int index)
Attempt to fill the internal buffer fully. The buffer will be modified such that the requested index will always be in the buffer. If the index is less than the head of the buffer, a runtime exception is thrown.

If the requested index is already in bounds of the buffer, then the buffer will just be filled.

Otherwise, if autoAdvance was set to true in the constructor, {@link #advanceTo(int)} will be called with the requested index, and then the buffer filled. If autoAdvance was set to false, we allocate a single larger buffer of a least multiple-of-two size that can contain the requested index. The elements in the old buffer are copied over to the head of the new buffer. Then the entire buffer is filled.

param
index The requested index.
return
True if the byte at the requested index has been filled. False if the wrapped input stream ends before we reach the index.

        Trace.beginSection("fill");
        if (index < mOffset) {
            Trace.endSection();
            throw new IllegalStateException(
                    String.format("Index %d is before buffer %d", index, mOffset));
        }

        int i = index - mOffset;
        // Can't fill buffer anymore if input stream is consumed.
        if (mInputStream == null) {
            Trace.endSection();
            return false;
        }

        // Increase buffer size if necessary.
        int length = i + 1;
        if (length > mBuffer.length) {
            if (mAutoAdvance) {
                advanceTo(index);
                i = index - mOffset;
            } else {
                length = leastPowerOf2(length);
                Log.w(TAG, String.format(
                        "Increasing buffer length from %d to %d. Bad buffer size chosen, "
                                + "or advanceTo() not called.",
                        mBuffer.length, length));
                mBuffer = Arrays.copyOf(mBuffer, length);
            }
        }

        // Read from input stream to fill buffer.
        int read = -1;
        try {
            read = mInputStream.read(mBuffer, mFilled, mBuffer.length - mFilled);
        } catch (IOException ignored) {
        }

        if (read != -1) {
            mFilled = mFilled + read;
        } else {
            // Mark input stream as consumed.
            mInputStream = null;
        }

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, String.format("fill %d      buffer: %s", i, this));
        }

        Trace.endSection();
        return i < mFilled;
    
public byteget(int index)
Attempt to get byte at the requested index from the wrapped input stream. If the internal buffer contains the requested index, return immediately. If the index is less than the head of the buffer, or the index is greater or equal to the size of the wrapped input stream, a runtime exception is thrown.

If the index is not in the internal buffer, but it can be requested from the input stream, {@link #fill(int)} will be called first, and the byte at the index returned.

You should always call {@link #has(int)} with the same index, unless you are sure that no exceptions will be thrown as described above.

Consider calling {@link #advanceTo(int)} if you know that you will never request a lesser index in the future.

param
index The requested index.
return
The byte at that index.

        Trace.beginSection("get");
        if (has(index)) {
            final int i = index - mOffset;
            Trace.endSection();
            return mBuffer[i];
        } else {
            Trace.endSection();
            throw new IndexOutOfBoundsException(
                    String.format("Index %d beyond length.", index));
        }
    
public booleanhas(int index)
Attempt to return whether the requested index is within the size of the wrapped input stream. One side effect is {@link #fill(int)} will be called.

If this method returns true, it is guaranteed that {@link #get(int)} with the same index will not fail. That means that if the requested index is within the size of the wrapped input stream, but the index is less than the head of the internal buffer, a runtime exception is thrown.

See {@link #get(int)} for caveats. A lot of the same warnings about exceptions and advanceTo() apply.

param
index The requested index.
return
True if requested index is within the size of the wrapped input stream. False if the index is beyond the size.

        Trace.beginSection("has");
        if (index < mOffset) {
            Trace.endSection();
            throw new IllegalStateException(
                    String.format("Index %d is before buffer %d", index, mOffset));
        }

        final int i = index - mOffset;

        // Requested index not in internal buffer.
        if (i >= mFilled || i >= mBuffer.length) {
            Trace.endSection();
            return fill(index);
        }

        Trace.endSection();
        return true;
    
private static intleastPowerOf2(int n)
Calculate the least power of two greater than or equal to the input.

        n--;
        n |= n >> 1;
        n |= n >> 2;
        n |= n >> 4;
        n |= n >> 8;
        n |= n >> 16;
        n++;
        return n;
    
private voidshiftToBeginning(int i)
Modify the internal buffer so that all the bytes are shifted towards the head by i. In other words, the byte at index i will now be at index 0. Bytes from a lesser index are tossed.

param
i How much to shift left.

        if (i >= mBuffer.length) {
            throw new IndexOutOfBoundsException(
                    String.format("Index %d out of bounds. Length %d", i, mBuffer.length));
        }
        for (int j = 0; j + i < mFilled; j++) {
            mBuffer[j] = mBuffer[j + i];
        }
    
public java.lang.StringtoDebugString()

        Trace.beginSection("to debug string");
        final StringBuilder sb = new StringBuilder();
        sb.append("+").append(mOffset);
        sb.append("+").append(mBuffer.length);
        sb.append(" [");
        for (int i = 0; i < mBuffer.length && i < DEBUG_MAX_BUFFER_SIZE; i++) {
            if (i > 0) {
                sb.append(",");
            }
            if (i < mFilled) {
                sb.append(String.format("%02X", mBuffer[i]));
            } else {
                sb.append("__");
            }
        }
        if (mInputStream != null) {
            sb.append("...");
        }
        sb.append("]");

        Trace.endSection();
        return sb.toString();
    
public java.lang.StringtoString()

        if (DEBUG) {
            return toDebugString();
        }
        return String.format("+%d+%d [%d]", mOffset, mBuffer.length, mFilled);