688 lines
21 KiB
Java
688 lines
21 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2012 The Android Open Source Project
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*/
|
||
|
|
||
|
package android.util.proto;
|
||
|
|
||
|
import android.annotation.TestApi;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import java.util.ArrayList;
|
||
|
|
||
|
/**
|
||
|
* A stream of bytes containing a read pointer and a write pointer,
|
||
|
* backed by a set of fixed-size buffers. There are write functions for the
|
||
|
* primitive types stored by protocol buffers, but none of the logic
|
||
|
* for tags, inner objects, or any of that.
|
||
|
*
|
||
|
* Terminology:
|
||
|
* *Pos: Position in the whole data set (as if it were a single buffer).
|
||
|
* *Index: Position within a buffer.
|
||
|
* *BufIndex: Index of a buffer within the mBuffers list
|
||
|
* @hide
|
||
|
*/
|
||
|
@TestApi
|
||
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
||
|
public final class EncodedBuffer {
|
||
|
private static final String TAG = "EncodedBuffer";
|
||
|
|
||
|
private final ArrayList<byte[]> mBuffers = new ArrayList<byte[]>();
|
||
|
|
||
|
private final int mChunkSize;
|
||
|
|
||
|
/**
|
||
|
* The number of buffers in mBuffers. Stored separately to avoid the extra
|
||
|
* function call to size() everywhere for bounds checking.
|
||
|
*/
|
||
|
private int mBufferCount;
|
||
|
|
||
|
/**
|
||
|
* The buffer we are currently writing to.
|
||
|
*/
|
||
|
private byte[] mWriteBuffer;
|
||
|
|
||
|
/**
|
||
|
* The index into mWriteBuffer that we will write to next.
|
||
|
* It may point to the end of the buffer, in which case,
|
||
|
* the NEXT write will allocate a new buffer.
|
||
|
*/
|
||
|
private int mWriteIndex;
|
||
|
|
||
|
/**
|
||
|
* The index of mWriteBuffer in mBuffers.
|
||
|
*/
|
||
|
private int mWriteBufIndex;
|
||
|
|
||
|
/**
|
||
|
* The buffer we are currently reading from.
|
||
|
*/
|
||
|
private byte[] mReadBuffer;
|
||
|
|
||
|
/**
|
||
|
* The index of mReadBuffer in mBuffers.
|
||
|
*/
|
||
|
private int mReadBufIndex;
|
||
|
|
||
|
/**
|
||
|
* The index into mReadBuffer that we will read from next.
|
||
|
* It may point to the end of the buffer, in which case,
|
||
|
* the NEXT read will advance to the next buffer.
|
||
|
*/
|
||
|
private int mReadIndex;
|
||
|
|
||
|
/**
|
||
|
* The amount of data in the last buffer.
|
||
|
*/
|
||
|
private int mReadLimit = -1;
|
||
|
|
||
|
/**
|
||
|
* How much data there is total.
|
||
|
*/
|
||
|
private int mReadableSize = -1;
|
||
|
|
||
|
public EncodedBuffer() {
|
||
|
this(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Construct an EncodedBuffer object.
|
||
|
*
|
||
|
* @param chunkSize The size of the buffers to use. If chunkSize <= 0, a default
|
||
|
* size will be used instead.
|
||
|
*/
|
||
|
public EncodedBuffer(int chunkSize) {
|
||
|
if (chunkSize <= 0) {
|
||
|
chunkSize = 8 * 1024;
|
||
|
}
|
||
|
mChunkSize = chunkSize;
|
||
|
mWriteBuffer = new byte[mChunkSize];
|
||
|
mBuffers.add(mWriteBuffer);
|
||
|
mBufferCount = 1;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Buffer management.
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Rewind the read and write pointers, and record how much data was last written.
|
||
|
*/
|
||
|
public void startEditing() {
|
||
|
mReadableSize = ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
|
||
|
mReadLimit = mWriteIndex;
|
||
|
|
||
|
mWriteBuffer = mBuffers.get(0);
|
||
|
mWriteIndex = 0;
|
||
|
mWriteBufIndex = 0;
|
||
|
|
||
|
mReadBuffer = mWriteBuffer;
|
||
|
mReadBufIndex = 0;
|
||
|
mReadIndex = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Rewind the read pointer. Don't touch the write pointer.
|
||
|
*/
|
||
|
public void rewindRead() {
|
||
|
mReadBuffer = mBuffers.get(0);
|
||
|
mReadBufIndex = 0;
|
||
|
mReadIndex = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Only valid after startEditing. Returns -1 before that.
|
||
|
*/
|
||
|
public int getReadableSize() {
|
||
|
return mReadableSize;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the buffer size
|
||
|
* @return the buffer size
|
||
|
*/
|
||
|
public int getSize() {
|
||
|
return ((mBufferCount - 1) * mChunkSize) + mWriteIndex;
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Reading from the read position.
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Only valid after startEditing.
|
||
|
*/
|
||
|
public int getReadPos() {
|
||
|
return ((mReadBufIndex) * mChunkSize) + mReadIndex;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Skip over _amount_ bytes.
|
||
|
*/
|
||
|
public void skipRead(int amount) {
|
||
|
if (amount < 0) {
|
||
|
throw new RuntimeException("skipRead with negative amount=" + amount);
|
||
|
}
|
||
|
if (amount == 0) {
|
||
|
return;
|
||
|
}
|
||
|
if (amount <= mChunkSize - mReadIndex) {
|
||
|
mReadIndex += amount;
|
||
|
} else {
|
||
|
amount -= mChunkSize - mReadIndex;
|
||
|
mReadIndex = amount % mChunkSize;
|
||
|
if (mReadIndex == 0) {
|
||
|
mReadIndex = mChunkSize;
|
||
|
mReadBufIndex += (amount / mChunkSize);
|
||
|
} else {
|
||
|
mReadBufIndex += 1 + (amount / mChunkSize);
|
||
|
}
|
||
|
mReadBuffer = mBuffers.get(mReadBufIndex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read one byte from the stream and advance the read pointer.
|
||
|
*
|
||
|
* @throws IndexOutOfBoundsException if the read point is past the end of
|
||
|
* the buffer or past the read limit previously set by startEditing().
|
||
|
*/
|
||
|
public byte readRawByte() {
|
||
|
if (mReadBufIndex > mBufferCount
|
||
|
|| (mReadBufIndex == mBufferCount - 1 && mReadIndex >= mReadLimit)) {
|
||
|
throw new IndexOutOfBoundsException("Trying to read too much data"
|
||
|
+ " mReadBufIndex=" + mReadBufIndex + " mBufferCount=" + mBufferCount
|
||
|
+ " mReadIndex=" + mReadIndex + " mReadLimit=" + mReadLimit);
|
||
|
}
|
||
|
if (mReadIndex >= mChunkSize) {
|
||
|
mReadBufIndex++;
|
||
|
mReadBuffer = mBuffers.get(mReadBufIndex);
|
||
|
mReadIndex = 0;
|
||
|
}
|
||
|
return mReadBuffer[mReadIndex++];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read an unsigned varint. The value will be returend in a java signed long.
|
||
|
*/
|
||
|
public long readRawUnsigned() {
|
||
|
int bits = 0;
|
||
|
long result = 0;
|
||
|
while (true) {
|
||
|
final byte b = readRawByte();
|
||
|
result |= ((long)(b & 0x7F)) << bits;
|
||
|
if ((b & 0x80) == 0) {
|
||
|
return result;
|
||
|
}
|
||
|
bits += 7;
|
||
|
if (bits > 64) {
|
||
|
throw new ProtoParseException("Varint too long -- " + getDebugString());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read 32 little endian bits from the stream.
|
||
|
*/
|
||
|
public int readRawFixed32() {
|
||
|
return (readRawByte() & 0x0ff)
|
||
|
| ((readRawByte() & 0x0ff) << 8)
|
||
|
| ((readRawByte() & 0x0ff) << 16)
|
||
|
| ((readRawByte() & 0x0ff) << 24);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Writing at a the end of the stream.
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Advance to the next write buffer, allocating it if necessary.
|
||
|
*
|
||
|
* Must be called immediately <b>before</b> the next write, not after a write,
|
||
|
* so that a dangling empty buffer is not created. Doing so will interfere
|
||
|
* with the expectation that mWriteIndex will point past the end of the buffer
|
||
|
* until the next read happens.
|
||
|
*/
|
||
|
private void nextWriteBuffer() {
|
||
|
mWriteBufIndex++;
|
||
|
if (mWriteBufIndex >= mBufferCount) {
|
||
|
mWriteBuffer = new byte[mChunkSize];
|
||
|
mBuffers.add(mWriteBuffer);
|
||
|
mBufferCount++;
|
||
|
} else {
|
||
|
mWriteBuffer = mBuffers.get(mWriteBufIndex);
|
||
|
}
|
||
|
mWriteIndex = 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write a single byte to the stream.
|
||
|
*/
|
||
|
public void writeRawByte(byte val) {
|
||
|
if (mWriteIndex >= mChunkSize) {
|
||
|
nextWriteBuffer();
|
||
|
}
|
||
|
mWriteBuffer[mWriteIndex++] = val;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return how many bytes a 32 bit unsigned varint will take when written to the stream.
|
||
|
*/
|
||
|
public static int getRawVarint32Size(int val) {
|
||
|
if ((val & (0xffffffff << 7)) == 0) return 1;
|
||
|
if ((val & (0xffffffff << 14)) == 0) return 2;
|
||
|
if ((val & (0xffffffff << 21)) == 0) return 3;
|
||
|
if ((val & (0xffffffff << 28)) == 0) return 4;
|
||
|
return 5;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write an unsigned varint to the stream. A signed value would need to take 10 bytes.
|
||
|
*
|
||
|
* @param val treated as unsigned.
|
||
|
*/
|
||
|
public void writeRawVarint32(int val) {
|
||
|
while (true) {
|
||
|
if ((val & ~0x7F) == 0) {
|
||
|
writeRawByte((byte)val);
|
||
|
return;
|
||
|
} else {
|
||
|
writeRawByte((byte)((val & 0x7F) | 0x80));
|
||
|
val >>>= 7;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return how many bytes a 32 bit signed zig zag value will take when written to the stream.
|
||
|
*/
|
||
|
public static int getRawZigZag32Size(int val) {
|
||
|
return getRawVarint32Size(zigZag32(val));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write a zig-zag encoded value.
|
||
|
*
|
||
|
* @param val treated as signed
|
||
|
*/
|
||
|
public void writeRawZigZag32(int val) {
|
||
|
writeRawVarint32(zigZag32(val));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return how many bytes a 64 bit varint will take when written to the stream.
|
||
|
*/
|
||
|
public static int getRawVarint64Size(long val) {
|
||
|
if ((val & (0xffffffffffffffffL << 7)) == 0) return 1;
|
||
|
if ((val & (0xffffffffffffffffL << 14)) == 0) return 2;
|
||
|
if ((val & (0xffffffffffffffffL << 21)) == 0) return 3;
|
||
|
if ((val & (0xffffffffffffffffL << 28)) == 0) return 4;
|
||
|
if ((val & (0xffffffffffffffffL << 35)) == 0) return 5;
|
||
|
if ((val & (0xffffffffffffffffL << 42)) == 0) return 6;
|
||
|
if ((val & (0xffffffffffffffffL << 49)) == 0) return 7;
|
||
|
if ((val & (0xffffffffffffffffL << 56)) == 0) return 8;
|
||
|
if ((val & (0xffffffffffffffffL << 63)) == 0) return 9;
|
||
|
return 10;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write a 64 bit varint to the stream.
|
||
|
*/
|
||
|
public void writeRawVarint64(long val) {
|
||
|
while (true) {
|
||
|
if ((val & ~0x7FL) == 0) {
|
||
|
writeRawByte((byte)val);
|
||
|
return;
|
||
|
} else {
|
||
|
writeRawByte((byte)((val & 0x7F) | 0x80));
|
||
|
val >>>= 7;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return how many bytes a signed 64 bit zig zag value will take when written to the stream.
|
||
|
*/
|
||
|
public static int getRawZigZag64Size(long val) {
|
||
|
return getRawVarint64Size(zigZag64(val));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write a 64 bit signed zig zag value to the stream.
|
||
|
*/
|
||
|
public void writeRawZigZag64(long val) {
|
||
|
writeRawVarint64(zigZag64(val));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write 4 little endian bytes to the stream.
|
||
|
*/
|
||
|
public void writeRawFixed32(int val) {
|
||
|
writeRawByte((byte)(val));
|
||
|
writeRawByte((byte)(val >> 8));
|
||
|
writeRawByte((byte)(val >> 16));
|
||
|
writeRawByte((byte)(val >> 24));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write 8 little endian bytes to the stream.
|
||
|
*/
|
||
|
public void writeRawFixed64(long val) {
|
||
|
writeRawByte((byte)(val));
|
||
|
writeRawByte((byte)(val >> 8));
|
||
|
writeRawByte((byte)(val >> 16));
|
||
|
writeRawByte((byte)(val >> 24));
|
||
|
writeRawByte((byte)(val >> 32));
|
||
|
writeRawByte((byte)(val >> 40));
|
||
|
writeRawByte((byte)(val >> 48));
|
||
|
writeRawByte((byte)(val >> 56));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write a buffer to the stream. Writes nothing if val is null or zero-length.
|
||
|
*/
|
||
|
public void writeRawBuffer(byte[] val) {
|
||
|
if (val != null && val.length > 0) {
|
||
|
writeRawBuffer(val, 0, val.length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Write part of an array of bytes.
|
||
|
*/
|
||
|
public void writeRawBuffer(byte[] val, int offset, int length) {
|
||
|
if (val == null) {
|
||
|
return;
|
||
|
}
|
||
|
// Write up to the amount left in the first chunk to write.
|
||
|
int amt = length < (mChunkSize - mWriteIndex) ? length : (mChunkSize - mWriteIndex);
|
||
|
if (amt > 0) {
|
||
|
System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
|
||
|
mWriteIndex += amt;
|
||
|
length -= amt;
|
||
|
offset += amt;
|
||
|
}
|
||
|
while (length > 0) {
|
||
|
// We know we're now at the beginning of a chunk
|
||
|
nextWriteBuffer();
|
||
|
amt = length < mChunkSize ? length : mChunkSize;
|
||
|
System.arraycopy(val, offset, mWriteBuffer, mWriteIndex, amt);
|
||
|
mWriteIndex += amt;
|
||
|
length -= amt;
|
||
|
offset += amt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copies data _size_ bytes of data within this buffer from _srcOffset_
|
||
|
* to the current write position. Like memmov but handles the chunked buffer.
|
||
|
*/
|
||
|
public void writeFromThisBuffer(int srcOffset, int size) {
|
||
|
if (mReadLimit < 0) {
|
||
|
throw new IllegalStateException("writeFromThisBuffer before startEditing");
|
||
|
}
|
||
|
if (srcOffset < getWritePos()) {
|
||
|
throw new IllegalArgumentException("Can only move forward in the buffer --"
|
||
|
+ " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
|
||
|
}
|
||
|
if (srcOffset + size > mReadableSize) {
|
||
|
throw new IllegalArgumentException("Trying to move more data than there is --"
|
||
|
+ " srcOffset=" + srcOffset + " size=" + size + " " + getDebugString());
|
||
|
}
|
||
|
if (size == 0) {
|
||
|
return;
|
||
|
}
|
||
|
if (srcOffset == ((mWriteBufIndex) * mChunkSize) + mWriteIndex /* write pos */) {
|
||
|
// Writing to the same location. Just advance the write pointer. We already
|
||
|
// checked that size is in bounds, so we don't need to do any more range
|
||
|
// checking.
|
||
|
if (size <= mChunkSize - mWriteIndex) {
|
||
|
mWriteIndex += size;
|
||
|
} else {
|
||
|
size -= mChunkSize - mWriteIndex;
|
||
|
mWriteIndex = size % mChunkSize;
|
||
|
if (mWriteIndex == 0) {
|
||
|
// Roll it back so nextWriteBuffer can do its job
|
||
|
// on the next call (also makes mBuffers.get() not
|
||
|
// fail if we're at the end).
|
||
|
mWriteIndex = mChunkSize;
|
||
|
mWriteBufIndex += (size / mChunkSize);
|
||
|
} else {
|
||
|
mWriteBufIndex += 1 + (size / mChunkSize);
|
||
|
}
|
||
|
mWriteBuffer = mBuffers.get(mWriteBufIndex);
|
||
|
}
|
||
|
} else {
|
||
|
// Loop through the buffer, copying as much as we can each time.
|
||
|
// We already bounds checked so we don't need to do it again here,
|
||
|
// and nextWriteBuffer will never allocate.
|
||
|
int readBufIndex = srcOffset / mChunkSize;
|
||
|
byte[] readBuffer = mBuffers.get(readBufIndex);
|
||
|
int readIndex = srcOffset % mChunkSize;
|
||
|
while (size > 0) {
|
||
|
if (mWriteIndex >= mChunkSize) {
|
||
|
nextWriteBuffer();
|
||
|
}
|
||
|
if (readIndex >= mChunkSize) {
|
||
|
readBufIndex++;
|
||
|
readBuffer = mBuffers.get(readBufIndex);
|
||
|
readIndex = 0;
|
||
|
}
|
||
|
final int spaceInWriteBuffer = mChunkSize - mWriteIndex;
|
||
|
final int availableInReadBuffer = mChunkSize - readIndex;
|
||
|
final int amt = Math.min(size, Math.min(spaceInWriteBuffer, availableInReadBuffer));
|
||
|
System.arraycopy(readBuffer, readIndex, mWriteBuffer, mWriteIndex, amt);
|
||
|
mWriteIndex += amt;
|
||
|
readIndex += amt;
|
||
|
size -= amt;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Writing at a particular location.
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Returns the index into the virtual array of the write pointer.
|
||
|
*/
|
||
|
public int getWritePos() {
|
||
|
return ((mWriteBufIndex) * mChunkSize) + mWriteIndex;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Resets the write pointer to a virtual location as returned by getWritePos.
|
||
|
*/
|
||
|
public void rewindWriteTo(int writePos) {
|
||
|
if (writePos > getWritePos()) {
|
||
|
throw new RuntimeException("rewindWriteTo only can go backwards" + writePos);
|
||
|
}
|
||
|
mWriteBufIndex = writePos / mChunkSize;
|
||
|
mWriteIndex = writePos % mChunkSize;
|
||
|
if (mWriteIndex == 0 && mWriteBufIndex != 0) {
|
||
|
// Roll back so nextWriteBuffer can do its job on the next call
|
||
|
// but at the first write we're at 0.
|
||
|
mWriteIndex = mChunkSize;
|
||
|
mWriteBufIndex--;
|
||
|
}
|
||
|
mWriteBuffer = mBuffers.get(mWriteBufIndex);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Read a 32 bit value from the stream.
|
||
|
*
|
||
|
* Doesn't touch or affect mWritePos.
|
||
|
*/
|
||
|
public int getRawFixed32At(int pos) {
|
||
|
return (0x00ff & (int)mBuffers.get(pos / mChunkSize)[pos % mChunkSize])
|
||
|
| ((0x0ff & (int)mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize]) << 8)
|
||
|
| ((0x0ff & (int)mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize]) << 16)
|
||
|
| ((0x0ff & (int)mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize]) << 24);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Overwrite a 32 bit value in the stream.
|
||
|
*
|
||
|
* Doesn't touch or affect mWritePos.
|
||
|
*/
|
||
|
public void editRawFixed32(int pos, int val) {
|
||
|
mBuffers.get(pos / mChunkSize)[pos % mChunkSize] = (byte)(val);
|
||
|
mBuffers.get((pos+1) / mChunkSize)[(pos+1) % mChunkSize] = (byte)(val >> 8);
|
||
|
mBuffers.get((pos+2) / mChunkSize)[(pos+2) % mChunkSize] = (byte)(val >> 16);
|
||
|
mBuffers.get((pos+3) / mChunkSize)[(pos+3) % mChunkSize] = (byte)(val >> 24);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Zigging and zagging
|
||
|
//
|
||
|
|
||
|
/**
|
||
|
* Zig-zag encode a 32 bit value.
|
||
|
*/
|
||
|
private static int zigZag32(int val) {
|
||
|
return (val << 1) ^ (val >> 31);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Zig-zag encode a 64 bit value.
|
||
|
*/
|
||
|
private static long zigZag64(long val) {
|
||
|
return (val << 1) ^ (val >> 63);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// Debugging / testing
|
||
|
//
|
||
|
// VisibleForTesting
|
||
|
|
||
|
/**
|
||
|
* Get a copy of the first _size_ bytes of data. This is not range
|
||
|
* checked, and if the bounds are outside what has been written you will
|
||
|
* get garbage and if it is outside the buffers that have been allocated,
|
||
|
* you will get an exception.
|
||
|
*/
|
||
|
public byte[] getBytes(int size) {
|
||
|
final byte[] result = new byte[size];
|
||
|
|
||
|
final int bufCount = size / mChunkSize;
|
||
|
int bufIndex;
|
||
|
int writeIndex = 0;
|
||
|
|
||
|
for (bufIndex=0; bufIndex<bufCount; bufIndex++) {
|
||
|
System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, mChunkSize);
|
||
|
writeIndex += mChunkSize;
|
||
|
}
|
||
|
|
||
|
final int lastSize = size - (bufCount * mChunkSize);
|
||
|
if (lastSize > 0) {
|
||
|
System.arraycopy(mBuffers.get(bufIndex), 0, result, writeIndex, lastSize);
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the number of chunks allocated.
|
||
|
*/
|
||
|
// VisibleForTesting
|
||
|
public int getChunkCount() {
|
||
|
return mBuffers.size();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the write position inside the current write chunk.
|
||
|
*/
|
||
|
// VisibleForTesting
|
||
|
public int getWriteIndex() {
|
||
|
return mWriteIndex;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the index of the current write chunk in the list of chunks.
|
||
|
*/
|
||
|
// VisibleForTesting
|
||
|
public int getWriteBufIndex() {
|
||
|
return mWriteBufIndex;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return debugging information about this EncodedBuffer object.
|
||
|
*/
|
||
|
public String getDebugString() {
|
||
|
return "EncodedBuffer( mChunkSize=" + mChunkSize + " mBuffers.size=" + mBuffers.size()
|
||
|
+ " mBufferCount=" + mBufferCount + " mWriteIndex=" + mWriteIndex
|
||
|
+ " mWriteBufIndex=" + mWriteBufIndex + " mReadBufIndex=" + mReadBufIndex
|
||
|
+ " mReadIndex=" + mReadIndex + " mReadableSize=" + mReadableSize
|
||
|
+ " mReadLimit=" + mReadLimit + " )";
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the internal buffer chunks.
|
||
|
*/
|
||
|
public void dumpBuffers(String tag) {
|
||
|
final int N = mBuffers.size();
|
||
|
int start = 0;
|
||
|
for (int i=0; i<N; i++) {
|
||
|
start += dumpByteString(tag, "{" + i + "} ", start, mBuffers.get(i));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the internal buffer chunks.
|
||
|
*/
|
||
|
public static void dumpByteString(String tag, String prefix, byte[] buf) {
|
||
|
dumpByteString(tag, prefix, 0, buf);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print the internal buffer chunks.
|
||
|
*/
|
||
|
private static int dumpByteString(String tag, String prefix, int start, byte[] buf) {
|
||
|
StringBuilder sb = new StringBuilder();
|
||
|
final int length = buf.length;
|
||
|
final int lineLen = 16;
|
||
|
int i;
|
||
|
for (i=0; i<length; i++) {
|
||
|
if (i % lineLen == 0) {
|
||
|
if (i != 0) {
|
||
|
Log.d(tag, sb.toString());
|
||
|
sb = new StringBuilder();
|
||
|
}
|
||
|
sb.append(prefix);
|
||
|
sb.append('[');
|
||
|
sb.append(start + i);
|
||
|
sb.append(']');
|
||
|
sb.append(' ');
|
||
|
} else {
|
||
|
sb.append(' ');
|
||
|
}
|
||
|
byte b = buf[i];
|
||
|
byte c = (byte)((b >> 4) & 0x0f);
|
||
|
if (c < 10) {
|
||
|
sb.append((char)('0' + c));
|
||
|
} else {
|
||
|
sb.append((char)('a' - 10 + c));
|
||
|
}
|
||
|
byte d = (byte)(b & 0x0f);
|
||
|
if (d < 10) {
|
||
|
sb.append((char)('0' + d));
|
||
|
} else {
|
||
|
sb.append((char)('a' - 10 + d));
|
||
|
}
|
||
|
}
|
||
|
Log.d(tag, sb.toString());
|
||
|
return length;
|
||
|
}
|
||
|
}
|