// 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 static com.google.protobuf.ArrayDecoders.decodeBoolList;
import static com.google.protobuf.ArrayDecoders.decodeBytes;
import static com.google.protobuf.ArrayDecoders.decodeBytesList;
import static com.google.protobuf.ArrayDecoders.decodeDouble;
import static com.google.protobuf.ArrayDecoders.decodeDoubleList;
import static com.google.protobuf.ArrayDecoders.decodeExtensionOrUnknownField;
import static com.google.protobuf.ArrayDecoders.decodeFixed32;
import static com.google.protobuf.ArrayDecoders.decodeFixed32List;
import static com.google.protobuf.ArrayDecoders.decodeFixed64;
import static com.google.protobuf.ArrayDecoders.decodeFixed64List;
import static com.google.protobuf.ArrayDecoders.decodeFloat;
import static com.google.protobuf.ArrayDecoders.decodeFloatList;
import static com.google.protobuf.ArrayDecoders.decodeGroupList;
import static com.google.protobuf.ArrayDecoders.decodeMessageField;
import static com.google.protobuf.ArrayDecoders.decodeMessageList;
import static com.google.protobuf.ArrayDecoders.decodePackedBoolList;
import static com.google.protobuf.ArrayDecoders.decodePackedDoubleList;
import static com.google.protobuf.ArrayDecoders.decodePackedFixed32List;
import static com.google.protobuf.ArrayDecoders.decodePackedFixed64List;
import static com.google.protobuf.ArrayDecoders.decodePackedFloatList;
import static com.google.protobuf.ArrayDecoders.decodePackedSInt32List;
import static com.google.protobuf.ArrayDecoders.decodePackedSInt64List;
import static com.google.protobuf.ArrayDecoders.decodePackedVarint32List;
import static com.google.protobuf.ArrayDecoders.decodePackedVarint64List;
import static com.google.protobuf.ArrayDecoders.decodeSInt32List;
import static com.google.protobuf.ArrayDecoders.decodeSInt64List;
import static com.google.protobuf.ArrayDecoders.decodeString;
import static com.google.protobuf.ArrayDecoders.decodeStringList;
import static com.google.protobuf.ArrayDecoders.decodeStringListRequireUtf8;
import static com.google.protobuf.ArrayDecoders.decodeStringRequireUtf8;
import static com.google.protobuf.ArrayDecoders.decodeUnknownField;
import static com.google.protobuf.ArrayDecoders.decodeVarint32;
import static com.google.protobuf.ArrayDecoders.decodeVarint32List;
import static com.google.protobuf.ArrayDecoders.decodeVarint64;
import static com.google.protobuf.ArrayDecoders.decodeVarint64List;
import static com.google.protobuf.ArrayDecoders.mergeGroupField;
import static com.google.protobuf.ArrayDecoders.mergeMessageField;
import static com.google.protobuf.ArrayDecoders.skipField;

import com.google.protobuf.ArrayDecoders.Registers;
import com.google.protobuf.ByteString.CodedBuilder;
import com.google.protobuf.FieldSet.FieldDescriptorLite;
import com.google.protobuf.Internal.EnumVerifier;
import com.google.protobuf.Internal.ProtobufList;
import com.google.protobuf.MapEntryLite.Metadata;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/** Schema used for standard messages. */
@CheckReturnValue
final class MessageSchema<T> implements Schema<T> {
  private static final int INTS_PER_FIELD = 3;
  private static final int OFFSET_BITS = 20;
  private static final int OFFSET_MASK = 0XFFFFF;
  private static final int FIELD_TYPE_MASK = 0x0FF00000;
  private static final int REQUIRED_MASK = 0x10000000;
  private static final int ENFORCE_UTF8_MASK = 0x20000000;
  private static final int LEGACY_ENUM_IS_CLOSED_MASK = 0x80000000;
  private static final int NO_PRESENCE_SENTINEL = -1 & OFFSET_MASK;
  private static final int[] EMPTY_INT_ARRAY = new int[0];

  /**
   * Bit masks for field type extra feature bits encoded in Java gencode via
   * GetExperimentalJavaFieldType in helpers.cc.
   */
  private static final int REQUIRED_BIT = 0x100;

  private static final int UTF8_CHECK_BIT = 0x200;
  private static final int CHECK_INITIALIZED_BIT = 0x400;
  private static final int LEGACY_ENUM_IS_CLOSED_BIT = 0x800;
  private static final int HAS_HAS_BIT = 0x1000;

  /** An offset applied to the field type ID for scalar fields that are a member of a oneof. */
  static final int ONEOF_TYPE_OFFSET = 51 /* FieldType.MAP + 1 */;

  /**
   * Keep a direct reference to the unsafe object so we don't need to go through the UnsafeUtil
   * wrapper for every call.
   */
  private static final sun.misc.Unsafe UNSAFE = UnsafeUtil.getUnsafe();

  /**
   * Holds all information for accessing the message fields. The layout is as follows (field
   * positions are relative to the offset of the start of the field in the buffer):
   *
   * <p>
   *
   * <pre>
   * buffer[i]
   * [ 0 -    3] unused
   * [ 4 -   31] field number
   *
   * buffer[i+1]
   * [32 -   33] unused
   * [34 -   34] whether UTF-8 enforcement should be applied to a string field.
   * [35 -   35] whether the field is required
   * [36 -   43] field type / for oneof: field type + {@link #ONEOF_TYPE_OFFSET}
   * [44 -   63] field offset / oneof value field offset
   *
   * buffer[i+2]
   * [64 -   69] unused
   * [70 -   75] field presence mask shift (unused for oneof/repeated fields)
   * [76 -   95] presence field offset / oneof case field offset / cached size field offset
   * </pre>
   *
   * Offset refer to the field offsets returned by Unsafe.objectFieldOffset() for reflective access
   * to corresponding field.
   *
   * <p>Note that presence field offset can only use 20 bits - 1. All bits set to 1 is the sentinel
   * value for non-presence. This is not validated at runtime, we simply assume message layouts will
   * not exceed 1MB (assuming ~10 bytes per field, that implies 100k fields which should hit other
   * javac limits first). This corresponds to a shared bitFieldN_, which must have the field
   * presence mask shift applied to get the corresponding field's presence.
   */
  private final int[] buffer;

  /**
   * Holds object references for fields. For each field entry in {@code buffer}, there are two
   * corresponding entries in this array. The content is different from different field types:
   *
   * <pre>
   *   Map fields:
   *     objects[pos] = map default entry instance
   *     objects[pos + 1] = EnumVerifier if map value is enum, or message class reference if map
   *                        value is message.
   *   Message fields:
   *     objects[pos] = null or cached message schema
   *     objects[pos + 1] = message class reference
   *   Enum fields:
   *     objects[pos] = null
   *     objects[pos + 1] = EnumVerifier
   * </pre>
   */
  private final Object[] objects;

  private final int minFieldNumber;
  private final int maxFieldNumber;

  private final MessageLite defaultInstance;
  private final boolean hasExtensions;
  private final boolean lite;
  private final ProtoSyntax syntax;
  // TODO: Make both full-runtime and lite-runtime support cached field size.
  private final boolean useCachedSizeField;

  /** Represents [checkInitialized positions, map field positions, repeated field offsets]. */
  private final int[] intArray;

  /**
   * Values at indices 0 -> checkInitializedCount in intArray are positions to check for
   * initialization.
   */
  private final int checkInitializedCount;

  /**
   * Values at indices checkInitializedCount -> repeatedFieldOffsetStart are map positions.
   * Everything after that are repeated field offsets.
   */
  private final int repeatedFieldOffsetStart;

  private final NewInstanceSchema newInstanceSchema;
  private final ListFieldSchema listFieldSchema;
  private final UnknownFieldSchema<?, ?> unknownFieldSchema;
  private final ExtensionSchema<?> extensionSchema;
  private final MapFieldSchema mapFieldSchema;

  private MessageSchema(
      int[] buffer,
      Object[] objects,
      int minFieldNumber,
      int maxFieldNumber,
      MessageLite defaultInstance,
      ProtoSyntax syntax,
      boolean useCachedSizeField,
      int[] intArray,
      int checkInitialized,
      int mapFieldPositions,
      NewInstanceSchema newInstanceSchema,
      ListFieldSchema listFieldSchema,
      UnknownFieldSchema<?, ?> unknownFieldSchema,
      ExtensionSchema<?> extensionSchema,
      MapFieldSchema mapFieldSchema) {
    this.buffer = buffer;
    this.objects = objects;
    this.minFieldNumber = minFieldNumber;
    this.maxFieldNumber = maxFieldNumber;

    this.lite = defaultInstance instanceof GeneratedMessageLite;
    this.syntax = syntax;
    this.hasExtensions = extensionSchema != null && extensionSchema.hasExtensions(defaultInstance);
    this.useCachedSizeField = useCachedSizeField;

    this.intArray = intArray;
    this.checkInitializedCount = checkInitialized;
    this.repeatedFieldOffsetStart = mapFieldPositions;

    this.newInstanceSchema = newInstanceSchema;
    this.listFieldSchema = listFieldSchema;
    this.unknownFieldSchema = unknownFieldSchema;
    this.extensionSchema = extensionSchema;
    this.defaultInstance = defaultInstance;
    this.mapFieldSchema = mapFieldSchema;
  }

  static <T> MessageSchema<T> newSchema(
      Class<T> messageClass,
      MessageInfo messageInfo,
      NewInstanceSchema newInstanceSchema,
      ListFieldSchema listFieldSchema,
      UnknownFieldSchema<?, ?> unknownFieldSchema,
      ExtensionSchema<?> extensionSchema,
      MapFieldSchema mapFieldSchema) {
    if (messageInfo instanceof RawMessageInfo) {
      return newSchemaForRawMessageInfo(
          (RawMessageInfo) messageInfo,
          newInstanceSchema,
          listFieldSchema,
          unknownFieldSchema,
          extensionSchema,
          mapFieldSchema);

    } else {
      return newSchemaForMessageInfo(
          (StructuralMessageInfo) messageInfo,
          newInstanceSchema,
          listFieldSchema,
          unknownFieldSchema,
          extensionSchema,
          mapFieldSchema);
    }
  }

  static <T> MessageSchema<T> newSchemaForRawMessageInfo(
      RawMessageInfo messageInfo,
      NewInstanceSchema newInstanceSchema,
      ListFieldSchema listFieldSchema,
      UnknownFieldSchema<?, ?> unknownFieldSchema,
      ExtensionSchema<?> extensionSchema,
      MapFieldSchema mapFieldSchema) {
    String info = messageInfo.getStringInfo();
    final int length = info.length();
    int i = 0;

    int next = info.charAt(i++);
    if (next >= 0xD800) {
      int result = next & 0x1FFF;
      int shift = 13;
      while ((next = info.charAt(i++)) >= 0xD800) {
        result |= (next & 0x1FFF) << shift;
        shift += 13;
      }
      next = result | (next << shift);
    }
    final int unusedFlags = next;

    next = info.charAt(i++);
    if (next >= 0xD800) {
      int result = next & 0x1FFF;
      int shift = 13;
      while ((next = info.charAt(i++)) >= 0xD800) {
        result |= (next & 0x1FFF) << shift;
        shift += 13;
      }
      next = result | (next << shift);
    }
    final int fieldCount = next;

    final int oneofCount;
    final int hasBitsCount;
    final int minFieldNumber;
    final int maxFieldNumber;
    final int numEntries;
    final int mapFieldCount;
    final int repeatedFieldCount;
    final int checkInitialized;
    final int[] intArray;
    int objectsPosition;
    if (fieldCount == 0) {
      oneofCount = 0;
      hasBitsCount = 0;
      minFieldNumber = 0;
      maxFieldNumber = 0;
      numEntries = 0;
      mapFieldCount = 0;
      repeatedFieldCount = 0;
      checkInitialized = 0;
      intArray = EMPTY_INT_ARRAY;
      objectsPosition = 0;
    } else {
      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      oneofCount = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      hasBitsCount = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      minFieldNumber = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      maxFieldNumber = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      numEntries = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      mapFieldCount = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      repeatedFieldCount = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      checkInitialized = next;
      intArray = new int[checkInitialized + mapFieldCount + repeatedFieldCount];
      // Field objects are after a list of (oneof, oneofCase) pairs  + a list of hasbits fields.
      objectsPosition = oneofCount * 2 + hasBitsCount;
    }

    final sun.misc.Unsafe unsafe = UNSAFE;
    final Object[] messageInfoObjects = messageInfo.getObjects();
    int checkInitializedPosition = 0;
    final Class<?> messageClass = messageInfo.getDefaultInstance().getClass();
    int[] buffer = new int[numEntries * INTS_PER_FIELD];
    Object[] objects = new Object[numEntries * 2];

    int mapFieldIndex = checkInitialized;
    int repeatedFieldIndex = checkInitialized + mapFieldCount;

    int bufferIndex = 0;
    while (i < length) {
      final int fieldNumber;
      final int fieldTypeWithExtraBits;
      final int fieldType;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      fieldNumber = next;

      next = info.charAt(i++);
      if (next >= 0xD800) {
        int result = next & 0x1FFF;
        int shift = 13;
        while ((next = info.charAt(i++)) >= 0xD800) {
          result |= (next & 0x1FFF) << shift;
          shift += 13;
        }
        next = result | (next << shift);
      }
      fieldTypeWithExtraBits = next;
      fieldType = fieldTypeWithExtraBits & 0xFF;

      if ((fieldTypeWithExtraBits & CHECK_INITIALIZED_BIT) != 0) {
        intArray[checkInitializedPosition++] = bufferIndex;
      }

      final int fieldOffset;
      final int presenceMaskShift;
      final int presenceFieldOffset;

      // Oneof
      if (fieldType >= ONEOF_TYPE_OFFSET) {
        next = info.charAt(i++);
        if (next >= 0xD800) {
          int result = next & 0x1FFF;
          int shift = 13;
          while ((next = info.charAt(i++)) >= 0xD800) {
            result |= (next & 0x1FFF) << shift;
            shift += 13;
          }
          next = result | (next << shift);
        }
        int oneofIndex = next;

        final int oneofFieldType = fieldType - ONEOF_TYPE_OFFSET;
        if (oneofFieldType == 9 /* FieldType.MESSAGE */
            || oneofFieldType == 17 /* FieldType.GROUP */) {
          objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
        } else if (oneofFieldType == 12 /* FieldType.ENUM */) {
          // TODO: Remove proto2 check once legacy gencode not setting this bit
          // no longer needs to be supported.
          if (messageInfo.getSyntax().equals(ProtoSyntax.PROTO2)
              || (fieldTypeWithExtraBits & LEGACY_ENUM_IS_CLOSED_BIT) != 0) {
            objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
          }
        }

        final java.lang.reflect.Field oneofField;
        int index = oneofIndex * 2;
        Object o = messageInfoObjects[index];
        if (o instanceof java.lang.reflect.Field) {
          oneofField = (java.lang.reflect.Field) o;
        } else {
          oneofField = reflectField(messageClass, (String) o);
          // Memoize java.lang.reflect.Field instances for oneof/hasbits fields, since they're
          // potentially used for many Protobuf fields.  Since there's a 1-1 mapping from the
          // Protobuf field to the Java Field for non-oneofs, there's no benefit for memoizing
          // those.
          messageInfoObjects[index] = oneofField;
        }

        fieldOffset = (int) unsafe.objectFieldOffset(oneofField);

        final java.lang.reflect.Field oneofCaseField;
        index++;
        o = messageInfoObjects[index];
        if (o instanceof java.lang.reflect.Field) {
          oneofCaseField = (java.lang.reflect.Field) o;
        } else {
          oneofCaseField = reflectField(messageClass, (String) o);
          messageInfoObjects[index] = oneofCaseField;
        }

        presenceFieldOffset = (int) unsafe.objectFieldOffset(oneofCaseField);
        presenceMaskShift = 0;
      } else {
        Field field = reflectField(messageClass, (String) messageInfoObjects[objectsPosition++]);
        if (fieldType == 9 /* FieldType.MESSAGE */ || fieldType == 17 /* FieldType.GROUP */) {
          objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = field.getType();
        } else if (fieldType == 27 /* FieldType.MESSAGE_LIST */
            || fieldType == 49 /* FieldType.GROUP_LIST */) {
          objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
        } else if (fieldType == 12 /* FieldType.ENUM */
            || fieldType == 30 /* FieldType.ENUM_LIST */
            || fieldType == 44 /* FieldType.ENUM_LIST_PACKED */) {
          // TODO: Remove proto2 check once legacy gencode not setting this bit
          // no longer needs to be supported.
          if (messageInfo.getSyntax() == ProtoSyntax.PROTO2
              || (fieldTypeWithExtraBits & LEGACY_ENUM_IS_CLOSED_BIT) != 0) {
            objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
          }
        } else if (fieldType == 50 /* FieldType.MAP */) {
          intArray[mapFieldIndex++] = bufferIndex;
          objects[bufferIndex / INTS_PER_FIELD * 2] = messageInfoObjects[objectsPosition++];
          if ((fieldTypeWithExtraBits & LEGACY_ENUM_IS_CLOSED_BIT) != 0) {
            objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
          }
        }

        fieldOffset = (int) unsafe.objectFieldOffset(field);
        boolean hasHasBit = (fieldTypeWithExtraBits & HAS_HAS_BIT) != 0;
        if (hasHasBit && fieldType <= 17 /* FieldType.GROUP */) {
          next = info.charAt(i++);
          if (next >= 0xD800) {
            int result = next & 0x1FFF;
            int shift = 13;
            while ((next = info.charAt(i++)) >= 0xD800) {
              result |= (next & 0x1FFF) << shift;
              shift += 13;
            }
            next = result | (next << shift);
          }
          int hasBitsIndex = next;

          final java.lang.reflect.Field hasBitsField;
          int index = oneofCount * 2 + hasBitsIndex / 32;
          Object o = messageInfoObjects[index];
          if (o instanceof java.lang.reflect.Field) {
            hasBitsField = (java.lang.reflect.Field) o;
          } else {
            hasBitsField = reflectField(messageClass, (String) o);
            messageInfoObjects[index] = hasBitsField;
          }

          presenceFieldOffset = (int) unsafe.objectFieldOffset(hasBitsField);
          presenceMaskShift = hasBitsIndex % 32;
        } else {
          presenceFieldOffset = NO_PRESENCE_SENTINEL;
          presenceMaskShift = 0;
        }

        if (fieldType >= 18 && fieldType <= 49) {
          // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
          // 49 (GROUP_LIST).
          intArray[repeatedFieldIndex++] = fieldOffset;
        }
      }

      buffer[bufferIndex++] = fieldNumber;
      buffer[bufferIndex++] =
          ((fieldTypeWithExtraBits & UTF8_CHECK_BIT) != 0 ? ENFORCE_UTF8_MASK : 0)
              | ((fieldTypeWithExtraBits & REQUIRED_BIT) != 0 ? REQUIRED_MASK : 0)
              | ((fieldTypeWithExtraBits & LEGACY_ENUM_IS_CLOSED_BIT) != 0
                  ? LEGACY_ENUM_IS_CLOSED_MASK
                  : 0)
              | (fieldType << OFFSET_BITS)
              | fieldOffset;
      buffer[bufferIndex++] = (presenceMaskShift << OFFSET_BITS) | presenceFieldOffset;
    }

    return new MessageSchema<T>(
        buffer,
        objects,
        minFieldNumber,
        maxFieldNumber,
        messageInfo.getDefaultInstance(),
        messageInfo.getSyntax(),
        /* useCachedSizeField= */ false,
        intArray,
        checkInitialized,
        checkInitialized + mapFieldCount,
        newInstanceSchema,
        listFieldSchema,
        unknownFieldSchema,
        extensionSchema,
        mapFieldSchema);
  }

