/*
 * 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.
 *
 * <p>
 * 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.
 *
 * <p>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.
 *
 * <p>The ID codes have type information embedded into them, so if you call
 * the incorrect function you will get an {@link IllegalArgumentException}.
 *
 * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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}.
     *
     * <p>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}.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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<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.
     *
     * @deprecated Use {@link #write(long, float)} instead.
     * @hide
     */
    @Deprecated
    public void writeFloat(long fieldId, float val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FLOAT);

        writeFloatImpl(id, val);
    }

    private void writeFloatImpl(int id, float val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_FIXED32);
            mBuffer.writeRawFixed32(Float.floatToIntBits(val));
        }
    }

    /**
     * Write a single repeated proto "float" type field value.
     *
     * @deprecated Use {@link #write(long, float)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedFloat(long fieldId, float val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FLOAT);

        writeRepeatedFloatImpl(id, val);
    }

    private void writeRepeatedFloatImpl(int id, float val) {
        writeTag(id, WIRE_TYPE_FIXED32);
        mBuffer.writeRawFixed32(Float.floatToIntBits(val));
    }

    /**
     * Write a list of packed proto "float" type field value.
     *
     * @deprecated Use {@link #write(long, float)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedFloat(long fieldId, @Nullable 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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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<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.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_INT64);

        writeInt64Impl(id, val);
    }

    private void writeInt64Impl(int id, long val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            mBuffer.writeRawVarint64(val);
        }
    }

    /**
     * Write a single repeated proto "int64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_INT64);

        writeRepeatedInt64Impl(id, val);
    }

    private void writeRepeatedInt64Impl(int id, long val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawVarint64(val);
    }

    /**
     * Write a list of packed proto "int64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedInt64(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeUInt32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT32);

        writeUInt32Impl(id, val);
    }

    private void writeUInt32Impl(int id, int val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            mBuffer.writeRawVarint32(val);
        }
    }

    /**
     * Write a single repeated proto "uint32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedUInt32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT32);

        writeRepeatedUInt32Impl(id, val);
    }

    private void writeRepeatedUInt32Impl(int id, int val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawVarint32(val);
    }

    /**
     * Write a list of packed proto "uint32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedUInt32(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeUInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_UINT64);

        writeUInt64Impl(id, val);
    }

    private void writeUInt64Impl(int id, long val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            mBuffer.writeRawVarint64(val);
        }
    }

    /**
     * Write a single proto "uint64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedUInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_UINT64);

        writeRepeatedUInt64Impl(id, val);
    }

    private void writeRepeatedUInt64Impl(int id, long val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawVarint64(val);
    }

    /**
     * Write a single proto "uint64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedUInt64(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeSInt32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT32);

        writeSInt32Impl(id, val);
    }

    private void writeSInt32Impl(int id, int val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            mBuffer.writeRawZigZag32(val);
        }
    }

    /**
     * Write a single repeated proto "sint32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedSInt32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT32);

        writeRepeatedSInt32Impl(id, val);
    }

    private void writeRepeatedSInt32Impl(int id, int val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawZigZag32(val);
    }

    /**
     * Write a list of packed proto "sint32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedSInt32(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeSInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SINT64);

        writeSInt64Impl(id, val);
    }

    private void writeSInt64Impl(int id, long val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            mBuffer.writeRawZigZag64(val);
        }
    }

    /**
     * Write a single repeated proto "sint64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedSInt64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SINT64);

        writeRepeatedSInt64Impl(id, val);
    }

    private void writeRepeatedSInt64Impl(int id, long val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawZigZag64(val);
    }

    /**
     * Write a list of packed proto "sint64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedSInt64(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeFixed32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED32);

        writeFixed32Impl(id, val);
    }

    private void writeFixed32Impl(int id, int val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_FIXED32);
            mBuffer.writeRawFixed32(val);
        }
    }

    /**
     * Write a single repeated proto "fixed32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedFixed32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED32);

        writeRepeatedFixed32Impl(id, val);
    }

    private void writeRepeatedFixed32Impl(int id, int val) {
        writeTag(id, WIRE_TYPE_FIXED32);
        mBuffer.writeRawFixed32(val);
    }

    /**
     * Write a list of packed proto "fixed32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedFixed32(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeFixed64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_FIXED64);

        writeFixed64Impl(id, val);
    }

    private void writeFixed64Impl(int id, long val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_FIXED64);
            mBuffer.writeRawFixed64(val);
        }
    }

    /**
     * Write a single repeated proto "fixed64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedFixed64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_FIXED64);

        writeRepeatedFixed64Impl(id, val);
    }

    private void writeRepeatedFixed64Impl(int id, long val) {
        writeTag(id, WIRE_TYPE_FIXED64);
        mBuffer.writeRawFixed64(val);
    }

    /**
     * Write a list of packed proto "fixed64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedFixed64(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeSFixed32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED32);

        writeSFixed32Impl(id, val);
    }

    private void writeSFixed32Impl(int id, int val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_FIXED32);
            mBuffer.writeRawFixed32(val);
        }
    }

    /**
     * Write a single repeated proto "sfixed32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedSFixed32(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED32);

        writeRepeatedSFixed32Impl(id, val);
    }

    private void writeRepeatedSFixed32Impl(int id, int val) {
        writeTag(id, WIRE_TYPE_FIXED32);
        mBuffer.writeRawFixed32(val);
    }

    /**
     * Write a list of packed proto "sfixed32" type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedSFixed32(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeSFixed64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_SFIXED64);

        writeSFixed64Impl(id, val);
    }

    private void writeSFixed64Impl(int id, long val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_FIXED64);
            mBuffer.writeRawFixed64(val);
        }
    }

    /**
     * Write a single repeated proto "sfixed64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedSFixed64(long fieldId, long val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_SFIXED64);

        writeRepeatedSFixed64Impl(id, val);
    }

    private void writeRepeatedSFixed64Impl(int id, long val) {
        writeTag(id, WIRE_TYPE_FIXED64);
        mBuffer.writeRawFixed64(val);
    }

    /**
     * Write a list of packed proto "sfixed64" type field value.
     *
     * @deprecated Use {@link #write(long, long)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedSFixed64(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, boolean)} instead.
     * @hide
     */
    @Deprecated
    public void writeBool(long fieldId, boolean val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BOOL);

        writeBoolImpl(id, val);
    }

    private void writeBoolImpl(int id, boolean val) {
        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.
     *
     * @deprecated Use {@link #write(long, boolean)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedBool(long fieldId, boolean val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BOOL);

        writeRepeatedBoolImpl(id, val);
    }

    private void writeRepeatedBoolImpl(int id, boolean val) {
        writeTag(id, WIRE_TYPE_VARINT);
        mBuffer.writeRawByte((byte)(val ? 1 : 0));
    }

    /**
     * Write a list of packed proto "bool" type field value.
     *
     * @deprecated Use {@link #write(long, boolean)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedBool(long fieldId, @Nullable 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.
     *
     * @deprecated Use {@link #write(long, String)} instead.
     * @hide
     */
    @Deprecated
    public void writeString(long fieldId, @Nullable String val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_STRING);

        writeStringImpl(id, val);
    }

    private void writeStringImpl(int id, String val) {
        if (val != null && val.length() > 0) {
            writeUtf8String(id, val);
        }
    }

    /**
     * Write a single repeated proto "string" type field value.
     *
     * @deprecated Use {@link #write(long, String)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedString(long fieldId, @Nullable String val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_STRING);

        writeRepeatedStringImpl(id, val);
    }

    private void writeRepeatedStringImpl(int id, String val) {
        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.
     *
     * @deprecated Use {@link #write(long, byte[])} instead.
     * @hide
     */
    @Deprecated
    public void writeBytes(long fieldId, @Nullable byte[] val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_BYTES);

        writeBytesImpl(id, val);
    }

    private void writeBytesImpl(int id, byte[] val) {
        if (val != null && val.length > 0) {
            writeKnownLengthHeader(id, val.length);
            mBuffer.writeRawBuffer(val);
        }
    }

    /**
     * Write a single repeated proto "bytes" type field value.
     *
     * @deprecated Use {@link #write(long, byte[])} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedBytes(long fieldId, @Nullable byte[] val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_BYTES);

        writeRepeatedBytesImpl(id, val);
    }

    private void writeRepeatedBytesImpl(int id, byte[] val) {
        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.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeEnum(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_ENUM);

        writeEnumImpl(id, val);
    }

    private void writeEnumImpl(int id, int val) {
        if (val != 0) {
            writeTag(id, WIRE_TYPE_VARINT);
            writeUnsignedVarintFromSignedInt(val);
        }
    }

    /**
     * Write a single repeated proto enum type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedEnum(long fieldId, int val) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_ENUM);

        writeRepeatedEnumImpl(id, val);
    }

    private void writeRepeatedEnumImpl(int id, int val) {
        writeTag(id, WIRE_TYPE_VARINT);
        writeUnsignedVarintFromSignedInt(val);
    }

    /**
     * Write a list of packed proto enum type field value.
     *
     * @deprecated Use {@link #write(long, int)} instead.
     * @hide
     */
    @Deprecated
    public void writePackedEnum(long fieldId, @Nullable 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]);
            }
        }
    }


    /**
     * Start a child object.
     *
     * Returns a token which should be passed to endObject.  Calls to endObject must be
     * nested properly.
     *
     * @deprecated Use {@link #start(long)} instead.
     * @hide
     */
    @Deprecated
    public long startObject(long fieldId) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);

        return startObjectImpl(id, false);
    }

    /**
     * End a child object. Pass in the token from the correspoinding startObject call.
     *
     * @deprecated Use {@link #end(long)} instead.
     * @hide
     */
    @Deprecated
    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.
     *
     * @deprecated Use {@link #start(long)} instead.
     * @hide
     */
    @Deprecated
    public long startRepeatedObject(long fieldId) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);

        return startObjectImpl(id, true);
    }

    /**
     * End a child object. Pass in the token from the correspoinding startRepeatedObject call.
     *
     * @deprecated Use {@link #end(long)} instead.
     * @hide
     */
    @Deprecated
    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 = getOffsetFromToken(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));
        }
    }

    /**
     * Write an object that has already been flattened.
     *
     * @deprecated Use {@link #write(long, byte[])} instead.
     * @hide
     */
    @Deprecated
    public void writeObject(long fieldId, @Nullable byte[] value) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_SINGLE | FIELD_TYPE_MESSAGE);

        writeObjectImpl(id, value);
    }

    void writeObjectImpl(int id, byte[] value) {
        if (value != null && value.length != 0) {
            writeKnownLengthHeader(id, value.length);
            mBuffer.writeRawBuffer(value);
        }
    }

    /**
     * Write an object that has already been flattened.
     *
     * @deprecated Use {@link #write(long, byte[])} instead.
     * @hide
     */
    @Deprecated
    public void writeRepeatedObject(long fieldId, @Nullable byte[] value) {
        assertNotCompacted();
        final int id = checkFieldId(fieldId, FIELD_COUNT_REPEATED | FIELD_TYPE_MESSAGE);

        writeRepeatedObjectImpl(id, value);
    }

    void writeRepeatedObjectImpl(int id, byte[] value) {
        writeKnownLengthHeader(id, value == null ? 0 : value.length);
        mBuffer.writeRawBuffer(value);
    }

    //
    // 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 provided is of the type and count from expectedType.
     *
     * <p>The type must match exactly to pass this check.
     *
     * <p>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 <a href="https://developers.google.com/protocol-buffers/docs/encoding">Protobuf
     * Encoding</a> 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.
     *
     * <p>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);
    }
}
