content: // Protocol Buffers - Google's data interchange format
  // Copyright 2008 Google Inc.  All rights reserved.
  // https://developers.google.com/protocol-buffers/
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions are
  // met:
  //
  //     * Redistributions of source code must retain the above copyright
  // notice, this list of conditions and the following disclaimer.
  //     * Redistributions in binary form must reproduce the above
  // copyright notice, this list of conditions and the following disclaimer
  // in the documentation and/or other materials provided with the
  // distribution.
  //     * Neither the name of Google Inc. nor the names of its
  // contributors may be used to endorse or promote products derived from
  // this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  
  package com.google.protobuf;
  
  import static com.google.protobuf.Internal.checkNotNull;
  import static com.google.protobuf.WireFormat.FIXED32_SIZE;
  import static com.google.protobuf.WireFormat.FIXED64_SIZE;
  import static com.google.protobuf.WireFormat.MAX_VARINT32_SIZE;
  import static com.google.protobuf.WireFormat.MAX_VARINT64_SIZE;
  import static com.google.protobuf.WireFormat.MESSAGE_SET_ITEM;
  import static com.google.protobuf.WireFormat.MESSAGE_SET_MESSAGE;
  import static com.google.protobuf.WireFormat.MESSAGE_SET_TYPE_ID;
  import static com.google.protobuf.WireFormat.WIRETYPE_END_GROUP;
  import static com.google.protobuf.WireFormat.WIRETYPE_FIXED32;
  import static com.google.protobuf.WireFormat.WIRETYPE_FIXED64;
  import static com.google.protobuf.WireFormat.WIRETYPE_LENGTH_DELIMITED;
  import static com.google.protobuf.WireFormat.WIRETYPE_START_GROUP;
  import static com.google.protobuf.WireFormat.WIRETYPE_VARINT;
  
  import java.io.IOException;
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.ArrayDeque;
  import java.util.List;
  import java.util.Map;
  import java.util.Queue;
  
  /**
   * A protobuf writer that serializes messages in their binary form. Messages are serialized in
   * reverse in order to avoid calculating the serialized size of each nested message. Since the
   * message size is not known in advance, the writer employs a strategy of chunking and buffer
   * chaining. Buffers are allocated as-needed by a provided {@link BufferAllocator}. Once writing is
   * finished, the application can access the buffers in forward-writing order by calling {@link
   * #complete()}.
   *
   * <p>Once {@link #complete()} has been called, the writer can not be reused for additional writes.
   * The {@link #getTotalBytesWritten()} will continue to reflect the total of the write and will not
   * be reset.
   */
  @ExperimentalApi
  abstract class BinaryWriter extends ByteOutput implements Writer {
    public static final int DEFAULT_CHUNK_SIZE = 4096;
  
    private final BufferAllocator alloc;
    private final int chunkSize;
  
    final ArrayDeque<AllocatedBuffer> buffers = new ArrayDeque<AllocatedBuffer>(4);
    int totalDoneBytes;
  
    /**
     * Creates a new {@link BinaryWriter} that will allocate heap buffers of {@link
     * #DEFAULT_CHUNK_SIZE} as necessary.
     */
    public static BinaryWriter newHeapInstance(BufferAllocator alloc) {
      return newHeapInstance(alloc, DEFAULT_CHUNK_SIZE);
    }
  
    /**
     * Creates a new {@link BinaryWriter} that will allocate heap buffers of {@code chunkSize} as
     * necessary.
     */
    public static BinaryWriter newHeapInstance(BufferAllocator alloc, int chunkSize) {
      return isUnsafeHeapSupported()
          ? newUnsafeHeapInstance(alloc, chunkSize)
          : newSafeHeapInstance(alloc, chunkSize);
    }
  
    /**
     * Creates a new {@link BinaryWriter} that will allocate direct (i.e. non-heap) buffers of {@link
     * #DEFAULT_CHUNK_SIZE} as necessary.
     */
    public static BinaryWriter newDirectInstance(BufferAllocator alloc) {
      return newDirectInstance(alloc, DEFAULT_CHUNK_SIZE);
    }
  
    /**
     * Creates a new {@link BinaryWriter} that will allocate direct (i.e. non-heap) buffers of {@code
     * chunkSize} as necessary.
     */
    public static BinaryWriter newDirectInstance(BufferAllocator alloc, int chunkSize) {
      return isUnsafeDirectSupported()
          ? newUnsafeDirectInstance(alloc, chunkSize)
          : newSafeDirectInstance(alloc, chunkSize);
    }
  
    static boolean isUnsafeHeapSupported() {
      return UnsafeHeapWriter.isSupported();
    }
  
    static boolean isUnsafeDirectSupported() {
      return UnsafeDirectWriter.isSupported();
    }
  
    static BinaryWriter newSafeHeapInstance(BufferAllocator alloc, int chunkSize) {
      return new SafeHeapWriter(alloc, chunkSize);
    }
  
    static BinaryWriter newUnsafeHeapInstance(BufferAllocator alloc, int chunkSize) {
      if (!isUnsafeHeapSupported()) {
        throw new UnsupportedOperationException("Unsafe operations not supported");
      }
      return new UnsafeHeapWriter(alloc, chunkSize);
    }
  
    static BinaryWriter newSafeDirectInstance(BufferAllocator alloc, int chunkSize) {
      return new SafeDirectWriter(alloc, chunkSize);
    }
  
    static BinaryWriter newUnsafeDirectInstance(BufferAllocator alloc, int chunkSize) {
      if (!isUnsafeDirectSupported()) {
        throw new UnsupportedOperationException("Unsafe operations not supported");
      }
      return new UnsafeDirectWriter(alloc, chunkSize);
    }
  
    /** Only allow subclassing for inner classes. */
    private BinaryWriter(BufferAllocator alloc, int chunkSize) {
      if (chunkSize <= 0) {
        throw new IllegalArgumentException("chunkSize must be > 0");
      }
      this.alloc = checkNotNull(alloc, "alloc");
      this.chunkSize = chunkSize;
    }
  
    @Override
    public final FieldOrder fieldOrder() {
      return FieldOrder.DESCENDING;
    }
  
    /**
     * Completes the write operation and returns a queue of {@link AllocatedBuffer} objects in
     * forward-writing order. This method should only be called once.
     *
     * <p>After calling this method, the writer can not be reused. Create a new writer for future
     * writes.
     */
    public final Queue<AllocatedBuffer> complete() {
      finishCurrentBuffer();
      return buffers;
    }
  
    @Override
    public final void writeSFixed32(int fieldNumber, int value) throws IOException {
      writeFixed32(fieldNumber, value);
    }
  
    @Override
    public final void writeInt64(int fieldNumber, long value) throws IOException {
      writeUInt64(fieldNumber, value);
    }
  
    @Override
    public final void writeSFixed64(int fieldNumber, long value) throws IOException {
      writeFixed64(fieldNumber, value);
    }
  
    @Override
    public final void writeFloat(int fieldNumber, float value) throws IOException {
      writeFixed32(fieldNumber, Float.floatToRawIntBits(value));
    }
  
    @Override
    public final void writeDouble(int fieldNumber, double value) throws IOException {
      writeFixed64(fieldNumber, Double.doubleToRawLongBits(value));
    }
  
    @Override
    public final void writeEnum(int fieldNumber, int value) throws IOException {
      writeInt32(fieldNumber, value);
    }
  
    @Override
    public final void writeInt32List(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (list instanceof IntArrayList) {
        writeInt32List_Internal(fieldNumber, (IntArrayList) list, packed);
      } else {
        writeInt32List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeInt32(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeInt32(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeInt32(list.getInt(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeInt32(fieldNumber, list.getInt(i));
        }
      }
    }
  
    @Override
    public final void writeFixed32List(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (list instanceof IntArrayList) {
        writeFixed32List_Internal(fieldNumber, (IntArrayList) list, packed);
      } else {
        writeFixed32List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeFixed32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeFixed32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(list.getInt(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(fieldNumber, list.getInt(i));
        }
      }
    }
  
    @Override
    public final void writeInt64List(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      writeUInt64List(fieldNumber, list, packed);
    }
  
    @Override
    public final void writeUInt64List(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (list instanceof LongArrayList) {
        writeUInt64List_Internal(fieldNumber, (LongArrayList) list, packed);
      } else {
        writeUInt64List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeUInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeVarint64(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeUInt64(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeUInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeVarint64(list.getLong(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeUInt64(fieldNumber, list.getLong(i));
        }
      }
    }
  
    @Override
    public final void writeFixed64List(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (list instanceof LongArrayList) {
        writeFixed64List_Internal(fieldNumber, (LongArrayList) list, packed);
      } else {
        writeFixed64List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeFixed64List_Internal(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeFixed64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(list.getLong(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(fieldNumber, list.getLong(i));
        }
      }
    }
  
    @Override
    public final void writeFloatList(int fieldNumber, List<Float> list, boolean packed)
        throws IOException {
      if (list instanceof FloatArrayList) {
        writeFloatList_Internal(fieldNumber, (FloatArrayList) list, packed);
      } else {
        writeFloatList_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeFloatList_Internal(int fieldNumber, List<Float> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(Float.floatToRawIntBits(list.get(i)));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFloat(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeFloatList_Internal(int fieldNumber, FloatArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed32(Float.floatToRawIntBits(list.getFloat(i)));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFloat(fieldNumber, list.getFloat(i));
        }
      }
    }
  
    @Override
    public final void writeDoubleList(int fieldNumber, List<Double> list, boolean packed)
        throws IOException {
      if (list instanceof DoubleArrayList) {
        writeDoubleList_Internal(fieldNumber, (DoubleArrayList) list, packed);
      } else {
        writeDoubleList_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeDoubleList_Internal(int fieldNumber, List<Double> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(Double.doubleToRawLongBits(list.get(i)));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeDouble(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeDoubleList_Internal(int fieldNumber, DoubleArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * FIXED64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeFixed64(Double.doubleToRawLongBits(list.getDouble(i)));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeDouble(fieldNumber, list.getDouble(i));
        }
      }
    }
  
    @Override
    public final void writeEnumList(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      writeInt32List(fieldNumber, list, packed);
    }
  
    @Override
    public final void writeBoolList(int fieldNumber, List<Boolean> list, boolean packed)
        throws IOException {
      if (list instanceof BooleanArrayList) {
        writeBoolList_Internal(fieldNumber, (BooleanArrayList) list, packed);
      } else {
        writeBoolList_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeBoolList_Internal(int fieldNumber, List<Boolean> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + list.size());
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeBool(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeBool(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeBoolList_Internal(int fieldNumber, BooleanArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + list.size());
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeBool(list.getBoolean(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeBool(fieldNumber, list.getBoolean(i));
        }
      }
    }
  
    @Override
    public final void writeStringList(int fieldNumber, List<String> list) throws IOException {
      if (list instanceof LazyStringList) {
        final LazyStringList lazyList = (LazyStringList) list;
        for (int i = list.size() - 1; i >= 0; i--) {
          writeLazyString(fieldNumber, lazyList.getRaw(i));
        }
      } else {
        for (int i = list.size() - 1; i >= 0; i--) {
          writeString(fieldNumber, list.get(i));
        }
      }
    }
  
    private void writeLazyString(int fieldNumber, Object value) throws IOException {
      if (value instanceof String) {
        writeString(fieldNumber, (String) value);
      } else {
        writeBytes(fieldNumber, (ByteString) value);
      }
    }
  
    @Override
    public final void writeBytesList(int fieldNumber, List<ByteString> list) throws IOException {
      for (int i = list.size() - 1; i >= 0; i--) {
        writeBytes(fieldNumber, list.get(i));
      }
    }
  
    @Override
    public final void writeUInt32List(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (list instanceof IntArrayList) {
        writeUInt32List_Internal(fieldNumber, (IntArrayList) list, packed);
      } else {
        writeUInt32List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeUInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeVarint32(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeUInt32(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeUInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeVarint32(list.getInt(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeUInt32(fieldNumber, list.getInt(i));
        }
      }
    }
  
    @Override
    public final void writeSFixed32List(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      writeFixed32List(fieldNumber, list, packed);
    }
  
    @Override
    public final void writeSFixed64List(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      writeFixed64List(fieldNumber, list, packed);
    }
  
    @Override
    public final void writeSInt32List(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (list instanceof IntArrayList) {
        writeSInt32List_Internal(fieldNumber, (IntArrayList) list, packed);
      } else {
        writeSInt32List_Internal(fieldNumber, list, packed);
      }
    }
  
    private final void writeSInt32List_Internal(int fieldNumber, List<Integer> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt32(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt32(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeSInt32List_Internal(int fieldNumber, IntArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT32_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt32(list.getInt(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt32(fieldNumber, list.getInt(i));
        }
      }
    }
  
    @Override
    public final void writeSInt64List(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (list instanceof LongArrayList) {
        writeSInt64List_Internal(fieldNumber, (LongArrayList) list, packed);
      } else {
        writeSInt64List_Internal(fieldNumber, list, packed);
      }
    }
  
    private static final int MAP_KEY_NUMBER = 1;
    private static final int MAP_VALUE_NUMBER = 2;
  
    @Override
    public <K, V> void writeMap(int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map)
        throws IOException {
      // TODO(liujisi): Reverse write those entries.
      for (Map.Entry<K, V> entry : map.entrySet()) {
        int prevBytes = getTotalBytesWritten();
        writeMapEntryField(this, MAP_VALUE_NUMBER, metadata.valueType, entry.getValue());
        writeMapEntryField(this, MAP_KEY_NUMBER, metadata.keyType, entry.getKey());
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
    }
  
    static final void writeMapEntryField(
        Writer writer, int fieldNumber, WireFormat.FieldType fieldType, Object object)
        throws IOException {
      switch (fieldType) {
        case BOOL:
          writer.writeBool(fieldNumber, (Boolean) object);
          break;
        case FIXED32:
          writer.writeFixed32(fieldNumber, (Integer) object);
          break;
        case FIXED64:
          writer.writeFixed64(fieldNumber, (Long) object);
          break;
        case INT32:
          writer.writeInt32(fieldNumber, (Integer) object);
          break;
        case INT64:
          writer.writeInt64(fieldNumber, (Long) object);
          break;
        case SFIXED32:
          writer.writeSFixed32(fieldNumber, (Integer) object);
          break;
        case SFIXED64:
          writer.writeSFixed64(fieldNumber, (Long) object);
          break;
        case SINT32:
          writer.writeSInt32(fieldNumber, (Integer) object);
          break;
        case SINT64:
          writer.writeSInt64(fieldNumber, (Long) object);
          break;
        case STRING:
          writer.writeString(fieldNumber, (String) object);
          break;
        case UINT32:
          writer.writeUInt32(fieldNumber, (Integer) object);
          break;
        case UINT64:
          writer.writeUInt64(fieldNumber, (Long) object);
          break;
        case FLOAT:
          writer.writeFloat(fieldNumber, (Float) object);
          break;
        case DOUBLE:
          writer.writeDouble(fieldNumber, (Double) object);
          break;
        case MESSAGE:
          writer.writeMessage(fieldNumber, object);
          break;
        case BYTES:
          writer.writeBytes(fieldNumber, (ByteString) object);
          break;
        case ENUM:
          if (object instanceof Internal.EnumLite) {
            writer.writeEnum(fieldNumber, ((Internal.EnumLite) object).getNumber());
          } else if (object instanceof Integer) {
            writer.writeEnum(fieldNumber, (Integer) object);
          } else {
            throw new IllegalArgumentException("Unexpected type for enum in map.");
          }
          break;
        default:
          throw new IllegalArgumentException("Unsupported map value type for: " + fieldType);
      }
    }
  
    private final void writeSInt64List_Internal(int fieldNumber, List<Long> list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt64(list.get(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt64(fieldNumber, list.get(i));
        }
      }
    }
  
    private final void writeSInt64List_Internal(int fieldNumber, LongArrayList list, boolean packed)
        throws IOException {
      if (packed) {
        requireSpace((MAX_VARINT32_SIZE * 2) + (list.size() * MAX_VARINT64_SIZE));
        int prevBytes = getTotalBytesWritten();
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt64(list.getLong(i));
        }
        int length = getTotalBytesWritten() - prevBytes;
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      } else {
        for (int i = list.size() - 1; i >= 0; --i) {
          writeSInt64(fieldNumber, list.getLong(i));
        }
      }
    }
  
    @Override
    public final void writeMessageList(int fieldNumber, List<?> list) throws IOException {
      for (int i = list.size() - 1; i >= 0; i--) {
        writeMessage(fieldNumber, list.get(i));
      }
    }
  
    @Override
    public final void writeMessageList(int fieldNumber, List<?> list, Schema schema)
        throws IOException {
      for (int i = list.size() - 1; i >= 0; i--) {
        writeMessage(fieldNumber, list.get(i), schema);
      }
    }
  
    @Override
    public final void writeGroupList(int fieldNumber, List<?> list) throws IOException {
      for (int i = list.size() - 1; i >= 0; i--) {
        writeGroup(fieldNumber, list.get(i));
      }
    }
  
    @Override
    public final void writeGroupList(int fieldNumber, List<?> list, Schema schema)
        throws IOException {
      for (int i = list.size() - 1; i >= 0; i--) {
        writeGroup(fieldNumber, list.get(i), schema);
      }
    }
  
    @Override
    public final void writeMessageSetItem(int fieldNumber, Object value) throws IOException {
      writeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP);
      if (value instanceof ByteString) {
        writeBytes(MESSAGE_SET_MESSAGE, (ByteString) value);
      } else {
        writeMessage(MESSAGE_SET_MESSAGE, value);
      }
      writeUInt32(MESSAGE_SET_TYPE_ID, fieldNumber);
      writeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP);
    }
  
    final AllocatedBuffer newHeapBuffer() {
      return alloc.allocateHeapBuffer(chunkSize);
    }
  
    final AllocatedBuffer newHeapBuffer(int capacity) {
      return alloc.allocateHeapBuffer(Math.max(capacity, chunkSize));
    }
  
    final AllocatedBuffer newDirectBuffer() {
      return alloc.allocateDirectBuffer(chunkSize);
    }
  
    final AllocatedBuffer newDirectBuffer(int capacity) {
      return alloc.allocateDirectBuffer(Math.max(capacity, chunkSize));
    }
  
    /**
     * Gets the total number of bytes that have been written. This will not be reset by a call to
     * {@link #complete()}.
     */
    public abstract int getTotalBytesWritten();
  
    abstract void requireSpace(int size);
  
    abstract void finishCurrentBuffer();
  
    abstract void writeTag(int fieldNumber, int wireType);
  
    abstract void writeVarint32(int value);
  
    abstract void writeInt32(int value);
  
    abstract void writeSInt32(int value);
  
    abstract void writeFixed32(int value);
  
    abstract void writeVarint64(long value);
  
    abstract void writeSInt64(long value);
  
    abstract void writeFixed64(long value);
  
    abstract void writeBool(boolean value);
  
    abstract void writeString(String in);
  
    /**
     * Not using the version in CodedOutputStream due to the fact that benchmarks have shown a
     * performance improvement when returning a byte (rather than an int).
     */
    private static byte computeUInt64SizeNoTag(long value) {
      // handle two popular special cases up front ...
      if ((value & (~0L << 7)) == 0L) {
        // Byte 1
        return 1;
      }
      if (value < 0L) {
        // Byte 10
        return 10;
      }
      // ... leaving us with 8 remaining, which we can divide and conquer
      byte n = 2;
      if ((value & (~0L << 35)) != 0L) {
        // Byte 6-9
        n += 4; // + (value >>> 63);
        value >>>= 28;
      }
      if ((value & (~0L << 21)) != 0L) {
        // Byte 4-5 or 8-9
        n += 2;
        value >>>= 14;
      }
      if ((value & (~0L << 14)) != 0L) {
        // Byte 3 or 7
        n += 1;
      }
      return n;
    }
  
    /** Writer that uses safe operations on target array. */
    private static final class SafeHeapWriter extends BinaryWriter {
      private AllocatedBuffer allocatedBuffer;
      private byte[] buffer;
      private int offset;
      private int limit;
      private int offsetMinusOne;
      private int limitMinusOne;
      private int pos;
  
      SafeHeapWriter(BufferAllocator alloc, int chunkSize) {
        super(alloc, chunkSize);
        nextBuffer();
      }
  
      @Override
      void finishCurrentBuffer() {
        if (allocatedBuffer != null) {
          totalDoneBytes += bytesWrittenToCurrentBuffer();
          allocatedBuffer.position((pos - allocatedBuffer.arrayOffset()) + 1);
          allocatedBuffer = null;
          pos = 0;
          limitMinusOne = 0;
        }
      }
  
      private void nextBuffer() {
        nextBuffer(newHeapBuffer());
      }
  
      private void nextBuffer(int capacity) {
        nextBuffer(newHeapBuffer(capacity));
      }
  
      private void nextBuffer(AllocatedBuffer allocatedBuffer) {
        if (!allocatedBuffer.hasArray()) {
          throw new RuntimeException("Allocator returned non-heap buffer");
        }
  
        finishCurrentBuffer();
  
        buffers.addFirst(allocatedBuffer);
  
        this.allocatedBuffer = allocatedBuffer;
        this.buffer = allocatedBuffer.array();
        int arrayOffset = allocatedBuffer.arrayOffset();
        this.limit = arrayOffset + allocatedBuffer.limit();
        this.offset = arrayOffset + allocatedBuffer.position();
        this.offsetMinusOne = offset - 1;
        this.limitMinusOne = limit - 1;
        this.pos = limitMinusOne;
      }
  
      @Override
      public int getTotalBytesWritten() {
        return totalDoneBytes + bytesWrittenToCurrentBuffer();
      }
  
      int bytesWrittenToCurrentBuffer() {
        return limitMinusOne - pos;
      }
  
      int spaceLeft() {
        return pos - offsetMinusOne;
      }
  
      @Override
      public void writeUInt32(int fieldNumber, int value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeInt32(int fieldNumber, int value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt32(int fieldNumber, int value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeSInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed32(int fieldNumber, int value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + FIXED32_SIZE);
        writeFixed32(value);
        writeTag(fieldNumber, WIRETYPE_FIXED32);
      }
  
      @Override
      public void writeUInt64(int fieldNumber, long value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeVarint64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt64(int fieldNumber, long value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeSInt64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed64(int fieldNumber, long value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + FIXED64_SIZE);
        writeFixed64(value);
        writeTag(fieldNumber, WIRETYPE_FIXED64);
      }
  
      @Override
      public void writeBool(int fieldNumber, boolean value) throws IOException {
        requireSpace(MAX_VARINT32_SIZE + 1);
        write((byte) (value ? 1 : 0));
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeString(int fieldNumber, String value) throws IOException {
        int prevBytes = getTotalBytesWritten();
        writeString(value);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(2 * MAX_VARINT32_SIZE);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeBytes(int fieldNumber, ByteString value) throws IOException {
        try {
          value.writeToReverse(this);
        } catch (IOException e) {
          // Should never happen since the writer does not throw.
          throw new RuntimeException(e);
        }
  
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value.size());
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value) throws IOException {
        int prevBytes = getTotalBytesWritten();
        Protobuf.getInstance().writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
        int prevBytes = getTotalBytesWritten();
        schema.writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        Protobuf.getInstance().writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        schema.writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeStartGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeEndGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
      }
  
      @Override
      void writeInt32(int value) {
        if (value >= 0) {
          writeVarint32(value);
        } else {
          writeVarint64(value);
        }
      }
  
      @Override
      void writeSInt32(int value) {
        writeVarint32(CodedOutputStream.encodeZigZag32(value));
      }
  
      @Override
      void writeSInt64(long value) {
        writeVarint64(CodedOutputStream.encodeZigZag64(value));
      }
  
      @Override
      void writeBool(boolean value) {
        write((byte) (value ? 1 : 0));
      }
  
      @Override
      void writeTag(int fieldNumber, int wireType) {
        writeVarint32(WireFormat.makeTag(fieldNumber, wireType));
      }
  
      @Override
      void writeVarint32(int value) {
        if ((value & (~0 << 7)) == 0) {
          writeVarint32OneByte(value);
        } else if ((value & (~0 << 14)) == 0) {
          writeVarint32TwoBytes(value);
        } else if ((value & (~0 << 21)) == 0) {
          writeVarint32ThreeBytes(value);
        } else if ((value & (~0 << 28)) == 0) {
          writeVarint32FourBytes(value);
        } else {
          writeVarint32FiveBytes(value);
        }
      }
  
      private void writeVarint32OneByte(int value) {
        buffer[pos--] = (byte) value;
      }
  
      private void writeVarint32TwoBytes(int value) {
        buffer[pos--] = (byte) (value >>> 7);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint32ThreeBytes(int value) {
        buffer[pos--] = (byte) (value >>> 14);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint32FourBytes(int value) {
        buffer[pos--] = (byte) (value >>> 21);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint32FiveBytes(int value) {
        buffer[pos--] = (byte) (value >>> 28);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      @Override
      void writeVarint64(long value) {
        switch (computeUInt64SizeNoTag(value)) {
          case 1:
            writeVarint64OneByte(value);
            break;
          case 2:
            writeVarint64TwoBytes(value);
            break;
          case 3:
            writeVarint64ThreeBytes(value);
            break;
          case 4:
            writeVarint64FourBytes(value);
            break;
          case 5:
            writeVarint64FiveBytes(value);
            break;
          case 6:
            writeVarint64SixBytes(value);
            break;
          case 7:
            writeVarint64SevenBytes(value);
            break;
          case 8:
            writeVarint64EightBytes(value);
            break;
          case 9:
            writeVarint64NineBytes(value);
            break;
          case 10:
            writeVarint64TenBytes(value);
            break;
        }
      }
  
      private void writeVarint64OneByte(long value) {
        buffer[pos--] = (byte) value;
      }
  
      private void writeVarint64TwoBytes(long value) {
        buffer[pos--] = (byte) (value >>> 7);
        buffer[pos--] = (byte) (((int) value & 0x7F) | 0x80);
      }
  
      private void writeVarint64ThreeBytes(long value) {
        buffer[pos--] = (byte) (((int) value) >>> 14);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64FourBytes(long value) {
        buffer[pos--] = (byte) (value >>> 21);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64FiveBytes(long value) {
        buffer[pos--] = (byte) (value >>> 28);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64SixBytes(long value) {
        buffer[pos--] = (byte) (value >>> 35);
        buffer[pos--] = (byte) (((value >>> 28) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64SevenBytes(long value) {
        buffer[pos--] = (byte) (value >>> 42);
        buffer[pos--] = (byte) (((value >>> 35) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 28) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64EightBytes(long value) {
        buffer[pos--] = (byte) (value >>> 49);
        buffer[pos--] = (byte) (((value >>> 42) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 35) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 28) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64NineBytes(long value) {
        buffer[pos--] = (byte) (value >>> 56);
        buffer[pos--] = (byte) (((value >>> 49) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 42) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 35) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 28) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      private void writeVarint64TenBytes(long value) {
        buffer[pos--] = (byte) (value >>> 63);
        buffer[pos--] = (byte) (((value >>> 56) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 49) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 42) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 35) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 28) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 21) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 14) & 0x7F) | 0x80);
        buffer[pos--] = (byte) (((value >>> 7) & 0x7F) | 0x80);
        buffer[pos--] = (byte) ((value & 0x7F) | 0x80);
      }
  
      @Override
      void writeFixed32(int value) {
        buffer[pos--] = (byte) ((value >> 24) & 0xFF);
        buffer[pos--] = (byte) ((value >> 16) & 0xFF);
        buffer[pos--] = (byte) ((value >> 8) & 0xFF);
        buffer[pos--] = (byte) (value & 0xFF);
      }
  
      @Override
      void writeFixed64(long value) {
        buffer[pos--] = (byte) ((int) (value >> 56) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 48) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 40) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 32) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 24) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 16) & 0xFF);
        buffer[pos--] = (byte) ((int) (value >> 8) & 0xFF);
        buffer[pos--] = (byte) ((int) (value) & 0xFF);
      }
  
      @Override
      void writeString(String in) {
        // Request enough space to write the ASCII string.
        requireSpace(in.length());
  
        // We know the buffer is big enough...
        int i = in.length() - 1;
        // Set pos to the start of the ASCII string.
        pos -= i;
        // Designed to take advantage of
        // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
        for (char c; i >= 0 && (c = in.charAt(i)) < 0x80; i--) {
          buffer[pos + i] = (byte) c;
        }
        if (i == -1) {
          // Move pos past the String.
          pos -= 1;
          return;
        }
        pos += i;
        for (char c; i >= 0; i--) {
          c = in.charAt(i);
          if (c < 0x80 && pos > offsetMinusOne) {
            buffer[pos--] = (byte) c;
          } else if (c < 0x800 && pos > offset) { // 11 bits, two UTF-8 bytes
            buffer[pos--] = (byte) (0x80 | (0x3F & c));
            buffer[pos--] = (byte) ((0xF << 6) | (c >>> 6));
          } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c)
              && pos > (offset + 1)) {
            // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
            buffer[pos--] = (byte) (0x80 | (0x3F & c));
            buffer[pos--] = (byte) (0x80 | (0x3F & (c >>> 6)));
            buffer[pos--] = (byte) ((0xF << 5) | (c >>> 12));
          } else if (pos > (offset + 2)) {
            // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
            // four UTF-8 bytes
            char high = 0;
            if (i == 0 || !Character.isSurrogatePair(high = in.charAt(i - 1), c)) {
              throw new Utf8.UnpairedSurrogateException(i - 1, i);
            }
            i--;
            int codePoint = Character.toCodePoint(high, c);
            buffer[pos--] = (byte) (0x80 | (0x3F & codePoint));
            buffer[pos--] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
            buffer[pos--] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
            buffer[pos--] = (byte) ((0xF << 4) | (codePoint >>> 18));
          } else {
            // Buffer is full - allocate a new one and revisit the current character.
            requireSpace(i);
            i++;
          }
        }
      }
  
      @Override
      public void write(byte value) {
        buffer[pos--] = value;
      }
  
      @Override
      public void write(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        System.arraycopy(value, offset, buffer, pos + 1, length);
      }
  
      @Override
      public void writeLazy(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value, offset, length));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        System.arraycopy(value, offset, buffer, pos + 1, length);
      }
  
      @Override
      public void write(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        value.get(buffer, pos + 1, length);
      }
  
      @Override
      public void writeLazy(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
        }
  
        pos -= length;
        value.get(buffer, pos + 1, length);
      }
  
      @Override
      void requireSpace(int size) {
        if (spaceLeft() < size) {
          nextBuffer(size);
        }
      }
    }
  
    /** Writer that uses unsafe operations on a target array. */
    private static final class UnsafeHeapWriter extends BinaryWriter {
      private AllocatedBuffer allocatedBuffer;
      private byte[] buffer;
      private long offset;
      private long limit;
      private long offsetMinusOne;
      private long limitMinusOne;
      private long pos;
  
      UnsafeHeapWriter(BufferAllocator alloc, int chunkSize) {
        super(alloc, chunkSize);
        nextBuffer();
      }
  
      /** Indicates whether the required unsafe operations are supported on this platform. */
      static boolean isSupported() {
        return UnsafeUtil.hasUnsafeArrayOperations();
      }
  
      @Override
      void finishCurrentBuffer() {
        if (allocatedBuffer != null) {
          totalDoneBytes += bytesWrittenToCurrentBuffer();
          allocatedBuffer.position((arrayPos() - allocatedBuffer.arrayOffset()) + 1);
          allocatedBuffer = null;
          pos = 0;
          limitMinusOne = 0;
        }
      }
  
      private int arrayPos() {
        return (int) pos;
      }
  
      private void nextBuffer() {
        nextBuffer(newHeapBuffer());
      }
  
      private void nextBuffer(int capacity) {
        nextBuffer(newHeapBuffer(capacity));
      }
  
      private void nextBuffer(AllocatedBuffer allocatedBuffer) {
        if (!allocatedBuffer.hasArray()) {
          throw new RuntimeException("Allocator returned non-heap buffer");
        }
  
        finishCurrentBuffer();
        buffers.addFirst(allocatedBuffer);
  
        this.allocatedBuffer = allocatedBuffer;
        this.buffer = allocatedBuffer.array();
        int arrayOffset = allocatedBuffer.arrayOffset();
        this.limit = arrayOffset + allocatedBuffer.limit();
        this.offset = arrayOffset + allocatedBuffer.position();
        this.offsetMinusOne = offset - 1;
        this.limitMinusOne = limit - 1;
        this.pos = limitMinusOne;
      }
  
      @Override
      public int getTotalBytesWritten() {
        return totalDoneBytes + bytesWrittenToCurrentBuffer();
      }
  
      int bytesWrittenToCurrentBuffer() {
        return (int) (limitMinusOne - pos);
      }
  
      int spaceLeft() {
        return (int) (pos - offsetMinusOne);
      }
  
      @Override
      public void writeUInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeSInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED32_SIZE);
        writeFixed32(value);
        writeTag(fieldNumber, WIRETYPE_FIXED32);
      }
  
      @Override
      public void writeUInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeVarint64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeSInt64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED64_SIZE);
        writeFixed64(value);
        writeTag(fieldNumber, WIRETYPE_FIXED64);
      }
  
      @Override
      public void writeBool(int fieldNumber, boolean value) {
        requireSpace(MAX_VARINT32_SIZE + 1);
        write((byte) (value ? 1 : 0));
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeString(int fieldNumber, String value) {
        int prevBytes = getTotalBytesWritten();
        writeString(value);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(2 * MAX_VARINT32_SIZE);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeBytes(int fieldNumber, ByteString value) {
        try {
          value.writeToReverse(this);
        } catch (IOException e) {
          // Should never happen since the writer does not throw.
          throw new RuntimeException(e);
        }
  
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value.size());
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value) throws IOException {
        int prevBytes = getTotalBytesWritten();
        Protobuf.getInstance().writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
        int prevBytes = getTotalBytesWritten();
        schema.writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        Protobuf.getInstance().writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        schema.writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeStartGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeEndGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
      }
  
      @Override
      void writeInt32(int value) {
        if (value >= 0) {
          writeVarint32(value);
        } else {
          writeVarint64(value);
        }
      }
  
      @Override
      void writeSInt32(int value) {
        writeVarint32(CodedOutputStream.encodeZigZag32(value));
      }
  
      @Override
      void writeSInt64(long value) {
        writeVarint64(CodedOutputStream.encodeZigZag64(value));
      }
  
      @Override
      void writeBool(boolean value) {
        write((byte) (value ? 1 : 0));
      }
  
      @Override
      void writeTag(int fieldNumber, int wireType) {
        writeVarint32(WireFormat.makeTag(fieldNumber, wireType));
      }
  
      @Override
      void writeVarint32(int value) {
        if ((value & (~0 << 7)) == 0) {
          writeVarint32OneByte(value);
        } else if ((value & (~0 << 14)) == 0) {
          writeVarint32TwoBytes(value);
        } else if ((value & (~0 << 21)) == 0) {
          writeVarint32ThreeBytes(value);
        } else if ((value & (~0 << 28)) == 0) {
          writeVarint32FourBytes(value);
        } else {
          writeVarint32FiveBytes(value);
        }
      }
  
      private void writeVarint32OneByte(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) value);
      }
  
      private void writeVarint32TwoBytes(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 7));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32ThreeBytes(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 14));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32FourBytes(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 21));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32FiveBytes(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 28));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      @Override
      void writeVarint64(long value) {
        switch (computeUInt64SizeNoTag(value)) {
          case 1:
            writeVarint64OneByte(value);
            break;
          case 2:
            writeVarint64TwoBytes(value);
            break;
          case 3:
            writeVarint64ThreeBytes(value);
            break;
          case 4:
            writeVarint64FourBytes(value);
            break;
          case 5:
            writeVarint64FiveBytes(value);
            break;
          case 6:
            writeVarint64SixBytes(value);
            break;
          case 7:
            writeVarint64SevenBytes(value);
            break;
          case 8:
            writeVarint64EightBytes(value);
            break;
          case 9:
            writeVarint64NineBytes(value);
            break;
          case 10:
            writeVarint64TenBytes(value);
            break;
        }
      }
  
      private void writeVarint64OneByte(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) value);
      }
  
      private void writeVarint64TwoBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 7));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((int) value & 0x7F) | 0x80));
      }
  
      private void writeVarint64ThreeBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (((int) value) >>> 14));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64FourBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 21));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64FiveBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 28));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64SixBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 35));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64SevenBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 42));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64EightBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 49));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64NineBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 56));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 49) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64TenBytes(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) (value >>> 63));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 56) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 49) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      @Override
      void writeFixed32(int value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value >> 24) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value >> 16) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((value >> 8) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) (value & 0xFF));
      }
  
      @Override
      void writeFixed64(long value) {
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 56) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 48) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 40) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 32) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 24) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 16) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value >> 8) & 0xFF));
        UnsafeUtil.putByte(buffer, pos--, (byte) ((int) (value) & 0xFF));
      }
  
      @Override
      void writeString(String in) {
        // Request enough space to write the ASCII string.
        requireSpace(in.length());
  
        // We know the buffer is big enough...
        int i = in.length() - 1;
        // Set pos to the start of the ASCII string.
        // pos -= i;
        // Designed to take advantage of
        // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
        for (char c; i >= 0 && (c = in.charAt(i)) < 0x80; i--) {
          UnsafeUtil.putByte(buffer, pos--, (byte) c);
        }
        if (i == -1) {
          // Move pos past the String.
          return;
        }
        for (char c; i >= 0; i--) {
          c = in.charAt(i);
          if (c < 0x80 && pos > offsetMinusOne) {
            UnsafeUtil.putByte(buffer, pos--, (byte) c);
          } else if (c < 0x800 && pos > offset) { // 11 bits, two UTF-8 bytes
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & c)));
            UnsafeUtil.putByte(buffer, pos--, (byte) ((0xF << 6) | (c >>> 6)));
          } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c)
              && pos > offset + 1) {
            // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & c)));
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & (c >>> 6))));
            UnsafeUtil.putByte(buffer, pos--, (byte) ((0xF << 5) | (c >>> 12)));
          } else if (pos > offset + 2) {
            // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
            // four UTF-8 bytes
            final char high;
            if (i == 0 || !Character.isSurrogatePair(high = in.charAt(i - 1), c)) {
              throw new Utf8.UnpairedSurrogateException(i - 1, i);
            }
            i--;
            int codePoint = Character.toCodePoint(high, c);
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & codePoint)));
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
            UnsafeUtil.putByte(buffer, pos--, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
            UnsafeUtil.putByte(buffer, pos--, (byte) ((0xF << 4) | (codePoint >>> 18)));
          } else {
            // Buffer is full - allocate a new one and revisit the current character.
            requireSpace(i);
            i++;
          }
        }
      }
  
      @Override
      public void write(byte value) {
        UnsafeUtil.putByte(buffer, pos--, value);
      }
  
      @Override
      public void write(byte[] value, int offset, int length) {
        if (offset < 0 || offset + length > value.length) {
          throw new ArrayIndexOutOfBoundsException(
              String.format("value.length=%d, offset=%d, length=%d", value.length, offset, length));
        }
        requireSpace(length);
  
        pos -= length;
        System.arraycopy(value, offset, buffer, arrayPos() + 1, length);
      }
  
      @Override
      public void writeLazy(byte[] value, int offset, int length) {
        if (offset < 0 || offset + length > value.length) {
          throw new ArrayIndexOutOfBoundsException(
              String.format("value.length=%d, offset=%d, length=%d", value.length, offset, length));
        }
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value, offset, length));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        System.arraycopy(value, offset, buffer, arrayPos() + 1, length);
      }
  
      @Override
      public void write(ByteBuffer value) {
        int length = value.remaining();
        requireSpace(length);
  
        pos -= length;
        value.get(buffer, arrayPos() + 1, length);
      }
  
      @Override
      public void writeLazy(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
        }
  
        pos -= length;
        value.get(buffer, arrayPos() + 1, length);
      }
  
      @Override
      void requireSpace(int size) {
        if (spaceLeft() < size) {
          nextBuffer(size);
        }
      }
    }
  
    /** Writer that uses safe operations on a target {@link ByteBuffer}. */
    private static final class SafeDirectWriter extends BinaryWriter {
      private ByteBuffer buffer;
      private int limitMinusOne;
      private int pos;
  
      SafeDirectWriter(BufferAllocator alloc, int chunkSize) {
        super(alloc, chunkSize);
        nextBuffer();
      }
  
      private void nextBuffer() {
        nextBuffer(newDirectBuffer());
      }
  
      private void nextBuffer(int capacity) {
        nextBuffer(newDirectBuffer(capacity));
      }
  
      private void nextBuffer(AllocatedBuffer allocatedBuffer) {
        if (!allocatedBuffer.hasNioBuffer()) {
          throw new RuntimeException("Allocated buffer does not have NIO buffer");
        }
        ByteBuffer nioBuffer = allocatedBuffer.nioBuffer();
        if (!nioBuffer.isDirect()) {
          throw new RuntimeException("Allocator returned non-direct buffer");
        }
  
        finishCurrentBuffer();
        buffers.addFirst(allocatedBuffer);
  
        buffer = nioBuffer;
        buffer.limit(buffer.capacity());
        buffer.position(0);
        // Set byte order to little endian for fast writing of fixed 32/64.
        buffer.order(ByteOrder.LITTLE_ENDIAN);
  
        limitMinusOne = buffer.limit() - 1;
        pos = limitMinusOne;
      }
  
      @Override
      public int getTotalBytesWritten() {
        return totalDoneBytes + bytesWrittenToCurrentBuffer();
      }
  
      private int bytesWrittenToCurrentBuffer() {
        return limitMinusOne - pos;
      }
  
      private int spaceLeft() {
        return pos + 1;
      }
  
      @Override
      void finishCurrentBuffer() {
        if (buffer != null) {
          totalDoneBytes += bytesWrittenToCurrentBuffer();
          // Update the indices on the netty buffer.
          buffer.position(pos + 1);
          buffer = null;
          pos = 0;
          limitMinusOne = 0;
        }
      }
  
      @Override
      public void writeUInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeSInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED32_SIZE);
        writeFixed32(value);
        writeTag(fieldNumber, WIRETYPE_FIXED32);
      }
  
      @Override
      public void writeUInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeVarint64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeSInt64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED64_SIZE);
        writeFixed64(value);
        writeTag(fieldNumber, WIRETYPE_FIXED64);
      }
  
      @Override
      public void writeBool(int fieldNumber, boolean value) {
        requireSpace(MAX_VARINT32_SIZE + 1);
        write((byte) (value ? 1 : 0));
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeString(int fieldNumber, String value) {
        int prevBytes = getTotalBytesWritten();
        writeString(value);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(2 * MAX_VARINT32_SIZE);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeBytes(int fieldNumber, ByteString value) {
        try {
          value.writeToReverse(this);
        } catch (IOException e) {
          // Should never happen since the writer does not throw.
          throw new RuntimeException(e);
        }
  
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value.size());
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value) throws IOException {
        int prevBytes = getTotalBytesWritten();
        Protobuf.getInstance().writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
        int prevBytes = getTotalBytesWritten();
        schema.writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        Protobuf.getInstance().writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        schema.writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeStartGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeEndGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
      }
  
      @Override
      void writeInt32(int value) {
        if (value >= 0) {
          writeVarint32(value);
        } else {
          writeVarint64(value);
        }
      }
  
      @Override
      void writeSInt32(int value) {
        writeVarint32(CodedOutputStream.encodeZigZag32(value));
      }
  
      @Override
      void writeSInt64(long value) {
        writeVarint64(CodedOutputStream.encodeZigZag64(value));
      }
  
      @Override
      void writeBool(boolean value) {
        write((byte) (value ? 1 : 0));
      }
  
      @Override
      void writeTag(int fieldNumber, int wireType) {
        writeVarint32(WireFormat.makeTag(fieldNumber, wireType));
      }
  
      @Override
      void writeVarint32(int value) {
        if ((value & (~0 << 7)) == 0) {
          writeVarint32OneByte(value);
        } else if ((value & (~0 << 14)) == 0) {
          writeVarint32TwoBytes(value);
        } else if ((value & (~0 << 21)) == 0) {
          writeVarint32ThreeBytes(value);
        } else if ((value & (~0 << 28)) == 0) {
          writeVarint32FourBytes(value);
        } else {
          writeVarint32FiveBytes(value);
        }
      }
  
      private void writeVarint32OneByte(int value) {
        buffer.put(pos--, (byte) value);
      }
  
      private void writeVarint32TwoBytes(int value) {
        // Byte order is little-endian.
        pos -= 2;
        buffer.putShort(pos + 1, (short) (((value & (0x7F << 7)) << 1) | ((value & 0x7F) | 0x80)));
      }
  
      private void writeVarint32ThreeBytes(int value) {
        // Byte order is little-endian.
        pos -= 3;
        buffer.putInt(
            pos,
            ((value & (0x7F << 14)) << 10)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 9)
                | ((value & 0x7F) | 0x80) << 8);
      }
  
      private void writeVarint32FourBytes(int value) {
        // Byte order is little-endian.
        pos -= 4;
        buffer.putInt(
            pos + 1,
            ((value & (0x7F << 21)) << 3)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 2)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 1)
                | ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32FiveBytes(int value) {
        // Byte order is little-endian.
        buffer.put(pos--, (byte) (value >>> 28));
        pos -= 4;
        buffer.putInt(
            pos + 1,
            ((((value >>> 21) & 0x7F) | 0x80) << 24)
                | ((((value >>> 14) & 0x7F) | 0x80) << 16)
                | ((((value >>> 7) & 0x7F) | 0x80) << 8)
                | ((value & 0x7F) | 0x80));
      }
  
      @Override
      void writeVarint64(long value) {
        switch (computeUInt64SizeNoTag(value)) {
          case 1:
            writeVarint64OneByte(value);
            break;
          case 2:
            writeVarint64TwoBytes(value);
            break;
          case 3:
            writeVarint64ThreeBytes(value);
            break;
          case 4:
            writeVarint64FourBytes(value);
            break;
          case 5:
            writeVarint64FiveBytes(value);
            break;
          case 6:
            writeVarint64SixBytes(value);
            break;
          case 7:
            writeVarint64SevenBytes(value);
            break;
          case 8:
            writeVarint64EightBytes(value);
            break;
          case 9:
            writeVarint64NineBytes(value);
            break;
          case 10:
            writeVarint64TenBytes(value);
            break;
        }
      }
  
      private void writeVarint64OneByte(long value) {
        writeVarint32OneByte((int) value);
      }
  
      private void writeVarint64TwoBytes(long value) {
        writeVarint32TwoBytes((int) value);
      }
  
      private void writeVarint64ThreeBytes(long value) {
        writeVarint32ThreeBytes((int) value);
      }
  
      private void writeVarint64FourBytes(long value) {
        writeVarint32FourBytes((int) value);
      }
  
      private void writeVarint64FiveBytes(long value) {
        // Byte order is little-endian.
        pos -= 5;
        buffer.putLong(
            pos - 2,
            ((value & (0x7FL << 28)) << 28)
                | (((value & (0x7F << 21)) | (0x80 << 21)) << 27)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 26)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 25)
                | (((value & 0x7F) | 0x80)) << 24);
      }
  
      private void writeVarint64SixBytes(long value) {
        // Byte order is little-endian.
        pos -= 6;
        buffer.putLong(
            pos - 1,
            ((value & (0x7FL << 35)) << 21)
                | (((value & (0x7FL << 28)) | (0x80L << 28)) << 20)
                | (((value & (0x7F << 21)) | (0x80 << 21)) << 19)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 18)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 17)
                | (((value & 0x7F) | 0x80)) << 16);
      }
  
      private void writeVarint64SevenBytes(long value) {
        // Byte order is little-endian.
        pos -= 7;
        buffer.putLong(
            pos,
            ((value & (0x7FL << 42)) << 14)
                | (((value & (0x7FL << 35)) | (0x80L << 35)) << 13)
                | (((value & (0x7FL << 28)) | (0x80L << 28)) << 12)
                | (((value & (0x7F << 21)) | (0x80 << 21)) << 11)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 10)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 9)
                | (((value & 0x7F) | 0x80)) << 8);
      }
  
      private void writeVarint64EightBytes(long value) {
        // Byte order is little-endian.
        pos -= 8;
        buffer.putLong(
            pos + 1,
            ((value & (0x7FL << 49)) << 7)
                | (((value & (0x7FL << 42)) | (0x80L << 42)) << 6)
                | (((value & (0x7FL << 35)) | (0x80L << 35)) << 5)
                | (((value & (0x7FL << 28)) | (0x80L << 28)) << 4)
                | (((value & (0x7F << 21)) | (0x80 << 21)) << 3)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 2)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 1)
                | ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64EightBytesWithSign(long value) {
        // Byte order is little-endian.
        pos -= 8;
        buffer.putLong(
            pos + 1,
            (((value & (0x7FL << 49)) | (0x80L << 49)) << 7)
                | (((value & (0x7FL << 42)) | (0x80L << 42)) << 6)
                | (((value & (0x7FL << 35)) | (0x80L << 35)) << 5)
                | (((value & (0x7FL << 28)) | (0x80L << 28)) << 4)
                | (((value & (0x7F << 21)) | (0x80 << 21)) << 3)
                | (((value & (0x7F << 14)) | (0x80 << 14)) << 2)
                | (((value & (0x7F << 7)) | (0x80 << 7)) << 1)
                | ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64NineBytes(long value) {
        buffer.put(pos--, (byte) (value >>> 56));
        writeVarint64EightBytesWithSign(value & 0xFFFFFFFFFFFFFFL);
      }
  
      private void writeVarint64TenBytes(long value) {
        buffer.put(pos--, (byte) (value >>> 63));
        buffer.put(pos--, (byte) (((value >>> 56) & 0x7F) | 0x80));
        writeVarint64EightBytesWithSign(value & 0xFFFFFFFFFFFFFFL);
      }
  
      @Override
      void writeFixed32(int value) {
        pos -= 4;
        buffer.putInt(pos + 1, value);
      }
  
      @Override
      void writeFixed64(long value) {
        pos -= 8;
        buffer.putLong(pos + 1, value);
      }
  
      @Override
      void writeString(String in) {
        // Request enough space to write the ASCII string.
        requireSpace(in.length());
  
        // We know the buffer is big enough...
        int i = in.length() - 1;
        pos -= i;
        // Designed to take advantage of
        // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
        for (char c; i >= 0 && (c = in.charAt(i)) < 0x80; i--) {
          buffer.put(pos + i, (byte) c);
        }
        if (i == -1) {
          // Move the position past the ASCII string.
          pos -= 1;
          return;
        }
        pos += i;
        for (char c; i >= 0; i--) {
          c = in.charAt(i);
          if (c < 0x80 && pos >= 0) {
            buffer.put(pos--, (byte) c);
          } else if (c < 0x800 && pos > 0) { // 11 bits, two UTF-8 bytes
            buffer.put(pos--, (byte) (0x80 | (0x3F & c)));
            buffer.put(pos--, (byte) ((0xF << 6) | (c >>> 6)));
          } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && pos > 1) {
            // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
            buffer.put(pos--, (byte) (0x80 | (0x3F & c)));
            buffer.put(pos--, (byte) (0x80 | (0x3F & (c >>> 6))));
            buffer.put(pos--, (byte) ((0xF << 5) | (c >>> 12)));
          } else if (pos > 2) {
            // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
            // four UTF-8 bytes
            char high = 0;
            if (i == 0 || !Character.isSurrogatePair(high = in.charAt(i - 1), c)) {
              throw new Utf8.UnpairedSurrogateException(i - 1, i);
            }
            i--;
            int codePoint = Character.toCodePoint(high, c);
            buffer.put(pos--, (byte) (0x80 | (0x3F & codePoint)));
            buffer.put(pos--, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
            buffer.put(pos--, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
            buffer.put(pos--, (byte) ((0xF << 4) | (codePoint >>> 18)));
          } else {
            // Buffer is full - allocate a new one and revisit the current character.
            requireSpace(i);
            i++;
          }
        }
      }
  
      @Override
      public void write(byte value) {
        buffer.put(pos--, value);
      }
  
      @Override
      public void write(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        buffer.position(pos + 1);
        buffer.put(value, offset, length);
      }
  
      @Override
      public void writeLazy(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value, offset, length));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        buffer.position(pos + 1);
        buffer.put(value, offset, length);
      }
  
      @Override
      public void write(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        buffer.position(pos + 1);
        buffer.put(value);
      }
  
      @Override
      public void writeLazy(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        buffer.position(pos + 1);
        buffer.put(value);
      }
  
      @Override
      void requireSpace(int size) {
        if (spaceLeft() < size) {
          nextBuffer(size);
        }
      }
    }
  
    /** Writer that uses unsafe operations on a target {@link ByteBuffer}. */
    private static final class UnsafeDirectWriter extends BinaryWriter {
      private ByteBuffer buffer;
      private long bufferOffset;
      private long limitMinusOne;
      private long pos;
  
      UnsafeDirectWriter(BufferAllocator alloc, int chunkSize) {
        super(alloc, chunkSize);
        nextBuffer();
      }
  
      /** Indicates whether the required unsafe operations are supported on this platform. */
      private static boolean isSupported() {
        return UnsafeUtil.hasUnsafeByteBufferOperations();
      }
  
      private void nextBuffer() {
        nextBuffer(newDirectBuffer());
      }
  
      private void nextBuffer(int capacity) {
        nextBuffer(newDirectBuffer(capacity));
      }
  
      private void nextBuffer(AllocatedBuffer allocatedBuffer) {
        if (!allocatedBuffer.hasNioBuffer()) {
          throw new RuntimeException("Allocated buffer does not have NIO buffer");
        }
        ByteBuffer nioBuffer = allocatedBuffer.nioBuffer();
        if (!nioBuffer.isDirect()) {
          throw new RuntimeException("Allocator returned non-direct buffer");
        }
  
        finishCurrentBuffer();
        buffers.addFirst(allocatedBuffer);
  
        buffer = nioBuffer;
        buffer.limit(buffer.capacity());
        buffer.position(0);
  
        bufferOffset = UnsafeUtil.addressOffset(buffer);
        limitMinusOne = bufferOffset + (buffer.limit() - 1);
        pos = limitMinusOne;
      }
  
      @Override
      public int getTotalBytesWritten() {
        return totalDoneBytes + bytesWrittenToCurrentBuffer();
      }
  
      private int bytesWrittenToCurrentBuffer() {
        return (int) (limitMinusOne - pos);
      }
  
      private int spaceLeft() {
        return bufferPos() + 1;
      }
  
      @Override
      void finishCurrentBuffer() {
        if (buffer != null) {
          totalDoneBytes += bytesWrittenToCurrentBuffer();
          // Update the indices on the netty buffer.
          buffer.position(bufferPos() + 1);
          buffer = null;
          pos = 0;
          limitMinusOne = 0;
        }
      }
  
      private int bufferPos() {
        return (int) (pos - bufferOffset);
      }
  
      @Override
      public void writeUInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeSInt32(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed32(int fieldNumber, int value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED32_SIZE);
        writeFixed32(value);
        writeTag(fieldNumber, WIRETYPE_FIXED32);
      }
  
      @Override
      public void writeUInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeVarint64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeSInt64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + MAX_VARINT64_SIZE);
        writeSInt64(value);
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeFixed64(int fieldNumber, long value) {
        requireSpace(MAX_VARINT32_SIZE + FIXED64_SIZE);
        writeFixed64(value);
        writeTag(fieldNumber, WIRETYPE_FIXED64);
      }
  
      @Override
      public void writeBool(int fieldNumber, boolean value) {
        requireSpace(MAX_VARINT32_SIZE + 1);
        write((byte) (value ? 1 : 0));
        writeTag(fieldNumber, WIRETYPE_VARINT);
      }
  
      @Override
      public void writeString(int fieldNumber, String value) {
        int prevBytes = getTotalBytesWritten();
        writeString(value);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(2 * MAX_VARINT32_SIZE);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeBytes(int fieldNumber, ByteString value) {
        try {
          value.writeToReverse(this);
        } catch (IOException e) {
          // Should never happen since the writer does not throw.
          throw new RuntimeException(e);
        }
  
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(value.size());
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value) throws IOException {
        int prevBytes = getTotalBytesWritten();
        Protobuf.getInstance().writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
        int prevBytes = getTotalBytesWritten();
        schema.writeTo(value, this);
        int length = getTotalBytesWritten() - prevBytes;
        requireSpace(MAX_VARINT32_SIZE * 2);
        writeVarint32(length);
        writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        Protobuf.getInstance().writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
        schema.writeTo(value, this);
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeStartGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_START_GROUP);
      }
  
      @Override
      public void writeEndGroup(int fieldNumber) {
        writeTag(fieldNumber, WIRETYPE_END_GROUP);
      }
  
      @Override
      void writeInt32(int value) {
        if (value >= 0) {
          writeVarint32(value);
        } else {
          writeVarint64(value);
        }
      }
  
      @Override
      void writeSInt32(int value) {
        writeVarint32(CodedOutputStream.encodeZigZag32(value));
      }
  
      @Override
      void writeSInt64(long value) {
        writeVarint64(CodedOutputStream.encodeZigZag64(value));
      }
  
      @Override
      void writeBool(boolean value) {
        write((byte) (value ? 1 : 0));
      }
  
      @Override
      void writeTag(int fieldNumber, int wireType) {
        writeVarint32(WireFormat.makeTag(fieldNumber, wireType));
      }
  
      @Override
      void writeVarint32(int value) {
        if ((value & (~0 << 7)) == 0) {
          writeVarint32OneByte(value);
        } else if ((value & (~0 << 14)) == 0) {
          writeVarint32TwoBytes(value);
        } else if ((value & (~0 << 21)) == 0) {
          writeVarint32ThreeBytes(value);
        } else if ((value & (~0 << 28)) == 0) {
          writeVarint32FourBytes(value);
        } else {
          writeVarint32FiveBytes(value);
        }
      }
  
      private void writeVarint32OneByte(int value) {
        UnsafeUtil.putByte(pos--, (byte) value);
      }
  
      private void writeVarint32TwoBytes(int value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 7));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32ThreeBytes(int value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 14));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32FourBytes(int value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 21));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint32FiveBytes(int value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 28));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      @Override
      void writeVarint64(long value) {
        switch (computeUInt64SizeNoTag(value)) {
          case 1:
            writeVarint64OneByte(value);
            break;
          case 2:
            writeVarint64TwoBytes(value);
            break;
          case 3:
            writeVarint64ThreeBytes(value);
            break;
          case 4:
            writeVarint64FourBytes(value);
            break;
          case 5:
            writeVarint64FiveBytes(value);
            break;
          case 6:
            writeVarint64SixBytes(value);
            break;
          case 7:
            writeVarint64SevenBytes(value);
            break;
          case 8:
            writeVarint64EightBytes(value);
            break;
          case 9:
            writeVarint64NineBytes(value);
            break;
          case 10:
            writeVarint64TenBytes(value);
            break;
        }
      }
  
      private void writeVarint64OneByte(long value) {
        UnsafeUtil.putByte(pos--, (byte) value);
      }
  
      private void writeVarint64TwoBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 7));
        UnsafeUtil.putByte(pos--, (byte) (((int) value & 0x7F) | 0x80));
      }
  
      private void writeVarint64ThreeBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (((int) value) >>> 14));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64FourBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 21));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64FiveBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 28));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64SixBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 35));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64SevenBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 42));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64EightBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 49));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64NineBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 56));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 49) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      private void writeVarint64TenBytes(long value) {
        UnsafeUtil.putByte(pos--, (byte) (value >>> 63));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 56) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 49) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 42) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 35) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 28) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 21) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 14) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) (((value >>> 7) & 0x7F) | 0x80));
        UnsafeUtil.putByte(pos--, (byte) ((value & 0x7F) | 0x80));
      }
  
      @Override
      void writeFixed32(int value) {
        UnsafeUtil.putByte(pos--, (byte) ((value >> 24) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((value >> 16) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((value >> 8) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) (value & 0xFF));
      }
  
      @Override
      void writeFixed64(long value) {
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 56) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 48) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 40) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 32) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 24) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 16) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value >> 8) & 0xFF));
        UnsafeUtil.putByte(pos--, (byte) ((int) (value) & 0xFF));
      }
  
      @Override
      void writeString(String in) {
        // Request enough space to write the ASCII string.
        requireSpace(in.length());
  
        // We know the buffer is big enough...
        int i = in.length() - 1;
        // Designed to take advantage of
        // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
        for (char c; i >= 0 && (c = in.charAt(i)) < 0x80; i--) {
          UnsafeUtil.putByte(pos--, (byte) c);
        }
        if (i == -1) {
          // ASCII.
          return;
        }
        for (char c; i >= 0; i--) {
          c = in.charAt(i);
          if (c < 0x80 && pos >= bufferOffset) {
            UnsafeUtil.putByte(pos--, (byte) c);
          } else if (c < 0x800 && pos > bufferOffset) { // 11 bits, two UTF-8 bytes
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & c)));
            UnsafeUtil.putByte(pos--, (byte) ((0xF << 6) | (c >>> 6)));
          } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c)
              && pos > bufferOffset + 1) {
            // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & c)));
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & (c >>> 6))));
            UnsafeUtil.putByte(pos--, (byte) ((0xF << 5) | (c >>> 12)));
          } else if (pos > bufferOffset + 2) {
            // Minimum code point represented by a surrogate pair is 0x10000, 17 bits,
            // four UTF-8 bytes
            final char high;
            if (i == 0 || !Character.isSurrogatePair(high = in.charAt(i - 1), c)) {
              throw new Utf8.UnpairedSurrogateException(i - 1, i);
            }
            i--;
            int codePoint = Character.toCodePoint(high, c);
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & codePoint)));
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
            UnsafeUtil.putByte(pos--, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
            UnsafeUtil.putByte(pos--, (byte) ((0xF << 4) | (codePoint >>> 18)));
          } else {
            // Buffer is full - allocate a new one and revisit the current character.
            requireSpace(i);
            i++;
          }
        }
      }
  
      @Override
      public void write(byte value) {
        UnsafeUtil.putByte(pos--, value);
      }
  
      @Override
      public void write(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        buffer.position(bufferPos() + 1);
        buffer.put(value, offset, length);
      }
  
      @Override
      public void writeLazy(byte[] value, int offset, int length) {
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value, offset, length));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        buffer.position(bufferPos() + 1);
        buffer.put(value, offset, length);
      }
  
      @Override
      public void write(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          nextBuffer(length);
        }
  
        pos -= length;
        buffer.position(bufferPos() + 1);
        buffer.put(value);
      }
  
      @Override
      public void writeLazy(ByteBuffer value) {
        int length = value.remaining();
        if (spaceLeft() < length) {
          // We consider the value to be immutable (likely the internals of a ByteString). Just
          // wrap it in a Netty buffer and add it to the output buffer.
          totalDoneBytes += length;
          buffers.addFirst(AllocatedBuffer.wrap(value));
  
          // Advance the writer to the next buffer.
          // TODO(nathanmittler): Consider slicing if space available above some threshold.
          nextBuffer();
          return;
        }
  
        pos -= length;
        buffer.position(bufferPos() + 1);
        buffer.put(value);
      }
  
      @Override
      void requireSpace(int size) {
        if (spaceLeft() < size) {
          nextBuffer(size);
        }
      }
    }
  }
  