  private static java.lang.reflect.Field reflectField(Class<?> messageClass, String fieldName) {
    try {
      return messageClass.getDeclaredField(fieldName);
    } catch (NoSuchFieldException e) {
      // Some Samsung devices lie about what fields are present via the getDeclaredField API so
      // we do the for loop properly that they seem to have messed up...
      java.lang.reflect.Field[] fields = messageClass.getDeclaredFields();
      for (java.lang.reflect.Field field : fields) {
        if (fieldName.equals(field.getName())) {
          return field;
        }
      }

      // If we make it here, the runtime still lies about what we know to be true at compile
      // time. We throw to alert server monitoring for further remediation.
      throw new RuntimeException(
          "Field "
              + fieldName
              + " for "
              + messageClass.getName()
              + " not found. Known fields are "
              + Arrays.toString(fields));
    }
  }

  static <T> MessageSchema<T> newSchemaForMessageInfo(
      StructuralMessageInfo messageInfo,
      NewInstanceSchema newInstanceSchema,
      ListFieldSchema listFieldSchema,
      UnknownFieldSchema<?, ?> unknownFieldSchema,
      ExtensionSchema<?> extensionSchema,
      MapFieldSchema mapFieldSchema) {
    FieldInfo[] fis = messageInfo.getFields();
    final int minFieldNumber;
    final int maxFieldNumber;
    if (fis.length == 0) {
      minFieldNumber = 0;
      maxFieldNumber = 0;
    } else {
      minFieldNumber = fis[0].getFieldNumber();
      maxFieldNumber = fis[fis.length - 1].getFieldNumber();
    }

    final int numEntries = fis.length;

    int[] buffer = new int[numEntries * INTS_PER_FIELD];
    Object[] objects = new Object[numEntries * 2];

    int mapFieldCount = 0;
    int repeatedFieldCount = 0;
    for (FieldInfo fi : fis) {
      if (fi.getType() == FieldType.MAP) {
        mapFieldCount++;
      } else if (fi.getType().id() >= 18 && fi.getType().id() <= 49) {
        // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
        // 49 (GROUP_LIST).
        repeatedFieldCount++;
      }
    }
    int[] mapFieldPositions = mapFieldCount > 0 ? new int[mapFieldCount] : null;
    int[] repeatedFieldOffsets = repeatedFieldCount > 0 ? new int[repeatedFieldCount] : null;
    mapFieldCount = 0;
    repeatedFieldCount = 0;

    int[] checkInitialized = messageInfo.getCheckInitialized();
    if (checkInitialized == null) {
      checkInitialized = EMPTY_INT_ARRAY;
    }
    int checkInitializedIndex = 0;
    // Fill in the manifest data from the descriptors.
    int fieldIndex = 0;
    for (int bufferIndex = 0; fieldIndex < fis.length; bufferIndex += INTS_PER_FIELD) {
      final FieldInfo fi = fis[fieldIndex];
      final int fieldNumber = fi.getFieldNumber();

      // We found the entry for the next field. Store the entry in the manifest for
      // this field and increment the field index.
      storeFieldData(fi, buffer, bufferIndex, objects);

      // Convert field number to index
      if (checkInitializedIndex < checkInitialized.length
          && checkInitialized[checkInitializedIndex] == fieldNumber) {
        checkInitialized[checkInitializedIndex++] = bufferIndex;
      }

      if (fi.getType() == FieldType.MAP) {
        mapFieldPositions[mapFieldCount++] = bufferIndex;
      } else if (fi.getType().id() >= 18 && fi.getType().id() <= 49) {
        // Field types of repeated fields are in a consecutive range from 18 (DOUBLE_LIST) to
        // 49 (GROUP_LIST).
        repeatedFieldOffsets[repeatedFieldCount++] =
            (int) UnsafeUtil.objectFieldOffset(fi.getField());
      }

      fieldIndex++;
    }

    if (mapFieldPositions == null) {
      mapFieldPositions = EMPTY_INT_ARRAY;
    }
    if (repeatedFieldOffsets == null) {
      repeatedFieldOffsets = EMPTY_INT_ARRAY;
    }
    int[] combined =
        new int[checkInitialized.length + mapFieldPositions.length + repeatedFieldOffsets.length];
    System.arraycopy(checkInitialized, 0, combined, 0, checkInitialized.length);
    System.arraycopy(
        mapFieldPositions, 0, combined, checkInitialized.length, mapFieldPositions.length);
    System.arraycopy(
        repeatedFieldOffsets,
        0,
        combined,
        checkInitialized.length + mapFieldPositions.length,
        repeatedFieldOffsets.length);

    return new MessageSchema<T>(
        buffer,
        objects,
        minFieldNumber,
        maxFieldNumber,
        messageInfo.getDefaultInstance(),
        messageInfo.getSyntax(),
        /* useCachedSizeField= */ true,
        combined,
        checkInitialized.length,
        checkInitialized.length + mapFieldPositions.length,
        newInstanceSchema,
        listFieldSchema,
        unknownFieldSchema,
        extensionSchema,
        mapFieldSchema);
  }

