// Protocol Buffers - Google's data interchange format
// Copyright 2008 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

package com.google.protobuf;

import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

/**
 * {@code UnknownFieldSet} keeps track of fields which were seen when parsing a protocol
 * message but whose field numbers or types are unrecognized. This most frequently occurs when new
 * fields are added to a message type and then messages containing those fields are read by old
 * software that was compiled before the new types were added.
 *
 * <p>Every {@link Message} contains an {@code UnknownFieldSet} (and every {@link Message.Builder}
 * contains a {@link Builder}).
 *
 * <p>Most users will never need to use this class.
 *
 * @author kenton@google.com Kenton Varda
 */
public final class UnknownFieldSet implements MessageLite {

  private final TreeMap<Integer, Field> fields;

  /**
   * Construct an {@code UnknownFieldSet} around the given map.
   */
  private UnknownFieldSet(TreeMap<Integer, Field> fields) {
    this.fields = fields;
  }

  /** Create a new {@link Builder}. */
  public static Builder newBuilder() {
    return Builder.create();
  }

  /** Create a new {@link Builder} and initialize it to be a copy of {@code copyFrom}. */
  public static Builder newBuilder(UnknownFieldSet copyFrom) {
    return newBuilder().mergeFrom(copyFrom);
  }

  /** Get an empty {@code UnknownFieldSet}. */
  public static UnknownFieldSet getDefaultInstance() {
    return defaultInstance;
  }

  @Override
  public UnknownFieldSet getDefaultInstanceForType() {
    return defaultInstance;
  }

  private static final UnknownFieldSet defaultInstance =
      new UnknownFieldSet(new TreeMap<Integer, Field>());

  @Override
  public boolean equals(
          Object other) {
    if (this == other) {
      return true;
    }
    return (other instanceof UnknownFieldSet) && fields.equals(((UnknownFieldSet) other).fields);
  }

  @Override
  public int hashCode() {
    if (fields.isEmpty()) { // avoid allocation of iterator.
      // This optimization may not be helpful but it is needed for the allocation tests to pass.
      return 0;
    }
    return fields.hashCode();
  }

  /** Whether the field set has no fields. */
  public boolean isEmpty() {
    return fields.isEmpty();
  }

  /** Get a map of fields in the set by number. */
  public Map<Integer, Field> asMap() {
    // Avoid an allocation for the common case of an empty map.
    if (fields.isEmpty()) {
      return Collections.emptyMap();
    }
    return (Map<Integer, Field>) fields.clone();
  }

  /** Check if the given field number is present in the set. */
  public boolean hasField(int number) {
    return fields.containsKey(number);
  }

  /** Get a field by number. Returns an empty field if not present. Never returns {@code null}. */
  public Field getField(int number) {
    Field result = fields.get(number);
    return (result == null) ? Field.getDefaultInstance() : result;
  }

