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.WireFormat.FIXED32_SIZE;
  import static com.google.protobuf.WireFormat.FIXED64_SIZE;
  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.util.List;
  import java.util.Map;
  
  /**
   * A {@link Reader} that reads from a buffer containing a message serialized with the binary
   * protocol.
   */
  @ExperimentalApi
  abstract class BinaryReader implements Reader {
    private static final int FIXED32_MULTIPLE_MASK = FIXED32_SIZE - 1;
    private static final int FIXED64_MULTIPLE_MASK = FIXED64_SIZE - 1;
  
    /**
     * Creates a new reader using the given {@code buffer} as input.
     *
     * @param buffer the input buffer. The buffer (including position, limit, etc.) will not be
     *     modified. To increment the buffer position after the read completes, use the value returned
     *     by {@link #getTotalBytesRead()}.
     * @param bufferIsImmutable if {@code true} the reader assumes that the content of {@code buffer}
     *     will never change and any allocated {@link ByteString} instances will by directly wrap
     *     slices of {@code buffer}.
     * @return the reader
     */
    public static BinaryReader newInstance(ByteBuffer buffer, boolean bufferIsImmutable) {
      if (buffer.hasArray()) {
        // TODO(nathanmittler): Add support for unsafe operations.
        return new SafeHeapReader(buffer, bufferIsImmutable);
      }
      // TODO(nathanmittler): Add support for direct buffers
      throw new IllegalArgumentException("Direct buffers not yet supported");
    }
  
    /** Only allow subclassing for inner classes. */
    private BinaryReader() {}
  
    /** Returns the total number of bytes read so far from the input buffer. */
    public abstract int getTotalBytesRead();
  
    @Override
    public boolean shouldDiscardUnknownFields() {
      return false;
    }
  
    /**
     * A {@link BinaryReader} implementation that operates on a heap {@link ByteBuffer}. Uses only
     * safe operations on the underlying array.
     */
    private static final class SafeHeapReader extends BinaryReader {
      private final boolean bufferIsImmutable;
      private final byte[] buffer;
      private int pos;
      private final int initialPos;
      private int limit;
      private int tag;
      private int endGroupTag;
  
      public SafeHeapReader(ByteBuffer bytebuf, boolean bufferIsImmutable) {
        this.bufferIsImmutable = bufferIsImmutable;
        buffer = bytebuf.array();
        initialPos = pos = bytebuf.arrayOffset() + bytebuf.position();
        limit = bytebuf.arrayOffset() + bytebuf.limit();
      }
  
      private boolean isAtEnd() {
        return pos == limit;
      }
  
      @Override
      public int getTotalBytesRead() {
        return pos - initialPos;
      }
  
      @Override
      public int getFieldNumber() throws IOException {
        if (isAtEnd()) {
          return Reader.READ_DONE;
        }
        tag = readVarint32();
        if (tag == endGroupTag) {
          return Reader.READ_DONE;
        }
        return WireFormat.getTagFieldNumber(tag);
      }
  
      @Override
      public int getTag() {
        return tag;
      }
  
      @Override
      public boolean skipField() throws IOException {
        if (isAtEnd() || tag == endGroupTag) {
          return false;
        }
  
        switch (WireFormat.getTagWireType(tag)) {
          case WIRETYPE_VARINT:
            skipVarint();
            return true;
          case WIRETYPE_FIXED64:
            skipBytes(FIXED64_SIZE);
            return true;
          case WIRETYPE_LENGTH_DELIMITED:
            skipBytes(readVarint32());
            return true;
          case WIRETYPE_FIXED32:
            skipBytes(FIXED32_SIZE);
            return true;
          case WIRETYPE_START_GROUP:
            skipGroup();
            return true;
          default:
            throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      @Override
      public double readDouble() throws IOException {
        requireWireType(WIRETYPE_FIXED64);
        return Double.longBitsToDouble(readLittleEndian64());
      }
  
      @Override
      public float readFloat() throws IOException {
        requireWireType(WIRETYPE_FIXED32);
        return Float.intBitsToFloat(readLittleEndian32());
      }
  
      @Override
      public long readUInt64() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint64();
      }
  
      @Override
      public long readInt64() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint64();
      }
  
      @Override
      public int readInt32() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint32();
      }
  
      @Override
      public long readFixed64() throws IOException {
        requireWireType(WIRETYPE_FIXED64);
        return readLittleEndian64();
      }
  
      @Override
      public int readFixed32() throws IOException {
        requireWireType(WIRETYPE_FIXED32);
        return readLittleEndian32();
      }
  
      @Override
      public boolean readBool() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint32() != 0;
      }
  
      @Override
      public String readString() throws IOException {
        return readStringInternal(false);
      }
  
      @Override
      public String readStringRequireUtf8() throws IOException {
        return readStringInternal(true);
      }
  
      public String readStringInternal(boolean requireUtf8) throws IOException {
        requireWireType(WIRETYPE_LENGTH_DELIMITED);
        final int size = readVarint32();
        if (size == 0) {
          return "";
        }
  
        requireBytes(size);
        if (requireUtf8 && !Utf8.isValidUtf8(buffer, pos, pos + size)) {
          throw InvalidProtocolBufferException.invalidUtf8();
        }
        String result = new String(buffer, pos, size, Internal.UTF_8);
        pos += size;
        return result;
      }
  
      @Override
      public <T> T readMessage(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        requireWireType(WIRETYPE_LENGTH_DELIMITED);
        return readMessage(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
      }
  
      @Override
      public <T> T readMessageBySchemaWithCheck(
          Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException {
        requireWireType(WIRETYPE_LENGTH_DELIMITED);
        return readMessage(schema, extensionRegistry);
      }
  
      private <T> T readMessage(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        int size = readVarint32();
        requireBytes(size);
  
        // Update the limit.
        int prevLimit = limit;
        int newLimit = pos + size;
        limit = newLimit;
  
        try {
          // Allocate and read the message.
          T message = schema.newInstance();
          schema.mergeFrom(message, this, extensionRegistry);
          schema.makeImmutable(message);
  
          if (pos != newLimit) {
            throw InvalidProtocolBufferException.parseFailure();
          }
          return message;
        } finally {
          // Restore the limit.
          limit = prevLimit;
        }
      }
  
      @Override
      public <T> T readGroup(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        requireWireType(WIRETYPE_START_GROUP);
        return readGroup(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
      }
  
      @Override
      public <T> T readGroupBySchemaWithCheck(
          Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException {
        requireWireType(WIRETYPE_START_GROUP);
        return readGroup(schema, extensionRegistry);
      }
  
      private <T> T readGroup(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        int prevEndGroupTag = endGroupTag;
        endGroupTag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WIRETYPE_END_GROUP);
  
        try {
          // Allocate and read the message.
          T message = schema.newInstance();
          schema.mergeFrom(message, this, extensionRegistry);
          schema.makeImmutable(message);
  
          if (tag != endGroupTag) {
            throw InvalidProtocolBufferException.parseFailure();
          }
          return message;
        } finally {
          // Restore the old end group tag.
          endGroupTag = prevEndGroupTag;
        }
      }
  
      @Override
      public ByteString readBytes() throws IOException {
        requireWireType(WIRETYPE_LENGTH_DELIMITED);
        int size = readVarint32();
        if (size == 0) {
          return ByteString.EMPTY;
        }
  
        requireBytes(size);
        ByteString bytes =
            bufferIsImmutable
                ? ByteString.wrap(buffer, pos, size)
                : ByteString.copyFrom(buffer, pos, size);
        pos += size;
        return bytes;
      }
  
      @Override
      public int readUInt32() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint32();
      }
  
      @Override
      public int readEnum() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return readVarint32();
      }
  
      @Override
      public int readSFixed32() throws IOException {
        requireWireType(WIRETYPE_FIXED32);
        return readLittleEndian32();
      }
  
      @Override
      public long readSFixed64() throws IOException {
        requireWireType(WIRETYPE_FIXED64);
        return readLittleEndian64();
      }
  
      @Override
      public int readSInt32() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return CodedInputStream.decodeZigZag32(readVarint32());
      }
  
      @Override
      public long readSInt64() throws IOException {
        requireWireType(WIRETYPE_VARINT);
        return CodedInputStream.decodeZigZag64(readVarint64());
      }
  
      @Override
      public void readDoubleList(List<Double> target) throws IOException {
        if (target instanceof DoubleArrayList) {
          DoubleArrayList plist = (DoubleArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addDouble(Double.longBitsToDouble(readLittleEndian64_NoCheck()));
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                plist.addDouble(readDouble());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(Double.longBitsToDouble(readLittleEndian64_NoCheck()));
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                target.add(readDouble());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readFloatList(List<Float> target) throws IOException {
        if (target instanceof FloatArrayList) {
          FloatArrayList plist = (FloatArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addFloat(Float.intBitsToFloat(readLittleEndian32_NoCheck()));
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                plist.addFloat(readFloat());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(Float.intBitsToFloat(readLittleEndian32_NoCheck()));
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                target.add(readFloat());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readUInt64List(List<Long> target) throws IOException {
        if (target instanceof LongArrayList) {
          LongArrayList plist = (LongArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addLong(readVarint64());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addLong(readUInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint64());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readUInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readInt64List(List<Long> target) throws IOException {
        if (target instanceof LongArrayList) {
          LongArrayList plist = (LongArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addLong(readVarint64());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addLong(readInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint64());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readInt32List(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(readVarint32());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addInt(readInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint32());
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readFixed64List(List<Long> target) throws IOException {
        if (target instanceof LongArrayList) {
          LongArrayList plist = (LongArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addLong(readLittleEndian64_NoCheck());
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                plist.addLong(readFixed64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readLittleEndian64_NoCheck());
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                target.add(readFixed64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readFixed32List(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(readLittleEndian32_NoCheck());
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                plist.addInt(readFixed32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readLittleEndian32_NoCheck());
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                target.add(readFixed32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readBoolList(List<Boolean> target) throws IOException {
        if (target instanceof BooleanArrayList) {
          BooleanArrayList plist = (BooleanArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addBoolean(readVarint32() != 0);
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addBoolean(readBool());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint32() != 0);
              }
              requirePosition(fieldEndPos);
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readBool());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readStringList(List<String> target) throws IOException {
        readStringListInternal(target, false);
      }
  
      @Override
      public void readStringListRequireUtf8(List<String> target) throws IOException {
        readStringListInternal(target, true);
      }
  
      public void readStringListInternal(List<String> target, boolean requireUtf8)
          throws IOException {
        if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
          throw InvalidProtocolBufferException.invalidWireType();
        }
  
        if (target instanceof LazyStringList && !requireUtf8) {
          LazyStringList lazyList = (LazyStringList) target;
          while (true) {
            lazyList.add(readBytes());
  
            if (isAtEnd()) {
              return;
            }
            int prevPos = pos;
            int nextTag = readVarint32();
            if (nextTag != tag) {
              // We've reached the end of the repeated field. Rewind the buffer position to before
              // the new tag.
              pos = prevPos;
              return;
            }
          }
        } else {
          while (true) {
            target.add(readStringInternal(requireUtf8));
  
            if (isAtEnd()) {
              return;
            }
            int prevPos = pos;
            int nextTag = readVarint32();
            if (nextTag != tag) {
              // We've reached the end of the repeated field. Rewind the buffer position to before
              // the new tag.
              pos = prevPos;
              return;
            }
          }
        }
      }
  
      @Override
      public <T> void readMessageList(
          List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
        readMessageList(target, schema, extensionRegistry);
      }
  
      @Override
      public <T> void readMessageList(
          List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
          throw InvalidProtocolBufferException.invalidWireType();
        }
        final int listTag = tag;
        while (true) {
          target.add(readMessage(schema, extensionRegistry));
  
          if (isAtEnd()) {
            return;
          }
          int prevPos = pos;
          int nextTag = readVarint32();
          if (nextTag != listTag) {
            // We've reached the end of the repeated field. Rewind the buffer position to before
            // the new tag.
            pos = prevPos;
            return;
          }
        }
      }
  
      @Override
      public <T> void readGroupList(
          List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
        readGroupList(target, schema, extensionRegistry);
      }
  
      @Override
      public <T> void readGroupList(
          List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
          throws IOException {
        if (WireFormat.getTagWireType(tag) != WIRETYPE_START_GROUP) {
          throw InvalidProtocolBufferException.invalidWireType();
        }
        final int listTag = tag;
        while (true) {
          target.add(readGroup(schema, extensionRegistry));
  
          if (isAtEnd()) {
            return;
          }
          int prevPos = pos;
          int nextTag = readVarint32();
          if (nextTag != listTag) {
            // We've reached the end of the repeated field. Rewind the buffer position to before
            // the new tag.
            pos = prevPos;
            return;
          }
        }
      }
  
      @Override
      public void readBytesList(List<ByteString> target) throws IOException {
        if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
          throw InvalidProtocolBufferException.invalidWireType();
        }
  
        while (true) {
          target.add(readBytes());
  
          if (isAtEnd()) {
            return;
          }
          int prevPos = pos;
          int nextTag = readVarint32();
          if (nextTag != tag) {
            // We've reached the end of the repeated field. Rewind the buffer position to before
            // the new tag.
            pos = prevPos;
            return;
          }
        }
      }
  
      @Override
      public void readUInt32List(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(readVarint32());
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addInt(readUInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint32());
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readUInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readEnumList(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(readVarint32());
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addInt(readEnum());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readVarint32());
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readEnum());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readSFixed32List(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(readLittleEndian32_NoCheck());
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                plist.addInt(readSFixed32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed32Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readLittleEndian32_NoCheck());
              }
              break;
            case WIRETYPE_FIXED32:
              while (true) {
                target.add(readSFixed32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readSFixed64List(List<Long> target) throws IOException {
        if (target instanceof LongArrayList) {
          LongArrayList plist = (LongArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addLong(readLittleEndian64_NoCheck());
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                plist.addLong(readSFixed64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              verifyPackedFixed64Length(bytes);
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(readLittleEndian64_NoCheck());
              }
              break;
            case WIRETYPE_FIXED64:
              while (true) {
                target.add(readSFixed64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readSInt32List(List<Integer> target) throws IOException {
        if (target instanceof IntArrayList) {
          IntArrayList plist = (IntArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addInt(CodedInputStream.decodeZigZag32(readVarint32()));
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addInt(readSInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(CodedInputStream.decodeZigZag32(readVarint32()));
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readSInt32());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @Override
      public void readSInt64List(List<Long> target) throws IOException {
        if (target instanceof LongArrayList) {
          LongArrayList plist = (LongArrayList) target;
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                plist.addLong(CodedInputStream.decodeZigZag64(readVarint64()));
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                plist.addLong(readSInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        } else {
          switch (WireFormat.getTagWireType(tag)) {
            case WIRETYPE_LENGTH_DELIMITED:
              final int bytes = readVarint32();
              final int fieldEndPos = pos + bytes;
              while (pos < fieldEndPos) {
                target.add(CodedInputStream.decodeZigZag64(readVarint64()));
              }
              break;
            case WIRETYPE_VARINT:
              while (true) {
                target.add(readSInt64());
  
                if (isAtEnd()) {
                  return;
                }
                int prevPos = pos;
                int nextTag = readVarint32();
                if (nextTag != tag) {
                  // We've reached the end of the repeated field. Rewind the buffer position to before
                  // the new tag.
                  pos = prevPos;
                  return;
                }
              }
            default:
              throw InvalidProtocolBufferException.invalidWireType();
          }
        }
      }
  
      @SuppressWarnings("unchecked")
      @Override
      public <K, V> void readMap(
          Map<K, V> target,
          MapEntryLite.Metadata<K, V> metadata,
          ExtensionRegistryLite extensionRegistry)
          throws IOException {
        requireWireType(WIRETYPE_LENGTH_DELIMITED);
        int size = readVarint32();
        requireBytes(size);
  
        // Update the limit.
        int prevLimit = limit;
        int newLimit = pos + size;
        limit = newLimit;
  
        try {
          K key = metadata.defaultKey;
          V value = metadata.defaultValue;
          while (true) {
            int number = getFieldNumber();
            if (number == READ_DONE) {
              break;
            }
            try {
              switch (number) {
                case 1:
                  key = (K) readField(metadata.keyType, null, null);
                  break;
                case 2:
                  value =
                      (V)
                          readField(
                              metadata.valueType,
                              metadata.defaultValue.getClass(),
                              extensionRegistry);
                  break;
                default:
                  if (!skipField()) {
                    throw new InvalidProtocolBufferException("Unable to parse map entry.");
                  }
                  break;
              }
            } catch (InvalidProtocolBufferException.InvalidWireTypeException ignore) {
              // the type doesn't match, skip the field.
              if (!skipField()) {
                throw new InvalidProtocolBufferException("Unable to parse map entry.");
              }
            }
          }
          target.put(key, value);
        } finally {
          // Restore the limit.
          limit = prevLimit;
        }
      }
  
      private Object readField(
          WireFormat.FieldType fieldType,
          Class<?> messageType,
          ExtensionRegistryLite extensionRegistry)
          throws IOException {
        switch (fieldType) {
          case BOOL:
            return readBool();
          case BYTES:
            return readBytes();
          case DOUBLE:
            return readDouble();
          case ENUM:
            return readEnum();
          case FIXED32:
            return readFixed32();
          case FIXED64:
            return readFixed64();
          case FLOAT:
            return readFloat();
          case INT32:
            return readInt32();
          case INT64:
            return readInt64();
          case MESSAGE:
            return readMessage(messageType, extensionRegistry);
          case SFIXED32:
            return readSFixed32();
          case SFIXED64:
            return readSFixed64();
          case SINT32:
            return readSInt32();
          case SINT64:
            return readSInt64();
          case STRING:
            return readStringRequireUtf8();
          case UINT32:
            return readUInt32();
          case UINT64:
            return readUInt64();
          default:
            throw new RuntimeException("unsupported field type.");
        }
      }
  
      /** Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. */
      private int readVarint32() throws IOException {
        // See implementation notes for readRawVarint64
        int i = pos;
  
        if (limit == pos) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        int x;
        if ((x = buffer[i++]) >= 0) {
          pos = i;
          return x;
        } else if (limit - i < 9) {
          return (int) readVarint64SlowPath();
        } else if ((x ^= (buffer[i++] << 7)) < 0) {
          x ^= (~0 << 7);
        } else if ((x ^= (buffer[i++] << 14)) >= 0) {
          x ^= (~0 << 7) ^ (~0 << 14);
        } else if ((x ^= (buffer[i++] << 21)) < 0) {
          x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
        } else {
          int y = buffer[i++];
          x ^= y << 28;
          x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
          if (y < 0
              && buffer[i++] < 0
              && buffer[i++] < 0
              && buffer[i++] < 0
              && buffer[i++] < 0
              && buffer[i++] < 0) {
            throw InvalidProtocolBufferException.malformedVarint();
          }
        }
        pos = i;
        return x;
      }
  
      public long readVarint64() throws IOException {
        // Implementation notes:
        //
        // Optimized for one-byte values, expected to be common.
        // The particular code below was selected from various candidates
        // empirically, by winning VarintBenchmark.
        //
        // Sign extension of (signed) Java bytes is usually a nuisance, but
        // we exploit it here to more easily obtain the sign of bytes read.
        // Instead of cleaning up the sign extension bits by masking eagerly,
        // we delay until we find the final (positive) byte, when we clear all
        // accumulated bits with one xor.  We depend on javac to constant fold.
        int i = pos;
  
        if (limit == i) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
  
        final byte[] buffer = this.buffer;
        long x;
        int y;
        if ((y = buffer[i++]) >= 0) {
          pos = i;
          return y;
        } else if (limit - i < 9) {
          return readVarint64SlowPath();
        } else if ((y ^= (buffer[i++] << 7)) < 0) {
          x = y ^ (~0 << 7);
        } else if ((y ^= (buffer[i++] << 14)) >= 0) {
          x = y ^ ((~0 << 7) ^ (~0 << 14));
        } else if ((y ^= (buffer[i++] << 21)) < 0) {
          x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
        } else if ((x = y ^ ((long) buffer[i++] << 28)) >= 0L) {
          x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
        } else if ((x ^= ((long) buffer[i++] << 35)) < 0L) {
          x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
        } else if ((x ^= ((long) buffer[i++] << 42)) >= 0L) {
          x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
        } else if ((x ^= ((long) buffer[i++] << 49)) < 0L) {
          x ^=
              (~0L << 7)
                  ^ (~0L << 14)
                  ^ (~0L << 21)
                  ^ (~0L << 28)
                  ^ (~0L << 35)
                  ^ (~0L << 42)
                  ^ (~0L << 49);
        } else {
          x ^= ((long) buffer[i++] << 56);
          x ^=
              (~0L << 7)
                  ^ (~0L << 14)
                  ^ (~0L << 21)
                  ^ (~0L << 28)
                  ^ (~0L << 35)
                  ^ (~0L << 42)
                  ^ (~0L << 49)
                  ^ (~0L << 56);
          if (x < 0L) {
            if (buffer[i++] < 0L) {
              throw InvalidProtocolBufferException.malformedVarint();
            }
          }
        }
        pos = i;
        return x;
      }
  
      private long readVarint64SlowPath() throws IOException {
        long result = 0;
        for (int shift = 0; shift < 64; shift += 7) {
          final byte b = readByte();
          result |= (long) (b & 0x7F) << shift;
          if ((b & 0x80) == 0) {
            return result;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      private byte readByte() throws IOException {
        if (pos == limit) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
        return buffer[pos++];
      }
  
      private int readLittleEndian32() throws IOException {
        requireBytes(FIXED32_SIZE);
        return readLittleEndian32_NoCheck();
      }
  
      private long readLittleEndian64() throws IOException {
        requireBytes(FIXED64_SIZE);
        return readLittleEndian64_NoCheck();
      }
  
      private int readLittleEndian32_NoCheck() {
        int p = pos;
        final byte[] buffer = this.buffer;
        pos = p + FIXED32_SIZE;
        return (((buffer[p] & 0xff))
            | ((buffer[p + 1] & 0xff) << 8)
            | ((buffer[p + 2] & 0xff) << 16)
            | ((buffer[p + 3] & 0xff) << 24));
      }
  
      private long readLittleEndian64_NoCheck() {
        int p = pos;
        final byte[] buffer = this.buffer;
        pos = p + FIXED64_SIZE;
        return (((buffer[p] & 0xffL))
            | ((buffer[p + 1] & 0xffL) << 8)
            | ((buffer[p + 2] & 0xffL) << 16)
            | ((buffer[p + 3] & 0xffL) << 24)
            | ((buffer[p + 4] & 0xffL) << 32)
            | ((buffer[p + 5] & 0xffL) << 40)
            | ((buffer[p + 6] & 0xffL) << 48)
            | ((buffer[p + 7] & 0xffL) << 56));
      }
  
      private void skipVarint() throws IOException {
        if (limit - pos >= 10) {
          final byte[] buffer = this.buffer;
          int p = pos;
          for (int i = 0; i < 10; i++) {
            if (buffer[p++] >= 0) {
              pos = p;
              return;
            }
          }
        }
        skipVarintSlowPath();
      }
  
      private void skipVarintSlowPath() throws IOException {
        for (int i = 0; i < 10; i++) {
          if (readByte() >= 0) {
            return;
          }
        }
        throw InvalidProtocolBufferException.malformedVarint();
      }
  
      private void skipBytes(final int size) throws IOException {
        requireBytes(size);
  
        pos += size;
      }
  
      private void skipGroup() throws IOException {
        int prevEndGroupTag = endGroupTag;
        endGroupTag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WIRETYPE_END_GROUP);
        while (true) {
          if (getFieldNumber() == READ_DONE || !skipField()) {
            break;
          }
        }
        if (tag != endGroupTag) {
          throw InvalidProtocolBufferException.parseFailure();
        }
        endGroupTag = prevEndGroupTag;
      }
  
      private void requireBytes(int size) throws IOException {
        if (size < 0 || size > (limit - pos)) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
      }
  
      private void requireWireType(int requiredWireType) throws IOException {
        if (WireFormat.getTagWireType(tag) != requiredWireType) {
          throw InvalidProtocolBufferException.invalidWireType();
        }
      }
  
      private void verifyPackedFixed64Length(int bytes) throws IOException {
        requireBytes(bytes);
        if ((bytes & FIXED64_MULTIPLE_MASK) != 0) {
          // Require that the number of bytes be a multiple of 8.
          throw InvalidProtocolBufferException.parseFailure();
        }
      }
  
      private void verifyPackedFixed32Length(int bytes) throws IOException {
        requireBytes(bytes);
        if ((bytes & FIXED32_MULTIPLE_MASK) != 0) {
          // Require that the number of bytes be a multiple of 4.
          throw InvalidProtocolBufferException.parseFailure();
        }
      }
  
      private void requirePosition(int expectedPosition) throws IOException {
        if (pos != expectedPosition) {
          throw InvalidProtocolBufferException.truncatedMessage();
        }
      }
    }
  }
  