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.WIRETYPE_LENGTH_DELIMITED;
  
  import java.io.IOException;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Map;
  
  /** An adapter between the {@link Writer} interface and {@link CodedOutputStream}. */
  @ExperimentalApi
  final class CodedOutputStreamWriter implements Writer {
    private final CodedOutputStream output;
  
    public static CodedOutputStreamWriter forCodedOutput(CodedOutputStream output) {
      if (output.wrapper != null) {
        return output.wrapper;
      }
      return new CodedOutputStreamWriter(output);
    }
  
    private CodedOutputStreamWriter(CodedOutputStream output) {
      this.output = checkNotNull(output, "output");
      this.output.wrapper = this;
    }
  
    @Override
    public FieldOrder fieldOrder() {
      return FieldOrder.ASCENDING;
    }
  
    public int getTotalBytesWritten() {
      return output.getTotalBytesWritten();
    }
  
    @Override
    public void writeSFixed32(int fieldNumber, int value) throws IOException {
      output.writeSFixed32(fieldNumber, value);
    }
  
    @Override
    public void writeInt64(int fieldNumber, long value) throws IOException {
      output.writeInt64(fieldNumber, value);
    }
  
    @Override
    public void writeSFixed64(int fieldNumber, long value) throws IOException {
      output.writeSFixed64(fieldNumber, value);
    }
  
    @Override
    public void writeFloat(int fieldNumber, float value) throws IOException {
      output.writeFloat(fieldNumber, value);
    }
  
    @Override
    public void writeDouble(int fieldNumber, double value) throws IOException {
      output.writeDouble(fieldNumber, value);
    }
  
    @Override
    public void writeEnum(int fieldNumber, int value) throws IOException {
      output.writeEnum(fieldNumber, value);
    }
  
    @Override
    public void writeUInt64(int fieldNumber, long value) throws IOException {
      output.writeUInt64(fieldNumber, value);
    }
  
    @Override
    public void writeInt32(int fieldNumber, int value) throws IOException {
      output.writeInt32(fieldNumber, value);
    }
  
    @Override
    public void writeFixed64(int fieldNumber, long value) throws IOException {
      output.writeFixed64(fieldNumber, value);
    }
  
    @Override
    public void writeFixed32(int fieldNumber, int value) throws IOException {
      output.writeFixed32(fieldNumber, value);
    }
  
    @Override
    public void writeBool(int fieldNumber, boolean value) throws IOException {
      output.writeBool(fieldNumber, value);
    }
  
    @Override
    public void writeString(int fieldNumber, String value) throws IOException {
      output.writeString(fieldNumber, value);
    }
  
    @Override
    public void writeBytes(int fieldNumber, ByteString value) throws IOException {
      output.writeBytes(fieldNumber, value);
    }
  
    @Override
    public void writeUInt32(int fieldNumber, int value) throws IOException {
      output.writeUInt32(fieldNumber, value);
    }
  
    @Override
    public void writeSInt32(int fieldNumber, int value) throws IOException {
      output.writeSInt32(fieldNumber, value);
    }
  
    @Override
    public void writeSInt64(int fieldNumber, long value) throws IOException {
      output.writeSInt64(fieldNumber, value);
    }
  
    @Override
    public void writeMessage(int fieldNumber, Object value) throws IOException {
      output.writeMessage(fieldNumber, (MessageLite) value);
    }
  
    @Override
    public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
      output.writeMessage(fieldNumber, (MessageLite) value, schema);
    }
  
    @Override
    public void writeGroup(int fieldNumber, Object value) throws IOException {
      output.writeGroup(fieldNumber, (MessageLite) value);
    }
  
    @Override
    public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
      output.writeGroup(fieldNumber, (MessageLite) value, schema);
    }
  
    @Override
    public void writeStartGroup(int fieldNumber) throws IOException {
      output.writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP);
    }
  
    @Override
    public void writeEndGroup(int fieldNumber) throws IOException {
      output.writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
    }
  
    @Override
    public final void writeMessageSetItem(int fieldNumber, Object value) throws IOException {
      if (value instanceof ByteString) {
        output.writeRawMessageSetExtension(fieldNumber, (ByteString) value);
      } else {
        output.writeMessageSetExtension(fieldNumber, (MessageLite) value);
      }
    }
  
    @Override
    public void writeInt32List(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeInt32SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeInt32NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeInt32(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeFixed32List(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeFixed32SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeFixed32NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeFixed32(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeInt64List(int fieldNumber, List<Long> value, boolean packed) throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeInt64SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeInt64NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeInt64(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeUInt64List(int fieldNumber, List<Long> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeUInt64SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeUInt64NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeUInt64(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeFixed64List(int fieldNumber, List<Long> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeFixed64SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeFixed64NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeFixed64(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeFloatList(int fieldNumber, List<Float> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeFloatSizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeFloatNoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeFloat(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeDoubleList(int fieldNumber, List<Double> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeDoubleSizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeDoubleNoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeDouble(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeEnumList(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeEnumSizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeEnumNoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeEnum(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeBoolList(int fieldNumber, List<Boolean> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeBoolSizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeBoolNoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeBool(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeStringList(int fieldNumber, List<String> value) throws IOException {
      if (value instanceof LazyStringList) {
        final LazyStringList lazyList = (LazyStringList) value;
        for (int i = 0; i < value.size(); ++i) {
          writeLazyString(fieldNumber, lazyList.getRaw(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeString(fieldNumber, value.get(i));
        }
      }
    }
  
    private void writeLazyString(int fieldNumber, Object value) throws IOException {
      if (value instanceof String) {
        output.writeString(fieldNumber, (String) value);
      } else {
        output.writeBytes(fieldNumber, (ByteString) value);
      }
    }
  
    @Override
    public void writeBytesList(int fieldNumber, List<ByteString> value) throws IOException {
      for (int i = 0; i < value.size(); ++i) {
        output.writeBytes(fieldNumber, value.get(i));
      }
    }
  
    @Override
    public void writeUInt32List(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeUInt32SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeUInt32NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeUInt32(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeSFixed32List(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeSFixed32SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeSFixed32NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeSFixed32(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeSFixed64List(int fieldNumber, List<Long> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeSFixed64SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeSFixed64NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeSFixed64(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeSInt32List(int fieldNumber, List<Integer> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeSInt32SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeSInt32NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeSInt32(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeSInt64List(int fieldNumber, List<Long> value, boolean packed)
        throws IOException {
      if (packed) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
  
        // Compute and write the length of the data.
        int dataSize = 0;
        for (int i = 0; i < value.size(); ++i) {
          dataSize += CodedOutputStream.computeSInt64SizeNoTag(value.get(i));
        }
        output.writeUInt32NoTag(dataSize);
  
        // Write the data itself, without any tags.
        for (int i = 0; i < value.size(); ++i) {
          output.writeSInt64NoTag(value.get(i));
        }
      } else {
        for (int i = 0; i < value.size(); ++i) {
          output.writeSInt64(fieldNumber, value.get(i));
        }
      }
    }
  
    @Override
    public void writeMessageList(int fieldNumber, List<?> value) throws IOException {
      for (int i = 0; i < value.size(); ++i) {
        writeMessage(fieldNumber, value.get(i));
      }
    }
  
    @Override
    public void writeMessageList(int fieldNumber, List<?> value, Schema schema) throws IOException {
      for (int i = 0; i < value.size(); ++i) {
        writeMessage(fieldNumber, value.get(i), schema);
      }
    }
  
    @Override
    public void writeGroupList(int fieldNumber, List<?> value) throws IOException {
      for (int i = 0; i < value.size(); ++i) {
        writeGroup(fieldNumber, value.get(i));
      }
    }
  
    @Override
    public void writeGroupList(int fieldNumber, List<?> value, Schema schema) throws IOException {
      for (int i = 0; i < value.size(); ++i) {
        writeGroup(fieldNumber, value.get(i), schema);
      }
    }
  
    @Override
    public <K, V> void writeMap(int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map)
        throws IOException {
      if (output.isSerializationDeterministic()) {
        writeDeterministicMap(fieldNumber, metadata, map);
        return;
      }
      for (Map.Entry<K, V> entry : map.entrySet()) {
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
        output.writeUInt32NoTag(
            MapEntryLite.computeSerializedSize(metadata, entry.getKey(), entry.getValue()));
        MapEntryLite.writeTo(output, metadata, entry.getKey(), entry.getValue());
      }
    }
  
    @SuppressWarnings("unchecked")
    private <K, V> void writeDeterministicMap(
        int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map) throws IOException {
      switch (metadata.keyType) {
        case BOOL:
          V value;
          if ((value = map.get(Boolean.FALSE)) != null) {
            writeDeterministicBooleanMapEntry(
                fieldNumber, /* key= */ false, value, (MapEntryLite.Metadata<Boolean, V>) metadata);
          }
          if ((value = map.get(Boolean.TRUE)) != null) {
            writeDeterministicBooleanMapEntry(
                fieldNumber, /* key= */ true, value, (MapEntryLite.Metadata<Boolean, V>) metadata);
          }
          break;
        case FIXED32:
        case INT32:
        case SFIXED32:
        case SINT32:
        case UINT32:
          writeDeterministicIntegerMap(
              fieldNumber, (MapEntryLite.Metadata<Integer, V>) metadata, (Map<Integer, V>) map);
          break;
        case FIXED64:
        case INT64:
        case SFIXED64:
        case SINT64:
        case UINT64:
          writeDeterministicLongMap(
              fieldNumber, (MapEntryLite.Metadata<Long, V>) metadata, (Map<Long, V>) map);
          break;
        case STRING:
          writeDeterministicStringMap(
              fieldNumber, (MapEntryLite.Metadata<String, V>) metadata, (Map<String, V>) map);
          break;
        default:
          throw new IllegalArgumentException("does not support key type: " + metadata.keyType);
      }
    }
  
    private <V> void writeDeterministicBooleanMapEntry(
        int fieldNumber, boolean key, V value, MapEntryLite.Metadata<Boolean, V> metadata)
        throws IOException {
      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
      output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
      MapEntryLite.writeTo(output, metadata, key, value);
    }
  
    private <V> void writeDeterministicIntegerMap(
        int fieldNumber, MapEntryLite.Metadata<Integer, V> metadata, Map<Integer, V> map)
        throws IOException {
      int[] keys = new int[map.size()];
      int index = 0;
      for (int k : map.keySet()) {
        keys[index++] = k;
      }
      Arrays.sort(keys);
      for (int key : keys) {
        V value = map.get(key);
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
        output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
        MapEntryLite.writeTo(output, metadata, key, value);
      }
    }
  
    private <V> void writeDeterministicLongMap(
        int fieldNumber, MapEntryLite.Metadata<Long, V> metadata, Map<Long, V> map)
        throws IOException {
      long[] keys = new long[map.size()];
      int index = 0;
      for (long k : map.keySet()) {
        keys[index++] = k;
      }
      Arrays.sort(keys);
      for (long key : keys) {
        V value = map.get(key);
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
        output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
        MapEntryLite.writeTo(output, metadata, key, value);
      }
    }
  
    private <V> void writeDeterministicStringMap(
        int fieldNumber, MapEntryLite.Metadata<String, V> metadata, Map<String, V> map)
        throws IOException {
      String[] keys = new String[map.size()];
      int index = 0;
      for (String k : map.keySet()) {
        keys[index++] = k;
      }
      Arrays.sort(keys);
      for (String key : keys) {
        V value = map.get(key);
        output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
        output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
        MapEntryLite.writeTo(output, metadata, key, value);
      }
    }
  }
  