summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/test-current.txt150
-rw-r--r--core/java/android/util/proto/EncodedBuffer.java678
-rw-r--r--core/java/android/util/proto/ProtoOutputStream.java1599
-rw-r--r--core/java/android/util/proto/ProtoParseException.java38
-rw-r--r--core/java/android/util/proto/package.html5
-rw-r--r--tools/streaming_proto/Android.mk41
-rw-r--r--tools/streaming_proto/Errors.cpp87
-rw-r--r--tools/streaming_proto/Errors.h48
-rw-r--r--tools/streaming_proto/main.cpp391
-rw-r--r--tools/streaming_proto/string_utils.cpp95
-rw-r--r--tools/streaming_proto/string_utils.h32
-rw-r--r--tools/streaming_proto/test/imported.proto26
-rw-r--r--tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java7
-rw-r--r--tools/streaming_proto/test/test.proto124
14 files changed, 3321 insertions, 0 deletions
diff --git a/api/test-current.txt b/api/test-current.txt
index 4be2e7f47573..7119141fb0ff 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -41025,6 +41025,156 @@ package android.util {
}
+package android.util.proto {
+
+ public final class EncodedBuffer {
+ ctor public EncodedBuffer();
+ ctor public EncodedBuffer(int);
+ method public void dumpBuffers(java.lang.String);
+ method public static void dumpByteString(java.lang.String, java.lang.String, byte[]);
+ method public void editRawFixed32(int, int);
+ method public byte[] getBytes(int);
+ method public int getChunkCount();
+ method public java.lang.String getDebugString();
+ method public int getRawFixed32At(int);
+ method public static int getRawVarint32Size(int);
+ method public static int getRawVarint64Size(long);
+ method public static int getRawZigZag32Size(int);
+ method public static int getRawZigZag64Size(long);
+ method public int getReadPos();
+ method public int getReadableSize();
+ method public int getWriteBufIndex();
+ method public int getWriteIndex();
+ method public int getWritePos();
+ method public byte readRawByte();
+ method public int readRawFixed32();
+ method public long readRawUnsigned();
+ method public void rewindRead();
+ method public void rewindWriteTo(int);
+ method public void skipRead(int);
+ method public void startEditing();
+ method public void writeFromThisBuffer(int, int);
+ method public void writeRawBuffer(byte[]);
+ method public void writeRawBuffer(byte[], int, int);
+ method public void writeRawByte(byte);
+ method public void writeRawFixed32(int);
+ method public void writeRawFixed64(long);
+ method public void writeRawVarint32(int);
+ method public void writeRawVarint64(long);
+ method public void writeRawZigZag32(int);
+ method public void writeRawZigZag64(long);
+ }
+
+ public final class ProtoOutputStream {
+ ctor public ProtoOutputStream();
+ ctor public ProtoOutputStream(int);
+ method public static int checkFieldId(long, long);
+ method public static int convertObjectIdToOrdinal(int);
+ method public void dump(java.lang.String);
+ method public void endObject(long);
+ method public void endRepeatedObject(long);
+ method public byte[] getBytes();
+ method public static int getDepthFromToken(long);
+ method public static int getObjectIdFromToken(long);
+ method public static boolean getRepeatedFromToken(long);
+ method public static int getSizePosFromToken(long);
+ method public static int getTagSizeFromToken(long);
+ method public static long makeFieldId(int, long);
+ method public static long makeToken(int, boolean, int, int, int);
+ method public long startObject(long);
+ method public long startRepeatedObject(long);
+ method public static java.lang.String token2String(long);
+ method public void writeBool(long, boolean);
+ method public void writeBytes(long, byte[]);
+ method public void writeDouble(long, double);
+ method public void writeEnum(long, int);
+ method public void writeFixed32(long, int);
+ method public void writeFixed64(long, long);
+ method public void writeFloat(long, float);
+ method public void writeInt32(long, int);
+ method public void writeInt64(long, long);
+ method public void writePackedBool(long, boolean[]);
+ method public void writePackedDouble(long, double[]);
+ method public void writePackedEnum(long, int[]);
+ method public void writePackedFixed32(long, int[]);
+ method public void writePackedFixed64(long, long[]);
+ method public void writePackedFloat(long, float[]);
+ method public void writePackedInt32(long, int[]);
+ method public void writePackedInt64(long, long[]);
+ method public void writePackedSFixed32(long, int[]);
+ method public void writePackedSFixed64(long, long[]);
+ method public void writePackedSInt32(long, int[]);
+ method public void writePackedSInt64(long, long[]);
+ method public void writePackedUInt32(long, int[]);
+ method public void writePackedUInt64(long, long[]);
+ method public void writeRepeatedBool(long, boolean);
+ method public void writeRepeatedBytes(long, byte[]);
+ method public void writeRepeatedDouble(long, double);
+ method public void writeRepeatedEnum(long, int);
+ method public void writeRepeatedFixed32(long, int);
+ method public void writeRepeatedFixed64(long, long);
+ method public void writeRepeatedFloat(long, float);
+ method public void writeRepeatedInt32(long, int);
+ method public void writeRepeatedInt64(long, long);
+ method public void writeRepeatedSFixed32(long, int);
+ method public void writeRepeatedSFixed64(long, long);
+ method public void writeRepeatedSInt32(long, int);
+ method public void writeRepeatedSInt64(long, long);
+ method public void writeRepeatedString(long, java.lang.String);
+ method public void writeRepeatedUInt32(long, int);
+ method public void writeRepeatedUInt64(long, long);
+ method public void writeSFixed32(long, int);
+ method public void writeSFixed64(long, long);
+ method public void writeSInt32(long, int);
+ method public void writeSInt64(long, long);
+ method public void writeString(long, java.lang.String);
+ method public void writeTag(int, int);
+ method public void writeUInt32(long, int);
+ method public void writeUInt64(long, long);
+ field public static final long FIELD_COUNT_MASK = 16492674416640L; // 0xf0000000000L
+ field public static final long FIELD_COUNT_PACKED = 5497558138880L; // 0x50000000000L
+ field public static final long FIELD_COUNT_REPEATED = 2199023255552L; // 0x20000000000L
+ field public static final int FIELD_COUNT_SHIFT = 40; // 0x28
+ field public static final long FIELD_COUNT_SINGLE = 1099511627776L; // 0x10000000000L
+ field public static final long FIELD_COUNT_UNKNOWN = 0L; // 0x0L
+ field public static final int FIELD_ID_MASK = -8; // 0xfffffff8
+ field public static final int FIELD_ID_SHIFT = 3; // 0x3
+ field public static final long FIELD_TYPE_BOOL = 55834574848L; // 0xd00000000L
+ field public static final long FIELD_TYPE_BYTES = 64424509440L; // 0xf00000000L
+ field public static final long FIELD_TYPE_DOUBLE = 4294967296L; // 0x100000000L
+ field public static final long FIELD_TYPE_ENUM = 68719476736L; // 0x1000000000L
+ field public static final long FIELD_TYPE_FIXED32 = 38654705664L; // 0x900000000L
+ field public static final long FIELD_TYPE_FIXED64 = 42949672960L; // 0xa00000000L
+ field public static final long FIELD_TYPE_FLOAT = 8589934592L; // 0x200000000L
+ field public static final long FIELD_TYPE_INT32 = 12884901888L; // 0x300000000L
+ field public static final long FIELD_TYPE_INT64 = 17179869184L; // 0x400000000L
+ field public static final long FIELD_TYPE_MASK = 1095216660480L; // 0xff00000000L
+ field public static final long FIELD_TYPE_OBJECT = 73014444032L; // 0x1100000000L
+ field public static final long FIELD_TYPE_SFIXED32 = 47244640256L; // 0xb00000000L
+ field public static final long FIELD_TYPE_SFIXED64 = 51539607552L; // 0xc00000000L
+ field public static final int FIELD_TYPE_SHIFT = 32; // 0x20
+ field public static final long FIELD_TYPE_SINT32 = 30064771072L; // 0x700000000L
+ field public static final long FIELD_TYPE_SINT64 = 34359738368L; // 0x800000000L
+ field public static final long FIELD_TYPE_STRING = 60129542144L; // 0xe00000000L
+ field public static final long FIELD_TYPE_UINT32 = 21474836480L; // 0x500000000L
+ field public static final long FIELD_TYPE_UINT64 = 25769803776L; // 0x600000000L
+ field public static final long FIELD_TYPE_UNKNOWN = 0L; // 0x0L
+ field public static final java.lang.String TAG = "ProtoOutputStream";
+ field public static final int WIRE_TYPE_END_GROUP = 4; // 0x4
+ field public static final int WIRE_TYPE_FIXED32 = 5; // 0x5
+ field public static final int WIRE_TYPE_FIXED64 = 1; // 0x1
+ field public static final int WIRE_TYPE_LENGTH_DELIMITED = 2; // 0x2
+ field public static final int WIRE_TYPE_MASK = 7; // 0x7
+ field public static final int WIRE_TYPE_START_GROUP = 3; // 0x3
+ field public static final int WIRE_TYPE_VARINT = 0; // 0x0
+ }
+
+ public class ProtoParseException extends java.lang.RuntimeException {
+ ctor public ProtoParseException(java.lang.String);
+ }
+
+}
+
package android.view {
public abstract class AbsSavedState implements android.os.Parcelable {
diff --git a/core/java/android/util/proto/EncodedBuffer.java b/core/java/android/util/proto/EncodedBuffer.java
new file mode 100644
index 000000000000..ed38e6ffd302
--- /dev/null
+++ b/core/java/android/util/proto/EncodedBuffer.java
@@ -0,0 +1,678 @@
+/*
+ * 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
+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 &lt;= 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;
+ }
+
+ //
+ // 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) {
+ StringBuffer sb = new StringBuffer();
+ 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 StringBuffer();
+ }
+ 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;
+ }
+}
diff --git a/core/java/android/util/proto/ProtoOutputStream.java b/core/java/android/util/proto/ProtoOutputStream.java
new file mode 100644
index 000000000000..8f99399cc155
--- /dev/null
+++ b/core/java/android/util/proto/ProtoOutputStream.java
@@ -0,0 +1,1599 @@
+/*
+ * 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.io.UnsupportedEncodingException;
+import java.util.List;
+
+/**
+ * Class to write to a protobuf stream.
+ *
+ * Each write method takes an ID code from the protoc generated classes
+ * and the value to write. To make a nested object, call startObject
+ * and then endObject when you are done.
+ *
+ * The ID codes have type information embedded into them, so if you call
+ * the incorrect function you will get an IllegalArgumentException.
+ *
+ * To retrieve the encoded protobuf stream, call getBytes().
+ *
+ * TODO: Add a constructor that takes an OutputStream and write to that
+ * stream as the top-level objects are finished.
+ *
+ * @hide
+ */
+
+/* IMPLEMENTATION NOTES
+ *
+ * Because protobuf has inner values, and they are length prefixed, and
+ * those sizes themselves are stored with a variable length encoding, it
+ * is impossible to know how big an object will be in a single pass.
+ *
+ * The traditional way is to copy the in-memory representation of an object
+ * into the generated proto Message objects, do a traversal of those to
+ * cache the size, and then write the size-prefixed buffers.
+ *
+ * We are trying to avoid too much generated code here, but this class still
+ * needs to have a somewhat sane API. We can't have the multiple passes be
+ * done by the calling code. In addition, we want to avoid the memory high
+ * water mark of duplicating all of the values into the traditional in-memory
+ * Message objects. We need to find another way.
+ *
+ * So what we do here is to let the calling code write the data into a
+ * byte[] (actually a collection of them wrapped in the EncodedBuffer) class,
+ * but not do the varint encoding of the sub-message sizes. Then, we do a
+ * recursive traversal of the buffer itself, calculating the sizes (which are
+ * then knowable, although still not the actual sizes in the buffer because of
+ * possible further nesting). Then we do a third pass, compacting the
+ * buffer and varint encoding the sizes.
+ *
+ * This gets us a relatively small number number of fixed-size allocations,
+ * which is less likely to cause memory fragmentation or churn the GC, and
+ * the same number of data copies as would have gotten with setting it
+ * field-by-field in generated code, and no code bloat from generated code.
+ * The final data copy is also done with System.arraycopy, which will be
+ * more efficient, in general, than doing the individual fields twice (as in
+ * the traditional way).
+ *
+ * To accomplish the multiple passes, whenever we write a
+ * WIRE_TYPE_LENGTH_DELIMITED field, we write the size occupied in our
+ * buffer as a fixed 32 bit int (called childRawSize), not variable length
+ * one. We reserve another 32 bit slot for the computed size (called
+ * childEncodedSize). If we know the size up front, as we do for strings
+ * and byte[], then we also put that into childEncodedSize, if we don't, we
+ * write the negative of childRawSize, as a sentiel that we need to
+ * compute it during the second pass and recursively compact it during the
+ * third pass.
+ *
+ * Unsgigned size varints can be up to five bytes long, but we reserve eight
+ * bytes for overhead, so we know that when we compact the buffer, there
+ * will always be space for the encoded varint.
+ *
+ * When we can figure out the size ahead of time, we do, in order
+ * to save overhead with recalculating it, and with the later arraycopy.
+ *
+ * During the period between when the caller has called startObject, but
+ * not yet called endObject, we maintain a linked list of the tokens
+ * returned by startObject, stored in those 8 bytes of size storage space.
+ * We use that linked list of tokens to ensure that the caller has
+ * correctly matched pairs of startObject and endObject calls, and issue
+ * errors if they are not matched.
+ */
+@TestApi
+public final class ProtoOutputStream {
+ public static final String TAG = "ProtoOutputStream";
+
+ public static final int FIELD_ID_SHIFT = 3;
+ public static final int WIRE_TYPE_MASK = (1<<FIELD_ID_SHIFT)-1;
+ public static final int FIELD_ID_MASK = ~WIRE_TYPE_MASK;
+
+ public static final int WIRE_TYPE_VARINT = 0;
+ public static final int WIRE_TYPE_FIXED64 = 1;
+ public static final int WIRE_TYPE_LENGTH_DELIMITED = 2;
+ public static final int WIRE_TYPE_START_GROUP = 3;
+ public static final int WIRE_TYPE_END_GROUP = 4;
+ public static final int WIRE_TYPE_FIXED32 = 5;
+
+ /**
+ * Position of the field type in a (long) fieldId.
+ */
+ public static final int FIELD_TYPE_SHIFT = 32;
+
+ /**
+ * Mask for the field types stored in a fieldId. Leaves a whole
+ * byte for future expansion, even though there are currently only 17 types.
+ */
+ public static final long FIELD_TYPE_MASK = 0x0ffL << FIELD_TYPE_SHIFT;
+
+ public static final long FIELD_TYPE_UNKNOWN = 0;
+
+ public static final long FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+ public static final long FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+ private static final String[] FIELD_TYPE_NAMES = new String[] {
+ "Double",
+ "Float",
+ "Int32",
+ "Int64",
+ "UInt32",
+ "UInt64",
+ "SInt32",
+ "SInt64",
+ "Fixed32",
+ "Fixed64",
+ "SFixed32",
+ "SFixed64",
+ "Bool",
+ "String",
+ "Bytes",
+ "Enum",
+ "Object",
+ };
+
+ //
+ // FieldId flags for whether the field is single, repeated or packed.
+ //
+ public static final int FIELD_COUNT_SHIFT = 40;
+ public static final long FIELD_COUNT_MASK = 0x0fL << FIELD_COUNT_SHIFT;
+
+ public static final long FIELD_COUNT_UNKNOWN = 0;
+ public static final long FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+ public static final long FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+ /**
+ * Our buffer.
+ */
+ private EncodedBuffer mBuffer;
+
+ /**
+ * Current nesting depth of startObject calls.
+ */
+ private int mDepth;
+
+ /**
+ * An ID given to objects and returned in the token from startObject
+ * and stored in the buffer until endObject is called, where the two
+ * are checked. Starts at -1 and becomes more negative, so the values
+ * aren't likely to alias with the size it will be overwritten with,
+ * which tend to be small, and we will be more likely to catch when
+ * the caller of endObject uses a stale token that they didn't intend
+ * to (e.g. copy and paste error).
+ */
+ private int mNextObjectId = -1;
+
+ /**
+ * The object token we are expecting in endObject. If another call to
+ * startObject happens, this is written to that location, which gives
+ * us a stack, stored in the space for the as-yet unused size fields.
+ */
+ private long mExpectedObjectToken;
+
+ /**
+ * Index in mBuffer that we should start copying from on the next
+ * pass of compaction.
+ */
+ private int mCopyBegin;
+
+ /**
+ * Whether we've already compacted
+ */
+ private boolean mCompacted;
+
+ /**
+ * Construct a ProtoOutputStream with the default chunk size.
+ */
+ public ProtoOutputStream() {
+ this(0);
+ }
+
+ /**
+ * Construct a ProtoOutputStream with the given chunk size.
+ */
+ public ProtoOutputStream(int chunkSize) {
+ mBuffer = new EncodedBuffer(chunkSize);
+ }
+
+ //
+ // proto3 type: double
+ // java type: double
+ // encoding: fixed64
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "double" type field value.
+ */
+ public void writeDouble(long fieldId, double val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+ }
+ }
+
+ /**
+ * Write a single repeated proto "double" type field value.
+ */
+ public void writeRepeatedDouble(long fieldId, double val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
+ }
+
+ /**
+ * Write a list of packed proto "double" type field values.
+ */
+ public void writePackedDouble(long fieldId, double[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_DOUBLE);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(Double.doubleToLongBits(val[i]));
+ }
+ }
+ }
+
+ //
+ // proto3 type: float
+ // java type: float
+ // encoding: fixed32
+ // wire type: WIRE_TYPE_FIXED32
+ //
+
+ /**
+ * Write a single proto "float" type field value.
+ */
+ public void writeFloat(long fieldId, float val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+ }
+ }
+
+ /**
+ * Write a single repeated proto "float" type field value.
+ */
+ public void writeRepeatedFloat(long fieldId, float val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val));
+ }
+
+ /**
+ * Write a list of packed proto "float" type field value.
+ */
+ public void writePackedFloat(long fieldId, float[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FLOAT);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(Float.floatToIntBits(val[i]));
+ }
+ }
+ }
+
+ //
+ // proto3 type: int32
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Writes a java int as an usigned varint.
+ *
+ * The unadorned int32 type in protobuf is unfortunate because it
+ * is stored in memory as a signed value, but encodes as unsigned
+ * varints, which are formally always longs. So here, we encode
+ * negative values as 64 bits, which will get the sign-extension,
+ * and positive values as 32 bits, which saves a marginal amount
+ * of work in that it processes ints instead of longs.
+ */
+ private void writeUnsignedVarintFromSignedInt(int val) {
+ if (val >= 0) {
+ mBuffer.writeRawVarint32(val);
+ } else {
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writeInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writeRepeatedInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+
+ /**
+ * Write a list of packed proto "int32" type field value.
+ *
+ * Note that these are stored in memory as signed values and written as unsigned
+ * varints, which if negative, are 10 bytes long. If you know the data is likely
+ * to be negative, use "sint32".
+ */
+ public void writePackedInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ final int v = val[i];
+ size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ writeUnsignedVarintFromSignedInt(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: int64
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "int64" type field value.
+ */
+ public void writeInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "int64" type field value.
+ */
+ public void writeRepeatedInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+
+ /**
+ * Write a list of packed proto "int64" type field value.
+ */
+ public void writePackedInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_INT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: uint32
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "uint32" type field value.
+ */
+ public void writeUInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "uint32" type field value.
+ */
+ public void writeRepeatedUInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint32(val);
+ }
+
+ /**
+ * Write a list of packed proto "uint32" type field value.
+ */
+ public void writePackedUInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint32Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: uint64
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writeUInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+ }
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writeRepeatedUInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawVarint64(val);
+ }
+
+ /**
+ * Write a single proto "uint64" type field value.
+ */
+ public void writePackedUInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_UINT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawVarint64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawVarint64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sint32
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: zig-zag
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "sint32" type field value.
+ */
+ public void writeSInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sint32" type field value.
+ */
+ public void writeRepeatedSInt32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag32(val);
+ }
+
+ /**
+ * Write a list of packed proto "sint32" type field value.
+ */
+ public void writePackedSInt32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawZigZag32Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawZigZag32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sint64
+ // java type: int
+ // signed/unsigned: signed
+ // encoding: zig-zag
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "sint64" type field value.
+ */
+ public void writeSInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sint64" type field value.
+ */
+ public void writeRepeatedSInt64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawZigZag64(val);
+ }
+
+ /**
+ * Write a list of packed proto "sint64" type field value.
+ */
+ public void writePackedSInt64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SINT64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ size += EncodedBuffer.getRawZigZag64Size(val[i]);
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawZigZag64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: fixed32
+ // java type: int
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED32
+ //
+
+ /**
+ * Write a single proto "fixed32" type field value.
+ */
+ public void writeFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "fixed32" type field value.
+ */
+ public void writeRepeatedFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+
+ /**
+ * Write a list of packed proto "fixed32" type field value.
+ */
+ public void writePackedFixed32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: fixed64
+ // java type: long
+ // encoding: fixed64
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "fixed64" type field value.
+ */
+ public void writeFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "fixed64" type field value.
+ */
+ public void writeRepeatedFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+
+ /**
+ * Write a list of packed proto "fixed64" type field value.
+ */
+ public void writePackedFixed64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_FIXED64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sfixed32
+ // java type: int
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED32
+ //
+ /**
+ * Write a single proto "sfixed32" type field value.
+ */
+ public void writeSFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sfixed32" type field value.
+ */
+ public void writeRepeatedSFixed32(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);
+
+ writeTag(id, WIRE_TYPE_FIXED32);
+ mBuffer.writeRawFixed32(val);
+ }
+
+ /**
+ * Write a list of packed proto "sfixed32" type field value.
+ */
+ public void writePackedSFixed32(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED32);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 4);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed32(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: sfixed64
+ // java type: long
+ // encoding: little endian
+ // wire type: WIRE_TYPE_FIXED64
+ //
+
+ /**
+ * Write a single proto "sfixed64" type field value.
+ */
+ public void writeSFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "sfixed64" type field value.
+ */
+ public void writeRepeatedSFixed64(long fieldId, long val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);
+
+ writeTag(id, WIRE_TYPE_FIXED64);
+ mBuffer.writeRawFixed64(val);
+ }
+
+ /**
+ * Write a list of packed proto "sfixed64" type field value.
+ */
+ public void writePackedSFixed64(long fieldId, long[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_SFIXED64);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ writeKnownLengthHeader(id, N * 8);
+ for (int i=0; i<N; i++) {
+ mBuffer.writeRawFixed64(val[i]);
+ }
+ }
+ }
+
+ //
+ // proto3 type: bool
+ // java type: boolean
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "bool" type field value.
+ */
+ public void writeBool(long fieldId, boolean val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);
+
+ if (val) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ // 0 and 1 are the same as their varint counterparts
+ mBuffer.writeRawByte((byte)1);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "bool" type field value.
+ */
+ public void writeRepeatedBool(long fieldId, boolean val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ mBuffer.writeRawByte((byte)(val ? 1 : 0));
+ }
+
+ /**
+ * Write a list of packed proto "bool" type field value.
+ */
+ public void writePackedBool(long fieldId, boolean[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_BOOL);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ // Write the header
+ writeKnownLengthHeader(id, N);
+
+ // Write the data
+ for (int i=0; i<N; i++) {
+ // 0 and 1 are the same as their varint counterparts
+ mBuffer.writeRawByte((byte)(val[i] ? 1 : 0));
+ }
+ }
+ }
+
+ //
+ // proto3 type: string
+ // java type: String
+ // encoding: utf-8
+ // wire type: WIRE_TYPE_LENGTH_DELIMITED
+ //
+
+ /**
+ * Write a single proto "string" type field value.
+ */
+ public void writeString(long fieldId, String val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);
+
+ if (val != null && val.length() > 0) {
+ writeUtf8String(id, val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "string" type field value.
+ */
+ public void writeRepeatedString(long fieldId, String val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);
+
+ if (val == null || val.length() == 0) {
+ writeKnownLengthHeader(id, 0);
+ } else {
+ writeUtf8String(id, val);
+ }
+ }
+
+ /**
+ * Write a list of packed proto "string" type field value.
+ */
+ private void writeUtf8String(int id, String val) {
+ // TODO: Is it worth converting by hand in order to not allocate?
+ try {
+ final byte[] buf = val.getBytes("UTF-8");
+ writeKnownLengthHeader(id, buf.length);
+ mBuffer.writeRawBuffer(buf);
+ } catch (UnsupportedEncodingException ex) {
+ throw new RuntimeException("not possible");
+ }
+ }
+
+ //
+ // proto3 type: bytes
+ // java type: byte[]
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto "bytes" type field value.
+ */
+ public void writeBytes(long fieldId, byte[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);
+
+ if (val != null && val.length > 0) {
+ writeKnownLengthHeader(id, val.length);
+ mBuffer.writeRawBuffer(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto "bytes" type field value.
+ */
+ public void writeRepeatedBytes(long fieldId, byte[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);
+
+ writeKnownLengthHeader(id, val == null ? 0 : val.length);
+ mBuffer.writeRawBuffer(val);
+ }
+
+ //
+ // proto3 type: enum
+ // java type: int
+ // signed/unsigned: unsigned
+ // encoding: varint
+ // wire type: WIRE_TYPE_VARINT
+ //
+
+ /**
+ * Write a single proto enum type field value.
+ */
+ public void writeEnum(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);
+
+ if (val != 0) {
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+ }
+
+ /**
+ * Write a single repeated proto enum type field value.
+ */
+ public void writeRepeatedEnum(long fieldId, int val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);
+
+ writeTag(id, WIRE_TYPE_VARINT);
+ writeUnsignedVarintFromSignedInt(val);
+ }
+
+ /**
+ * Write a list of packed proto enum type field value.
+ */
+ public void writePackedEnum(long fieldId, int[] val) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_PACKED | FIELD_TYPE_ENUM);
+
+ final int N = val != null ? val.length : 0;
+ if (N > 0) {
+ int size = 0;
+ for (int i=0; i<N; i++) {
+ final int v = val[i];
+ size += v >= 0 ? EncodedBuffer.getRawVarint32Size(v) : 10;
+ }
+ writeKnownLengthHeader(id, size);
+ for (int i=0; i<N; i++) {
+ writeUnsignedVarintFromSignedInt(val[i]);
+ }
+ }
+ }
+
+ //
+ // Child objects
+ //
+
+ /**
+ * Make a token.
+ * Bits 61-63 - tag size (So we can go backwards later if the object had not data)
+ * - 3 bits, max value 7, max value needed 5
+ * Bit 60 - true if the object is repeated (lets us require endObject or endRepeatedObject)
+ * Bits 59-51 - depth (For error checking)
+ * - 9 bits, max value 512, when checking, value is masked (if we really
+ * are more than 512 levels deep)
+ * Bits 32-50 - objectId (For error checking)
+ * - 19 bits, max value 524,288. that's a lot of objects. IDs will wrap
+ * because of the overflow, and only the tokens are compared.
+ * Bits 0-31 - offset of the first size field in the buffer.
+ */
+ // VisibleForTesting
+ public static long makeToken(int tagSize, boolean repeated, int depth, int objectId,
+ int sizePos) {
+ return ((0x07L & (long)tagSize) << 61)
+ | (repeated ? (1L << 60) : 0)
+ | (0x01ffL & (long)depth) << 51
+ | (0x07ffffL & (long)objectId) << 32
+ | (0x0ffffffffL & (long)sizePos);
+ }
+
+ /**
+ * Get the encoded tag size from the token.
+ */
+ public static int getTagSizeFromToken(long token) {
+ return (int)(0x7 & (token >> 61));
+ }
+
+ /**
+ * Get whether this is a call to startObject (false) or startRepeatedObject (true).
+ */
+ public static boolean getRepeatedFromToken(long token) {
+ return (0x1 & (token >> 60)) != 0;
+ }
+
+ /**
+ * Get the nesting depth of startObject calls from the token.
+ */
+ public static int getDepthFromToken(long token) {
+ return (int)(0x01ff & (token >> 51));
+ }
+
+ /**
+ * Get the object ID from the token. The object ID is a serial number for the
+ * startObject calls that have happened on this object. The values are truncated
+ * to 9 bits, but that is sufficient for error checking.
+ */
+ public static int getObjectIdFromToken(long token) {
+ return (int)(0x07ffff & (token >> 32));
+ }
+
+ /**
+ * Get the location of the childRawSize (the first 32 bit size field) in this object.
+ */
+ public static int getSizePosFromToken(long token) {
+ return (int)token;
+ }
+
+ /**
+ * Convert the object ID to the ordinal value -- the n-th call to startObject.
+ * The object IDs start at -1 and count backwards, so that the value is unlikely
+ * to alias with an actual size field that had been written.
+ */
+ public static int convertObjectIdToOrdinal(int objectId) {
+ return (-1 & 0x07ffff) - objectId;
+ }
+
+ /**
+ * Return a debugging string of a token.
+ */
+ public static String token2String(long token) {
+ if (token == 0L) {
+ return "Token(0)";
+ } else {
+ return "Token(val=0x" + Long.toHexString(token)
+ + " depth=" + getDepthFromToken(token)
+ + " object=" + convertObjectIdToOrdinal(getObjectIdFromToken(token))
+ + " tagSize=" + getTagSizeFromToken(token)
+ + " sizePos=" + getSizePosFromToken(token)
+ + ')';
+ }
+ }
+
+ /**
+ * Start a child object.
+ *
+ * Returns a token which should be passed to endObject. Calls to endObject must be
+ * nested properly.
+ */
+ public long startObject(long fieldId) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_OBJECT);
+
+ return startObjectImpl(id, false);
+ }
+
+ /**
+ * End a child object. Pass in the token from the correspoinding startObject call.
+ */
+ public void endObject(long token) {
+ assertNotCompacted();
+
+ endObjectImpl(token, false);
+ }
+
+ /**
+ * Start a repeated child object.
+ *
+ * Returns a token which should be passed to endObject. Calls to endObject must be
+ * nested properly.
+ */
+ public long startRepeatedObject(long fieldId) {
+ assertNotCompacted();
+ final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_OBJECT);
+
+ return startObjectImpl(id, true);
+ }
+
+ /**
+ * End a child object. Pass in the token from the correspoinding startRepeatedObject call.
+ */
+ public void endRepeatedObject(long token) {
+ assertNotCompacted();
+
+ endObjectImpl(token, true);
+ }
+
+ /**
+ * Common implementation of startObject and startRepeatedObject.
+ */
+ private long startObjectImpl(final int id, boolean repeated) {
+ writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+ final int sizePos = mBuffer.getWritePos();
+ mDepth++;
+ mNextObjectId--;
+
+ // Write the previous token, giving us a stack of expected tokens.
+ // After endObject returns, the first fixed32 becomeschildRawSize (set in endObject)
+ // and the second one becomes childEncodedSize (set in editEncodedSize).
+ mBuffer.writeRawFixed32((int)(mExpectedObjectToken >> 32));
+ mBuffer.writeRawFixed32((int)mExpectedObjectToken);
+
+ long old = mExpectedObjectToken;
+
+ mExpectedObjectToken = makeToken(getTagSize(id), repeated, mDepth, mNextObjectId, sizePos);
+ return mExpectedObjectToken;
+ }
+
+ /**
+ * Common implementation of endObject and endRepeatedObject.
+ */
+ private void endObjectImpl(long token, boolean repeated) {
+ // The upper 32 bits of the token is the depth of startObject /
+ // endObject calls. We could get aritrarily sophisticated, but
+ // that's enough to prevent the common error of missing an
+ // endObject somewhere.
+ // The lower 32 bits of the token is the offset in the buffer
+ // at which to write the size.
+ final int depth = getDepthFromToken(token);
+ final boolean expectedRepeated = getRepeatedFromToken(token);
+ final int sizePos = getSizePosFromToken(token);
+ final int childRawSize = mBuffer.getWritePos() - sizePos - 8;
+
+ if (repeated != expectedRepeated) {
+ if (repeated) {
+ throw new IllegalArgumentException("endRepeatedObject called where endObject should"
+ + " have been");
+ } else {
+ throw new IllegalArgumentException("endObject called where endRepeatedObject should"
+ + " have been");
+ }
+ }
+
+ // Check that we're getting the token and depth that we are expecting.
+ if ((mDepth & 0x01ff) != depth || mExpectedObjectToken != token) {
+ // This text of exception is united tested. That test also implicity checks
+ // that we're tracking the objectIds and depths correctly.
+ throw new IllegalArgumentException("Mismatched startObject/endObject calls."
+ + " Current depth " + mDepth
+ + " token=" + token2String(token)
+ + " expectedToken=" + token2String(mExpectedObjectToken));
+ }
+
+ // Get the next expected token that we stashed away in the buffer.
+ mExpectedObjectToken = (((long)mBuffer.getRawFixed32At(sizePos)) << 32)
+ | (0x0ffffffffL & (long)mBuffer.getRawFixed32At(sizePos+4));
+
+ mDepth--;
+ if (childRawSize > 0) {
+ mBuffer.editRawFixed32(sizePos, -childRawSize);
+ mBuffer.editRawFixed32(sizePos+4, -1);
+ } else if (repeated) {
+ mBuffer.editRawFixed32(sizePos, 0);
+ mBuffer.editRawFixed32(sizePos+4, 0);
+ } else {
+ // The object has no data. Don't include it.
+ mBuffer.rewindWriteTo(sizePos - getTagSizeFromToken(token));
+ }
+ }
+
+ //
+ // Tags
+ //
+
+ /**
+ * Combine a fieldId (the field keys in the proto file) and the field flags.
+ * Mostly useful for testing because the generated code contains the fieldId
+ * constants.
+ */
+ public static long makeFieldId(int id, long fieldFlags) {
+ return fieldFlags | (((long)id) & 0x0ffffffffL);
+ }
+
+ /**
+ * Validates that the fieldId providied is of the type and count from expectedType.
+ *
+ * The type must match exactly to pass this check.
+ *
+ * The count must match according to this truth table to pass the check:
+ *
+ * expectedFlags
+ * UNKNOWN SINGLE REPEATED PACKED
+ * fieldId
+ * UNKNOWN true false false false
+ * SINGLE x true false false
+ * REPEATED x false true false
+ * PACKED x false true true
+ *
+ * @throws IllegalArgumentException if it is not.
+ *
+ * @return The raw ID of that field.
+ */
+ public static int checkFieldId(long fieldId, long expectedFlags) {
+ final long fieldCount = fieldId & FIELD_COUNT_MASK;
+ final long fieldType = fieldId & FIELD_TYPE_MASK;
+ final long expectedCount = expectedFlags & FIELD_COUNT_MASK;
+ final long expectedType = expectedFlags & FIELD_TYPE_MASK;
+ if (((int)fieldId) == 0) {
+ throw new IllegalArgumentException("Invalid proto field " + (int)fieldId
+ + " fieldId=" + Long.toHexString(fieldId));
+ }
+ if (fieldType != expectedType
+ || !((fieldCount == expectedCount)
+ || (fieldCount == FIELD_COUNT_PACKED
+ && expectedCount == FIELD_COUNT_REPEATED))) {
+ final String countString = getFieldCountString(fieldCount);
+ final String typeString = getFieldTypeString(fieldType);
+ if (typeString != null && countString != null) {
+ final StringBuilder sb = new StringBuilder();
+ if (expectedType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(getFieldCountString(expectedCount));
+ sb.append(getFieldTypeString(expectedType));
+ sb.append(" called for field ");
+ sb.append((int)fieldId);
+ sb.append(" which should be used with ");
+ if (fieldType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(countString);
+ sb.append(typeString);
+ if (fieldCount == FIELD_COUNT_PACKED) {
+ sb.append(" or writeRepeated");
+ sb.append(typeString);
+ }
+ sb.append('.');
+ throw new IllegalArgumentException(sb.toString());
+ } else {
+ final StringBuilder sb = new StringBuilder();
+ if (expectedType == FIELD_TYPE_OBJECT) {
+ sb.append("start");
+ } else {
+ sb.append("write");
+ }
+ sb.append(getFieldCountString(expectedCount));
+ sb.append(getFieldTypeString(expectedType));
+ sb.append(" called with an invalid fieldId: 0x");
+ sb.append(Long.toHexString(fieldId));
+ sb.append(". The proto field ID might be ");
+ sb.append((int)fieldId);
+ sb.append('.');
+ throw new IllegalArgumentException(sb.toString());
+ }
+ }
+ return (int)fieldId;
+ }
+
+ /**
+ * Get the developer-usable name of a field type.
+ */
+ private static String getFieldTypeString(long fieldType) {
+ int index = ((int)((fieldType & FIELD_TYPE_MASK) >>> FIELD_TYPE_SHIFT)) - 1;
+ if (index >= 0 && index < FIELD_TYPE_NAMES.length) {
+ return FIELD_TYPE_NAMES[index];
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Get the developer-usable name of a field count.
+ */
+ private static String getFieldCountString(long fieldCount) {
+ if (fieldCount == FIELD_COUNT_SINGLE) {
+ return "";
+ } else if (fieldCount == FIELD_COUNT_REPEATED) {
+ return "Repeated";
+ } else if (fieldCount == FIELD_COUNT_PACKED) {
+ return "Packed";
+ } else {
+ return null;
+ }
+ }
+
+
+ /**
+ * Return how many bytes an encoded field tag will require.
+ */
+ private static int getTagSize(int id) {
+ return EncodedBuffer.getRawVarint32Size(id << FIELD_ID_SHIFT);
+ }
+
+ /**
+ * Write a field tage to the stream.
+ */
+ public void writeTag(int id, int wireType) {
+ mBuffer.writeRawVarint32((id << FIELD_ID_SHIFT) | wireType);
+ }
+
+ /**
+ * Write the header of a WIRE_TYPE_LENGTH_DELIMITED field for one where
+ * we know the size in advance and do not need to compute and compact.
+ */
+ private void writeKnownLengthHeader(int id, int size) {
+ // Write the tag
+ writeTag(id, WIRE_TYPE_LENGTH_DELIMITED);
+ // Size will be compacted later, but we know the size, so write it,
+ // once for the rawSize and once for the encodedSize.
+ mBuffer.writeRawFixed32(size);
+ mBuffer.writeRawFixed32(size);
+ }
+
+ //
+ // Getting the buffer and compaction
+ //
+
+ /**
+ * Assert that the compact call has not already occured.
+ *
+ * TODO: Will change when we add the OutputStream version of ProtoOutputStream.
+ */
+ private void assertNotCompacted() {
+ if (mCompacted) {
+ throw new IllegalArgumentException("write called after compact");
+ }
+ }
+
+ /**
+ * Finish the encoding of the data, and return a byte[] with
+ * the protobuf formatted data.
+ *
+ * After this call, do not call any of the write* functions. The
+ * behavior is undefined.
+ */
+ public byte[] getBytes() {
+ compactIfNecessary();
+
+ return mBuffer.getBytes(mBuffer.getReadableSize());
+ }
+
+ /**
+ * If the buffer hasn't already had the nested object size fields compacted
+ * and turned into an actual protobuf format, then do so.
+ */
+ private void compactIfNecessary() {
+ if (!mCompacted) {
+ if (mDepth != 0) {
+ throw new IllegalArgumentException("Trying to compact with " + mDepth
+ + " missing calls to endObject");
+ }
+
+ // The buffer must be compacted.
+ mBuffer.startEditing();
+ final int readableSize = mBuffer.getReadableSize();
+
+ // Cache the sizes of the objects
+ editEncodedSize(readableSize);
+
+ // Re-write the buffer with the sizes as proper varints instead
+ // of pairs of uint32s. We know this will always fit in the same
+ // buffer because the pair of uint32s is exactly 8 bytes long, and
+ // the single varint size will be no more than 5 bytes long.
+ mBuffer.rewindRead();
+ compactSizes(readableSize);
+
+ // If there is any data left over that wasn't copied yet, copy it.
+ if (mCopyBegin < readableSize) {
+ mBuffer.writeFromThisBuffer(mCopyBegin, readableSize - mCopyBegin);
+ }
+
+ // Set the new readableSize
+ mBuffer.startEditing();
+
+ // It's not valid to write to this object anymore. The write
+ // pointers are off, and then some of the data would be compacted
+ // and some not.
+ mCompacted = true;
+ }
+ }
+
+ /**
+ * First compaction pass. Iterate through the data, and fill in the
+ * nested object sizes so the next pass can compact them.
+ */
+ private int editEncodedSize(int rawSize) {
+ int objectStart = mBuffer.getReadPos();
+ int objectEnd = objectStart + rawSize;
+ int encodedSize = 0;
+ int tagPos;
+
+ while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+ int tag = readRawTag();
+ encodedSize += EncodedBuffer.getRawVarint32Size(tag);
+
+ final int wireType = tag & WIRE_TYPE_MASK;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ encodedSize++;
+ while ((mBuffer.readRawByte() & 0x80) != 0) {
+ encodedSize++;
+ }
+ break;
+ case WIRE_TYPE_FIXED64:
+ encodedSize += 8;
+ mBuffer.skipRead(8);
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED: {
+ // This object is not of a fixed-size type. So we need to figure
+ // out how big it should be.
+ final int childRawSize = mBuffer.readRawFixed32();
+ final int childEncodedSizePos = mBuffer.getReadPos();
+ int childEncodedSize = mBuffer.readRawFixed32();
+ if (childRawSize >= 0) {
+ // We know the size, just skip ahead.
+ if (childEncodedSize != childRawSize) {
+ throw new RuntimeException("Pre-computed size where the"
+ + " precomputed size and the raw size in the buffer"
+ + " don't match! childRawSize=" + childRawSize
+ + " childEncodedSize=" + childEncodedSize
+ + " childEncodedSizePos=" + childEncodedSizePos);
+ }
+ mBuffer.skipRead(childRawSize);
+ } else {
+ // We need to compute the size. Recurse.
+ childEncodedSize = editEncodedSize(-childRawSize);
+ mBuffer.editRawFixed32(childEncodedSizePos, childEncodedSize);
+ }
+ encodedSize += EncodedBuffer.getRawVarint32Size(childEncodedSize)
+ + childEncodedSize;
+ break;
+ }
+ case WIRE_TYPE_START_GROUP:
+ case WIRE_TYPE_END_GROUP:
+ throw new RuntimeException("groups not supported at index " + tagPos);
+ case WIRE_TYPE_FIXED32:
+ encodedSize += 4;
+ mBuffer.skipRead(4);
+ break;
+ default:
+ throw new ProtoParseException("editEncodedSize Bad tag tag=0x"
+ + Integer.toHexString(tag) + " wireType=" + wireType
+ + " -- " + mBuffer.getDebugString());
+ }
+ }
+
+ return encodedSize;
+ }
+
+ /**
+ * Second compaction pass. Iterate through the data, and copy the data
+ * forward in the buffer, converting the pairs of uint32s into a single
+ * unsigned varint of the size.
+ */
+ private void compactSizes(int rawSize) {
+ int objectStart = mBuffer.getReadPos();
+ int objectEnd = objectStart + rawSize;
+ int tagPos;
+ while ((tagPos = mBuffer.getReadPos()) < objectEnd) {
+ int tag = readRawTag();
+
+ // For all the non-length-delimited field types, just skip over them,
+ // and we'll just System.arraycopy it later, either in the case for
+ // WIRE_TYPE_LENGTH_DELIMITED or at the top of the stack in compactIfNecessary().
+ final int wireType = tag & WIRE_TYPE_MASK;
+ switch (wireType) {
+ case WIRE_TYPE_VARINT:
+ while ((mBuffer.readRawByte() & 0x80) != 0) { }
+ break;
+ case WIRE_TYPE_FIXED64:
+ mBuffer.skipRead(8);
+ break;
+ case WIRE_TYPE_LENGTH_DELIMITED: {
+ // Copy everything up to now, including the tag for this field.
+ mBuffer.writeFromThisBuffer(mCopyBegin, mBuffer.getReadPos() - mCopyBegin);
+ // Write the new size.
+ final int childRawSize = mBuffer.readRawFixed32();
+ final int childEncodedSize = mBuffer.readRawFixed32();
+ mBuffer.writeRawVarint32(childEncodedSize);
+ // Next time, start copying from here.
+ mCopyBegin = mBuffer.getReadPos();
+ if (childRawSize >= 0) {
+ // This is raw data, not an object. Skip ahead by the size.
+ // Recurse into the child
+ mBuffer.skipRead(childEncodedSize);
+ } else {
+ compactSizes(-childRawSize);
+ }
+ break;
+ // TODO: What does regular proto do if the object would be 0 size
+ // (e.g. if it is all default values).
+ }
+ case WIRE_TYPE_START_GROUP:
+ case WIRE_TYPE_END_GROUP:
+ throw new RuntimeException("groups not supported at index " + tagPos);
+ case WIRE_TYPE_FIXED32:
+ mBuffer.skipRead(4);
+ break;
+ default:
+ throw new ProtoParseException("compactSizes Bad tag tag=0x"
+ + Integer.toHexString(tag) + " wireType=" + wireType
+ + " -- " + mBuffer.getDebugString());
+ }
+ }
+ }
+
+ /**
+ * Read a raw tag from the buffer.
+ */
+ private int readRawTag() {
+ if (mBuffer.getReadPos() == mBuffer.getReadableSize()) {
+ return 0;
+ }
+ return (int)mBuffer.readRawUnsigned();
+ }
+
+ /**
+ * Dump debugging data about the buffers with the given log tag.
+ */
+ public void dump(String tag) {
+ Log.d(tag, mBuffer.getDebugString());
+ mBuffer.dumpBuffers(tag);
+ }
+}
diff --git a/core/java/android/util/proto/ProtoParseException.java b/core/java/android/util/proto/ProtoParseException.java
new file mode 100644
index 000000000000..5ba9bf8ea518
--- /dev/null
+++ b/core/java/android/util/proto/ProtoParseException.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * Thrown when there is an error parsing protobuf data.
+ *
+ * @hide
+ */
+@TestApi
+public class ProtoParseException extends RuntimeException {
+
+ /**
+ * Construct a ProtoParseException.
+ *
+ * @param msg The message.
+ */
+ public ProtoParseException(String msg) {
+ super(msg);
+ }
+}
+
diff --git a/core/java/android/util/proto/package.html b/core/java/android/util/proto/package.html
new file mode 100644
index 000000000000..a636bd457d3d
--- /dev/null
+++ b/core/java/android/util/proto/package.html
@@ -0,0 +1,5 @@
+<body>
+Provides utility classes to export protocol buffers from the system.
+
+{@hide}
+</body>
diff --git a/tools/streaming_proto/Android.mk b/tools/streaming_proto/Android.mk
new file mode 100644
index 000000000000..5a54fd10415d
--- /dev/null
+++ b/tools/streaming_proto/Android.mk
@@ -0,0 +1,41 @@
+#
+# Copyright (C) 2015 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.
+#
+LOCAL_PATH:= $(call my-dir)
+
+# ==========================================================
+# Build the host executable: protoc-gen-javastream
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_MODULE := protoc-gen-javastream
+LOCAL_SRC_FILES := \
+ Errors.cpp \
+ string_utils.cpp \
+ main.cpp
+LOCAL_SHARED_LIBRARIES := \
+ libprotoc
+include $(BUILD_HOST_EXECUTABLE)
+
+# ==========================================================
+# Build the java test
+# ==========================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, test) \
+ $(call all-proto-files-under, test)
+LOCAL_MODULE := StreamingProtoTest
+LOCAL_PROTOC_OPTIMIZE_TYPE := stream
+include $(BUILD_JAVA_LIBRARY)
+
diff --git a/tools/streaming_proto/Errors.cpp b/tools/streaming_proto/Errors.cpp
new file mode 100644
index 000000000000..91c6b9245de0
--- /dev/null
+++ b/tools/streaming_proto/Errors.cpp
@@ -0,0 +1,87 @@
+#include "Errors.h"
+
+#include <stdlib.h>
+
+namespace android {
+namespace javastream_proto {
+
+Errors ERRORS;
+
+const string UNKNOWN_FILE;
+const int UNKNOWN_LINE = 0;
+
+Error::Error()
+{
+}
+
+Error::Error(const Error& that)
+ :filename(that.filename),
+ lineno(that.lineno),
+ message(that.message)
+{
+}
+
+Error::Error(const string& f, int l, const char* m)
+ :filename(f),
+ lineno(l),
+ message(m)
+{
+}
+
+Errors::Errors()
+ :m_errors()
+{
+}
+
+Errors::~Errors()
+{
+}
+
+void
+Errors::Add(const string& filename, int lineno, const char* format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ AddImpl(filename, lineno, format, args);
+ va_end(args);
+}
+
+void
+Errors::AddImpl(const string& filename, int lineno, const char* format, va_list args)
+{
+ va_list args2;
+ va_copy(args2, args);
+ int message_size = vsnprintf((char*)NULL, 0, format, args2);
+ va_end(args2);
+
+ char* buffer = new char[message_size+1];
+ vsnprintf(buffer, message_size, format, args);
+ Error error(filename, lineno, buffer);
+ delete[] buffer;
+
+ m_errors.push_back(error);
+}
+
+void
+Errors::Print() const
+{
+ for (vector<Error>::const_iterator it = m_errors.begin(); it != m_errors.end(); it++) {
+ if (it->filename == UNKNOWN_FILE) {
+ fprintf(stderr, "%s", it->message.c_str());
+ } else if (it->lineno == UNKNOWN_LINE) {
+ fprintf(stderr, "%s:%s", it->filename.c_str(), it->message.c_str());
+ } else {
+ fprintf(stderr, "%s:%d:%s", it->filename.c_str(), it->lineno, it->message.c_str());
+ }
+ }
+}
+
+bool
+Errors::HasErrors() const
+{
+ return m_errors.size() > 0;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/tools/streaming_proto/Errors.h b/tools/streaming_proto/Errors.h
new file mode 100644
index 000000000000..109195a20b06
--- /dev/null
+++ b/tools/streaming_proto/Errors.h
@@ -0,0 +1,48 @@
+#include <stdio.h>
+
+#include <string>
+#include <vector>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+struct Error
+{
+ Error();
+ explicit Error(const Error& that);
+ Error(const string& filename, int lineno, const char* message);
+
+ string filename;
+ int lineno;
+ string message;
+};
+
+class Errors
+{
+public:
+ Errors();
+ ~Errors();
+
+ // Add an error
+ void Add(const string& filename, int lineno, const char* format, ...);
+
+ // Print the errors to stderr if there are any.
+ void Print() const;
+
+ bool HasErrors() const;
+
+private:
+ // The errors that have been added
+ vector<Error> m_errors;
+ void AddImpl(const string& filename, int lineno, const char* format, va_list ap);
+};
+
+extern Errors ERRORS;
+extern const string UNKNOWN_FILE;
+extern const int UNKNOWN_LINE;
+
+
+} // namespace javastream_proto
+} // namespace android
diff --git a/tools/streaming_proto/main.cpp b/tools/streaming_proto/main.cpp
new file mode 100644
index 000000000000..d2862137b216
--- /dev/null
+++ b/tools/streaming_proto/main.cpp
@@ -0,0 +1,391 @@
+#include "Errors.h"
+
+#include "string_utils.h"
+
+#include "google/protobuf/compiler/plugin.pb.h"
+#include "google/protobuf/io/zero_copy_stream_impl.h"
+#include "google/protobuf/text_format.h"
+
+#include <stdio.h>
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <map>
+
+using namespace android::javastream_proto;
+using namespace google::protobuf;
+using namespace google::protobuf::compiler;
+using namespace google::protobuf::io;
+using namespace std;
+
+const int FIELD_TYPE_SHIFT = 32;
+const uint64_t FIELD_TYPE_DOUBLE = 1L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FLOAT = 2L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT32 = 3L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_INT64 = 4L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT32 = 5L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_UINT64 = 6L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT32 = 7L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SINT64 = 8L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED32 = 9L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_FIXED64 = 10L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED32 = 11L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_SFIXED64 = 12L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BOOL = 13L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_STRING = 14L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_BYTES = 15L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_ENUM = 16L << FIELD_TYPE_SHIFT;
+const uint64_t FIELD_TYPE_OBJECT = 17L << FIELD_TYPE_SHIFT;
+
+const int FIELD_COUNT_SHIFT = 40;
+const uint64_t FIELD_COUNT_SINGLE = 1L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_REPEATED = 2L << FIELD_COUNT_SHIFT;
+const uint64_t FIELD_COUNT_PACKED = 5L << FIELD_COUNT_SHIFT;
+
+
+/**
+ * See if this is the file for this request, and not one of the imported ones.
+ */
+static bool
+should_generate_for_file(const CodeGeneratorRequest& request, const string& file)
+{
+ const int N = request.file_to_generate_size();
+ for (int i=0; i<N; i++) {
+ if (request.file_to_generate(i) == file) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * If the descriptor gives us a class name, use that. Otherwise make one up from
+ * the filename of the .proto file.
+ */
+static string
+make_outer_class_name(const FileDescriptorProto& file_descriptor)
+{
+ string name = file_descriptor.options().java_outer_classname();
+ if (name.size() == 0) {
+ name = to_camel_case(file_base_name(file_descriptor.name()));
+ if (name.size() == 0) {
+ ERRORS.Add(UNKNOWN_FILE, UNKNOWN_LINE,
+ "Unable to make an outer class name for file: %s",
+ file_descriptor.name().c_str());
+ name = "Unknown";
+ }
+ }
+ return name;
+}
+
+/**
+ * Figure out the package name that we are generating.
+ */
+static string
+make_java_package(const FileDescriptorProto& file_descriptor) {
+ if (file_descriptor.options().has_java_package()) {
+ return file_descriptor.options().java_package();
+ } else {
+ return file_descriptor.package();
+ }
+}
+
+/**
+ * Figure out the name of the file we are generating.
+ */
+static string
+make_file_name(const FileDescriptorProto& file_descriptor)
+{
+ string const package = make_java_package(file_descriptor);
+ string result;
+ if (package.size() > 0) {
+ result = replace_string(package, '.', '/');
+ result += '/';
+ }
+
+ result += make_outer_class_name(file_descriptor);
+ result += ".java";
+
+ return result;
+}
+
+static string
+indent_more(const string& indent)
+{
+ return indent + " ";
+}
+
+/**
+ * Write the constants for an enum.
+ */
+static void
+write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
+{
+ const int N = enu.value_size();
+ text << indent << "// enum " << enu.name() << endl;
+ for (int i=0; i<N; i++) {
+ const EnumValueDescriptorProto& value = enu.value(i);
+ text << indent << "public static final int "
+ << make_constant_name(value.name())
+ << " = " << value.number() << ";" << endl;
+ }
+ text << endl;
+}
+
+/**
+ * Get the string name for a field.
+ */
+static string
+get_proto_type(const FieldDescriptorProto& field)
+{
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ return "double";
+ case FieldDescriptorProto::TYPE_FLOAT:
+ return "float";
+ case FieldDescriptorProto::TYPE_INT64:
+ return "int64";
+ case FieldDescriptorProto::TYPE_UINT64:
+ return "uint64";
+ case FieldDescriptorProto::TYPE_INT32:
+ return "int32";
+ case FieldDescriptorProto::TYPE_FIXED64:
+ return "fixed64";
+ case FieldDescriptorProto::TYPE_FIXED32:
+ return "fixed32";
+ case FieldDescriptorProto::TYPE_BOOL:
+ return "bool";
+ case FieldDescriptorProto::TYPE_STRING:
+ return "string";
+ case FieldDescriptorProto::TYPE_GROUP:
+ return "group<unsupported!>";
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_BYTES:
+ return "bytes";
+ case FieldDescriptorProto::TYPE_UINT32:
+ return "uint32";
+ case FieldDescriptorProto::TYPE_ENUM:
+ return field.type_name();
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ return "sfixed32";
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ return "sfixed64";
+ case FieldDescriptorProto::TYPE_SINT32:
+ return "sint32";
+ case FieldDescriptorProto::TYPE_SINT64:
+ return "sint64";
+ default:
+ // won't happen
+ return "void";
+ }
+}
+
+static uint64_t
+get_field_id(const FieldDescriptorProto& field)
+{
+ // Number
+ uint64_t result = (uint32_t)field.number();
+
+ // Type
+ switch (field.type()) {
+ case FieldDescriptorProto::TYPE_DOUBLE:
+ result |= FIELD_TYPE_DOUBLE;
+ case FieldDescriptorProto::TYPE_FLOAT:
+ result |= FIELD_TYPE_FLOAT;
+ case FieldDescriptorProto::TYPE_INT64:
+ result |= FIELD_TYPE_INT64;
+ case FieldDescriptorProto::TYPE_UINT64:
+ result |= FIELD_TYPE_UINT64;
+ case FieldDescriptorProto::TYPE_INT32:
+ result |= FIELD_TYPE_INT32;
+ case FieldDescriptorProto::TYPE_FIXED64:
+ result |= FIELD_TYPE_FIXED64;
+ case FieldDescriptorProto::TYPE_FIXED32:
+ result |= FIELD_TYPE_FIXED32;
+ case FieldDescriptorProto::TYPE_BOOL:
+ result |= FIELD_TYPE_BOOL;
+ case FieldDescriptorProto::TYPE_STRING:
+ result |= FIELD_TYPE_STRING;
+ case FieldDescriptorProto::TYPE_MESSAGE:
+ result |= FIELD_TYPE_OBJECT;
+ case FieldDescriptorProto::TYPE_BYTES:
+ result |= FIELD_TYPE_BYTES;
+ case FieldDescriptorProto::TYPE_UINT32:
+ result |= FIELD_TYPE_UINT32;
+ case FieldDescriptorProto::TYPE_ENUM:
+ result |= FIELD_TYPE_ENUM;
+ case FieldDescriptorProto::TYPE_SFIXED32:
+ result |= FIELD_TYPE_SFIXED32;
+ case FieldDescriptorProto::TYPE_SFIXED64:
+ result |= FIELD_TYPE_SFIXED64;
+ case FieldDescriptorProto::TYPE_SINT32:
+ result |= FIELD_TYPE_SINT32;
+ case FieldDescriptorProto::TYPE_SINT64:
+ result |= FIELD_TYPE_SINT64;
+ default:
+ ;
+ }
+
+ // Count
+ if (field.options().packed()) {
+ result |= FIELD_COUNT_PACKED;
+ } else if (field.label() == FieldDescriptorProto::LABEL_REPEATED) {
+ result |= FIELD_COUNT_REPEATED;
+ } else {
+ result |= FIELD_COUNT_SINGLE;
+ }
+
+ return result;
+}
+
+/**
+ * Write a field.
+ */
+static void
+write_field(stringstream& text, const FieldDescriptorProto& field, const string& indent)
+{
+ string optional_comment = field.label() == FieldDescriptorProto::LABEL_OPTIONAL
+ ? "optional " : "";
+ string repeated_comment = field.label() == FieldDescriptorProto::LABEL_REPEATED
+ ? "repeated " : "";
+ string proto_type = get_proto_type(field);
+ string packed_comment = field.options().packed()
+ ? " [packed=true]" : "";
+ text << indent << "// " << optional_comment << repeated_comment << proto_type << ' '
+ << field.name() << " = " << field.number() << packed_comment << ';' << endl;
+
+ text << indent << "public static final long " << make_constant_name(field.name()) << " = 0x";
+
+ ios::fmtflags fmt(text.flags());
+ text << setfill('0') << setw(16) << hex << get_field_id(field);
+ text.flags(fmt);
+
+ text << "L;" << endl;
+
+ text << endl;
+}
+
+/**
+ * Write a Message constants class.
+ */
+static void
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
+{
+ int N;
+ const string indented = indent_more(indent);
+
+ text << indent << "// message " << message.name() << endl;
+ text << indent << "public final class " << message.name() << " {" << endl;
+ text << endl;
+
+ // Enums
+ N = message.enum_type_size();
+ for (int i=0; i<N; i++) {
+ write_enum(text, message.enum_type(i), indented);
+ }
+
+ // Nested classes
+ N = message.nested_type_size();
+ for (int i=0; i<N; i++) {
+ write_message(text, message.nested_type(i), indented);
+ }
+
+ // Fields
+ N = message.field_size();
+ for (int i=0; i<N; i++) {
+ write_field(text, message.field(i), indented);
+ }
+
+ text << indent << "}" << endl;
+ text << endl;
+}
+
+/**
+ * Write the contents of a file.
+ */
+static void
+write_file(stringstream& text, const FileDescriptorProto& file_descriptor)
+{
+ string const package_name = make_java_package(file_descriptor);
+ string const outer_class_name = make_outer_class_name(file_descriptor);
+
+ text << "// Generated by protoc-gen-javastream. DO NOT MODIFY." << endl;
+ text << "// source: " << file_descriptor.name() << endl << endl;
+
+ if (package_name.size() > 0) {
+ if (package_name.size() > 0) {
+ text << "package " << package_name << ";" << endl;
+ text << endl;
+ }
+ }
+
+ // This bit of policy is android api rules specific: Raw proto classes
+ // must never be in the API, but they should all be available for testing.
+ text << "/** @hide */" << endl;
+ text << "@android.annotation.TestApi" << endl;
+
+ text << "public final class " << outer_class_name << " {" << endl;
+ text << endl;
+
+ int N;
+ const string indented = indent_more("");
+
+ N = file_descriptor.enum_type_size();
+ for (int i=0; i<N; i++) {
+ write_enum(text, file_descriptor.enum_type(i), indented);
+ }
+
+ N = file_descriptor.message_type_size();
+ for (int i=0; i<N; i++) {
+ write_message(text, file_descriptor.message_type(i), indented);
+ }
+
+ text << "}" << endl;
+}
+
+/**
+ * Main.
+ */
+int
+main(int argc, char const*const* argv)
+{
+ (void)argc;
+ (void)argv;
+
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ CodeGeneratorRequest request;
+ CodeGeneratorResponse response;
+
+ // Read the request
+ request.ParseFromIstream(&cin);
+
+ // Build the files we need.
+ const int N = request.proto_file_size();
+ for (int i=0; i<N; i++) {
+ const FileDescriptorProto& file_descriptor = request.proto_file(i);
+ if (should_generate_for_file(request, file_descriptor.name())) {
+ // Generate the text
+ stringstream text;
+ write_file(text, file_descriptor);
+
+ // Put the text in the response
+ CodeGeneratorResponse::File* file_response = response.add_file();
+ file_response->set_name(make_file_name(file_descriptor));
+ file_response->set_content(text.str());
+
+ cerr << "writing file: " << file_response->name() << endl;
+ }
+ }
+
+ // If we had errors, don't write the response. Print the errors and exit.
+ if (ERRORS.HasErrors()) {
+ ERRORS.Print();
+ return 1;
+ }
+
+ // If we didn't have errors, write the response and exit happily.
+ response.SerializeToOstream(&cout);
+ return 0;
+}
diff --git a/tools/streaming_proto/string_utils.cpp b/tools/streaming_proto/string_utils.cpp
new file mode 100644
index 000000000000..cc738c4c108e
--- /dev/null
+++ b/tools/streaming_proto/string_utils.cpp
@@ -0,0 +1,95 @@
+
+#include "string_utils.h"
+#include <iostream>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+string
+to_camel_case(const string& str)
+{
+ string result;
+ const int N = str.size();
+ result.reserve(N);
+ bool capitalize_next = true;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c == '_') {
+ capitalize_next = true;
+ } else {
+ if (capitalize_next && c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ capitalize_next = false;
+ } else if (c >= 'A' && c <= 'Z') {
+ capitalize_next = false;
+ } else if (c >= '0' && c <= '9') {
+ capitalize_next = true;
+ } else {
+ // All other characters (e.g. non-latin) count as capital.
+ capitalize_next = false;
+ }
+ result += c;
+ }
+ }
+ return result;
+}
+
+string
+make_constant_name(const string& str)
+{
+ string result;
+ const int N = str.size();
+ bool underscore_next = false;
+ for (int i=0; i<N; i++) {
+ char c = str[i];
+ if (c >= 'A' && c <= 'Z') {
+ if (underscore_next) {
+ result += '_';
+ underscore_next = false;
+ }
+ } else if (c >= 'a' && c <= 'z') {
+ c = 'A' + c - 'a';
+ underscore_next = true;
+ } else if (c == '_') {
+ underscore_next = false;
+ }
+ result += c;
+ }
+ return result;
+}
+
+string
+file_base_name(const string& str)
+{
+ size_t start = str.rfind('/');
+ if (start == string::npos) {
+ start = 0;
+ } else {
+ start++;
+ }
+ size_t end = str.find('.', start);
+ if (end == string::npos) {
+ end = str.size();
+ }
+ return str.substr(start, end-start);
+}
+
+string
+replace_string(const string& str, const char replace, const char with)
+{
+ string result(str);
+ const int N = result.size();
+ for (int i=0; i<N; i++) {
+ if (result[i] == replace) {
+ result[i] = with;
+ }
+ }
+ return result;
+}
+
+} // namespace javastream_proto
+} // namespace android
+
+
diff --git a/tools/streaming_proto/string_utils.h b/tools/streaming_proto/string_utils.h
new file mode 100644
index 000000000000..ffe83ca99704
--- /dev/null
+++ b/tools/streaming_proto/string_utils.h
@@ -0,0 +1,32 @@
+#include <string>
+
+namespace android {
+namespace javastream_proto {
+
+using namespace std;
+
+/**
+ * Capitalizes the string, removes underscores and makes the next letter
+ * capitalized, and makes the letter following numbers capitalized.
+ */
+string to_camel_case(const string& str);
+
+/**
+ * Capitalize and insert underscores for CamelCase.
+ */
+string make_constant_name(const string& str);
+
+/**
+ * Returns the part of a file name that isn't a path and isn't a type suffix.
+ */
+string file_base_name(const string& str);
+
+/**
+ * Replace all occurances of 'replace' with 'with'.
+ */
+string replace_string(const string& str, const char replace, const char with);
+
+
+} // namespace javastream_proto
+} // namespace android
+
diff --git a/tools/streaming_proto/test/imported.proto b/tools/streaming_proto/test/imported.proto
new file mode 100644
index 000000000000..05c8f0c54fac
--- /dev/null
+++ b/tools/streaming_proto/test/imported.proto
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto2";
+
+package com.android.streaming_proto_test;
+
+/**
+ * Message that is used from the other file.
+ */
+message ImportedMessage {
+ optional int32 data = 1;
+};
diff --git a/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
new file mode 100644
index 000000000000..1246f539b44b
--- /dev/null
+++ b/tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java
@@ -0,0 +1,7 @@
+package com.android.streaming_proto_test;
+
+public class Main {
+ public void main(String[] argv) {
+ System.out.println("hello world");
+ }
+}
diff --git a/tools/streaming_proto/test/test.proto b/tools/streaming_proto/test/test.proto
new file mode 100644
index 000000000000..de80ed6612fc
--- /dev/null
+++ b/tools/streaming_proto/test/test.proto
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+syntax = "proto2";
+
+import "frameworks/base/tools/streaming_proto/test/imported.proto";
+
+package com.android.streaming_proto_test;
+
+/**
+ * Enum that outside the scope of any classes.
+ */
+enum Outside {
+ OUTSIDE_0 = 0;
+ OUTSIDE_1 = 1;
+};
+
+message Sibling {
+ optional int32 int32_field = 1;
+}
+
+/**
+ * Message with all of the field types.
+ */
+message All {
+ /**
+ * Enum that is inside the scope of a class.
+ */
+ enum Inside {
+ option allow_alias = true;
+ INSIDE_0 = 0;
+ INSIDE_1 = 1;
+ INSIDE_1A = 1;
+ };
+
+ /**
+ * Message that is recursive.
+ */
+ message Nested {
+ optional int32 data = 10001;
+ optional Nested nested = 10002;
+ };
+
+ optional double double_field = 10;
+ repeated double double_field_repeated = 11;
+ repeated double double_field_packed = 12 [packed=true];
+
+ optional float float_field = 20;
+ repeated float float_field_repeated = 21;
+ repeated float float_field_packed = 22 [packed=true];
+
+ optional int32 int32_field = 30;
+ repeated int32 int32_field_repeated = 31;
+ repeated int32 int32_field_packed = 32 [packed=true];
+
+ optional int64 int64_field = 40;
+ repeated int64 int64_field_repeated = 41;
+ repeated int64 int64_field_packed = 42 [packed=true];
+
+ optional uint32 uint32_field = 50;
+ repeated uint32 uint32_field_repeated = 51;
+ repeated uint32 uint32_field_packed = 52 [packed=true];
+
+ optional uint64 uint64_field = 60;
+ repeated uint64 uint64_field_repeated = 61;
+ repeated uint64 uint64_field_packed = 62 [packed=true];
+
+ optional sint32 sint32_field = 70;
+ repeated sint32 sint32_field_repeated = 71;
+ repeated sint32 sint32_field_packed = 72 [packed=true];
+
+ optional sint64 sint64_field = 80;
+ repeated sint64 sint64_field_repeated = 81;
+ repeated sint64 sint64_field_packed = 82 [packed=true];
+
+ optional fixed32 fixed32_field = 90;
+ repeated fixed32 fixed32_field_repeated = 91;
+ repeated fixed32 fixed32_field_packed = 92 [packed=true];
+
+ optional fixed64 fixed64_field = 100;
+ repeated fixed64 fixed64_field_repeated = 101;
+ repeated fixed64 fixed64_field_packed = 102 [packed=true];
+
+ optional sfixed32 sfixed32_field = 110;
+ repeated sfixed32 sfixed32_field_repeated = 111;
+ repeated sfixed32 sfixed32_field_packed = 112 [packed=true];
+
+ optional sfixed64 sfixed64_field = 120;
+ repeated sfixed64 sfixed64_field_repeated = 121;
+ repeated sfixed64 sfixed64_field_packed = 122 [packed=true];
+
+ optional bool bool_field = 130;
+ repeated bool bool_field_repeated = 131;
+ repeated bool bool_field_packed = 132 [packed=true];
+
+ optional string string_field = 140;
+ repeated string string_field_repeated = 141;
+
+ optional bytes bytes_field = 150;
+ repeated bytes bytes_field_repeated = 151;
+
+ optional Outside outside_field = 160;
+ repeated Outside outside_field_repeated = 161;
+ repeated Outside outside_field_packed = 162 [packed=true];
+
+ optional Nested nested_field = 170;
+ repeated Nested nested_field_repeated = 171;
+
+ optional ImportedMessage imported_field = 180;
+ repeated ImportedMessage imported_field_repeated = 181;
+};