  /** Serializes the set and writes it to {@code output}. */
  @Override
  public void writeTo(CodedOutputStream output) throws IOException {
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return;
    }
    for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
      Field field = entry.getValue();
      field.writeTo(entry.getKey(), output);
    }
  }

  /**
   * Converts the set to a string in protocol buffer text format. This is just a trivial wrapper
   * around {@link TextFormat.Printer#printToString(UnknownFieldSet)}.
   */
  @Override
  public String toString() {
    return TextFormat.printer().printToString(this);
  }

  /**
   * Serializes the message to a {@code ByteString} and returns it. This is just a trivial wrapper
   * around {@link #writeTo(CodedOutputStream)}.
   */
  @Override
  public ByteString toByteString() {
    try {
      ByteString.CodedBuilder out = ByteString.newCodedBuilder(getSerializedSize());
      writeTo(out.getCodedOutput());
      return out.build();
    } catch (IOException e) {
      throw new RuntimeException(
          "Serializing to a ByteString threw an IOException (should never happen).", e);
    }
  }

  /**
   * Serializes the message to a {@code byte} array and returns it. This is just a trivial wrapper
   * around {@link #writeTo(CodedOutputStream)}.
   */
  @Override
  public byte[] toByteArray() {
    try {
      byte[] result = new byte[getSerializedSize()];
      CodedOutputStream output = CodedOutputStream.newInstance(result);
      writeTo(output);
      output.checkNoSpaceLeft();
      return result;
    } catch (IOException e) {
      throw new RuntimeException(
          "Serializing to a byte array threw an IOException (should never happen).", e);
    }
  }

  /**
   * Serializes the message and writes it to {@code output}. This is just a trivial wrapper around
   * {@link #writeTo(CodedOutputStream)}.
   */
  @Override
  public void writeTo(OutputStream output) throws IOException {
    CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
    writeTo(codedOutput);
    codedOutput.flush();
  }

  @Override
  public void writeDelimitedTo(OutputStream output) throws IOException {
    CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
    codedOutput.writeUInt32NoTag(getSerializedSize());
    writeTo(codedOutput);
    codedOutput.flush();
  }

  /** Get the number of bytes required to encode this set. */
  @Override
  public int getSerializedSize() {
    int result = 0;
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return result;
    }
    for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
      result += entry.getValue().getSerializedSize(entry.getKey());
    }
    return result;
  }

  /** Serializes the set and writes it to {@code output} using {@code MessageSet} wire format. */
  public void writeAsMessageSetTo(CodedOutputStream output) throws IOException {
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return;
    }
    for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
      entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), output);
    }
  }

  /** Serializes the set and writes it to {@code writer}. */
  void writeTo(Writer writer) throws IOException {
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return;
    }
    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
      // Write fields in descending order.
      for (Map.Entry<Integer, Field> entry : fields.descendingMap().entrySet()) {
        entry.getValue().writeTo(entry.getKey(), writer);
      }
    } else {
      // Write fields in ascending order.
      for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
        entry.getValue().writeTo(entry.getKey(), writer);
      }
    }
  }

  /** Serializes the set and writes it to {@code writer} using {@code MessageSet} wire format. */
  void writeAsMessageSetTo(Writer writer) throws IOException {
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return;
    }
    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
      // Write fields in descending order.
      for (Map.Entry<Integer, Field> entry : fields.descendingMap().entrySet()) {
        entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
      }
    } else {
      // Write fields in ascending order.
      for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
        entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
      }
    }
  }

  /** Get the number of bytes required to encode this set using {@code MessageSet} wire format. */
  public int getSerializedSizeAsMessageSet() {
    int result = 0;
    if (fields.isEmpty()) {
      // Avoid allocating an iterator.
      return result;
    }
    for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
      result += entry.getValue().getSerializedSizeAsMessageSetExtension(entry.getKey());
    }
    return result;
  }

  @Override
  public boolean isInitialized() {
    // UnknownFieldSets do not have required fields, so they are always
    // initialized.
    return true;
  }

  /** Parse an {@code UnknownFieldSet} from the given input stream. */
  public static UnknownFieldSet parseFrom(CodedInputStream input) throws IOException {
    return newBuilder().mergeFrom(input).build();
  }

  /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */
  public static UnknownFieldSet parseFrom(ByteString data)
      throws InvalidProtocolBufferException {
    return newBuilder().mergeFrom(data).build();
  }

  /** Parse {@code data} as an {@code UnknownFieldSet} and return it. */
  public static UnknownFieldSet parseFrom(byte[] data) throws InvalidProtocolBufferException {
    return newBuilder().mergeFrom(data).build();
  }

  /** Parse an {@code UnknownFieldSet} from {@code input} and return it. */
  public static UnknownFieldSet parseFrom(InputStream input) throws IOException {
    return newBuilder().mergeFrom(input).build();
  }

  @Override
  public Builder newBuilderForType() {
    return newBuilder();
  }

  @Override
  public Builder toBuilder() {
    return newBuilder().mergeFrom(this);
  }

  /**
   * Builder for {@link UnknownFieldSet}s.
   *
   * <p>Note that this class maintains {@link Field.Builder}s for all fields in the set. Thus,
   * adding one element to an existing {@link Field} does not require making a copy. This is
   * important for efficient parsing of unknown repeated fields. However, it implies that {@link
   * Field}s cannot be constructed independently, nor can two {@link UnknownFieldSet}s share the
   * same {@code Field} object.
   *
   * <p>Use {@link UnknownFieldSet#newBuilder()} to construct a {@code Builder}.
   */
  public static final class Builder implements MessageLite.Builder {
    // This constructor should never be called directly (except from 'create').
    private Builder() {}

    private TreeMap<Integer, Field.Builder> fieldBuilders = new TreeMap<>();

    private static Builder create() {
      return new Builder();
    }

    /**
     * Get a field builder for the given field number which includes any values that already exist.
     */
    private Field.Builder getFieldBuilder(int number) {
      if (number == 0) {
        return null;
      } else {
        Field.Builder builder = fieldBuilders.get(number);
        if (builder == null) {
          builder = Field.newBuilder();
          fieldBuilders.put(number, builder);
        }
        return builder;
      }
    }

    /**
     * Build the {@link UnknownFieldSet} and return it.
     */
    @Override
    public UnknownFieldSet build() {
      UnknownFieldSet result;
      if (fieldBuilders.isEmpty()) {
        result = getDefaultInstance();
      } else {
        TreeMap<Integer, Field> fields = new TreeMap<>();
        for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
          fields.put(entry.getKey(), entry.getValue().build());
        }
        result = new UnknownFieldSet(fields);
      }
      return result;
    }

    @Override
    public UnknownFieldSet buildPartial() {
      // No required fields, so this is the same as build().
      return build();
    }

    @Override
    public Builder clone() {
      Builder clone = UnknownFieldSet.newBuilder();
      for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
        Integer key = entry.getKey();
        Field.Builder value = entry.getValue();
        clone.fieldBuilders.put(key, value.clone());
      }
      return clone;
    }

    @Override
    public UnknownFieldSet getDefaultInstanceForType() {
      return UnknownFieldSet.getDefaultInstance();
    }

    /** Reset the builder to an empty set. */
    @Override
    public Builder clear() {
      fieldBuilders = new TreeMap<>();
      return this;
    }

    /**
     * Clear fields from the set with a given field number.
     *
     * @throws IllegalArgumentException if number is not positive
     */
    public Builder clearField(int number) {
      if (number <= 0) {
        throw new IllegalArgumentException(number + " is not a valid field number.");
      }
      if (fieldBuilders.containsKey(number)) {
        fieldBuilders.remove(number);
      }
      return this;
    }

    /**
     * Merge the fields from {@code other} into this set. If a field number exists in both sets,
     * {@code other}'s values for that field will be appended to the values in this set.
     */
    public Builder mergeFrom(UnknownFieldSet other) {
      if (other != getDefaultInstance()) {
        for (Map.Entry<Integer, Field> entry : other.fields.entrySet()) {
          mergeField(entry.getKey(), entry.getValue());
        }
      }
      return this;
    }

    /**
     * Add a field to the {@code UnknownFieldSet}. If a field with the same number already exists,
     * the two are merged.
     *
     * @throws IllegalArgumentException if number is not positive
     */
    public Builder mergeField(int number, final Field field) {
      if (number <= 0) {
        throw new IllegalArgumentException(number + " is not a valid field number.");
      }
      if (hasField(number)) {
        getFieldBuilder(number).mergeFrom(field);
      } else {
        // Optimization:  We could call getFieldBuilder(number).mergeFrom(field)
        // in this case, but that would create a copy of the Field object.
        // We'd rather reuse the one passed to us, so call addField() instead.
        addField(number, field);
      }
      return this;
    }

    /**
     * Convenience method for merging a new field containing a single varint value. This is used in
     * particular when an unknown enum value is encountered.
     *
     * @throws IllegalArgumentException if number is not positive
     */
    public Builder mergeVarintField(int number, int value) {
      if (number <= 0) {
        throw new IllegalArgumentException(number + " is not a valid field number.");
      }
      getFieldBuilder(number).addVarint(value);
      return this;
    }

    /**
     * Convenience method for merging a length-delimited field.
     *
     * <p>For use by generated code only.
     *
     * @throws IllegalArgumentException if number is not positive
     */
    public Builder mergeLengthDelimitedField(int number, ByteString value) {
      if (number <= 0) {
        throw new IllegalArgumentException(number + " is not a valid field number.");
      }
      getFieldBuilder(number).addLengthDelimited(value);
      return this;
    }

    /** Check if the given field number is present in the set. */
    public boolean hasField(int number) {
      return fieldBuilders.containsKey(number);
    }

    /**
     * Add a field to the {@code UnknownFieldSet}. If a field with the same number already exists,
     * it is removed.
     *
     * @throws IllegalArgumentException if number is not positive
     */
    public Builder addField(int number, Field field) {
      if (number <= 0) {
        throw new IllegalArgumentException(number + " is not a valid field number.");
      }
      fieldBuilders.put(number, Field.newBuilder(field));
      return this;
    }

    /**
     * Get all present {@code Field}s as an immutable {@code Map}. If more fields are added, the
     * changes may or may not be reflected in this map.
     */
    public Map<Integer, Field> asMap() {
      // Avoid an allocation for the common case of an empty map.
      if (fieldBuilders.isEmpty()) {
        return Collections.emptyMap();
      }

      TreeMap<Integer, Field> fields = new TreeMap<>();
      for (Map.Entry<Integer, Field.Builder> entry : fieldBuilders.entrySet()) {
        fields.put(entry.getKey(), entry.getValue().build());
      }
      return Collections.unmodifiableMap(fields);
    }

    /** Parse an entire message from {@code input} and merge its fields into this set. */
    @Override
    public Builder mergeFrom(CodedInputStream input) throws IOException {
      while (true) {
        int tag = input.readTag();
        if (tag == 0 || !mergeFieldFrom(tag, input)) {
          break;
        }
      }
      return this;
    }

    /**
     * Parse a single field from {@code input} and merge it into this set.
     *
     * @param tag The field's tag number, which was already parsed.
     * @return {@code false} if the tag is an end group tag.
     */
    public boolean mergeFieldFrom(int tag, CodedInputStream input) throws IOException {
      int number = WireFormat.getTagFieldNumber(tag);
      switch (WireFormat.getTagWireType(tag)) {
        case WireFormat.WIRETYPE_VARINT:
          getFieldBuilder(number).addVarint(input.readInt64());
          return true;
        case WireFormat.WIRETYPE_FIXED64:
          getFieldBuilder(number).addFixed64(input.readFixed64());
          return true;
        case WireFormat.WIRETYPE_LENGTH_DELIMITED:
          getFieldBuilder(number).addLengthDelimited(input.readBytes());
          return true;
        case WireFormat.WIRETYPE_START_GROUP:
          Builder subBuilder = newBuilder();
          input.readGroup(number, subBuilder, ExtensionRegistry.getEmptyRegistry());
          getFieldBuilder(number).addGroup(subBuilder.build());
          return true;
        case WireFormat.WIRETYPE_END_GROUP:
          input.checkValidEndTag();
          return false;
        case WireFormat.WIRETYPE_FIXED32:
          getFieldBuilder(number).addFixed32(input.readFixed32());
          return true;
        default:
          throw InvalidProtocolBufferException.invalidWireType();
      }
    }

    /**
     * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the set being built. This
     * is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
     */
    @Override
    public Builder mergeFrom(ByteString data) throws InvalidProtocolBufferException {
      try {
        CodedInputStream input = data.newCodedInput();
        mergeFrom(input);
        input.checkLastTagWas(0);
        return this;
      } catch (InvalidProtocolBufferException e) {
        throw e;
      } catch (IOException e) {
        throw new RuntimeException(
            "Reading from a ByteString threw an IOException (should never happen).", e);
      }
    }

    /**
     * Parse {@code data} as an {@code UnknownFieldSet} and merge it with the set being built. This
     * is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
     */
    @Override
    public Builder mergeFrom(byte[] data) throws InvalidProtocolBufferException {
      try {
        CodedInputStream input = CodedInputStream.newInstance(data);
        mergeFrom(input);
        input.checkLastTagWas(0);
        return this;
      } catch (InvalidProtocolBufferException e) {
        throw e;
      } catch (IOException e) {
        throw new RuntimeException(
            "Reading from a byte array threw an IOException (should never happen).", e);
      }
    }

    /**
     * Parse an {@code UnknownFieldSet} from {@code input} and merge it with the set being built.
     * This is just a small wrapper around {@link #mergeFrom(CodedInputStream)}.
     */
    @Override
    public Builder mergeFrom(InputStream input) throws IOException {
      CodedInputStream codedInput = CodedInputStream.newInstance(input);
      mergeFrom(codedInput);
      codedInput.checkLastTagWas(0);
      return this;
    }

    @Override
    public boolean mergeDelimitedFrom(InputStream input) throws IOException {
      int firstByte = input.read();
      if (firstByte == -1) {
        return false;
      }
      int size = CodedInputStream.readRawVarint32(firstByte, input);
      InputStream limitedInput = new LimitedInputStream(input, size);
      mergeFrom(limitedInput);
      return true;
    }

    @Override
    public boolean mergeDelimitedFrom(InputStream input, ExtensionRegistryLite extensionRegistry)
        throws IOException {
      // UnknownFieldSet has no extensions.
      return mergeDelimitedFrom(input);
    }

    @Override
    public Builder mergeFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry)
        throws IOException {
      // UnknownFieldSet has no extensions.
      return mergeFrom(input);
    }

    @Override
    public Builder mergeFrom(ByteString data, ExtensionRegistryLite extensionRegistry)
        throws InvalidProtocolBufferException {
      // UnknownFieldSet has no extensions.
      return mergeFrom(data);
    }

    @Override
    public Builder mergeFrom(byte[] data, int off, int len) throws InvalidProtocolBufferException {
      try {
        CodedInputStream input = CodedInputStream.newInstance(data, off, len);
        mergeFrom(input);
        input.checkLastTagWas(0);
        return this;
      } catch (InvalidProtocolBufferException e) {
        throw e;
      } catch (IOException e) {
        throw new RuntimeException(
            "Reading from a byte array threw an IOException (should never happen).", e);
      }
    }

    @Override
    public Builder mergeFrom(byte[] data, ExtensionRegistryLite extensionRegistry)
        throws InvalidProtocolBufferException {
      // UnknownFieldSet has no extensions.
      return mergeFrom(data);
    }

    @Override
    public Builder mergeFrom(byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry)
        throws InvalidProtocolBufferException {
      // UnknownFieldSet has no extensions.
      return mergeFrom(data, off, len);
    }

    @Override
    public Builder mergeFrom(InputStream input, ExtensionRegistryLite extensionRegistry)
        throws IOException {
      // UnknownFieldSet has no extensions.
      return mergeFrom(input);
    }

    @Override
    public Builder mergeFrom(MessageLite m) {
      if (m instanceof UnknownFieldSet) {
        return mergeFrom((UnknownFieldSet) m);
      }
      throw new IllegalArgumentException(
          "mergeFrom(MessageLite) can only merge messages of the same type.");
    }

    @Override
    public boolean isInitialized() {
      // UnknownFieldSets do not have required fields, so they are always
      // initialized.
      return true;
    }
  }

  /**
   * Represents a single field in an {@code UnknownFieldSet}.
   *
   * <p>A {@code Field} consists of five lists of values. The lists correspond to the five "wire
   * types" used in the protocol buffer binary format. The wire type of each field can be determined
   * from the encoded form alone, without knowing the field's declared type. So, we are able to
   * parse unknown values at least this far and separate them. Normally, only one of the five lists
   * will contain any values, since it is impossible to define a valid message type that declares
   * two different types for the same field number. However, the code is designed to allow for the
   * case where the same unknown field number is encountered using multiple different wire types.
   *
   * <p>{@code Field} is an immutable class. To construct one, you must use a {@link Builder}.
   *
   * @see UnknownFieldSet
   */
  public static final class Field {
    private Field() {}

    /** Construct a new {@link Builder}. */
    public static Builder newBuilder() {
      return Builder.create();
    }

    /** Construct a new {@link Builder} and initialize it to a copy of {@code copyFrom}. */
    public static Builder newBuilder(Field copyFrom) {
      return newBuilder().mergeFrom(copyFrom);
    }

    /** Get an empty {@code Field}. */
    public static Field getDefaultInstance() {
      return fieldDefaultInstance;
    }

    private static final Field fieldDefaultInstance = newBuilder().build();

    /** Get the list of varint values for this field. */
    public List<Long> getVarintList() {
      return varint;
    }

    /** Get the list of fixed32 values for this field. */
    public List<Integer> getFixed32List() {
      return fixed32;
    }

    /** Get the list of fixed64 values for this field. */
    public List<Long> getFixed64List() {
      return fixed64;
    }

    /** Get the list of length-delimited values for this field. */
    public List<ByteString> getLengthDelimitedList() {
      return lengthDelimited;
    }

    /**
     * Get the list of embedded group values for this field. These are represented using {@link
     * UnknownFieldSet}s rather than {@link Message}s since the group's type is presumably unknown.
     */
    public List<UnknownFieldSet> getGroupList() {
      return group;
    }

    @Override
    public boolean equals(
            Object other) {
      if (this == other) {
        return true;
      }
      if (!(other instanceof Field)) {
        return false;
      }
      Field that = (Field) other;
      return Objects.equals(varint, that.varint)
          && Objects.equals(fixed32, that.fixed32)
          && Objects.equals(fixed64, that.fixed64)
          && Objects.equals(lengthDelimited, that.lengthDelimited)
          && Objects.equals(group, that.group);
    }

    @Override
    public int hashCode() {
      int result = 1;
      result = 31 * result + Objects.hashCode(varint);
      result = 31 * result + Objects.hashCode(fixed32);
      result = 31 * result + Objects.hashCode(fixed64);
      result = 31 * result + Objects.hashCode(lengthDelimited);
      result = 31 * result + Objects.hashCode(group);
      return result;
    }

    /**
     * Serializes the message to a {@code ByteString} and returns it. This is just a trivial wrapper
     * around {@link #writeTo(int, CodedOutputStream)}.
     */
    public ByteString toByteString(int fieldNumber) {
      try {
        // TODO: consider caching serialized size in a volatile long
        ByteString.CodedBuilder out =
            ByteString.newCodedBuilder(getSerializedSize(fieldNumber));
        writeTo(fieldNumber, out.getCodedOutput());
        return out.build();
      } catch (IOException e) {
        throw new RuntimeException(
            "Serializing to a ByteString should never fail with an IOException", e);
      }
    }

    /** Serializes the field, including field number, and writes it to {@code output}. */
    @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"}) // No iterator allocation.
    public void writeTo(int fieldNumber, CodedOutputStream output) throws IOException {
      for (int i = 0; i < varint.size(); i++) {
        long value = varint.getLong(i);
        output.writeUInt64(fieldNumber, value);
      }
      for (int i = 0; i < fixed32.size(); i++) {
        int value = fixed32.getInt(i);
        output.writeFixed32(fieldNumber, value);
      }
      for (int i = 0; i < fixed64.size(); i++) {
        long value = fixed64.getLong(i);
        output.writeFixed64(fieldNumber, value);
      }
      for (int i = 0; i < lengthDelimited.size(); i++) {
        ByteString value = lengthDelimited.get(i);
        output.writeBytes(fieldNumber, value);
      }
      for (int i = 0; i < group.size(); i++) {
        UnknownFieldSet value = group.get(i);
        output.writeGroup(fieldNumber, value);
      }
    }

    /** Get the number of bytes required to encode this field, including field number. */
    @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"}) // No iterator allocation.
    public int getSerializedSize(int fieldNumber) {
      int result = 0;
      for (int i = 0; i < varint.size(); i++) {
        long value = varint.getLong(i);
        result += CodedOutputStream.computeUInt64Size(fieldNumber, value);
      }
      for (int i = 0; i < fixed32.size(); i++) {
        int value = fixed32.getInt(i);
        result += CodedOutputStream.computeFixed32Size(fieldNumber, value);
      }
      for (int i = 0; i < fixed64.size(); i++) {
        long value = fixed64.getLong(i);
        result += CodedOutputStream.computeFixed64Size(fieldNumber, value);
      }
      for (int i = 0; i < lengthDelimited.size(); i++) {
        ByteString value = lengthDelimited.get(i);
        result += CodedOutputStream.computeBytesSize(fieldNumber, value);
      }
      for (int i = 0; i < group.size(); i++) {
        UnknownFieldSet value = group.get(i);
        result += CodedOutputStream.computeGroupSize(fieldNumber, value);
      }
      return result;
    }

    /**
     * Serializes the field, including field number, and writes it to {@code output}, using {@code
     * MessageSet} wire format.
     */
    @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"}) // No iterator allocation.
    public void writeAsMessageSetExtensionTo(int fieldNumber, CodedOutputStream output)
        throws IOException {
      for (int i = 0; i < lengthDelimited.size(); i++) {
        ByteString value = lengthDelimited.get(i);
        output.writeRawMessageSetExtension(fieldNumber, value);
      }
    }

    /** Serializes the field, including field number, and writes it to {@code writer}. */
    void writeTo(int fieldNumber, Writer writer) throws IOException {
      writer.writeInt64List(fieldNumber, varint, false);
      writer.writeFixed32List(fieldNumber, fixed32, false);
      writer.writeFixed64List(fieldNumber, fixed64, false);
      writer.writeBytesList(fieldNumber, lengthDelimited);

      if (writer.fieldOrder() == Writer.FieldOrder.ASCENDING) {
        for (int i = 0; i < group.size(); i++) {
          writer.writeStartGroup(fieldNumber);
          group.get(i).writeTo(writer);
          writer.writeEndGroup(fieldNumber);
        }
      } else {
        for (int i = group.size() - 1; i >= 0; i--) {
          writer.writeEndGroup(fieldNumber);
          group.get(i).writeTo(writer);
          writer.writeStartGroup(fieldNumber);
        }
      }
    }

    /**
     * Serializes the field, including field number, and writes it to {@code writer}, using {@code
     * MessageSet} wire format.
     */
    @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"}) // No iterator allocation.
    private void writeAsMessageSetExtensionTo(int fieldNumber, Writer writer) throws IOException {
      if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
        // Write in descending field order.
        for (int i = lengthDelimited.size() - 1; i >= 0; i--) {
          ByteString value = lengthDelimited.get(i);
          writer.writeMessageSetItem(fieldNumber, value);
        }
      } else {
        // Write in ascending field order.
        for (int i = 0; i < lengthDelimited.size(); i++) {
          ByteString value = lengthDelimited.get(i);
          writer.writeMessageSetItem(fieldNumber, value);
        }
      }
    }

    /**
     * Get the number of bytes required to encode this field, including field number, using {@code
     * MessageSet} wire format.
     */
    @SuppressWarnings({"ForeachList", "ForeachListWithUserVar"}) // No iterator allocation.
    public int getSerializedSizeAsMessageSetExtension(int fieldNumber) {
      int result = 0;
      for (int i = 0; i < lengthDelimited.size(); i++) {
        ByteString value = lengthDelimited.get(i);
        result += CodedOutputStream.computeRawMessageSetExtensionSize(fieldNumber, value);
      }
      return result;
    }

    private LongArrayList varint;
    private IntArrayList fixed32;
    private LongArrayList fixed64;
    private List<ByteString> lengthDelimited;
    private List<UnknownFieldSet> group;

    /**
     * Used to build a {@link Field} within an {@link UnknownFieldSet}.
     *
     * <p>Use {@link Field#newBuilder()} to construct a {@code Builder}.
     */
    public static final class Builder {
      // This constructor should only be called directly from 'create' and 'clone'.
      private Builder() {
        result = new Field();
      }

      private static Builder create() {
        Builder builder = new Builder();
        return builder;
      }

      private Field result;

      @Override
      public Builder clone() {
        Field copy = new Field();
        if (result.varint == null) {
          copy.varint = null;
        } else {
          copy.varint = new LongArrayList(result.varint, /* isMutable= */ true);
        }
        if (result.fixed32 == null) {
          copy.fixed32 = null;
        } else {
          copy.fixed32 = new IntArrayList(result.fixed32, /* isMutable= */ true);
        }
        if (result.fixed64 == null) {
          copy.fixed64 = null;
        } else {
          copy.fixed64 = new LongArrayList(result.fixed64, /* isMutable= */ true);
        }
        if (result.lengthDelimited == null) {
          copy.lengthDelimited = null;
        } else {
          copy.lengthDelimited = new ArrayList<>(result.lengthDelimited);
        }
        if (result.group == null) {
          copy.group = null;
        } else {
          copy.group = new ArrayList<>(result.group);
        }

        Builder clone = new Builder();
        clone.result = copy;
        return clone;
      }

      /**
       * Build the field.
       */
      public Field build() {
        Field built = new Field();
        if (result.varint == null) {
          built.varint = LongArrayList.emptyList();
        } else {
          built.varint = new LongArrayList(result.varint, /* isMutable= */ false);
        }
        if (result.fixed32 == null) {
          built.fixed32 = IntArrayList.emptyList();
        } else {
          built.fixed32 = new IntArrayList(result.fixed32, /* isMutable= */ false);
        }
        if (result.fixed64 == null) {
          built.fixed64 = LongArrayList.emptyList();
        } else {
          built.fixed64 = new LongArrayList(result.fixed64, /* isMutable= */ false);
        }
        if (result.lengthDelimited == null) {
          built.lengthDelimited = Collections.emptyList();
        } else {
          built.lengthDelimited = Collections.unmodifiableList(
              new ArrayList<>(result.lengthDelimited));
        }
        if (result.group == null) {
          built.group = Collections.emptyList();
        } else {
          built.group = Collections.unmodifiableList(new ArrayList<>(result.group));
        }

        return built;
      }

      /** Discard the field's contents. */
      @CanIgnoreReturnValue
      public Builder clear() {
        result = new Field();
        return this;
      }

      /**
       * Merge the values in {@code other} into this field. For each list of values, {@code other}'s
       * values are append to the ones in this field.
       */
      @CanIgnoreReturnValue
      public Builder mergeFrom(Field other) {
        if (!other.varint.isEmpty()) {
          if (result.varint == null) {
            result.varint = new LongArrayList();
          }
          result.varint.addAll(other.varint);
        }
        if (!other.fixed32.isEmpty()) {
          if (result.fixed32 == null) {
            result.fixed32 = new IntArrayList();
          }
          result.fixed32.addAll(other.fixed32);
        }
        if (!other.fixed64.isEmpty()) {
          if (result.fixed64 == null) {
            result.fixed64 = new LongArrayList();
          }
          result.fixed64.addAll(other.fixed64);
        }
        if (!other.lengthDelimited.isEmpty()) {
          if (result.lengthDelimited == null) {
            result.lengthDelimited = new ArrayList<>();
          }
          result.lengthDelimited.addAll(other.lengthDelimited);
        }
        if (!other.group.isEmpty()) {
          if (result.group == null) {
            result.group = new ArrayList<>();
          }
          result.group.addAll(other.group);
        }
        return this;
      }

      /** Add a varint value. */
      @CanIgnoreReturnValue
      public Builder addVarint(long value) {
        if (result.varint == null) {
          result.varint = new LongArrayList();
        }
        result.varint.addLong(value);
        return this;
      }

      /** Add a fixed32 value. */
      @CanIgnoreReturnValue
      public Builder addFixed32(int value) {
        if (result.fixed32 == null) {
          result.fixed32 = new IntArrayList();
        }
        result.fixed32.addInt(value);
        return this;
      }

      /** Add a fixed64 value. */
      @CanIgnoreReturnValue
      public Builder addFixed64(long value) {
        if (result.fixed64 == null) {
          result.fixed64 = new LongArrayList();
        }
        result.fixed64.addLong(value);
        return this;
      }

      /** Add a length-delimited value. */
      @CanIgnoreReturnValue
      public Builder addLengthDelimited(ByteString value) {
        if (result.lengthDelimited == null) {
          result.lengthDelimited = new ArrayList<>();
        }
        result.lengthDelimited.add(value);
        return this;
      }

      /** Add an embedded group. */
      @CanIgnoreReturnValue
      public Builder addGroup(UnknownFieldSet value) {
        if (result.group == null) {
          result.group = new ArrayList<>();
        }
        result.group.add(value);
        return this;
      }
    }
  }

  /** Parser to implement MessageLite interface. */
  public static final class Parser extends AbstractParser<UnknownFieldSet> {
    @Override
    public UnknownFieldSet parsePartialFrom(
        CodedInputStream input, ExtensionRegistryLite extensionRegistry)
        throws InvalidProtocolBufferException {
      Builder builder = newBuilder();
      try {
        builder.mergeFrom(input);
      } catch (InvalidProtocolBufferException e) {
        throw e.setUnfinishedMessage(builder.buildPartial());
      } catch (IOException e) {
        throw new InvalidProtocolBufferException(e).setUnfinishedMessage(builder.buildPartial());
      }
      return builder.buildPartial();
    }
  }

  private static final Parser PARSER = new Parser();

  @Override
  public final Parser getParserForType() {
    return PARSER;
  }
}
