/* * 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.NonNull; import android.annotation.Nullable; import android.util.Log; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * Class to write to a protobuf stream. * *
* This API is not as convenient or type safe as the standard protobuf * classes. If possible, the best recommended library is to use protobuf lite. * However, in environments (such as the Android platform itself), a * more memory efficient version is necessary. * *
Each write method takes an ID code from the protoc generated classes * and the value to write. To make a nested object, call {@link #start(long)} * and then {@link #end(long)} when you are done. * *
The ID codes have type information embedded into them, so if you call * the incorrect function you will get an {@link IllegalArgumentException}. * *
To retrieve the encoded protobuf stream, call {@link #getBytes()}. * * stream as the top-level objects are finished. * */ /* 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 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 of fixed-size allocations, * which is less likely to cause memory fragmentation or churn the GC, and * the same number of data copies as we 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 a 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 sentinel that we need to * compute it during the second pass and recursively compact it during the * third pass. * * Unsigned 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 #start, but * not yet called #end, we maintain a linked list of the tokens * returned by #start, 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 #start and #end calls, and issue * errors if they are not matched. */ @android.ravenwood.annotation.RavenwoodKeepWholeClass public final class ProtoOutputStream extends ProtoStream { /** * @hide */ public static final String TAG = "ProtoOutputStream"; /** * Our buffer. */ private EncodedBuffer mBuffer; /** * Our stream. If there is one. */ private OutputStream mStream; /** * 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 {@link ProtoOutputStream} with the default chunk size. * *
This is for an in-memory proto. The caller should use {@link #getBytes()} for the result. */ public ProtoOutputStream() { this(0); } /** * Construct a {@link ProtoOutputStream with the given chunk size. * *
This is for an in-memory proto. The caller should use {@link #getBytes()} for the result. */ public ProtoOutputStream(int chunkSize) { mBuffer = new EncodedBuffer(chunkSize); } /** * Construct a {@link ProtoOutputStream} that sits on top of an {@link OutputStream}. * *
The {@link #flush()} method must be called when done writing * to flush any remaining data, although data *may* be written at intermediate * points within the writing as well. */ public ProtoOutputStream(@NonNull OutputStream stream) { this(); mStream = stream; } /** * Construct a {@link ProtoOutputStream} that sits on top of a {@link FileDescriptor}. * *
The {@link #flush()} method must be called when done writing * to flush any remaining data, although data *may* be written at intermediate * points within the writing as well. * * @hide */ public ProtoOutputStream(@NonNull FileDescriptor fd) { this(new FileOutputStream(fd)); } /** * Returns the total size of the data that has been written, after full * protobuf encoding has occurred. * * @return the uncompressed buffer size */ public int getRawSize() { if (mCompacted) { return getBytes().length; } else { return mBuffer.getSize(); } } /** * Write a value for the given fieldId. * *
Will automatically convert for the following field types, and * throw an exception for others: double, float, int32, int64, uint32, uint64, * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, double val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // double case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeDoubleImpl(id, (double)val); break; case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedDoubleImpl(id, (double)val); break; // float case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFloatImpl(id, (float)val); break; case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFloatImpl(id, (float)val); break; // int32 case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt32Impl(id, (int)val); break; // int64 case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt64Impl(id, (long)val); break; // uint32 case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt32Impl(id, (int)val); break; // uint64 case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt64Impl(id, (long)val); break; // sint32 case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt32Impl(id, (int)val); break; // sint64 case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt64Impl(id, (long)val); break; // fixed32 case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed32Impl(id, (int)val); break; // fixed64 case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed64Impl(id, (long)val); break; // sfixed32 case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed32Impl(id, (int)val); break; // sfixed64 case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed64Impl(id, (long)val); break; // bool case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBoolImpl(id, val != 0); break; case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBoolImpl(id, val != 0); break; // enum case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeEnumImpl(id, (int)val); break; case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedEnumImpl(id, (int)val); break; // string, bytes, object not allowed here. default: { throw new IllegalArgumentException("Attempt to call write(long, double) with " + getFieldIdString(fieldId)); } } } /** * Write a value for the given fieldId. * *
Will automatically convert for the following field types, and * throw an exception for others: double, float, int32, int64, uint32, uint64, * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, float val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // double case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeDoubleImpl(id, (double)val); break; case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedDoubleImpl(id, (double)val); break; // float case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFloatImpl(id, (float)val); break; case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFloatImpl(id, (float)val); break; // int32 case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt32Impl(id, (int)val); break; // int64 case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt64Impl(id, (long)val); break; // uint32 case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt32Impl(id, (int)val); break; // uint64 case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt64Impl(id, (long)val); break; // sint32 case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt32Impl(id, (int)val); break; // sint64 case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt64Impl(id, (long)val); break; // fixed32 case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed32Impl(id, (int)val); break; // fixed64 case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed64Impl(id, (long)val); break; // sfixed32 case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed32Impl(id, (int)val); break; // sfixed64 case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed64Impl(id, (long)val); break; // bool case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBoolImpl(id, val != 0); break; case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBoolImpl(id, val != 0); break; // enum case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeEnumImpl(id, (int)val); break; case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedEnumImpl(id, (int)val); break; // string, bytes, object not allowed here. default: { throw new IllegalArgumentException("Attempt to call write(long, float) with " + getFieldIdString(fieldId)); } } } /** * Write a value for the given fieldId. * *
Will automatically convert for the following field types, and * throw an exception for others: double, float, int32, int64, uint32, uint64, * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, int val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // double case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeDoubleImpl(id, (double)val); break; case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedDoubleImpl(id, (double)val); break; // float case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFloatImpl(id, (float)val); break; case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFloatImpl(id, (float)val); break; // int32 case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt32Impl(id, (int)val); break; // int64 case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt64Impl(id, (long)val); break; // uint32 case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt32Impl(id, (int)val); break; // uint64 case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt64Impl(id, (long)val); break; // sint32 case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt32Impl(id, (int)val); break; // sint64 case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt64Impl(id, (long)val); break; // fixed32 case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed32Impl(id, (int)val); break; // fixed64 case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed64Impl(id, (long)val); break; // sfixed32 case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed32Impl(id, (int)val); break; // sfixed64 case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed64Impl(id, (long)val); break; // bool case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBoolImpl(id, val != 0); break; case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBoolImpl(id, val != 0); break; // enum case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeEnumImpl(id, (int)val); break; case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedEnumImpl(id, (int)val); break; // string, bytes, object not allowed here. default: { throw new IllegalArgumentException("Attempt to call write(long, int) with " + getFieldIdString(fieldId)); } } } /** * Write a value for the given fieldId. * *
Will automatically convert for the following field types, and * throw an exception for others: double, float, int32, int64, uint32, uint64, * sint32, sint64, fixed32, fixed64, sfixed32, sfixed64, bool, enum. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, long val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // double case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeDoubleImpl(id, (double)val); break; case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_DOUBLE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedDoubleImpl(id, (double)val); break; // float case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFloatImpl(id, (float)val); break; case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FLOAT | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFloatImpl(id, (float)val); break; // int32 case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt32Impl(id, (int)val); break; // int64 case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_INT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedInt64Impl(id, (long)val); break; // uint32 case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt32Impl(id, (int)val); break; // uint64 case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeUInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_UINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedUInt64Impl(id, (long)val); break; // sint32 case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt32Impl(id, (int)val); break; // sint64 case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSInt64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SINT64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSInt64Impl(id, (long)val); break; // fixed32 case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed32Impl(id, (int)val); break; // fixed64 case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_FIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedFixed64Impl(id, (long)val); break; // sfixed32 case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed32Impl(id, (int)val); break; case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED32 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed32Impl(id, (int)val); break; // sfixed64 case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeSFixed64Impl(id, (long)val); break; case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_SFIXED64 | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedSFixed64Impl(id, (long)val); break; // bool case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBoolImpl(id, val != 0); break; case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBoolImpl(id, val != 0); break; // enum case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeEnumImpl(id, (int)val); break; case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_ENUM | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedEnumImpl(id, (int)val); break; // string, bytes, object not allowed here. default: { throw new IllegalArgumentException("Attempt to call write(long, long) with " + getFieldIdString(fieldId)); } } } /** * Write a boolean value for the given fieldId. * *
If the field is not a bool field, an {@link IllegalStateException} will be thrown. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, boolean val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // bool case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeBoolImpl(id, val); break; case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_BOOL | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedBoolImpl(id, val); break; // nothing else allowed default: { throw new IllegalArgumentException("Attempt to call write(long, boolean) with " + getFieldIdString(fieldId)); } } } /** * Write a string value for the given fieldId. * *
If the field is not a string field, an exception will be thrown. * * @param fieldId The field identifier constant from the generated class. * @param val The value. */ public void write(long fieldId, @Nullable String val) { assertNotCompacted(); final int id = (int)fieldId; switch ((int)((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) { // string case (int)((FIELD_TYPE_STRING | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT): writeStringImpl(id, val); break; case (int)((FIELD_TYPE_STRING | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT): case (int)((FIELD_TYPE_STRING | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT): writeRepeatedStringImpl(id, val); break; // nothing else allowed default: { throw new IllegalArgumentException("Attempt to call write(long, String) with " + getFieldIdString(fieldId)); } } } /** * Write a byte[] value for the given fieldId. * *
If the field is not a bytes or object field, an exception will be thrown.
*
* @param fieldId The field identifier constant from the generated class.
* @param val The value.
*/
public void write(long fieldId, @Nullable byte[] val) {
assertNotCompacted();
final int id = (int)fieldId;
switch ((int) ((fieldId & (FIELD_TYPE_MASK | FIELD_COUNT_MASK)) >> FIELD_TYPE_SHIFT)) {
// bytes
case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeBytesImpl(id, val);
break;
case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
case (int) ((FIELD_TYPE_BYTES | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedBytesImpl(id, val);
break;
// Object
case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_SINGLE) >> FIELD_TYPE_SHIFT):
writeObjectImpl(id, val);
break;
case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED) >> FIELD_TYPE_SHIFT):
case (int) ((FIELD_TYPE_MESSAGE | FIELD_COUNT_PACKED) >> FIELD_TYPE_SHIFT):
writeRepeatedObjectImpl(id, val);
break;
// nothing else allowed
default: {
throw new IllegalArgumentException("Attempt to call write(long, byte[]) with "
+ getFieldIdString(fieldId));
}
}
}
/**
* Start a sub object.
*
* @param fieldId The field identifier constant from the generated class.
* @return The token to call {@link #end(long)} with.
*/
public long start(long fieldId) {
assertNotCompacted();
final int id = (int)fieldId;
if ((fieldId & FIELD_TYPE_MASK) == FIELD_TYPE_MESSAGE) {
final long count = fieldId & FIELD_COUNT_MASK;
if (count == FIELD_COUNT_SINGLE) {
return startObjectImpl(id, false);
} else if (count == FIELD_COUNT_REPEATED || count == FIELD_COUNT_PACKED) {
return startObjectImpl(id, true);
}
}
throw new IllegalArgumentException("Attempt to call start(long) with "
+ getFieldIdString(fieldId));
}
/**
* End the object started by start() that returned token.
*
* @param token The token returned from {@link #start(long)}
*/
public void end(long token) {
endObjectImpl(token, getRepeatedFromToken(token));
}
//
// proto3 type: double
// java type: double
// encoding: fixed64
// wire type: WIRE_TYPE_FIXED64
//
/**
* Write a single proto "double" type field value.
*
* @deprecated Use {@link #write(long, double)} instead.
* @hide
*/
@Deprecated
public void writeDouble(long fieldId, double val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_DOUBLE);
writeDoubleImpl(id, val);
}
private void writeDoubleImpl(int id, double val) {
if (val != 0) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
}
}
/**
* Write a single repeated proto "double" type field value.
*
* @deprecated Use {@link #write(long, double)} instead.
* @hide
*/
@Deprecated
public void writeRepeatedDouble(long fieldId, double val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_DOUBLE);
writeRepeatedDoubleImpl(id, val);
}
private void writeRepeatedDoubleImpl(int id, double val) {
writeTag(id, WIRE_TYPE_FIXED64);
mBuffer.writeRawFixed64(Double.doubleToLongBits(val));
}
/**
* Write a list of packed proto "double" type field values.
*
* @deprecated Use {@link #write(long, double)} instead.
* @hide
*/
@Deprecated
public void writePackedDouble(long fieldId, @Nullable 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 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".
*
* @deprecated Use {@link #write(long, int)} instead.
* @hide
*/
@Deprecated
public void writeInt32(long fieldId, int val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT32);
writeInt32Impl(id, val);
}
private void writeInt32Impl(int id, int val) {
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".
*
* @deprecated Use {@link #write(long, int)} instead.
* @hide
*/
@Deprecated
public void writeRepeatedInt32(long fieldId, int val) {
assertNotCompacted();
final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT32);
writeRepeatedInt32Impl(id, val);
}
private void writeRepeatedInt32Impl(int id, int val) {
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".
*
* @deprecated Use {@link #write(long, int)} instead.
* @hide
*/
@Deprecated
public void writePackedInt32(long fieldId, @Nullable 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 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 {@link 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_MESSAGE) {
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_MESSAGE) {
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_MESSAGE) {
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;
}
/**
* Return how many bytes an encoded field tag will require.
*/
private static int getTagSize(int id) {
return EncodedBuffer.getRawVarint32Size(id << FIELD_ID_SHIFT);
}
/**
* Write an individual field tag by hand.
*
* See Protobuf
* Encoding for details on the structure of how tags and data are written.
*/
public void writeTag(int id, @WireType 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 @NonNull 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());
}
}
}
/**
* Write remaining data to the output stream. If there is no output stream,
* this function does nothing. Any currently open objects (i.e. ones that
* have not had {@link #end(long)} called for them will not be written). Whether this
* writes objects that are closed if there are remaining open objects is
* undefined (current implementation does not write it, future ones will).
* For now, can either call {@link #getBytes()} or {@link #flush()}, but not both.
*/
public void flush() {
if (mStream == null) {
return;
}
if (mDepth != 0) {
// TODO: The compacting code isn't ready yet to compact unless we're done.
// TODO: Fix that.
return;
}
if (mCompacted) {
// If we're compacted, we already wrote it finished.
return;
}
compactIfNecessary();
final byte[] data = mBuffer.getBytes(mBuffer.getReadableSize());
try {
mStream.write(data);
mStream.flush();
} catch (IOException ex) {
throw new RuntimeException("Error flushing proto to stream", ex);
}
}
/**
* 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(@NonNull String tag) {
Log.d(tag, mBuffer.getDebugString());
mBuffer.dumpBuffers(tag);
}
}