diff options
-rw-r--r-- | api/test-current.txt | 150 | ||||
-rw-r--r-- | core/java/android/util/proto/EncodedBuffer.java | 678 | ||||
-rw-r--r-- | core/java/android/util/proto/ProtoOutputStream.java | 1599 | ||||
-rw-r--r-- | core/java/android/util/proto/ProtoParseException.java | 38 | ||||
-rw-r--r-- | core/java/android/util/proto/package.html | 5 | ||||
-rw-r--r-- | tools/streaming_proto/Android.mk | 41 | ||||
-rw-r--r-- | tools/streaming_proto/Errors.cpp | 87 | ||||
-rw-r--r-- | tools/streaming_proto/Errors.h | 48 | ||||
-rw-r--r-- | tools/streaming_proto/main.cpp | 391 | ||||
-rw-r--r-- | tools/streaming_proto/string_utils.cpp | 95 | ||||
-rw-r--r-- | tools/streaming_proto/string_utils.h | 32 | ||||
-rw-r--r-- | tools/streaming_proto/test/imported.proto | 26 | ||||
-rw-r--r-- | tools/streaming_proto/test/src/com/android/streaming_proto_test/Main.java | 7 | ||||
-rw-r--r-- | tools/streaming_proto/test/test.proto | 124 |
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 <= 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; +}; |