  private static void storeFieldData(
      FieldInfo fi, int[] buffer, int bufferIndex, Object[] objects) {
    final int fieldOffset;
    final int typeId;
    final int presenceMaskShift;
    final int presenceFieldOffset;

    OneofInfo oneof = fi.getOneof();
    if (oneof != null) {
      typeId = fi.getType().id() + ONEOF_TYPE_OFFSET;
      fieldOffset = (int) UnsafeUtil.objectFieldOffset(oneof.getValueField());
      presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(oneof.getCaseField());
      presenceMaskShift = 0;
    } else {
      FieldType type = fi.getType();
      fieldOffset = (int) UnsafeUtil.objectFieldOffset(fi.getField());
      typeId = type.id();
      if (!type.isList() && !type.isMap()) {
        Field presenceField = fi.getPresenceField();
        if (presenceField == null) {
          presenceFieldOffset = NO_PRESENCE_SENTINEL;
        } else {
          presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(presenceField);
        }
        presenceMaskShift = Integer.numberOfTrailingZeros(fi.getPresenceMask());
      } else {
        if (fi.getCachedSizeField() == null) {
          presenceFieldOffset = 0;
          presenceMaskShift = 0;
        } else {
          presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(fi.getCachedSizeField());
          presenceMaskShift = 0;
        }
      }
    }

    buffer[bufferIndex] = fi.getFieldNumber();
    buffer[bufferIndex + 1] =
        (fi.isEnforceUtf8() ? ENFORCE_UTF8_MASK : 0)
            | (fi.isRequired() ? REQUIRED_MASK : 0)
            | (typeId << OFFSET_BITS)
            | fieldOffset;
    buffer[bufferIndex + 2] = (presenceMaskShift << OFFSET_BITS) | presenceFieldOffset;

    Object messageFieldClass = fi.getMessageFieldClass();
    if (fi.getMapDefaultEntry() != null) {
      objects[bufferIndex / INTS_PER_FIELD * 2] = fi.getMapDefaultEntry();
      if (messageFieldClass != null) {
        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageFieldClass;
      } else if (fi.getEnumVerifier() != null) {
        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = fi.getEnumVerifier();
      }
    } else {
      if (messageFieldClass != null) {
        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageFieldClass;
      } else if (fi.getEnumVerifier() != null) {
        objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = fi.getEnumVerifier();
      }
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  public T newInstance() {
    return (T) newInstanceSchema.newInstance(defaultInstance);
  }

  @Override
  public boolean equals(T message, T other) {
    final int bufferLength = buffer.length;
    for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
      if (!equals(message, other, pos)) {
        return false;
      }
    }

    Object messageUnknown = unknownFieldSchema.getFromMessage(message);
    Object otherUnknown = unknownFieldSchema.getFromMessage(other);
    if (!messageUnknown.equals(otherUnknown)) {
      return false;
    }

    if (hasExtensions) {
      FieldSet<?> messageExtensions = extensionSchema.getExtensions(message);
      FieldSet<?> otherExtensions = extensionSchema.getExtensions(other);
      return messageExtensions.equals(otherExtensions);
    }
    return true;
  }

  private boolean equals(T message, T other, int pos) {
    final int typeAndOffset = typeAndOffsetAt(pos);
    final long offset = offset(typeAndOffset);

    switch (type(typeAndOffset)) {
      case 0: // DOUBLE:
        return arePresentForEquals(message, other, pos)
            && Double.doubleToLongBits(UnsafeUtil.getDouble(message, offset))
                == Double.doubleToLongBits(UnsafeUtil.getDouble(other, offset));
      case 1: // FLOAT:
        return arePresentForEquals(message, other, pos)
            && Float.floatToIntBits(UnsafeUtil.getFloat(message, offset))
                == Float.floatToIntBits(UnsafeUtil.getFloat(other, offset));
      case 2: // INT64:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
      case 3: // UINT64:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
      case 4: // INT32:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 5: // FIXED64:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
      case 6: // FIXED32:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 7: // BOOL:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getBoolean(message, offset) == UnsafeUtil.getBoolean(other, offset);
      case 8: // STRING:
        return arePresentForEquals(message, other, pos)
            && SchemaUtil.safeEquals(
                UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      case 9: // MESSAGE:
        return arePresentForEquals(message, other, pos)
            && SchemaUtil.safeEquals(
                UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      case 10: // BYTES:
        return arePresentForEquals(message, other, pos)
            && SchemaUtil.safeEquals(
                UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      case 11: // UINT32:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 12: // ENUM:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 13: // SFIXED32:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 14: // SFIXED64:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
      case 15: // SINT32:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getInt(message, offset) == UnsafeUtil.getInt(other, offset);
      case 16: // SINT64:
        return arePresentForEquals(message, other, pos)
            && UnsafeUtil.getLong(message, offset) == UnsafeUtil.getLong(other, offset);
      case 17: // GROUP:
        return arePresentForEquals(message, other, pos)
            && SchemaUtil.safeEquals(
                UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));

      case 18: // DOUBLE_LIST:
      case 19: // FLOAT_LIST:
      case 20: // INT64_LIST:
      case 21: // UINT64_LIST:
      case 22: // INT32_LIST:
      case 23: // FIXED64_LIST:
      case 24: // FIXED32_LIST:
      case 25: // BOOL_LIST:
      case 26: // STRING_LIST:
      case 27: // MESSAGE_LIST:
      case 28: // BYTES_LIST:
      case 29: // UINT32_LIST:
      case 30: // ENUM_LIST:
      case 31: // SFIXED32_LIST:
      case 32: // SFIXED64_LIST:
      case 33: // SINT32_LIST:
      case 34: // SINT64_LIST:
      case 35: // DOUBLE_LIST_PACKED:
      case 36: // FLOAT_LIST_PACKED:
      case 37: // INT64_LIST_PACKED:
      case 38: // UINT64_LIST_PACKED:
      case 39: // INT32_LIST_PACKED:
      case 40: // FIXED64_LIST_PACKED:
      case 41: // FIXED32_LIST_PACKED:
      case 42: // BOOL_LIST_PACKED:
      case 43: // UINT32_LIST_PACKED:
      case 44: // ENUM_LIST_PACKED:
      case 45: // SFIXED32_LIST_PACKED:
      case 46: // SFIXED64_LIST_PACKED:
      case 47: // SINT32_LIST_PACKED:
      case 48: // SINT64_LIST_PACKED:
      case 49: // GROUP_LIST:
        return SchemaUtil.safeEquals(
            UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      case 50: // MAP:
        return SchemaUtil.safeEquals(
            UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      case 51: // ONEOF_DOUBLE:
      case 52: // ONEOF_FLOAT:
      case 53: // ONEOF_INT64:
      case 54: // ONEOF_UINT64:
      case 55: // ONEOF_INT32:
      case 56: // ONEOF_FIXED64:
      case 57: // ONEOF_FIXED32:
      case 58: // ONEOF_BOOL:
      case 59: // ONEOF_STRING:
      case 60: // ONEOF_MESSAGE:
      case 61: // ONEOF_BYTES:
      case 62: // ONEOF_UINT32:
      case 63: // ONEOF_ENUM:
      case 64: // ONEOF_SFIXED32:
      case 65: // ONEOF_SFIXED64:
      case 66: // ONEOF_SINT32:
      case 67: // ONEOF_SINT64:
      case 68: // ONEOF_GROUP:
        return isOneofCaseEqual(message, other, pos)
            && SchemaUtil.safeEquals(
                UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(other, offset));
      default:
        // Assume it's an empty entry - just go to the next entry.
        return true;
    }
  }

  @Override
  public int hashCode(T message) {
    int hashCode = 0;
    final int bufferLength = buffer.length;
    for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
      final int typeAndOffset = typeAndOffsetAt(pos);
      final int entryNumber = numberAt(pos);

      final long offset = offset(typeAndOffset);

      switch (type(typeAndOffset)) {
        case 0: // DOUBLE:
          hashCode =
              (hashCode * 53)
                  + Internal.hashLong(
                      Double.doubleToLongBits(UnsafeUtil.getDouble(message, offset)));
          break;
        case 1: // FLOAT:
          hashCode = (hashCode * 53) + Float.floatToIntBits(UnsafeUtil.getFloat(message, offset));
          break;
        case 2: // INT64:
          hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
          break;
        case 3: // UINT64:
          hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
          break;
        case 4: // INT32:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 5: // FIXED64:
          hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
          break;
        case 6: // FIXED32:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 7: // BOOL:
          hashCode = (hashCode * 53) + Internal.hashBoolean(UnsafeUtil.getBoolean(message, offset));
          break;
        case 8: // STRING:
          hashCode = (hashCode * 53) + ((String) UnsafeUtil.getObject(message, offset)).hashCode();
          break;
        case 9: // MESSAGE:
          {
            int protoHash = 37;
            Object submessage = UnsafeUtil.getObject(message, offset);
            if (submessage != null) {
              protoHash = submessage.hashCode();
            }
            hashCode = (53 * hashCode) + protoHash;
            break;
          }
        case 10: // BYTES:
          hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
          break;
        case 11: // UINT32:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 12: // ENUM:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 13: // SFIXED32:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 14: // SFIXED64:
          hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
          break;
        case 15: // SINT32:
          hashCode = (hashCode * 53) + (UnsafeUtil.getInt(message, offset));
          break;
        case 16: // SINT64:
          hashCode = (hashCode * 53) + Internal.hashLong(UnsafeUtil.getLong(message, offset));
          break;

        case 17: // GROUP:
          {
            int protoHash = 37;
            Object submessage = UnsafeUtil.getObject(message, offset);
            if (submessage != null) {
              protoHash = submessage.hashCode();
            }
            hashCode = (53 * hashCode) + protoHash;
            break;
          }
        case 18: // DOUBLE_LIST:
        case 19: // FLOAT_LIST:
        case 20: // INT64_LIST:
        case 21: // UINT64_LIST:
        case 22: // INT32_LIST:
        case 23: // FIXED64_LIST:
        case 24: // FIXED32_LIST:
        case 25: // BOOL_LIST:
        case 26: // STRING_LIST:
        case 27: // MESSAGE_LIST:
        case 28: // BYTES_LIST:
        case 29: // UINT32_LIST:
        case 30: // ENUM_LIST:
        case 31: // SFIXED32_LIST:
        case 32: // SFIXED64_LIST:
        case 33: // SINT32_LIST:
        case 34: // SINT64_LIST:
        case 35: // DOUBLE_LIST_PACKED:
        case 36: // FLOAT_LIST_PACKED:
        case 37: // INT64_LIST_PACKED:
        case 38: // UINT64_LIST_PACKED:
        case 39: // INT32_LIST_PACKED:
        case 40: // FIXED64_LIST_PACKED:
        case 41: // FIXED32_LIST_PACKED:
        case 42: // BOOL_LIST_PACKED:
        case 43: // UINT32_LIST_PACKED:
        case 44: // ENUM_LIST_PACKED:
        case 45: // SFIXED32_LIST_PACKED:
        case 46: // SFIXED64_LIST_PACKED:
        case 47: // SINT32_LIST_PACKED:
        case 48: // SINT64_LIST_PACKED:
        case 49: // GROUP_LIST:
          hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
          break;
        case 50: // MAP:
          hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
          break;
        case 51: // ONEOF_DOUBLE:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode =
                (hashCode * 53)
                    + Internal.hashLong(Double.doubleToLongBits(oneofDoubleAt(message, offset)));
          }
          break;
        case 52: // ONEOF_FLOAT:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Float.floatToIntBits(oneofFloatAt(message, offset));
          }
          break;
        case 53: // ONEOF_INT64:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
          }
          break;
        case 54: // ONEOF_UINT64:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
          }
          break;
        case 55: // ONEOF_INT32:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 56: // ONEOF_FIXED64:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
          }
          break;
        case 57: // ONEOF_FIXED32:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 58: // ONEOF_BOOL:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashBoolean(oneofBooleanAt(message, offset));
          }
          break;
        case 59: // ONEOF_STRING:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode =
                (hashCode * 53) + ((String) UnsafeUtil.getObject(message, offset)).hashCode();
          }
          break;
        case 60: // ONEOF_MESSAGE:
          if (isOneofPresent(message, entryNumber, pos)) {
            Object submessage = UnsafeUtil.getObject(message, offset);
            hashCode = (53 * hashCode) + submessage.hashCode();
          }
          break;
        case 61: // ONEOF_BYTES:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + UnsafeUtil.getObject(message, offset).hashCode();
          }
          break;
        case 62: // ONEOF_UINT32:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 63: // ONEOF_ENUM:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 64: // ONEOF_SFIXED32:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 65: // ONEOF_SFIXED64:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
          }
          break;
        case 66: // ONEOF_SINT32:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + (oneofIntAt(message, offset));
          }
          break;
        case 67: // ONEOF_SINT64:
          if (isOneofPresent(message, entryNumber, pos)) {
            hashCode = (hashCode * 53) + Internal.hashLong(oneofLongAt(message, offset));
          }
          break;
        case 68: // ONEOF_GROUP:
          if (isOneofPresent(message, entryNumber, pos)) {
            Object submessage = UnsafeUtil.getObject(message, offset);
            hashCode = (53 * hashCode) + submessage.hashCode();
          }
          break;
        default:
          // Assume it's an empty entry - just go to the next entry.
          break;
      }
    }

    hashCode = (hashCode * 53) + unknownFieldSchema.getFromMessage(message).hashCode();

    if (hasExtensions) {
      hashCode = (hashCode * 53) + extensionSchema.getExtensions(message).hashCode();
    }

    return hashCode;
  }

  @Override
  public void mergeFrom(T message, T other) {
    checkMutable(message);
    if (other == null) {
      throw new NullPointerException();
    }
    for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
      // A separate method allows for better JIT optimizations
      mergeSingleField(message, other, i);
    }

    SchemaUtil.mergeUnknownFields(unknownFieldSchema, message, other);

    if (hasExtensions) {
      SchemaUtil.mergeExtensions(extensionSchema, message, other);
    }
  }

  private void mergeSingleField(T message, T other, int pos) {
    final int typeAndOffset = typeAndOffsetAt(pos);
    final long offset = offset(typeAndOffset);
    final int number = numberAt(pos);

    switch (type(typeAndOffset)) {
      case 0: // DOUBLE:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putDouble(message, offset, UnsafeUtil.getDouble(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 1: // FLOAT:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putFloat(message, offset, UnsafeUtil.getFloat(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 2: // INT64:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 3: // UINT64:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 4: // INT32:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 5: // FIXED64:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 6: // FIXED32:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 7: // BOOL:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putBoolean(message, offset, UnsafeUtil.getBoolean(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 8: // STRING:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 9: // MESSAGE:
        mergeMessage(message, other, pos);
        break;
      case 10: // BYTES:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 11: // UINT32:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 12: // ENUM:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 13: // SFIXED32:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 14: // SFIXED64:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 15: // SINT32:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 16: // SINT64:
        if (isFieldPresent(other, pos)) {
          UnsafeUtil.putLong(message, offset, UnsafeUtil.getLong(other, offset));
          setFieldPresent(message, pos);
        }
        break;
      case 17: // GROUP:
        mergeMessage(message, other, pos);
        break;
      case 18: // DOUBLE_LIST:
      case 19: // FLOAT_LIST:
      case 20: // INT64_LIST:
      case 21: // UINT64_LIST:
      case 22: // INT32_LIST:
      case 23: // FIXED64_LIST:
      case 24: // FIXED32_LIST:
      case 25: // BOOL_LIST:
      case 26: // STRING_LIST:
      case 27: // MESSAGE_LIST:
      case 28: // BYTES_LIST:
      case 29: // UINT32_LIST:
      case 30: // ENUM_LIST:
      case 31: // SFIXED32_LIST:
      case 32: // SFIXED64_LIST:
      case 33: // SINT32_LIST:
      case 34: // SINT64_LIST:
      case 35: // DOUBLE_LIST_PACKED:
      case 36: // FLOAT_LIST_PACKED:
      case 37: // INT64_LIST_PACKED:
      case 38: // UINT64_LIST_PACKED:
      case 39: // INT32_LIST_PACKED:
      case 40: // FIXED64_LIST_PACKED:
      case 41: // FIXED32_LIST_PACKED:
      case 42: // BOOL_LIST_PACKED:
      case 43: // UINT32_LIST_PACKED:
      case 44: // ENUM_LIST_PACKED:
      case 45: // SFIXED32_LIST_PACKED:
      case 46: // SFIXED64_LIST_PACKED:
      case 47: // SINT32_LIST_PACKED:
      case 48: // SINT64_LIST_PACKED:
      case 49: // GROUP_LIST:
        listFieldSchema.mergeListsAt(message, other, offset);
        break;
      case 50: // MAP:
        SchemaUtil.mergeMap(mapFieldSchema, message, other, offset);
        break;
      case 51: // ONEOF_DOUBLE:
      case 52: // ONEOF_FLOAT:
      case 53: // ONEOF_INT64:
      case 54: // ONEOF_UINT64:
      case 55: // ONEOF_INT32:
      case 56: // ONEOF_FIXED64:
      case 57: // ONEOF_FIXED32:
      case 58: // ONEOF_BOOL:
      case 59: // ONEOF_STRING:
        if (isOneofPresent(other, number, pos)) {
          UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
          setOneofPresent(message, number, pos);
        }
        break;

      case 60: // ONEOF_MESSAGE:
        mergeOneofMessage(message, other, pos);
        break;
      case 61: // ONEOF_BYTES:
      case 62: // ONEOF_UINT32:
      case 63: // ONEOF_ENUM:
      case 64: // ONEOF_SFIXED32:
      case 65: // ONEOF_SFIXED64:
      case 66: // ONEOF_SINT32:
      case 67: // ONEOF_SINT64:
        if (isOneofPresent(other, number, pos)) {
          UnsafeUtil.putObject(message, offset, UnsafeUtil.getObject(other, offset));
          setOneofPresent(message, number, pos);
        }
        break;
      case 68: // ONEOF_GROUP:
        mergeOneofMessage(message, other, pos);
        break;
      default:
        break;
    }
  }

  private void mergeMessage(T targetParent, T sourceParent, int pos) {
    if (!isFieldPresent(sourceParent, pos)) {
      return;
    }

    final int typeAndOffset = typeAndOffsetAt(pos);
    final long offset = offset(typeAndOffset);

    final Object source = UNSAFE.getObject(sourceParent, offset);
    if (source == null) {
      throw new IllegalStateException(
          "Source subfield " + numberAt(pos) + " is present but null: " + sourceParent);
    }

    final Schema fieldSchema = getMessageFieldSchema(pos);
    if (!isFieldPresent(targetParent, pos)) {
      if (!isMutable(source)) {
        // Can safely share source if it is immutable
        UNSAFE.putObject(targetParent, offset, source);
      } else {
        // Make a safetey copy of source
        final Object copyOfSource = fieldSchema.newInstance();
        fieldSchema.mergeFrom(copyOfSource, source);
        UNSAFE.putObject(targetParent, offset, copyOfSource);
      }
      setFieldPresent(targetParent, pos);
      return;
    }

    // Sub-message is present, merge from source
    Object target = UNSAFE.getObject(targetParent, offset);
    if (!isMutable(target)) {
      Object newInstance = fieldSchema.newInstance();
      fieldSchema.mergeFrom(newInstance, target);
      UNSAFE.putObject(targetParent, offset, newInstance);
      target = newInstance;
    }
    fieldSchema.mergeFrom(target, source);
  }

  private void mergeOneofMessage(T targetParent, T sourceParent, int pos) {
    int number = numberAt(pos);
    if (!isOneofPresent(sourceParent, number, pos)) {
      return;
    }

    long offset = offset(typeAndOffsetAt(pos));
    final Object source = UNSAFE.getObject(sourceParent, offset);
    if (source == null) {
      throw new IllegalStateException(
          "Source subfield " + numberAt(pos) + " is present but null: " + sourceParent);
    }

    final Schema fieldSchema = getMessageFieldSchema(pos);
    if (!isOneofPresent(targetParent, number, pos)) {
      if (!isMutable(source)) {
        // Can safely share source if it is immutable
        UNSAFE.putObject(targetParent, offset, source);
      } else {
        // Make a safety copy of theirs
        final Object copyOfSource = fieldSchema.newInstance();
        fieldSchema.mergeFrom(copyOfSource, source);
        UNSAFE.putObject(targetParent, offset, copyOfSource);
      }
      setOneofPresent(targetParent, number, pos);
      return;
    }

    // Sub-message is present, merge from source
    Object target = UNSAFE.getObject(targetParent, offset);
    if (!isMutable(target)) {
      Object newInstance = fieldSchema.newInstance();
      fieldSchema.mergeFrom(newInstance, target);
      UNSAFE.putObject(targetParent, offset, newInstance);
      target = newInstance;
    }
    fieldSchema.mergeFrom(target, source);
  }

  @Override
  @SuppressWarnings("unchecked") // Field type checks guarantee type casts from Unsafe.
  public int getSerializedSize(T message) {
    int size = 0;

    final sun.misc.Unsafe unsafe = UNSAFE;
    int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
    int currentPresenceField = 0;
    for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
      final int typeAndOffset = typeAndOffsetAt(i);
      final int fieldType = type(typeAndOffset);
      final int number = numberAt(i);

      int presenceMask = 0;
      int presenceMaskAndOffset = buffer[i + 2];
      final int presenceOrCachedSizeFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
      if (fieldType <= 17) {
        // Performance optimization to cache the presence field which is shared for multiple
        // fields.
        // TODO: Improve caching for case when fields alternate between having and not
        // having presence by caching presence field for last field with presence only.
        if (presenceOrCachedSizeFieldOffset != currentPresenceFieldOffset) {
          currentPresenceFieldOffset = presenceOrCachedSizeFieldOffset;
          currentPresenceField =
              currentPresenceFieldOffset == NO_PRESENCE_SENTINEL
                  ? 0
                  : unsafe.getInt(message, (long) currentPresenceFieldOffset);
        }
        // Mask for presence bit of the current field from the shared presence field.
        presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
      }

      final long offset = offset(typeAndOffset);
      final int cachedSizeOffset =
          fieldType >= FieldType.DOUBLE_LIST_PACKED.id()
                  && fieldType <= FieldType.SINT64_LIST_PACKED.id()
              ? presenceOrCachedSizeFieldOffset
              : 0;

      switch (fieldType) {
        case 0: // DOUBLE:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeDoubleSize(number, 0);
          }
          break;
        case 1: // FLOAT:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeFloatSize(number, 0);
          }
          break;
        case 2: // INT64:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeInt64Size(number, unsafe.getLong(message, offset));
          }
          break;
        case 3: // UINT64:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeUInt64Size(number, unsafe.getLong(message, offset));
          }
          break;
        case 4: // INT32:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeInt32Size(number, unsafe.getInt(message, offset));
          }
          break;
        case 5: // FIXED64:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeFixed64Size(number, 0);
          }
          break;
        case 6: // FIXED32:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeFixed32Size(number, 0);
          }
          break;
        case 7: // BOOL:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeBoolSize(number, true);
          }
          break;
        case 8: // STRING:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            Object value = unsafe.getObject(message, offset);
            if (value instanceof ByteString) {
              size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
            } else {
              size += CodedOutputStream.computeStringSize(number, (String) value);
            }
          }
          break;
        case 9: // MESSAGE:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            Object value = unsafe.getObject(message, offset);
            size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
          }
          break;
        case 10: // BYTES:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            ByteString value = (ByteString) unsafe.getObject(message, offset);
            size += CodedOutputStream.computeBytesSize(number, value);
          }
          break;
        case 11: // UINT32:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeUInt32Size(number, unsafe.getInt(message, offset));
          }
          break;
        case 12: // ENUM:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeEnumSize(number, unsafe.getInt(message, offset));
          }
          break;
        case 13: // SFIXED32:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeSFixed32Size(number, 0);
          }
          break;
        case 14: // SFIXED64:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeSFixed64Size(number, 0);
          }
          break;
        case 15: // SINT32:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeSInt32Size(number, unsafe.getInt(message, offset));
          }
          break;
        case 16: // SINT64:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size += CodedOutputStream.computeSInt64Size(number, unsafe.getLong(message, offset));
          }
          break;
        case 17: // GROUP:
          if (isFieldPresent(
              message, i, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            size +=
                CodedOutputStream.computeGroupSize(
                    number,
                    (MessageLite) unsafe.getObject(message, offset),
                    getMessageFieldSchema(i));
          }
          break;
        case 18: // DOUBLE_LIST:
          size +=
              SchemaUtil.computeSizeFixed64List(
                  number, (List<?>) unsafe.getObject(message, offset), false);
          break;
        case 19: // FLOAT_LIST:
          size +=
              SchemaUtil.computeSizeFixed32List(
                  number, (List<?>) unsafe.getObject(message, offset), false);
          break;
        case 20: // INT64_LIST:
          size +=
              SchemaUtil.computeSizeInt64List(
                  number, (List<Long>) unsafe.getObject(message, offset), false);
          break;
        case 21: // UINT64_LIST:
          size +=
              SchemaUtil.computeSizeUInt64List(
                  number, (List<Long>) unsafe.getObject(message, offset), false);
          break;
        case 22: // INT32_LIST:
          size +=
              SchemaUtil.computeSizeInt32List(
                  number, (List<Integer>) unsafe.getObject(message, offset), false);
          break;
        case 23: // FIXED64_LIST:
          size +=
              SchemaUtil.computeSizeFixed64List(
                  number, (List<?>) unsafe.getObject(message, offset), false);
          break;
        case 24: // FIXED32_LIST:
          size +=
              SchemaUtil.computeSizeFixed32List(
                  number, (List<?>) unsafe.getObject(message, offset), false);
          break;
        case 25: // BOOL_LIST:
          size +=
              SchemaUtil.computeSizeBoolList(
                  number, (List<?>) unsafe.getObject(message, offset), false);
          break;
        case 26: // STRING_LIST:
          size +=
              SchemaUtil.computeSizeStringList(number, (List<?>) unsafe.getObject(message, offset));
          break;
        case 27: // MESSAGE_LIST:
          size +=
              SchemaUtil.computeSizeMessageList(
                  number, (List<?>) unsafe.getObject(message, offset), getMessageFieldSchema(i));
          break;
        case 28: // BYTES_LIST:
          size +=
              SchemaUtil.computeSizeByteStringList(
                  number, (List<ByteString>) unsafe.getObject(message, offset));
          break;
        case 29: // UINT32_LIST:
          size +=
              SchemaUtil.computeSizeUInt32List(
                  number, (List<Integer>) unsafe.getObject(message, offset), false);
          break;
        case 30: // ENUM_LIST:
          size +=
              SchemaUtil.computeSizeEnumList(
                  number, (List<Integer>) unsafe.getObject(message, offset), false);
          break;
        case 31: // SFIXED32_LIST:
          size +=
              SchemaUtil.computeSizeFixed32List(
                  number, (List<Integer>) unsafe.getObject(message, offset), false);
          break;
        case 32: // SFIXED64_LIST:
          size +=
              SchemaUtil.computeSizeFixed64List(
                  number, (List<Long>) unsafe.getObject(message, offset), false);
          break;
        case 33: // SINT32_LIST:
          size +=
              SchemaUtil.computeSizeSInt32List(
                  number, (List<Integer>) unsafe.getObject(message, offset), false);
          break;
        case 34: // SINT64_LIST:
          size +=
              SchemaUtil.computeSizeSInt64List(
                  number, (List<Long>) unsafe.getObject(message, offset), false);
          break;
        case 35:
          { // DOUBLE_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed64ListNoTag(
                    (List<Double>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 36:
          { // FLOAT_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed32ListNoTag(
                    (List<Float>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 37:
          { // INT64_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeInt64ListNoTag(
                    (List<Long>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 38:
          { // UINT64_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeUInt64ListNoTag(
                    (List<Long>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 39:
          { // INT32_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeInt32ListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 40:
          { // FIXED64_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed64ListNoTag(
                    (List<Long>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 41:
          { // FIXED32_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed32ListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 42:
          { // BOOL_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeBoolListNoTag(
                    (List<Boolean>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 43:
          { // UINT32_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeUInt32ListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 44:
          { // ENUM_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeEnumListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 45:
          { // SFIXED32_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed32ListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 46:
          { // SFIXED64_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeFixed64ListNoTag(
                    (List<Long>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 47:
          { // SINT32_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeSInt32ListNoTag(
                    (List<Integer>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 48:
          { // SINT64_LIST_PACKED:
            int fieldSize =
                SchemaUtil.computeSizeSInt64ListNoTag(
                    (List<Long>) unsafe.getObject(message, offset));
            if (fieldSize > 0) {
              if (useCachedSizeField) {
                unsafe.putInt(message, (long) cachedSizeOffset, fieldSize);
              }
              size +=
                  CodedOutputStream.computeTagSize(number)
                      + CodedOutputStream.computeUInt32SizeNoTag(fieldSize)
                      + fieldSize;
            }
            break;
          }
        case 49: // GROUP_LIST:
          size +=
              SchemaUtil.computeSizeGroupList(
                  number,
                  (List<MessageLite>) unsafe.getObject(message, offset),
                  getMessageFieldSchema(i));
          break;
        case 50: // MAP:
          // TODO: Use schema cache.
          size +=
              mapFieldSchema.getSerializedSize(
                  number, unsafe.getObject(message, offset), getMapFieldDefaultEntry(i));
          break;
        case 51: // ONEOF_DOUBLE:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeDoubleSize(number, 0);
          }
          break;
        case 52: // ONEOF_FLOAT:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeFloatSize(number, 0);
          }
          break;
        case 53: // ONEOF_INT64:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeInt64Size(number, oneofLongAt(message, offset));
          }
          break;
        case 54: // ONEOF_UINT64:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeUInt64Size(number, oneofLongAt(message, offset));
          }
          break;
        case 55: // ONEOF_INT32:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeInt32Size(number, oneofIntAt(message, offset));
          }
          break;
        case 56: // ONEOF_FIXED64:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeFixed64Size(number, 0);
          }
          break;
        case 57: // ONEOF_FIXED32:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeFixed32Size(number, 0);
          }
          break;
        case 58: // ONEOF_BOOL:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeBoolSize(number, true);
          }
          break;
        case 59: // ONEOF_STRING:
          if (isOneofPresent(message, number, i)) {
            Object value = unsafe.getObject(message, offset);
            if (value instanceof ByteString) {
              size += CodedOutputStream.computeBytesSize(number, (ByteString) value);
            } else {
              size += CodedOutputStream.computeStringSize(number, (String) value);
            }
          }
          break;
        case 60: // ONEOF_MESSAGE:
          if (isOneofPresent(message, number, i)) {
            Object value = unsafe.getObject(message, offset);
            size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
          }
          break;
        case 61: // ONEOF_BYTES:
          if (isOneofPresent(message, number, i)) {
            size +=
                CodedOutputStream.computeBytesSize(
                    number, (ByteString) unsafe.getObject(message, offset));
          }
          break;
        case 62: // ONEOF_UINT32:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeUInt32Size(number, oneofIntAt(message, offset));
          }
          break;
        case 63: // ONEOF_ENUM:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeEnumSize(number, oneofIntAt(message, offset));
          }
          break;
        case 64: // ONEOF_SFIXED32:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeSFixed32Size(number, 0);
          }
          break;
        case 65: // ONEOF_SFIXED64:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeSFixed64Size(number, 0);
          }
          break;
        case 66: // ONEOF_SINT32:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeSInt32Size(number, oneofIntAt(message, offset));
          }
          break;
        case 67: // ONEOF_SINT64:
          if (isOneofPresent(message, number, i)) {
            size += CodedOutputStream.computeSInt64Size(number, oneofLongAt(message, offset));
          }
          break;
        case 68: // ONEOF_GROUP:
          if (isOneofPresent(message, number, i)) {
            size +=
                CodedOutputStream.computeGroupSize(
                    number,
                    (MessageLite) unsafe.getObject(message, offset),
                    getMessageFieldSchema(i));
          }
          break;
        default:
          // Assume it's an empty entry.
      }
    }

    size += getUnknownFieldsSerializedSize(unknownFieldSchema, message);

    if (hasExtensions) {
      size += extensionSchema.getExtensions(message).getSerializedSize();
    }

    return size;
  }

  private <UT, UB> int getUnknownFieldsSerializedSize(
      UnknownFieldSchema<UT, UB> schema, T message) {
    UT unknowns = schema.getFromMessage(message);
    return schema.getSerializedSize(unknowns);
  }

  @Override
  // TODO: Consider serializing oneof fields last so that only one entry per
  // oneof is actually serialized. This would mean that we would violate the serialization order
  // contract. It should also be noted that Go currently does this.
  public void writeTo(T message, Writer writer) throws IOException {
    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
      writeFieldsInDescendingOrder(message, writer);
    } else {
      writeFieldsInAscendingOrder(message, writer);
    }
  }

  @SuppressWarnings("unchecked")
  private void writeFieldsInAscendingOrder(T message, Writer writer) throws IOException {
    Iterator<? extends Map.Entry<?, ?>> extensionIterator = null;
    Map.Entry nextExtension = null;
    if (hasExtensions) {
      FieldSet<?> extensions = extensionSchema.getExtensions(message);
      if (!extensions.isEmpty()) {
        extensionIterator = extensions.iterator();
        nextExtension = extensionIterator.next();
      }
    }

    int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
    int currentPresenceField = 0;
    final int bufferLength = buffer.length;
    final sun.misc.Unsafe unsafe = UNSAFE;
    for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
      final int typeAndOffset = typeAndOffsetAt(pos);
      final int number = numberAt(pos);
      final int fieldType = type(typeAndOffset);

      int presenceMask = 0;
      if (fieldType <= 17) {
        int presenceMaskAndOffset = buffer[pos + 2];
        final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;

        // Performance optimization to cache the presence field which is shared for multiple
        // fields.
        // TODO: Improve caching for case when fields alternate between having and not
        // having presence by caching presence field for last field with presence only.
        if (presenceFieldOffset != currentPresenceFieldOffset) {
          currentPresenceFieldOffset = presenceFieldOffset;
          currentPresenceField =
              currentPresenceFieldOffset == NO_PRESENCE_SENTINEL
                  ? 0
                  : unsafe.getInt(message, (long) presenceFieldOffset);
        }
        // Mask for presence bit of the current field from the shared presence field.
        presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
      }

      // Write any extensions that need to be written before the current field.
      while (nextExtension != null && extensionSchema.extensionNumber(nextExtension) <= number) {
        extensionSchema.serializeExtension(writer, nextExtension);
        nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
      }
      final long offset = offset(typeAndOffset);

      switch (fieldType) {
        case 0: // DOUBLE:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeDouble(number, doubleAt(message, offset));
          }
          break;
        case 1: // FLOAT:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeFloat(number, floatAt(message, offset));
          }
          break;
        case 2: // INT64:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeInt64(number, unsafe.getLong(message, offset));
          }
          break;
        case 3: // UINT64:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeUInt64(number, unsafe.getLong(message, offset));
          }
          break;
        case 4: // INT32:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeInt32(number, unsafe.getInt(message, offset));
          }
          break;
        case 5: // FIXED64:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeFixed64(number, unsafe.getLong(message, offset));
          }
          break;
        case 6: // FIXED32:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeFixed32(number, unsafe.getInt(message, offset));
          }
          break;
        case 7: // BOOL:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeBool(number, booleanAt(message, offset));
          }
          break;
        case 8: // STRING:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writeString(number, unsafe.getObject(message, offset), writer);
          }
          break;
        case 9: // MESSAGE:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            Object value = unsafe.getObject(message, offset);
            writer.writeMessage(number, value, getMessageFieldSchema(pos));
          }
          break;
        case 10: // BYTES:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeBytes(number, (ByteString) unsafe.getObject(message, offset));
          }
          break;
        case 11: // UINT32:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeUInt32(number, unsafe.getInt(message, offset));
          }
          break;
        case 12: // ENUM:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeEnum(number, unsafe.getInt(message, offset));
          }
          break;
        case 13: // SFIXED32:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeSFixed32(number, unsafe.getInt(message, offset));
          }
          break;
        case 14: // SFIXED64:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeSFixed64(number, unsafe.getLong(message, offset));
          }
          break;
        case 15: // SINT32:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeSInt32(number, unsafe.getInt(message, offset));
          }
          break;
        case 16: // SINT64:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeSInt64(number, unsafe.getLong(message, offset));
          }
          break;
        case 17: // GROUP:
          if (isFieldPresent(
              message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
            writer.writeGroup(
                number, unsafe.getObject(message, offset), getMessageFieldSchema(pos));
          }
          break;
        case 18: // DOUBLE_LIST:
          SchemaUtil.writeDoubleList(
              numberAt(pos), (List<Double>) unsafe.getObject(message, offset), writer, false);
          break;
        case 19: // FLOAT_LIST:
          SchemaUtil.writeFloatList(
              numberAt(pos), (List<Float>) unsafe.getObject(message, offset), writer, false);
          break;
        case 20: // INT64_LIST:
          SchemaUtil.writeInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
          break;
        case 21: // UINT64_LIST:
          SchemaUtil.writeUInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
          break;
        case 22: // INT32_LIST:
          SchemaUtil.writeInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 23: // FIXED64_LIST:
          SchemaUtil.writeFixed64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
          break;
        case 24: // FIXED32_LIST:
          SchemaUtil.writeFixed32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 25: // BOOL_LIST:
          SchemaUtil.writeBoolList(
              numberAt(pos), (List<Boolean>) unsafe.getObject(message, offset), writer, false);
          break;
        case 26: // STRING_LIST:
          SchemaUtil.writeStringList(
              numberAt(pos), (List<String>) unsafe.getObject(message, offset), writer);
          break;
        case 27: // MESSAGE_LIST:
          SchemaUtil.writeMessageList(
              numberAt(pos),
              (List<?>) unsafe.getObject(message, offset),
              writer,
              getMessageFieldSchema(pos));
          break;
        case 28: // BYTES_LIST:
          SchemaUtil.writeBytesList(
              numberAt(pos), (List<ByteString>) unsafe.getObject(message, offset), writer);
          break;
        case 29: // UINT32_LIST:
          SchemaUtil.writeUInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 30: // ENUM_LIST:
          SchemaUtil.writeEnumList(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 31: // SFIXED32_LIST:
          SchemaUtil.writeSFixed32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 32: // SFIXED64_LIST:
          SchemaUtil.writeSFixed64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
          break;
        case 33: // SINT32_LIST:
          SchemaUtil.writeSInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, false);
          break;
        case 34: // SINT64_LIST:
          SchemaUtil.writeSInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, false);
          break;
        case 35: // DOUBLE_LIST_PACKED:
          // TODO: Make use of cached field size to speed up serialization.
          SchemaUtil.writeDoubleList(
              numberAt(pos), (List<Double>) unsafe.getObject(message, offset), writer, true);
          break;
        case 36: // FLOAT_LIST_PACKED:
          SchemaUtil.writeFloatList(
              numberAt(pos), (List<Float>) unsafe.getObject(message, offset), writer, true);
          break;
        case 37: // INT64_LIST_PACKED:
          SchemaUtil.writeInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
          break;
        case 38: // UINT64_LIST_PACKED:
          SchemaUtil.writeUInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
          break;
        case 39: // INT32_LIST_PACKED:
          SchemaUtil.writeInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
          break;
        case 40: // FIXED64_LIST_PACKED:
          SchemaUtil.writeFixed64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
          break;
        case 41: // FIXED32_LIST_PACKED:
          SchemaUtil.writeFixed32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);

          break;
        case 42: // BOOL_LIST_PACKED:
          SchemaUtil.writeBoolList(
              numberAt(pos), (List<Boolean>) unsafe.getObject(message, offset), writer, true);
          break;
        case 43: // UINT32_LIST_PACKED:
          SchemaUtil.writeUInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
          break;
        case 44: // ENUM_LIST_PACKED:
          SchemaUtil.writeEnumList(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
          break;
        case 45: // SFIXED32_LIST_PACKED:
          SchemaUtil.writeSFixed32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
          break;
        case 46: // SFIXED64_LIST_PACKED:
          SchemaUtil.writeSFixed64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
          break;
        case 47: // SINT32_LIST_PACKED:
          SchemaUtil.writeSInt32List(
              numberAt(pos), (List<Integer>) unsafe.getObject(message, offset), writer, true);
          break;
        case 48: // SINT64_LIST_PACKED:
          SchemaUtil.writeSInt64List(
              numberAt(pos), (List<Long>) unsafe.getObject(message, offset), writer, true);
          break;
        case 49: // GROUP_LIST:
          SchemaUtil.writeGroupList(
              numberAt(pos),
              (List<?>) unsafe.getObject(message, offset),
              writer,
              getMessageFieldSchema(pos));
          break;
        case 50: // MAP:
          // TODO: Use schema cache.
          writeMapHelper(writer, number, unsafe.getObject(message, offset), pos);
          break;
        case 51: // ONEOF_DOUBLE:
          if (isOneofPresent(message, number, pos)) {
            writer.writeDouble(number, oneofDoubleAt(message, offset));
          }
          break;
        case 52: // ONEOF_FLOAT:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFloat(number, oneofFloatAt(message, offset));
          }
          break;
        case 53: // ONEOF_INT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeInt64(number, oneofLongAt(message, offset));
          }
          break;
        case 54: // ONEOF_UINT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeUInt64(number, oneofLongAt(message, offset));
          }
          break;
        case 55: // ONEOF_INT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeInt32(number, oneofIntAt(message, offset));
          }
          break;
        case 56: // ONEOF_FIXED64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFixed64(number, oneofLongAt(message, offset));
          }
          break;
        case 57: // ONEOF_FIXED32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFixed32(number, oneofIntAt(message, offset));
          }
          break;
        case 58: // ONEOF_BOOL:
          if (isOneofPresent(message, number, pos)) {
            writer.writeBool(number, oneofBooleanAt(message, offset));
          }
          break;
        case 59: // ONEOF_STRING:
          if (isOneofPresent(message, number, pos)) {
            writeString(number, unsafe.getObject(message, offset), writer);
          }
          break;
        case 60: // ONEOF_MESSAGE:
          if (isOneofPresent(message, number, pos)) {
            Object value = unsafe.getObject(message, offset);
            writer.writeMessage(number, value, getMessageFieldSchema(pos));
          }
          break;
        case 61: // ONEOF_BYTES:
          if (isOneofPresent(message, number, pos)) {
            writer.writeBytes(number, (ByteString) unsafe.getObject(message, offset));
          }
          break;
        case 62: // ONEOF_UINT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeUInt32(number, oneofIntAt(message, offset));
          }
          break;
        case 63: // ONEOF_ENUM:
          if (isOneofPresent(message, number, pos)) {
            writer.writeEnum(number, oneofIntAt(message, offset));
          }
          break;
        case 64: // ONEOF_SFIXED32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSFixed32(number, oneofIntAt(message, offset));
          }
          break;
        case 65: // ONEOF_SFIXED64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSFixed64(number, oneofLongAt(message, offset));
          }
          break;
        case 66: // ONEOF_SINT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSInt32(number, oneofIntAt(message, offset));
          }
          break;
        case 67: // ONEOF_SINT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSInt64(number, oneofLongAt(message, offset));
          }
          break;
        case 68: // ONEOF_GROUP:
          if (isOneofPresent(message, number, pos)) {
            writer.writeGroup(
                number, unsafe.getObject(message, offset), getMessageFieldSchema(pos));
          }
          break;
        default:
          // Assume it's an empty entry - just go to the next entry.
          break;
      }
    }
    while (nextExtension != null) {
      extensionSchema.serializeExtension(writer, nextExtension);
      nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
    }
    writeUnknownInMessageTo(unknownFieldSchema, message, writer);
  }

  @SuppressWarnings("unchecked")
  private void writeFieldsInDescendingOrder(T message, Writer writer) throws IOException {
    writeUnknownInMessageTo(unknownFieldSchema, message, writer);

    Iterator<? extends Map.Entry<?, ?>> extensionIterator = null;
    Map.Entry nextExtension = null;
    if (hasExtensions) {
      FieldSet<?> extensions = extensionSchema.getExtensions(message);
      if (!extensions.isEmpty()) {
        extensionIterator = extensions.descendingIterator();
        nextExtension = extensionIterator.next();
      }
    }

    for (int pos = buffer.length - INTS_PER_FIELD; pos >= 0; pos -= INTS_PER_FIELD) {
      final int typeAndOffset = typeAndOffsetAt(pos);
      final int number = numberAt(pos);

      // Write any extensions that need to be written before the current field.
      while (nextExtension != null && extensionSchema.extensionNumber(nextExtension) > number) {
        extensionSchema.serializeExtension(writer, nextExtension);
        nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
      }

      switch (type(typeAndOffset)) {
        case 0: // DOUBLE:
          if (isFieldPresent(message, pos)) {
            writer.writeDouble(number, doubleAt(message, offset(typeAndOffset)));
          }
          break;
        case 1: // FLOAT:
          if (isFieldPresent(message, pos)) {
            writer.writeFloat(number, floatAt(message, offset(typeAndOffset)));
          }
          break;
        case 2: // INT64:
          if (isFieldPresent(message, pos)) {
            writer.writeInt64(number, longAt(message, offset(typeAndOffset)));
          }
          break;
        case 3: // UINT64:
          if (isFieldPresent(message, pos)) {
            writer.writeUInt64(number, longAt(message, offset(typeAndOffset)));
          }
          break;
        case 4: // INT32:
          if (isFieldPresent(message, pos)) {
            writer.writeInt32(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 5: // FIXED64:
          if (isFieldPresent(message, pos)) {
            writer.writeFixed64(number, longAt(message, offset(typeAndOffset)));
          }
          break;
        case 6: // FIXED32:
          if (isFieldPresent(message, pos)) {
            writer.writeFixed32(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 7: // BOOL:
          if (isFieldPresent(message, pos)) {
            writer.writeBool(number, booleanAt(message, offset(typeAndOffset)));
          }
          break;
        case 8: // STRING:
          if (isFieldPresent(message, pos)) {
            writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
          }
          break;
        case 9: // MESSAGE:
          if (isFieldPresent(message, pos)) {
            Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
            writer.writeMessage(number, value, getMessageFieldSchema(pos));
          }
          break;
        case 10: // BYTES:
          if (isFieldPresent(message, pos)) {
            writer.writeBytes(
                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
          }
          break;
        case 11: // UINT32:
          if (isFieldPresent(message, pos)) {
            writer.writeUInt32(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 12: // ENUM:
          if (isFieldPresent(message, pos)) {
            writer.writeEnum(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 13: // SFIXED32:
          if (isFieldPresent(message, pos)) {
            writer.writeSFixed32(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 14: // SFIXED64:
          if (isFieldPresent(message, pos)) {
            writer.writeSFixed64(number, longAt(message, offset(typeAndOffset)));
          }
          break;
        case 15: // SINT32:
          if (isFieldPresent(message, pos)) {
            writer.writeSInt32(number, intAt(message, offset(typeAndOffset)));
          }
          break;
        case 16: // SINT64:
          if (isFieldPresent(message, pos)) {
            writer.writeSInt64(number, longAt(message, offset(typeAndOffset)));
          }
          break;
        case 17: // GROUP:
          if (isFieldPresent(message, pos)) {
            writer.writeGroup(
                number,
                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                getMessageFieldSchema(pos));
          }
          break;
        case 18: // DOUBLE_LIST:
          SchemaUtil.writeDoubleList(
              numberAt(pos),
              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 19: // FLOAT_LIST:
          SchemaUtil.writeFloatList(
              numberAt(pos),
              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 20: // INT64_LIST:
          SchemaUtil.writeInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 21: // UINT64_LIST:
          SchemaUtil.writeUInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 22: // INT32_LIST:
          SchemaUtil.writeInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 23: // FIXED64_LIST:
          SchemaUtil.writeFixed64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 24: // FIXED32_LIST:
          SchemaUtil.writeFixed32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 25: // BOOL_LIST:
          SchemaUtil.writeBoolList(
              numberAt(pos),
              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 26: // STRING_LIST:
          SchemaUtil.writeStringList(
              numberAt(pos),
              (List<String>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer);
          break;
        case 27: // MESSAGE_LIST:
          SchemaUtil.writeMessageList(
              numberAt(pos),
              (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              getMessageFieldSchema(pos));
          break;
        case 28: // BYTES_LIST:
          SchemaUtil.writeBytesList(
              numberAt(pos),
              (List<ByteString>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer);
          break;
        case 29: // UINT32_LIST:
          SchemaUtil.writeUInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 30: // ENUM_LIST:
          SchemaUtil.writeEnumList(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 31: // SFIXED32_LIST:
          SchemaUtil.writeSFixed32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 32: // SFIXED64_LIST:
          SchemaUtil.writeSFixed64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 33: // SINT32_LIST:
          SchemaUtil.writeSInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 34: // SINT64_LIST:
          SchemaUtil.writeSInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              false);
          break;
        case 35: // DOUBLE_LIST_PACKED:
          SchemaUtil.writeDoubleList(
              numberAt(pos),
              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 36: // FLOAT_LIST_PACKED:
          SchemaUtil.writeFloatList(
              numberAt(pos),
              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 37: // INT64_LIST_PACKED:
          SchemaUtil.writeInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 38: // UINT64_LIST_PACKED:
          SchemaUtil.writeUInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 39: // INT32_LIST_PACKED:
          SchemaUtil.writeInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 40: // FIXED64_LIST_PACKED:
          SchemaUtil.writeFixed64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 41: // FIXED32_LIST_PACKED:
          SchemaUtil.writeFixed32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);

          break;
        case 42: // BOOL_LIST_PACKED:
          SchemaUtil.writeBoolList(
              numberAt(pos),
              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 43: // UINT32_LIST_PACKED:
          SchemaUtil.writeUInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 44: // ENUM_LIST_PACKED:
          SchemaUtil.writeEnumList(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 45: // SFIXED32_LIST_PACKED:
          SchemaUtil.writeSFixed32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 46: // SFIXED64_LIST_PACKED:
          SchemaUtil.writeSFixed64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 47: // SINT32_LIST_PACKED:
          SchemaUtil.writeSInt32List(
              numberAt(pos),
              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 48: // SINT64_LIST_PACKED:
          SchemaUtil.writeSInt64List(
              numberAt(pos),
              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              true);
          break;
        case 49: // GROUP_LIST:
          SchemaUtil.writeGroupList(
              numberAt(pos),
              (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
              writer,
              getMessageFieldSchema(pos));
          break;
        case 50: // MAP:
          // TODO: Use schema cache.
          writeMapHelper(writer, number, UnsafeUtil.getObject(message, offset(typeAndOffset)), pos);
          break;
        case 51: // ONEOF_DOUBLE:
          if (isOneofPresent(message, number, pos)) {
            writer.writeDouble(number, oneofDoubleAt(message, offset(typeAndOffset)));
          }
          break;
        case 52: // ONEOF_FLOAT:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFloat(number, oneofFloatAt(message, offset(typeAndOffset)));
          }
          break;
        case 53: // ONEOF_INT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeInt64(number, oneofLongAt(message, offset(typeAndOffset)));
          }
          break;
        case 54: // ONEOF_UINT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeUInt64(number, oneofLongAt(message, offset(typeAndOffset)));
          }
          break;
        case 55: // ONEOF_INT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeInt32(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 56: // ONEOF_FIXED64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
          }
          break;
        case 57: // ONEOF_FIXED32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 58: // ONEOF_BOOL:
          if (isOneofPresent(message, number, pos)) {
            writer.writeBool(number, oneofBooleanAt(message, offset(typeAndOffset)));
          }
          break;
        case 59: // ONEOF_STRING:
          if (isOneofPresent(message, number, pos)) {
            writeString(number, UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
          }
          break;
        case 60: // ONEOF_MESSAGE:
          if (isOneofPresent(message, number, pos)) {
            Object value = UnsafeUtil.getObject(message, offset(typeAndOffset));
            writer.writeMessage(number, value, getMessageFieldSchema(pos));
          }
          break;
        case 61: // ONEOF_BYTES:
          if (isOneofPresent(message, number, pos)) {
            writer.writeBytes(
                number, (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
          }
          break;
        case 62: // ONEOF_UINT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeUInt32(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 63: // ONEOF_ENUM:
          if (isOneofPresent(message, number, pos)) {
            writer.writeEnum(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 64: // ONEOF_SFIXED32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSFixed32(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 65: // ONEOF_SFIXED64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSFixed64(number, oneofLongAt(message, offset(typeAndOffset)));
          }
          break;
        case 66: // ONEOF_SINT32:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSInt32(number, oneofIntAt(message, offset(typeAndOffset)));
          }
          break;
        case 67: // ONEOF_SINT64:
          if (isOneofPresent(message, number, pos)) {
            writer.writeSInt64(number, oneofLongAt(message, offset(typeAndOffset)));
          }
          break;
        case 68: // ONEOF_GROUP:
          if (isOneofPresent(message, number, pos)) {
            writer.writeGroup(
                number,
                UnsafeUtil.getObject(message, offset(typeAndOffset)),
                getMessageFieldSchema(pos));
          }
          break;
        default:
          break;
      }
    }
    while (nextExtension != null) {
      extensionSchema.serializeExtension(writer, nextExtension);
      nextExtension = extensionIterator.hasNext() ? extensionIterator.next() : null;
    }
  }

  @SuppressWarnings("unchecked")
  private <K, V> void writeMapHelper(Writer writer, int number, Object mapField, int pos)
      throws IOException {
    if (mapField != null) {
      writer.writeMap(
          number,
          (MapEntryLite.Metadata<K, V>) mapFieldSchema.forMapMetadata(getMapFieldDefaultEntry(pos)),
          (Map<K, V>) mapFieldSchema.forMapData(mapField));
    }
  }

  private <UT, UB> void writeUnknownInMessageTo(
      UnknownFieldSchema<UT, UB> schema, T message, Writer writer) throws IOException {
    schema.writeTo(schema.getFromMessage(message), writer);
  }

  @Override
  public void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry)
      throws IOException {
    if (extensionRegistry == null) {
      throw new NullPointerException();
    }
    checkMutable(message);
    mergeFromHelper(unknownFieldSchema, extensionSchema, message, reader, extensionRegistry);
  }

  /**
   * A helper method for wildcard capture of {@code unknownFieldSchema}. See:
   * https://docs.oracle.com/javase/tutorial/java/generics/capture.html
   */
  private <UT, UB, ET extends FieldDescriptorLite<ET>> void mergeFromHelper(
      UnknownFieldSchema<UT, UB> unknownFieldSchema,
      ExtensionSchema<ET> extensionSchema,
      T message,
      Reader reader,
      ExtensionRegistryLite extensionRegistry)
      throws IOException {
    UB unknownFields = null;
    FieldSet<ET> extensions = null;
    try {
      while (true) {
        final int number = reader.getFieldNumber();
        final int pos = positionForFieldNumber(number);
        if (pos < 0) {
          if (number == Reader.READ_DONE) {
            return;
          }
          // Check if it's an extension.
          Object extension =
              !hasExtensions
                  ? null
                  : extensionSchema.findExtensionByNumber(
                      extensionRegistry, defaultInstance, number);
          if (extension != null) {
            if (extensions == null) {
              extensions = extensionSchema.getMutableExtensions(message);
            }
            unknownFields =
                extensionSchema.parseExtension(
                    message,
                    reader,
                    extension,
                    extensionRegistry,
                    extensions,
                    unknownFields,
                    unknownFieldSchema);
            continue;
          }
          if (unknownFieldSchema.shouldDiscardUnknownFields(reader)) {
            if (reader.skipField()) {
              continue;
            }
          } else {
            if (unknownFields == null) {
              unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
            }
            // Unknown field.
            if (unknownFieldSchema.mergeOneFieldFrom(
                unknownFields, reader, /* currentDepth= */ 0)) {
              continue;
            }
          }
          // Done reading.
          return;
        }
        final int typeAndOffset = typeAndOffsetAt(pos);

        try {
          switch (type(typeAndOffset)) {
            case 0: // DOUBLE:
              UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
              setFieldPresent(message, pos);
              break;
            case 1: // FLOAT:
              UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
              setFieldPresent(message, pos);
              break;
            case 2: // INT64:
              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
              setFieldPresent(message, pos);
              break;
            case 3: // UINT64:
              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
              setFieldPresent(message, pos);
              break;
            case 4: // INT32:
              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
              setFieldPresent(message, pos);
              break;
            case 5: // FIXED64:
              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
              setFieldPresent(message, pos);
              break;
            case 6: // FIXED32:
              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
              setFieldPresent(message, pos);
              break;
            case 7: // BOOL:
              UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
              setFieldPresent(message, pos);
              break;
            case 8: // STRING:
              readString(message, typeAndOffset, reader);
              setFieldPresent(message, pos);
              break;
            case 9:
              { // MESSAGE:
                final MessageLite current = (MessageLite) mutableMessageFieldForMerge(message, pos);
                reader.mergeMessageField(
                    current, (Schema<MessageLite>) getMessageFieldSchema(pos), extensionRegistry);
                storeMessageField(message, pos, current);
                break;
              }
            case 10: // BYTES:
              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
              setFieldPresent(message, pos);
              break;
            case 11: // UINT32:
              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
              setFieldPresent(message, pos);
              break;
            case 12: // ENUM:
              {
                int enumValue = reader.readEnum();
                EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                  UnsafeUtil.putInt(message, offset(typeAndOffset), enumValue);
                  setFieldPresent(message, pos);
                } else {
                  unknownFields =
                      SchemaUtil.storeUnknownEnum(
                          message, number, enumValue, unknownFields, unknownFieldSchema);
                }
                break;
              }
            case 13: // SFIXED32:
              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
              setFieldPresent(message, pos);
              break;
            case 14: // SFIXED64:
              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
              setFieldPresent(message, pos);
              break;
            case 15: // SINT32:
              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
              setFieldPresent(message, pos);
              break;
            case 16: // SINT64:
              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
              setFieldPresent(message, pos);
              break;
            case 17:
              { // GROUP:
                final MessageLite current = (MessageLite) mutableMessageFieldForMerge(message, pos);
                reader.mergeGroupField(
                    current, (Schema<MessageLite>) getMessageFieldSchema(pos), extensionRegistry);
                storeMessageField(message, pos, current);
                break;
              }
            case 18: // DOUBLE_LIST:
              reader.readDoubleList(
                  listFieldSchema.<Double>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 19: // FLOAT_LIST:
              reader.readFloatList(
                  listFieldSchema.<Float>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 20: // INT64_LIST:
              reader.readInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 21: // UINT64_LIST:
              reader.readUInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 22: // INT32_LIST:
              reader.readInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 23: // FIXED64_LIST:
              reader.readFixed64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 24: // FIXED32_LIST:
              reader.readFixed32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 25: // BOOL_LIST:
              reader.readBoolList(
                  listFieldSchema.<Boolean>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 26: // STRING_LIST:
              readStringList(message, typeAndOffset, reader);
              break;
            case 27:
              { // MESSAGE_LIST:
                readMessageList(
                    message,
                    typeAndOffset,
                    reader,
                    (Schema<T>) getMessageFieldSchema(pos),
                    extensionRegistry);
                break;
              }
            case 28: // BYTES_LIST:
              reader.readBytesList(
                  listFieldSchema.<ByteString>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 29: // UINT32_LIST:
              reader.readUInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 30: // ENUM_LIST:
              {
                List<Integer> enumList =
                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset));
                reader.readEnumList(enumList);
                unknownFields =
                    SchemaUtil.filterUnknownEnumList(
                        message,
                        number,
                        enumList,
                        getEnumFieldVerifier(pos),
                        unknownFields,
                        unknownFieldSchema);
                break;
              }
            case 31: // SFIXED32_LIST:
              reader.readSFixed32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 32: // SFIXED64_LIST:
              reader.readSFixed64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 33: // SINT32_LIST:
              reader.readSInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 34: // SINT64_LIST:
              reader.readSInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 35: // DOUBLE_LIST_PACKED:
              reader.readDoubleList(
                  listFieldSchema.<Double>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 36: // FLOAT_LIST_PACKED:
              reader.readFloatList(
                  listFieldSchema.<Float>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 37: // INT64_LIST_PACKED:
              reader.readInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 38: // UINT64_LIST_PACKED:
              reader.readUInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 39: // INT32_LIST_PACKED:
              reader.readInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 40: // FIXED64_LIST_PACKED:
              reader.readFixed64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 41: // FIXED32_LIST_PACKED:
              reader.readFixed32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 42: // BOOL_LIST_PACKED:
              reader.readBoolList(
                  listFieldSchema.<Boolean>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 43: // UINT32_LIST_PACKED:
              reader.readUInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 44: // ENUM_LIST_PACKED:
              {
                List<Integer> enumList =
                    listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset));
                reader.readEnumList(enumList);
                unknownFields =
                    SchemaUtil.filterUnknownEnumList(
                        message,
                        number,
                        enumList,
                        getEnumFieldVerifier(pos),
                        unknownFields,
                        unknownFieldSchema);
                break;
              }
            case 45: // SFIXED32_LIST_PACKED:
              reader.readSFixed32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 46: // SFIXED64_LIST_PACKED:
              reader.readSFixed64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 47: // SINT32_LIST_PACKED:
              reader.readSInt32List(
                  listFieldSchema.<Integer>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 48: // SINT64_LIST_PACKED:
              reader.readSInt64List(
                  listFieldSchema.<Long>mutableListAt(message, offset(typeAndOffset)));
              break;
            case 49:
              { // GROUP_LIST:
                readGroupList(
                    message,
                    offset(typeAndOffset),
                    reader,
                    (Schema<T>) getMessageFieldSchema(pos),
                    extensionRegistry);
                break;
              }
            case 50: // MAP:
              mergeMap(message, pos, getMapFieldDefaultEntry(pos), extensionRegistry, reader);
              break;
            case 51: // ONEOF_DOUBLE:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Double.valueOf(reader.readDouble()));
              setOneofPresent(message, number, pos);
              break;
            case 52: // ONEOF_FLOAT:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Float.valueOf(reader.readFloat()));
              setOneofPresent(message, number, pos);
              break;
            case 53: // ONEOF_INT64:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Long.valueOf(reader.readInt64()));
              setOneofPresent(message, number, pos);
              break;
            case 54: // ONEOF_UINT64:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Long.valueOf(reader.readUInt64()));
              setOneofPresent(message, number, pos);
              break;
            case 55: // ONEOF_INT32:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Integer.valueOf(reader.readInt32()));
              setOneofPresent(message, number, pos);
              break;
            case 56: // ONEOF_FIXED64:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Long.valueOf(reader.readFixed64()));
              setOneofPresent(message, number, pos);
              break;
            case 57: // ONEOF_FIXED32:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Integer.valueOf(reader.readFixed32()));
              setOneofPresent(message, number, pos);
              break;
            case 58: // ONEOF_BOOL:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Boolean.valueOf(reader.readBool()));
              setOneofPresent(message, number, pos);
              break;
            case 59: // ONEOF_STRING:
              readString(message, typeAndOffset, reader);
              setOneofPresent(message, number, pos);
              break;
            case 60:
              { // ONEOF_MESSAGE:
                final MessageLite current =
                    (MessageLite) mutableOneofMessageFieldForMerge(message, number, pos);
                reader.mergeMessageField(
                    current, (Schema<MessageLite>) getMessageFieldSchema(pos), extensionRegistry);
                storeOneofMessageField(message, number, pos, current);
                break;
              }
            case 61: // ONEOF_BYTES:
              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
              setOneofPresent(message, number, pos);
              break;
            case 62: // ONEOF_UINT32:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Integer.valueOf(reader.readUInt32()));
              setOneofPresent(message, number, pos);
              break;
            case 63: // ONEOF_ENUM:
              {
                int enumValue = reader.readEnum();
                EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
                  UnsafeUtil.putObject(message, offset(typeAndOffset), enumValue);
                  setOneofPresent(message, number, pos);
                } else {
                  unknownFields =
                      SchemaUtil.storeUnknownEnum(
                          message, number, enumValue, unknownFields, unknownFieldSchema);
                }
                break;
              }
            case 64: // ONEOF_SFIXED32:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Integer.valueOf(reader.readSFixed32()));
              setOneofPresent(message, number, pos);
              break;
            case 65: // ONEOF_SFIXED64:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Long.valueOf(reader.readSFixed64()));
              setOneofPresent(message, number, pos);
              break;
            case 66: // ONEOF_SINT32:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Integer.valueOf(reader.readSInt32()));
              setOneofPresent(message, number, pos);
              break;
            case 67: // ONEOF_SINT64:
              UnsafeUtil.putObject(
                  message, offset(typeAndOffset), Long.valueOf(reader.readSInt64()));
              setOneofPresent(message, number, pos);
              break;
            case 68:
              { // ONEOF_GROUP:
                final MessageLite current =
                    (MessageLite) mutableOneofMessageFieldForMerge(message, number, pos);
                reader.mergeGroupField(
                    current, (Schema<MessageLite>) getMessageFieldSchema(pos), extensionRegistry);
                storeOneofMessageField(message, number, pos, current);
                break;
              }
            default:
              // Assume we've landed on an empty entry. Treat it as an unknown field.
              if (unknownFields == null) {
                unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
              }
              if (!unknownFieldSchema.mergeOneFieldFrom(
                  unknownFields, reader, /* currentDepth= */ 0)) {
                return;
              }
              break;
          }
        } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
          // Treat fields with an invalid wire type as unknown fields
          // (i.e. same as the default case).
          if (unknownFieldSchema.shouldDiscardUnknownFields(reader)) {
            if (!reader.skipField()) {
              return;
            }
          } else {
            if (unknownFields == null) {
              unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
            }
            if (!unknownFieldSchema.mergeOneFieldFrom(
                unknownFields, reader, /* currentDepth= */ 0)) {
              return;
            }
          }
        }
      }
    } finally {
      for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
        unknownFields =
            filterMapUnknownEnumValues(
                message, intArray[i], unknownFields, unknownFieldSchema, message);
      }
      if (unknownFields != null) {
        unknownFieldSchema.setBuilderToMessage(message, unknownFields);
      }
    }
  }

  @SuppressWarnings("ReferenceEquality")
  static UnknownFieldSetLite getMutableUnknownFields(Object message) {
    // TODO decide if we're keeping support for Full in schema classes and handle this
    // better.
    UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
    if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
      unknownFields = UnknownFieldSetLite.newInstance();
      ((GeneratedMessageLite) message).unknownFields = unknownFields;
    }
    return unknownFields;
  }

  /** Decodes a map entry key or value. Stores result in registers.object1. */
  private int decodeMapEntryValue(
      byte[] data,
      int position,
      int limit,
      WireFormat.FieldType fieldType,
      Class<?> messageType,
      Registers registers)
      throws IOException {
    switch (fieldType) {
      case BOOL:
        position = decodeVarint64(data, position, registers);
        registers.object1 = registers.long1 != 0;
        break;
      case BYTES:
        position = decodeBytes(data, position, registers);
        break;
      case DOUBLE:
        registers.object1 = decodeDouble(data, position);
        position += 8;
        break;
      case FIXED32:
      case SFIXED32:
        registers.object1 = decodeFixed32(data, position);
        position += 4;
        break;
      case FIXED64:
      case SFIXED64:
        registers.object1 = decodeFixed64(data, position);
        position += 8;
        break;
      case FLOAT:
        registers.object1 = decodeFloat(data, position);
        position += 4;
        break;
      case ENUM:
      case INT32:
      case UINT32:
        position = decodeVarint32(data, position, registers);
        registers.object1 = registers.int1;
        break;
      case INT64:
      case UINT64:
        position = decodeVarint64(data, position, registers);
        registers.object1 = registers.long1;
        break;
      case MESSAGE:
        position =
            decodeMessageField(
                Protobuf.getInstance().schemaFor(messageType), data, position, limit, registers);
        break;
      case SINT32:
        position = decodeVarint32(data, position, registers);
        registers.object1 = CodedInputStream.decodeZigZag32(registers.int1);
        break;
      case SINT64:
        position = decodeVarint64(data, position, registers);
        registers.object1 = CodedInputStream.decodeZigZag64(registers.long1);
        break;
      case STRING:
        position = decodeStringRequireUtf8(data, position, registers);
        break;
      default:
        throw new RuntimeException("unsupported field type.");
    }
    return position;
  }

  /** Decodes a map entry. */
  private <K, V> int decodeMapEntry(
      byte[] data,
      int position,
      int limit,
      MapEntryLite.Metadata<K, V> metadata,
      Map<K, V> target,
      Registers registers)
      throws IOException {
    position = decodeVarint32(data, position, registers);
    final int length = registers.int1;
    if (length < 0 || length > limit - position) {
      throw InvalidProtocolBufferException.truncatedMessage();
    }
    final int end = position + length;
    K key = metadata.defaultKey;
    V value = metadata.defaultValue;
    while (position < end) {
      int tag = data[position++];
      if (tag < 0) {
        position = decodeVarint32(tag, data, position, registers);
        tag = registers.int1;
      }
      final int fieldNumber = tag >>> 3;
      final int wireType = tag & 0x7;
      switch (fieldNumber) {
        case 1:
          if (wireType == metadata.keyType.getWireType()) {
            position =
                decodeMapEntryValue(data, position, limit, metadata.keyType, null, registers);
            key = (K) registers.object1;
            continue;
          }
          break;
        case 2:
          if (wireType == metadata.valueType.getWireType()) {
            position =
                decodeMapEntryValue(
                    data,
                    position,
                    limit,
                    metadata.valueType,
                    metadata.defaultValue.getClass(),
                    registers);
            value = (V) registers.object1;
            continue;
          }
          break;
        default:
          break;
      }
      position = skipField(tag, data, position, limit, registers);
    }
    if (position != end) {
      throw InvalidProtocolBufferException.parseFailure();
    }
    target.put(key, value);
    return end;
  }

  @SuppressWarnings("ReferenceEquality")
  private int parseRepeatedField(
      T message,
      byte[] data,
      int position,
      int limit,
      int tag,
      int number,
      int wireType,
      int bufferPosition,
      long typeAndOffset,
      int fieldType,
      long fieldOffset,
      Registers registers)
      throws IOException {
    ProtobufList<?> list = (ProtobufList<?>) UNSAFE.getObject(message, fieldOffset);
    if (!list.isModifiable()) {
      final int size = list.size();
      list =
          list.mutableCopyWithCapacity(
              size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
      UNSAFE.putObject(message, fieldOffset, list);
    }
    switch (fieldType) {
      case 18: // DOUBLE_LIST:
      case 35: // DOUBLE_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedDoubleList(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_FIXED64) {
          position = decodeDoubleList(tag, data, position, limit, list, registers);
        }
        break;
      case 19: // FLOAT_LIST:
      case 36: // FLOAT_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedFloatList(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_FIXED32) {
          position = decodeFloatList(tag, data, position, limit, list, registers);
        }
        break;
      case 20: // INT64_LIST:
      case 21: // UINT64_LIST:
      case 37: // INT64_LIST_PACKED:
      case 38: // UINT64_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedVarint64List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint64List(tag, data, position, limit, list, registers);
        }
        break;
      case 22: // INT32_LIST:
      case 29: // UINT32_LIST:
      case 39: // INT32_LIST_PACKED:
      case 43: // UINT32_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedVarint32List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint32List(tag, data, position, limit, list, registers);
        }
        break;
      case 23: // FIXED64_LIST:
      case 32: // SFIXED64_LIST:
      case 40: // FIXED64_LIST_PACKED:
      case 46: // SFIXED64_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedFixed64List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_FIXED64) {
          position = decodeFixed64List(tag, data, position, limit, list, registers);
        }
        break;
      case 24: // FIXED32_LIST:
      case 31: // SFIXED32_LIST:
      case 41: // FIXED32_LIST_PACKED:
      case 45: // SFIXED32_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedFixed32List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_FIXED32) {
          position = decodeFixed32List(tag, data, position, limit, list, registers);
        }
        break;
      case 25: // BOOL_LIST:
      case 42: // BOOL_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedBoolList(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeBoolList(tag, data, position, limit, list, registers);
        }
        break;
      case 26: // STRING_LIST:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          if ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
            position = decodeStringList(tag, data, position, limit, list, registers);
          } else {
            position = decodeStringListRequireUtf8(tag, data, position, limit, list, registers);
          }
        }
        break;
      case 27: // MESSAGE_LIST:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position =
              decodeMessageList(
                  getMessageFieldSchema(bufferPosition),
                  tag,
                  data,
                  position,
                  limit,
                  list,
                  registers);
        }
        break;
      case 28: // BYTES_LIST:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodeBytesList(tag, data, position, limit, list, registers);
        }
        break;
      case 30: // ENUM_LIST:
      case 44: // ENUM_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedVarint32List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint32List(tag, data, position, limit, list, registers);
        } else {
          break;
        }
        SchemaUtil.filterUnknownEnumList(
            message,
            number,
            (ProtobufList<Integer>) list,
            getEnumFieldVerifier(bufferPosition),
            null,
            (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema);
        break;
      case 33: // SINT32_LIST:
      case 47: // SINT32_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedSInt32List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeSInt32List(tag, data, position, limit, list, registers);
        }
        break;
      case 34: // SINT64_LIST:
      case 48: // SINT64_LIST_PACKED:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodePackedSInt64List(data, position, list, registers);
        } else if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeSInt64List(tag, data, position, limit, list, registers);
        }
        break;
      case 49: // GROUP_LIST:
        if (wireType == WireFormat.WIRETYPE_START_GROUP) {
          position =
              decodeGroupList(
                  getMessageFieldSchema(bufferPosition),
                  tag,
                  data,
                  position,
                  limit,
                  list,
                  registers);
        }
        break;
      default:
        break;
    }
    return position;
  }

  private <K, V> int parseMapField(
      T message,
      byte[] data,
      int position,
      int limit,
      int bufferPosition,
      long fieldOffset,
      Registers registers)
      throws IOException {
    final sun.misc.Unsafe unsafe = UNSAFE;
    Object mapDefaultEntry = getMapFieldDefaultEntry(bufferPosition);
    Object mapField = unsafe.getObject(message, fieldOffset);
    if (mapFieldSchema.isImmutable(mapField)) {
      Object oldMapField = mapField;
      mapField = mapFieldSchema.newMapField(mapDefaultEntry);
      mapFieldSchema.mergeFrom(mapField, oldMapField);
      unsafe.putObject(message, fieldOffset, mapField);
    }
    return decodeMapEntry(
        data,
        position,
        limit,
        (Metadata<K, V>) mapFieldSchema.forMapMetadata(mapDefaultEntry),
        (Map<K, V>) mapFieldSchema.forMutableMapData(mapField),
        registers);
  }

  private int parseOneofField(
      T message,
      byte[] data,
      int position,
      int limit,
      int tag,
      int number,
      int wireType,
      int typeAndOffset,
      int fieldType,
      long fieldOffset,
      int bufferPosition,
      Registers registers)
      throws IOException {
    final sun.misc.Unsafe unsafe = UNSAFE;
    final long oneofCaseOffset = buffer[bufferPosition + 2] & OFFSET_MASK;
    switch (fieldType) {
      case 51: // ONEOF_DOUBLE:
        if (wireType == WireFormat.WIRETYPE_FIXED64) {
          unsafe.putObject(message, fieldOffset, decodeDouble(data, position));
          position += 8;
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 52: // ONEOF_FLOAT:
        if (wireType == WireFormat.WIRETYPE_FIXED32) {
          unsafe.putObject(message, fieldOffset, decodeFloat(data, position));
          position += 4;
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 53: // ONEOF_INT64:
      case 54: // ONEOF_UINT64:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint64(data, position, registers);
          unsafe.putObject(message, fieldOffset, registers.long1);
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 55: // ONEOF_INT32:
      case 62: // ONEOF_UINT32:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint32(data, position, registers);
          unsafe.putObject(message, fieldOffset, registers.int1);
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 56: // ONEOF_FIXED64:
      case 65: // ONEOF_SFIXED64:
        if (wireType == WireFormat.WIRETYPE_FIXED64) {
          unsafe.putObject(message, fieldOffset, decodeFixed64(data, position));
          position += 8;
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 57: // ONEOF_FIXED32:
      case 64: // ONEOF_SFIXED32:
        if (wireType == WireFormat.WIRETYPE_FIXED32) {
          unsafe.putObject(message, fieldOffset, decodeFixed32(data, position));
          position += 4;
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 58: // ONEOF_BOOL:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint64(data, position, registers);
          unsafe.putObject(message, fieldOffset, registers.long1 != 0);
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 59: // ONEOF_STRING:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodeVarint32(data, position, registers);
          final int length = registers.int1;
          if (length == 0) {
            unsafe.putObject(message, fieldOffset, "");
          } else {
            if ((typeAndOffset & ENFORCE_UTF8_MASK) != 0
                && !Utf8.isValidUtf8(data, position, position + length)) {
              throw InvalidProtocolBufferException.invalidUtf8();
            }
            final String value = new String(data, position, length, Internal.UTF_8);
            unsafe.putObject(message, fieldOffset, value);
            position += length;
          }
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 60: // ONEOF_MESSAGE:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          final Object current = mutableOneofMessageFieldForMerge(message, number, bufferPosition);
          position =
              mergeMessageField(
                  current, getMessageFieldSchema(bufferPosition), data, position, limit, registers);
          storeOneofMessageField(message, number, bufferPosition, current);
        }
        break;
      case 61: // ONEOF_BYTES:
        if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
          position = decodeBytes(data, position, registers);
          unsafe.putObject(message, fieldOffset, registers.object1);
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 63: // ONEOF_ENUM:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint32(data, position, registers);
          final int enumValue = registers.int1;
          EnumVerifier enumVerifier = getEnumFieldVerifier(bufferPosition);
          if (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
            unsafe.putObject(message, fieldOffset, enumValue);
            unsafe.putInt(message, oneofCaseOffset, number);
          } else {
            // UnknownFieldSetLite requires varint to be represented as Long.
            getMutableUnknownFields(message).storeField(tag, (long) enumValue);
          }
        }
        break;
      case 66: // ONEOF_SINT32:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint32(data, position, registers);
          unsafe.putObject(message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 67: // ONEOF_SINT64:
        if (wireType == WireFormat.WIRETYPE_VARINT) {
          position = decodeVarint64(data, position, registers);
          unsafe.putObject(message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));
          unsafe.putInt(message, oneofCaseOffset, number);
        }
        break;
      case 68: // ONEOF_GROUP:
        if (wireType == WireFormat.WIRETYPE_START_GROUP) {
          final Object current = mutableOneofMessageFieldForMerge(message, number, bufferPosition);
          final int endTag = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
          position =
              mergeGroupField(
                  current,
                  getMessageFieldSchema(bufferPosition),
                  data,
                  position,
                  limit,
                  endTag,
                  registers);
          storeOneofMessageField(message, number, bufferPosition, current);
        }
        break;
      default:
        break;
    }
    return position;
  }

  private Schema getMessageFieldSchema(int pos) {
    final int index = pos / INTS_PER_FIELD * 2;
    Schema schema = (Schema) objects[index];
    if (schema != null) {
      return schema;
    }
    schema = Protobuf.getInstance().schemaFor((Class) objects[index + 1]);
    objects[index] = schema;
    return schema;
  }

  private Object getMapFieldDefaultEntry(int pos) {
    return objects[pos / INTS_PER_FIELD * 2];
  }

  private EnumVerifier getEnumFieldVerifier(int pos) {
    return (EnumVerifier) objects[pos / INTS_PER_FIELD * 2 + 1];
  }

  /**
   * Parses a message and returns the position after the message/group. If it's parsing a
   * LENGTH_PREFIXED message (endDelimited == 0), returns limit if parsing is successful; If it's
   * parsing a DELIMITED message aka group (endDelimited != 0), parsing ends when a tag ==
   * endDelimited is encountered and the position after that tag is returned.
   */
  @CanIgnoreReturnValue
  int parseMessage(
      T message, byte[] data, int position, int limit, int endDelimited, Registers registers)
      throws IOException {
    checkMutable(message);
    final sun.misc.Unsafe unsafe = UNSAFE;
    int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
    int currentPresenceField = 0;
    int tag = 0;
    int oldNumber = -1;
    int pos = 0;
    while (position < limit) {
      tag = data[position++];
      if (tag < 0) {
        position = decodeVarint32(tag, data, position, registers);
        tag = registers.int1;
      }
      final int number = tag >>> 3;
      final int wireType = tag & 0x7;
      if (number > oldNumber) {
        pos = positionForFieldNumber(number, pos / INTS_PER_FIELD);
      } else {
        pos = positionForFieldNumber(number);
      }
      oldNumber = number;
      if (pos == -1) {
        // need to reset
        pos = 0;
      } else {
        final int typeAndOffset = buffer[pos + 1];
        final int fieldType = type(typeAndOffset);
        final long fieldOffset = offset(typeAndOffset);
        if (fieldType <= 17) {
          // Fields with explicit presence (i.e. optional) have has-bits.
          final int presenceMaskAndOffset = buffer[pos + 2];
          final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
          final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
          // We cache the 32-bit presence integer value and only write it back when parsing a field
          // using a different presence integer.
          if (presenceFieldOffset != currentPresenceFieldOffset) {
            if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
              unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
            }
            currentPresenceFieldOffset = presenceFieldOffset;
            // For fields without presence, we unconditionally write and discard
            // the data.
            currentPresenceField =
                presenceFieldOffset == NO_PRESENCE_SENTINEL
                    ? 0
                    : unsafe.getInt(message, (long) presenceFieldOffset);
          }
          switch (fieldType) {
            case 0: // DOUBLE
              if (wireType == WireFormat.WIRETYPE_FIXED64) {
                UnsafeUtil.putDouble(message, fieldOffset, decodeDouble(data, position));
                position += 8;
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 1: // FLOAT
              if (wireType == WireFormat.WIRETYPE_FIXED32) {
                UnsafeUtil.putFloat(message, fieldOffset, decodeFloat(data, position));
                position += 4;
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 2: // INT64
            case 3: // UINT64
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint64(data, position, registers);
                unsafe.putLong(message, fieldOffset, registers.long1);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 4: // INT32
            case 11: // UINT32
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint32(data, position, registers);
                unsafe.putInt(message, fieldOffset, registers.int1);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 5: // FIXED64
            case 14: // SFIXED64
              if (wireType == WireFormat.WIRETYPE_FIXED64) {
                unsafe.putLong(message, fieldOffset, decodeFixed64(data, position));
                position += 8;
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 6: // FIXED32
            case 13: // SFIXED32
              if (wireType == WireFormat.WIRETYPE_FIXED32) {
                unsafe.putInt(message, fieldOffset, decodeFixed32(data, position));
                position += 4;
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 7: // BOOL
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint64(data, position, registers);
                UnsafeUtil.putBoolean(message, fieldOffset, registers.long1 != 0);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 8: // STRING
              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                if (isEnforceUtf8(typeAndOffset)) {
                  position = decodeStringRequireUtf8(data, position, registers);
                } else {
                  position = decodeString(data, position, registers);
                }
                unsafe.putObject(message, fieldOffset, registers.object1);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 9: // MESSAGE
              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                final Object current = mutableMessageFieldForMerge(message, pos);
                position =
                    mergeMessageField(
                        current, getMessageFieldSchema(pos), data, position, limit, registers);
                storeMessageField(message, pos, current);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 10: // BYTES
              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
                position = decodeBytes(data, position, registers);
                unsafe.putObject(message, fieldOffset, registers.object1);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 12: // ENUM
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint32(data, position, registers);
                final int enumValue = registers.int1;
                EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
                if (!isLegacyEnumIsClosed(typeAndOffset)
                    || enumVerifier == null
                    || enumVerifier.isInRange(enumValue)) {
                  // Parse open enums and in-range closed enums into their fields directly.
                  unsafe.putInt(message, fieldOffset, enumValue);
                  currentPresenceField |= presenceMask;
                } else {
                  // Store out-of-range closed enums in unknown fields.
                  // UnknownFieldSetLite requires varint to be represented as Long.
                  getMutableUnknownFields(message).storeField(tag, (long) enumValue);
                }
                continue;
              }
              break;
            case 15: // SINT32
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint32(data, position, registers);
                unsafe.putInt(
                    message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 16: // SINT64
              if (wireType == WireFormat.WIRETYPE_VARINT) {
                position = decodeVarint64(data, position, registers);
                unsafe.putLong(
                    message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));

                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            case 17: // GROUP
              if (wireType == WireFormat.WIRETYPE_START_GROUP) {
                final Object current = mutableMessageFieldForMerge(message, pos);
                final int endTag = (number << 3) | WireFormat.WIRETYPE_END_GROUP;
                position =
                    mergeGroupField(
                        current,
                        getMessageFieldSchema(pos),
                        data,
                        position,
                        limit,
                        endTag,
                        registers);
                storeMessageField(message, pos, current);
                currentPresenceField |= presenceMask;
                continue;
              }
              break;
            default:
              break;
          }
        } else if (fieldType == 27) {
          // Handle repeated message field.
          if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
            ProtobufList<?> list = (ProtobufList<?>) unsafe.getObject(message, fieldOffset);
            if (!list.isModifiable()) {
              final int size = list.size();
              list =
                  list.mutableCopyWithCapacity(
                      size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
              unsafe.putObject(message, fieldOffset, list);
            }
            position =
                decodeMessageList(
                    getMessageFieldSchema(pos), tag, data, position, limit, list, registers);
            continue;
          }
        } else if (fieldType <= 49) {
          // Handle all other repeated fields.
          final int oldPosition = position;
          position =
              parseRepeatedField(
                  message,
                  data,
                  position,
                  limit,
                  tag,
                  number,
                  wireType,
                  pos,
                  typeAndOffset,
                  fieldType,
                  fieldOffset,
                  registers);
          if (position != oldPosition) {
            continue;
          }
        } else if (fieldType == 50) {
          if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
            final int oldPosition = position;
            position = parseMapField(message, data, position, limit, pos, fieldOffset, registers);
            if (position != oldPosition) {
              continue;
            }
          }
        } else {
          final int oldPosition = position;
          position =
              parseOneofField(
                  message,
                  data,
                  position,
                  limit,
                  tag,
                  number,
                  wireType,
                  typeAndOffset,
                  fieldType,
                  fieldOffset,
                  pos,
                  registers);
          if (position != oldPosition) {
            continue;
          }
        }
      }
      if (tag == endDelimited && endDelimited != 0) {
        break;
      }

      if (hasExtensions
          && registers.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) {
        position = decodeExtensionOrUnknownField(
            tag, data, position, limit, message, defaultInstance,
            (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema,
            registers);
      } else {
        position = decodeUnknownField(
            tag, data, position, limit, getMutableUnknownFields(message), registers);
      }
    }
    if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
      unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
    }
    UnknownFieldSetLite unknownFields = null;
    for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
      unknownFields =
          filterMapUnknownEnumValues(
              message,
              intArray[i],
              unknownFields,
              (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema,
              message);
    }
    if (unknownFields != null) {
      ((UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema)
          .setBuilderToMessage(message, unknownFields);
    }
    if (endDelimited == 0) {
      if (position != limit) {
        throw InvalidProtocolBufferException.parseFailure();
      }
    } else {
      if (position > limit || tag != endDelimited) {
        throw InvalidProtocolBufferException.parseFailure();
      }
    }
    return position;
  }

  private Object mutableMessageFieldForMerge(T message, int pos) {
    final Schema fieldSchema = getMessageFieldSchema(pos);
    final long offset = offset(typeAndOffsetAt(pos));

    // Field not present, create a new one
    if (!isFieldPresent(message, pos)) {
      return fieldSchema.newInstance();
    }

    // Field present, if mutable, ready to merge
    final Object current = UNSAFE.getObject(message, offset);
    if (isMutable(current)) {
      return current;
    }

    // Field present but immutable, make a new mutable copy
    final Object newMessage = fieldSchema.newInstance();
    if (current != null) {
      fieldSchema.mergeFrom(newMessage, current);
    }
    return newMessage;
  }

  private void storeMessageField(T message, int pos, Object field) {
    UNSAFE.putObject(message, offset(typeAndOffsetAt(pos)), field);
    setFieldPresent(message, pos);
  }

  private Object mutableOneofMessageFieldForMerge(T message, int fieldNumber, int pos) {
    final Schema fieldSchema = getMessageFieldSchema(pos);

    // Field not present, create it and mark it present
    if (!isOneofPresent(message, fieldNumber, pos)) {
      return fieldSchema.newInstance();
    }

    // Field present, if mutable, ready to merge
    final Object current = UNSAFE.getObject(message, offset(typeAndOffsetAt(pos)));
    if (isMutable(current)) {
      return current;
    }

    // Field present but immutable, make a new mutable copy
    final Object newMessage = fieldSchema.newInstance();
    if (current != null) {
      fieldSchema.mergeFrom(newMessage, current);
    }
    return newMessage;
  }

  private void storeOneofMessageField(T message, int fieldNumber, int pos, Object field) {
    UNSAFE.putObject(message, offset(typeAndOffsetAt(pos)), field);
    setOneofPresent(message, fieldNumber, pos);
  }

  @Override
  public void mergeFrom(T message, byte[] data, int position, int limit, Registers registers)
      throws IOException {
    parseMessage(message, data, position, limit, 0, registers);
  }

  @Override
  public void makeImmutable(T message) {
    if (!isMutable(message)) {
      return;
    }

    // TODO decide if we're keeping support for Full in schema classes and handle this
    // better.
    if (message instanceof GeneratedMessageLite) {
      GeneratedMessageLite<?, ?> generatedMessage = ((GeneratedMessageLite<?, ?>) message);
      generatedMessage.clearMemoizedSerializedSize();
      generatedMessage.clearMemoizedHashCode();
      generatedMessage.markImmutable();
    }

    final int bufferLength = buffer.length;
    for (int pos = 0; pos < bufferLength; pos += INTS_PER_FIELD) {
      final int typeAndOffset = typeAndOffsetAt(pos);
      final long offset = offset(typeAndOffset);
      switch (type(typeAndOffset)) {
        case 17: // GROUP
        case 9: // MESSAGE
          if (isFieldPresent(message, pos)) {
            getMessageFieldSchema(pos).makeImmutable(UNSAFE.getObject(message, offset));
          }
          break;
        case 60: // ONEOF_MESSAGE
        case 68: // ONEOF_GROUP
          if (isOneofPresent(message, numberAt(pos), pos)) {
            getMessageFieldSchema(pos).makeImmutable(UNSAFE.getObject(message, offset));
          }
          break;
        case 18: // DOUBLE_LIST:
        case 19: // FLOAT_LIST:
        case 20: // INT64_LIST:
        case 21: // UINT64_LIST:
        case 22: // INT32_LIST:
        case 23: // FIXED64_LIST:
        case 24: // FIXED32_LIST:
        case 25: // BOOL_LIST:
        case 26: // STRING_LIST:
        case 27: // MESSAGE_LIST:
        case 28: // BYTES_LIST:
        case 29: // UINT32_LIST:
        case 30: // ENUM_LIST:
        case 31: // SFIXED32_LIST:
        case 32: // SFIXED64_LIST:
        case 33: // SINT32_LIST:
        case 34: // SINT64_LIST:
        case 35: // DOUBLE_LIST_PACKED:
        case 36: // FLOAT_LIST_PACKED:
        case 37: // INT64_LIST_PACKED:
        case 38: // UINT64_LIST_PACKED:
        case 39: // INT32_LIST_PACKED:
        case 40: // FIXED64_LIST_PACKED:
        case 41: // FIXED32_LIST_PACKED:
        case 42: // BOOL_LIST_PACKED:
        case 43: // UINT32_LIST_PACKED:
        case 44: // ENUM_LIST_PACKED:
        case 45: // SFIXED32_LIST_PACKED:
        case 46: // SFIXED64_LIST_PACKED:
        case 47: // SINT32_LIST_PACKED:
        case 48: // SINT64_LIST_PACKED:
        case 49: // GROUP_LIST:
          listFieldSchema.makeImmutableListAt(message, offset);
          break;
        case 50: // MAP:
          {
            Object mapField = UNSAFE.getObject(message, offset);
            if (mapField != null) {
              UNSAFE.putObject(message, offset, mapFieldSchema.toImmutable(mapField));
            }
          }
          break;
      }
    }
    unknownFieldSchema.makeImmutable(message);
    if (hasExtensions) {
      extensionSchema.makeImmutable(message);
    }
  }

  @SuppressWarnings("unchecked")
  private final <K, V> void mergeMap(
      Object message,
      int pos,
      Object mapDefaultEntry,
      ExtensionRegistryLite extensionRegistry,
      Reader reader)
      throws IOException {
    long offset = offset(typeAndOffsetAt(pos));
    Object mapField = UnsafeUtil.getObject(message, offset);
    // TODO: Consider creating separate implementations for full and lite. In lite
    // runtime map field will never be null but here we still need to check null because the
    // code is shared by both full and lite. It might be better if full/lite uses different
    // schema implementations.
    if (mapField == null) {
      mapField = mapFieldSchema.newMapField(mapDefaultEntry);
      UnsafeUtil.putObject(message, offset, mapField);
    } else if (mapFieldSchema.isImmutable(mapField)) {
      Object oldMapField = mapField;
      mapField = mapFieldSchema.newMapField(mapDefaultEntry);
      mapFieldSchema.mergeFrom(mapField, oldMapField);
      UnsafeUtil.putObject(message, offset, mapField);
    }
    reader.readMap(
        (Map<K, V>) mapFieldSchema.forMutableMapData(mapField),
        (Metadata<K, V>) mapFieldSchema.forMapMetadata(mapDefaultEntry),
        extensionRegistry);
  }

  private <UT, UB> UB filterMapUnknownEnumValues(
      Object message,
      int pos,
      UB unknownFields,
      UnknownFieldSchema<UT, UB> unknownFieldSchema,
      Object containerMessage) {
    int fieldNumber = numberAt(pos);
    long offset = offset(typeAndOffsetAt(pos));
    Object mapField = UnsafeUtil.getObject(message, offset);
    if (mapField == null) {
      return unknownFields;
    }
    EnumVerifier enumVerifier = getEnumFieldVerifier(pos);
    if (enumVerifier == null) {
      return unknownFields;
    }
    Map<?, ?> mapData = mapFieldSchema.forMutableMapData(mapField);
    // Filter unknown enum values.
    unknownFields =
        filterUnknownEnumMap(
            pos,
            fieldNumber,
            mapData,
            enumVerifier,
            unknownFields,
            unknownFieldSchema,
            containerMessage);
    return unknownFields;
  }

  @SuppressWarnings("unchecked")
  private <K, V, UT, UB> UB filterUnknownEnumMap(
      int pos,
      int number,
      Map<K, V> mapData,
      EnumVerifier enumVerifier,
      UB unknownFields,
      UnknownFieldSchema<UT, UB> unknownFieldSchema,
      Object containerMessage) {
    Metadata<K, V> metadata =
        (Metadata<K, V>) mapFieldSchema.forMapMetadata(getMapFieldDefaultEntry(pos));
    for (Iterator<Map.Entry<K, V>> it = mapData.entrySet().iterator(); it.hasNext(); ) {
      Map.Entry<K, V> entry = it.next();
      if (!enumVerifier.isInRange((Integer) entry.getValue())) {
        if (unknownFields == null) {
          unknownFields = unknownFieldSchema.getBuilderFromMessage(containerMessage);
        }
        int entrySize =
            MapEntryLite.computeSerializedSize(metadata, entry.getKey(), entry.getValue());
        CodedBuilder codedBuilder = ByteString.newCodedBuilder(entrySize);
        CodedOutputStream codedOutput = codedBuilder.getCodedOutput();
        try {
          MapEntryLite.writeTo(codedOutput, metadata, entry.getKey(), entry.getValue());
        } catch (IOException e) {
          // Writing to ByteString CodedOutputStream should not throw IOException.
          throw new RuntimeException(e);
        }
        unknownFieldSchema.addLengthDelimited(unknownFields, number, codedBuilder.build());
        it.remove();
      }
    }
    return unknownFields;
  }

  @Override
  public final boolean isInitialized(T message) {
    int currentPresenceFieldOffset = NO_PRESENCE_SENTINEL;
    int currentPresenceField = 0;
    for (int i = 0; i < checkInitializedCount; i++) {
      final int pos = intArray[i];
      final int number = numberAt(pos);
      final int typeAndOffset = typeAndOffsetAt(pos);

      int presenceMaskAndOffset = buffer[pos + 2];
      final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
      int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
      if (presenceFieldOffset != currentPresenceFieldOffset) {
        currentPresenceFieldOffset = presenceFieldOffset;
        if (currentPresenceFieldOffset != NO_PRESENCE_SENTINEL) {
          currentPresenceField = UNSAFE.getInt(message, (long) presenceFieldOffset);
        }
      }

      if (isRequired(typeAndOffset)) {
        if (!isFieldPresent(
            message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)) {
          return false;
        }
        // If a required message field is set but has no required fields of its own, we still
        // proceed and check the message is initialized. It should be fairly cheap to check these
        // messages but is worth documenting.
      }
      // Check nested message and groups.
      switch (type(typeAndOffset)) {
        case 9: // MESSAGE
        case 17: // GROUP
          if (isFieldPresent(
                  message, pos, currentPresenceFieldOffset, currentPresenceField, presenceMask)
              && !isInitialized(message, typeAndOffset, getMessageFieldSchema(pos))) {
            return false;
          }
          break;
        case 27: // MESSAGE_LIST
        case 49: // GROUP_LIST
          if (!isListInitialized(message, typeAndOffset, pos)) {
            return false;
          }
          break;
        case 60: // ONEOF_MESSAGE
        case 68: // ONEOF_GROUP
          if (isOneofPresent(message, number, pos)
              && !isInitialized(message, typeAndOffset, getMessageFieldSchema(pos))) {
            return false;
          }
          break;
        case 50: // MAP
          if (!isMapInitialized(message, typeAndOffset, pos)) {
            return false;
          }
          break;
        default:
          break;
      }
    }

    if (hasExtensions) {
      if (!extensionSchema.getExtensions(message).isInitialized()) {
        return false;
      }
    }

    return true;
  }

  private static boolean isInitialized(Object message, int typeAndOffset, Schema schema) {
    Object nested = UnsafeUtil.getObject(message, offset(typeAndOffset));
    return schema.isInitialized(nested);
  }

  private <N> boolean isListInitialized(Object message, int typeAndOffset, int pos) {
    @SuppressWarnings("unchecked")
    List<N> list = (List<N>) UnsafeUtil.getObject(message, offset(typeAndOffset));
    if (list.isEmpty()) {
      return true;
    }

    Schema schema = getMessageFieldSchema(pos);
    for (int i = 0; i < list.size(); i++) {
      N nested = list.get(i);
      if (!schema.isInitialized(nested)) {
        return false;
      }
    }
    return true;
  }

  private boolean isMapInitialized(T message, int typeAndOffset, int pos) {
    Map<?, ?> map = mapFieldSchema.forMapData(UnsafeUtil.getObject(message, offset(typeAndOffset)));
    if (map.isEmpty()) {
      return true;
    }
    Object mapDefaultEntry = getMapFieldDefaultEntry(pos);
    MapEntryLite.Metadata<?, ?> metadata = mapFieldSchema.forMapMetadata(mapDefaultEntry);
    if (metadata.valueType.getJavaType() != WireFormat.JavaType.MESSAGE) {
      return true;
    }
    // TODO: Use schema cache.
    Schema schema = null;
    for (Object nested : map.values()) {
      if (schema == null) {
        schema = Protobuf.getInstance().schemaFor(nested.getClass());
      }
      if (!schema.isInitialized(nested)) {
        return false;
      }
    }
    return true;
  }

  private void writeString(int fieldNumber, Object value, Writer writer) throws IOException {
    if (value instanceof String) {
      writer.writeString(fieldNumber, (String) value);
    } else {
      writer.writeBytes(fieldNumber, (ByteString) value);
    }
  }

  private void readString(Object message, int typeAndOffset, Reader reader) throws IOException {
    if (isEnforceUtf8(typeAndOffset)) {
      // Enforce valid UTF-8 on the read.
      UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readStringRequireUtf8());
    } else if (lite) {
      // Lite messages use String fields to store strings. Read a string but do not
      // enforce UTF-8
      UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
    } else {
      // Full runtime messages use Objects to store either a String or ByteString. Read
      // the string as a ByteString and do not enforce UTF-8.
      UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
    }
  }

  private void readStringList(Object message, int typeAndOffset, Reader reader) throws IOException {
    if (isEnforceUtf8(typeAndOffset)) {
      reader.readStringListRequireUtf8(
          listFieldSchema.<String>mutableListAt(message, offset(typeAndOffset)));
    } else {
      reader.readStringList(listFieldSchema.<String>mutableListAt(message, offset(typeAndOffset)));
    }
  }

  private <E> void readMessageList(
      Object message,
      int typeAndOffset,
      Reader reader,
      Schema<E> schema,
      ExtensionRegistryLite extensionRegistry)
      throws IOException {
    long offset = offset(typeAndOffset);
    reader.readMessageList(
        listFieldSchema.<E>mutableListAt(message, offset), schema, extensionRegistry);
  }

  private <E> void readGroupList(
      Object message,
      long offset,
      Reader reader,
      Schema<E> schema,
      ExtensionRegistryLite extensionRegistry)
      throws IOException {
    reader.readGroupList(
        listFieldSchema.<E>mutableListAt(message, offset), schema, extensionRegistry);
  }

  private int numberAt(int pos) {
    return buffer[pos];
  }

  private int typeAndOffsetAt(int pos) {
    return buffer[pos + 1];
  }

  private int presenceMaskAndOffsetAt(int pos) {
    return buffer[pos + 2];
  }

  private static int type(int value) {
    return (value & FIELD_TYPE_MASK) >>> OFFSET_BITS;
  }

  private static boolean isRequired(int value) {
    return (value & REQUIRED_MASK) != 0;
  }

  private static boolean isEnforceUtf8(int value) {
    return (value & ENFORCE_UTF8_MASK) != 0;
  }

  private static boolean isLegacyEnumIsClosed(int value) {
    return (value & LEGACY_ENUM_IS_CLOSED_MASK) != 0;
  }

  private static long offset(int value) {
    return value & OFFSET_MASK;
  }

  private static boolean isMutable(Object message) {
    if (message == null) {
      return false;
    }

    // TODO decide if we're keeping support for Full in schema classes and handle this
    // better.
    if (message instanceof GeneratedMessageLite<?, ?>) {
      return ((GeneratedMessageLite<?, ?>) message).isMutable();
    }

    // For other types, we'll assume this is true because that's what was
    // happening before we started checking.
    return true;
  }

  private static void checkMutable(Object message) {
    if (!isMutable(message)) {
      throw new IllegalArgumentException("Mutating immutable message: " + message);
    }
  }

  private static <T> double doubleAt(T message, long offset) {
    return UnsafeUtil.getDouble(message, offset);
  }

  private static <T> float floatAt(T message, long offset) {
    return UnsafeUtil.getFloat(message, offset);
  }

  private static <T> int intAt(T message, long offset) {
    return UnsafeUtil.getInt(message, offset);
  }

  private static <T> long longAt(T message, long offset) {
    return UnsafeUtil.getLong(message, offset);
  }

  private static <T> boolean booleanAt(T message, long offset) {
    return UnsafeUtil.getBoolean(message, offset);
  }

  private static <T> double oneofDoubleAt(T message, long offset) {
    return ((Double) UnsafeUtil.getObject(message, offset)).doubleValue();
  }

  private static <T> float oneofFloatAt(T message, long offset) {
    return ((Float) UnsafeUtil.getObject(message, offset)).floatValue();
  }

  private static <T> int oneofIntAt(T message, long offset) {
    return ((Integer) UnsafeUtil.getObject(message, offset)).intValue();
  }

  private static <T> long oneofLongAt(T message, long offset) {
    return ((Long) UnsafeUtil.getObject(message, offset)).longValue();
  }

  private static <T> boolean oneofBooleanAt(T message, long offset) {
    return ((Boolean) UnsafeUtil.getObject(message, offset)).booleanValue();
  }

  /** Returns true the field is present in both messages, or neither. */
  private boolean arePresentForEquals(T message, T other, int pos) {
    return isFieldPresent(message, pos) == isFieldPresent(other, pos);
  }

  private boolean isFieldPresent(
      T message, int pos, int presenceFieldOffset, int presenceField, int presenceMask) {
    if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
      return isFieldPresent(message, pos);
    } else {
      return (presenceField & presenceMask) != 0;
    }
  }

  private boolean isFieldPresent(T message, int pos) {
    final int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
    final long presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
    if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
      final int typeAndOffset = typeAndOffsetAt(pos);
      final long offset = offset(typeAndOffset);
      switch (type(typeAndOffset)) {
        case 0: // DOUBLE:
            return Double.doubleToRawLongBits(UnsafeUtil.getDouble(message, offset)) != 0L;
        case 1: // FLOAT:
            return Float.floatToRawIntBits(UnsafeUtil.getFloat(message, offset)) != 0;
        case 2: // INT64:
          return UnsafeUtil.getLong(message, offset) != 0L;
        case 3: // UINT64:
          return UnsafeUtil.getLong(message, offset) != 0L;
        case 4: // INT32:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 5: // FIXED64:
          return UnsafeUtil.getLong(message, offset) != 0L;
        case 6: // FIXED32:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 7: // BOOL:
          return UnsafeUtil.getBoolean(message, offset);
        case 8: // STRING:
          Object value = UnsafeUtil.getObject(message, offset);
          if (value instanceof String) {
            return !((String) value).isEmpty();
          } else if (value instanceof ByteString) {
            return !ByteString.EMPTY.equals(value);
          } else {
            throw new IllegalArgumentException();
          }
        case 9: // MESSAGE:
          return UnsafeUtil.getObject(message, offset) != null;
        case 10: // BYTES:
          return !ByteString.EMPTY.equals(UnsafeUtil.getObject(message, offset));
        case 11: // UINT32:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 12: // ENUM:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 13: // SFIXED32:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 14: // SFIXED64:
          return UnsafeUtil.getLong(message, offset) != 0L;
        case 15: // SINT32:
          return UnsafeUtil.getInt(message, offset) != 0;
        case 16: // SINT64:
          return UnsafeUtil.getLong(message, offset) != 0L;
        case 17: // GROUP:
          return UnsafeUtil.getObject(message, offset) != null;
        default:
          throw new IllegalArgumentException();
      }
    } else {
      final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
      return (UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK) & presenceMask) != 0;
    }
  }

  private void setFieldPresent(T message, int pos) {
    int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
    final long presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
    if (presenceFieldOffset == NO_PRESENCE_SENTINEL) {
      return;
    }
    final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
    UnsafeUtil.putInt(
        message,
        presenceFieldOffset,
        UnsafeUtil.getInt(message, presenceFieldOffset) | presenceMask);
  }

  private boolean isOneofPresent(T message, int fieldNumber, int pos) {
    int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
    return UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK) == fieldNumber;
  }

  private boolean isOneofCaseEqual(T message, T other, int pos) {
    int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
    return UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK)
        == UnsafeUtil.getInt(other, presenceMaskAndOffset & OFFSET_MASK);
  }

  private void setOneofPresent(T message, int fieldNumber, int pos) {
    int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
    UnsafeUtil.putInt(message, presenceMaskAndOffset & OFFSET_MASK, fieldNumber);
  }

  private int positionForFieldNumber(final int number) {
    if (number >= minFieldNumber && number <= maxFieldNumber) {
      return slowPositionForFieldNumber(number, 0);
    }
    return -1;
  }

  private int positionForFieldNumber(final int number, final int min) {
    if (number >= minFieldNumber && number <= maxFieldNumber) {
      return slowPositionForFieldNumber(number, min);
    }
    return -1;
  }

  private int slowPositionForFieldNumber(final int number, int min) {
    int max = buffer.length / INTS_PER_FIELD - 1;
    while (min <= max) {
      // Find the midpoint address.
      final int mid = (max + min) >>> 1;
      final int pos = mid * INTS_PER_FIELD;
      final int midFieldNumber = numberAt(pos);
      if (number == midFieldNumber) {
        // Found the field.
        return pos;
      }
      if (number < midFieldNumber) {
        // Search the lower half.
        max = mid - 1;
      } else {
        // Search the upper half.
        min = mid + 1;
      }
    }
    return -1;
  }

  int getSchemaSize() {
    return buffer.length * 3;
  }
}
