Hao Nguyen 6 жил өмнө
parent
commit
176f7db11d
100 өөрчлөгдсөн 30029 нэмэгдсэн , 1587 устгасан
  1. 1 1
      cmake/libprotobuf-lite.cmake
  2. 1 1
      cmake/tests.cmake
  3. 26 1
      conformance/text_format_conformance_suite.cc
  4. 2 0
      conformance/text_format_failure_list_java.txt
  5. 2 0
      conformance/text_format_failure_list_python.txt
  6. 50 0
      java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
  7. 263 0
      java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java
  8. 1076 0
      java/core/src/main/java/com/google/protobuf/ArrayDecoders.java
  9. 1729 0
      java/core/src/main/java/com/google/protobuf/BinaryReader.java
  10. 3071 0
      java/core/src/main/java/com/google/protobuf/BinaryWriter.java
  11. 64 0
      java/core/src/main/java/com/google/protobuf/BufferAllocator.java
  12. 24 8
      java/core/src/main/java/com/google/protobuf/ByteString.java
  13. 42 15
      java/core/src/main/java/com/google/protobuf/CodedInputStream.java
  14. 1333 0
      java/core/src/main/java/com/google/protobuf/CodedInputStreamReader.java
  15. 113 0
      java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
  16. 691 0
      java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java
  17. 690 0
      java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
  18. 12 1
      java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java
  19. 98 0
      java/core/src/main/java/com/google/protobuf/ExtensionSchema.java
  20. 547 0
      java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java
  21. 541 0
      java/core/src/main/java/com/google/protobuf/ExtensionSchemaLite.java
  22. 56 0
      java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java
  23. 577 0
      java/core/src/main/java/com/google/protobuf/FieldInfo.java
  24. 11 0
      java/core/src/main/java/com/google/protobuf/FieldSet.java
  25. 346 0
      java/core/src/main/java/com/google/protobuf/FieldType.java
  26. 65 0
      java/core/src/main/java/com/google/protobuf/GeneratedMessageInfoFactory.java
  27. 102 893
      java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
  28. 23 0
      java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
  29. 18 0
      java/core/src/main/java/com/google/protobuf/Internal.java
  30. 76 0
      java/core/src/main/java/com/google/protobuf/JavaType.java
  31. 12 0
      java/core/src/main/java/com/google/protobuf/LazyFieldLite.java
  32. 190 0
      java/core/src/main/java/com/google/protobuf/ListFieldSchema.java
  33. 172 0
      java/core/src/main/java/com/google/protobuf/ManifestSchemaFactory.java
  34. 63 0
      java/core/src/main/java/com/google/protobuf/MapFieldSchema.java
  35. 112 0
      java/core/src/main/java/com/google/protobuf/MapFieldSchemaFull.java
  36. 107 0
      java/core/src/main/java/com/google/protobuf/MapFieldSchemaLite.java
  37. 53 0
      java/core/src/main/java/com/google/protobuf/MapFieldSchemas.java
  38. 43 0
      java/core/src/main/java/com/google/protobuf/MessageInfo.java
  39. 41 0
      java/core/src/main/java/com/google/protobuf/MessageInfoFactory.java
  40. 5884 0
      java/core/src/main/java/com/google/protobuf/MessageSchema.java
  41. 392 0
      java/core/src/main/java/com/google/protobuf/MessageSetSchema.java
  42. 36 0
      java/core/src/main/java/com/google/protobuf/NewInstanceSchema.java
  43. 39 0
      java/core/src/main/java/com/google/protobuf/NewInstanceSchemaFull.java
  44. 39 0
      java/core/src/main/java/com/google/protobuf/NewInstanceSchemaLite.java
  45. 53 0
      java/core/src/main/java/com/google/protobuf/NewInstanceSchemas.java
  46. 66 0
      java/core/src/main/java/com/google/protobuf/OneofInfo.java
  47. 38 0
      java/core/src/main/java/com/google/protobuf/ProtoSyntax.java
  48. 152 0
      java/core/src/main/java/com/google/protobuf/Protobuf.java
  49. 94 0
      java/core/src/main/java/com/google/protobuf/ProtobufLists.java
  50. 220 0
      java/core/src/main/java/com/google/protobuf/RawMessageInfo.java
  51. 379 0
      java/core/src/main/java/com/google/protobuf/Reader.java
  52. 5 0
      java/core/src/main/java/com/google/protobuf/RopeByteString.java
  53. 85 0
      java/core/src/main/java/com/google/protobuf/Schema.java
  54. 38 0
      java/core/src/main/java/com/google/protobuf/SchemaFactory.java
  55. 991 0
      java/core/src/main/java/com/google/protobuf/SchemaUtil.java
  56. 65 0
      java/core/src/main/java/com/google/protobuf/SmallSortedMap.java
  57. 167 0
      java/core/src/main/java/com/google/protobuf/StructuralMessageInfo.java
  58. 31 23
      java/core/src/main/java/com/google/protobuf/TextFormat.java
  59. 133 0
      java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java
  60. 79 0
      java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
  61. 66 0
      java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java
  62. 140 0
      java/core/src/main/java/com/google/protobuf/UnknownFieldSetLiteSchema.java
  63. 132 0
      java/core/src/main/java/com/google/protobuf/UnknownFieldSetSchema.java
  64. 4 4
      java/core/src/main/java/com/google/protobuf/Utf8.java
  65. 219 0
      java/core/src/main/java/com/google/protobuf/Writer.java
  66. 199 0
      java/core/src/test/java/com/google/protobuf/AbstractProto2LiteSchemaTest.java
  67. 224 0
      java/core/src/test/java/com/google/protobuf/AbstractProto2SchemaTest.java
  68. 143 0
      java/core/src/test/java/com/google/protobuf/AbstractProto3LiteSchemaTest.java
  69. 151 0
      java/core/src/test/java/com/google/protobuf/AbstractProto3SchemaTest.java
  70. 157 0
      java/core/src/test/java/com/google/protobuf/AbstractSchemaTest.java
  71. 236 0
      java/core/src/test/java/com/google/protobuf/ArrayDecodersTest.java
  72. 90 0
      java/core/src/test/java/com/google/protobuf/BinaryProtocolTest.java
  73. 65 0
      java/core/src/test/java/com/google/protobuf/CachedFieldSizeTest.java
  74. 110 0
      java/core/src/test/java/com/google/protobuf/CodedAdapterTest.java
  75. 56 0
      java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java
  76. 21 0
      java/core/src/test/java/com/google/protobuf/DescriptorsTest.java
  77. 40 0
      java/core/src/test/java/com/google/protobuf/ExperimentalMessageFactory.java
  78. 113 0
      java/core/src/test/java/com/google/protobuf/ExperimentalSerializationUtil.java
  79. 189 0
      java/core/src/test/java/com/google/protobuf/ExperimentalTestDataProvider.java
  80. 4 39
      java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java
  81. 894 0
      java/core/src/test/java/com/google/protobuf/MapLiteTest.java
  82. 232 0
      java/core/src/test/java/com/google/protobuf/PackedFieldTest.java
  83. 191 0
      java/core/src/test/java/com/google/protobuf/ParserLiteTest.java
  84. 171 0
      java/core/src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java
  85. 49 0
      java/core/src/test/java/com/google/protobuf/Proto2LiteSchemaTest.java
  86. 555 0
      java/core/src/test/java/com/google/protobuf/Proto2MessageFactory.java
  87. 892 0
      java/core/src/test/java/com/google/protobuf/Proto2MessageInfoFactory.java
  88. 558 0
      java/core/src/test/java/com/google/protobuf/Proto2MessageLiteFactory.java
  89. 49 0
      java/core/src/test/java/com/google/protobuf/Proto2SchemaTest.java
  90. 111 0
      java/core/src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java
  91. 49 0
      java/core/src/test/java/com/google/protobuf/Proto3LiteSchemaTest.java
  92. 450 0
      java/core/src/test/java/com/google/protobuf/Proto3MessageFactory.java
  93. 506 0
      java/core/src/test/java/com/google/protobuf/Proto3MessageInfoFactory.java
  94. 452 0
      java/core/src/test/java/com/google/protobuf/Proto3MessageLiteFactory.java
  95. 816 0
      java/core/src/test/java/com/google/protobuf/Proto3MessageLiteInfoFactory.java
  96. 48 0
      java/core/src/test/java/com/google/protobuf/Proto3SchemaTest.java
  97. 94 0
      java/core/src/test/java/com/google/protobuf/TestSchemas.java
  98. 103 0
      java/core/src/test/java/com/google/protobuf/TestSchemasLite.java
  99. 21 0
      java/core/src/test/java/com/google/protobuf/TextFormatTest.java
  100. 0 601
      java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java

+ 1 - 1
cmake/libprotobuf-lite.cmake

@@ -7,6 +7,7 @@ set(libprotobuf_lite_files
   ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc
   ${protobuf_source_dir}/src/google/protobuf/implicit_weak_message.cc
   ${protobuf_source_dir}/src/google/protobuf/parse_context.cc
   ${protobuf_source_dir}/src/google/protobuf/parse_context.cc
   ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc
   ${protobuf_source_dir}/src/google/protobuf/io/coded_stream.cc
+  ${protobuf_source_dir}/src/google/protobuf/io/io_win32.cc
   ${protobuf_source_dir}/src/google/protobuf/io/strtod.cc
   ${protobuf_source_dir}/src/google/protobuf/io/strtod.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_impl_lite.cc
@@ -15,7 +16,6 @@ set(libprotobuf_lite_files
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128.cc
-  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece.cc

+ 1 - 1
cmake/tests.cmake

@@ -156,6 +156,7 @@ set(tests_files
   ${protobuf_source_dir}/src/google/protobuf/extension_set_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/extension_set_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/generated_message_reflection_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/coded_stream_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/coded_stream_unittest.cc
+  ${protobuf_source_dir}/src/google/protobuf/io/io_win32_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/printer_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/printer_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/tokenizer_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/tokenizer_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/io/zero_copy_stream_unittest.cc
@@ -175,7 +176,6 @@ set(tests_files
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/bytestream_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/common_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/int128_unittest.cc
-  ${protobuf_source_dir}/src/google/protobuf/stubs/io_win32_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status_test.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/status_test.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor_test.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/statusor_test.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece_unittest.cc
   ${protobuf_source_dir}/src/google/protobuf/stubs/stringpiece_unittest.cc

+ 26 - 1
conformance/text_format_conformance_suite.cc

@@ -236,7 +236,7 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
   RunValidTextFormatTest("FloatFieldWithVeryPreciseNumber", REQUIRED,
   RunValidTextFormatTest("FloatFieldWithVeryPreciseNumber", REQUIRED,
                          "optional_float: 3.123456789123456789");
                          "optional_float: 3.123456789123456789");
   RunValidTextFormatTest("FloatFieldMaxValue", REQUIRED,
   RunValidTextFormatTest("FloatFieldMaxValue", REQUIRED,
-                         "optional_float: 3.40282e+38");
+                         "optional_float: 3.4028235e+38");
   RunValidTextFormatTest("FloatFieldMinValue", REQUIRED,
   RunValidTextFormatTest("FloatFieldMinValue", REQUIRED,
                          "optional_float: 1.17549e-38");
                          "optional_float: 1.17549e-38");
   RunValidTextFormatTest("FloatFieldNaNValue", REQUIRED,
   RunValidTextFormatTest("FloatFieldNaNValue", REQUIRED,
@@ -286,6 +286,31 @@ void TextFormatConformanceTestSuite::RunSuiteImpl() {
   message.add_repeated_int32(2);
   message.add_repeated_int32(2);
   message.add_repeated_int32(3);
   message.add_repeated_int32(3);
   RunValidUnknownTextFormatTest("RepeatedUnknownFields", message);
   RunValidUnknownTextFormatTest("RepeatedUnknownFields", message);
+
+  // Any fields
+  RunValidTextFormatTest("AnyField", REQUIRED,
+                         R"(
+      optional_any: {
+        [type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3] {
+          optional_int32: 12345
+        }
+      }
+      )");
+  RunValidTextFormatTest("AnyFieldWithRawBytes", REQUIRED,
+                         R"(
+      optional_any: {
+        type_url: "type.googleapis.com/protobuf_test_messages.proto3.TestAllTypesProto3"
+        value: "\b\271`"
+      }
+      )");
+  ExpectParseFailure("AnyFieldWithInvalidType", REQUIRED,
+                     R"(
+      optional_any: {
+        [type.googleapis.com/unknown] {
+          optional_int32: 12345
+        }
+      }
+      )");
 }
 }
 
 
 }  // namespace protobuf
 }  // namespace protobuf

+ 2 - 0
conformance/text_format_failure_list_java.txt

@@ -2,3 +2,5 @@ Recommended.Proto3.ProtobufInput.GroupUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.MessageUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.RepeatedUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput
 Recommended.Proto3.ProtobufInput.ScalarUnknownFields_Drop.TextFormatOutput
+Required.Proto3.TextFormatInput.AnyField.ProtobufOutput
+Required.Proto3.TextFormatInput.AnyField.TextFormatOutput

+ 2 - 0
conformance/text_format_failure_list_python.txt

@@ -1,3 +1,5 @@
 # This is the list of text format conformance tests that are known to fail right
 # This is the list of text format conformance tests that are known to fail right
 # now.
 # now.
 # TODO: These should be fixed.
 # TODO: These should be fixed.
+Required.Proto3.TextFormatInput.FloatFieldMaxValue.ProtobufOutput
+Required.Proto3.TextFormatInput.FloatFieldMaxValue.TextFormatOutput

+ 50 - 0
java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java

@@ -106,6 +106,56 @@ public abstract class AbstractMessageLite<
   }
   }
 
 
 
 
+  @ExperimentalApi
+  protected final boolean isInitializedInternal() {
+    return Protobuf.getInstance().schemaFor(this).isInitialized(this);
+  }
+
+  @ExperimentalApi
+  protected final int getSerializedSizeInternal() {
+    return Protobuf.getInstance().schemaFor(this).getSerializedSize(this);
+  }
+
+  int getSerializedSize(Schema schema) {
+    int memoizedSerializedSize = getMemoizedSerializedSize();
+    if (memoizedSerializedSize == -1) {
+      memoizedSerializedSize = schema.getSerializedSize(this);
+      setMemoizedSerializedSize(memoizedSerializedSize);
+    }
+    return memoizedSerializedSize;
+  }
+
+  @ExperimentalApi
+  protected final void writeToInternal(CodedOutputStream output) throws IOException {
+    Protobuf.getInstance()
+        .schemaFor(getClassInternal())
+        .writeTo(this, CodedOutputStreamWriter.forCodedOutput(output));
+  }
+
+  @ExperimentalApi
+  protected void mergeFromInternal(CodedInputStream input, ExtensionRegistryLite extensionRegistry)
+      throws InvalidProtocolBufferException {
+    try {
+      Protobuf.getInstance()
+          .schemaFor(getClassInternal())
+          .mergeFrom(this, CodedInputStreamReader.forCodedInput(input), extensionRegistry);
+    } catch (InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (IOException e) {
+      throw new InvalidProtocolBufferException(e).setUnfinishedMessage(this);
+    }
+  }
+
+  @ExperimentalApi
+  protected void makeImmutableInternal() {
+    Protobuf.getInstance().schemaFor(getClassInternal()).makeImmutable(this);
+  }
+
+  @SuppressWarnings("unchecked")
+  private Class<AbstractMessageLite<MessageType, BuilderType>> getClassInternal() {
+    return (Class<AbstractMessageLite<MessageType, BuilderType>>) getClass();
+  }
+
   /** Package private helper method for AbstractParser to create UninitializedMessageException. */
   /** Package private helper method for AbstractParser to create UninitializedMessageException. */
   UninitializedMessageException newUninitializedMessageException() {
   UninitializedMessageException newUninitializedMessageException() {
     return new UninitializedMessageException(this);
     return new UninitializedMessageException(this);

+ 263 - 0
java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java

@@ -0,0 +1,263 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A buffer that was allocated by a {@link BufferAllocator}. For every buffer, it is guaranteed that
+ * at least one of {@link #hasArray()} or {@link #hasNioBuffer()} will be {@code true}.
+ */
+@ExperimentalApi
+abstract class AllocatedBuffer {
+  /**
+   * Indicates whether this buffer contains a backing {@link ByteBuffer} (i.e. it is safe to call
+   * {@link #nioBuffer()}).
+   */
+  public abstract boolean hasNioBuffer();
+
+  /**
+   * Indicates whether this buffer contains a backing array (i.e. it is safe to call {@link
+   * #array()}).
+   */
+  public abstract boolean hasArray();
+
+  /**
+   * Returns the {@link ByteBuffer} that backs this buffer <i>(optional operation)</i>.
+   *
+   * <p>Call {@link #hasNioBuffer()} before invoking this method in order to ensure that this buffer
+   * has a backing {@link ByteBuffer}.
+   *
+   * @return The {@link ByteBuffer} that backs this buffer
+   * @throws UnsupportedOperationException If this buffer is not backed by a {@link ByteBuffer}.
+   */
+  public abstract ByteBuffer nioBuffer();
+
+  /**
+   * Returns the byte array that backs this buffer <i>(optional operation)</i>.
+   *
+   * <p>Call {@link #hasArray()} before invoking this method in order to ensure that this buffer has
+   * an accessible backing array.
+   *
+   * @return The array that backs this buffer
+   * @throws java.nio.ReadOnlyBufferException If this buffer is backed by an array but is read-only
+   * @throws UnsupportedOperationException If this buffer is not backed by an accessible array
+   */
+  public abstract byte[] array();
+
+  /**
+   * Returns the offset within this buffer's backing array of the first element of the buffer
+   * <i>(optional operation)</i>.
+   *
+   * <p>If this buffer is backed by an array then {@link #position()} corresponds to the array index
+   * {@link #position()} {@code +} {@link #arrayOffset()}.
+   *
+   * <p>Invoke the {@link #hasArray hasArray} method before invoking this method in order to ensure
+   * that this buffer has an accessible backing array.
+   *
+   * @return The offset within this buffer's array of the first element of the buffer
+   * @throws java.nio.ReadOnlyBufferException If this buffer is backed by an array but is read-only
+   * @throws UnsupportedOperationException If this buffer is not backed by an accessible array
+   */
+  public abstract int arrayOffset();
+
+  /**
+   * Returns this buffer's position.
+   *
+   * @return The position of this buffer
+   */
+  public abstract int position();
+
+  /**
+   * Sets this buffer's position.
+   *
+   * @param position The new position value; must be non-negative and no larger than the current
+   *     limit
+   * @return This buffer
+   * @throws IllegalArgumentException If the preconditions on {@code position} do not hold
+   */
+  public abstract AllocatedBuffer position(int position);
+
+  /**
+   * Returns this buffer's limit.
+   *
+   * @return The limit of this buffer
+   */
+  public abstract int limit();
+
+  /**
+   * Returns the number of elements between the current {@link #position()} and the {@link #limit()}
+   * .
+   *
+   * @return The number of elements remaining in this buffer
+   */
+  public abstract int remaining();
+
+  /**
+   * Creates a new {@link AllocatedBuffer} that is backed by the given array. The returned buffer
+   * will have {@link #hasArray} == {@code true}, {@link #arrayOffset()} == {@code 0}, {@link
+   * #position()} == {@code 0} and {@link #limit()} equal to the length of {@code bytes}.
+   */
+  public static AllocatedBuffer wrap(byte[] bytes) {
+    return wrapNoCheck(bytes, 0, bytes.length);
+  }
+
+  /**
+   * Creates a new {@link AllocatedBuffer} that is backed by the given array. The returned buffer
+   * will have {@link #hasArray} == {@code true}, {@link #arrayOffset()} == {@code offset}, {@link
+   * #position()} == {@code 0} and {@link #limit()} == {@code length}.
+   */
+  public static AllocatedBuffer wrap(final byte[] bytes, final int offset, final int length) {
+    if (offset < 0 || length < 0 || (offset + length) > bytes.length) {
+      throw new IndexOutOfBoundsException(
+          String.format("bytes.length=%d, offset=%d, length=%d", bytes.length, offset, length));
+    }
+
+    return wrapNoCheck(bytes, offset, length);
+  }
+
+  /**
+   * Creates a new {@link AllocatedBuffer} that is backed by the given {@link ByteBuffer}. The
+   * returned buffer will have {@link #hasNioBuffer} == {@code true}.
+   */
+  public static AllocatedBuffer wrap(final ByteBuffer buffer) {
+    checkNotNull(buffer, "buffer");
+
+    return new AllocatedBuffer() {
+
+      @Override
+      public boolean hasNioBuffer() {
+        return true;
+      }
+
+      @Override
+      public ByteBuffer nioBuffer() {
+        return buffer;
+      }
+
+      @Override
+      public boolean hasArray() {
+        return buffer.hasArray();
+      }
+
+      @Override
+      public byte[] array() {
+        return buffer.array();
+      }
+
+      @Override
+      public int arrayOffset() {
+        return buffer.arrayOffset();
+      }
+
+      @Override
+      public int position() {
+        return buffer.position();
+      }
+
+      @Override
+      public AllocatedBuffer position(int position) {
+        buffer.position(position);
+        return this;
+      }
+
+      @Override
+      public int limit() {
+        return buffer.limit();
+      }
+
+      @Override
+      public int remaining() {
+        return buffer.remaining();
+      }
+    };
+  }
+
+  private static AllocatedBuffer wrapNoCheck(
+      final byte[] bytes, final int offset, final int length) {
+    return new AllocatedBuffer() {
+      // Relative to offset.
+      private int position;
+
+      @Override
+      public boolean hasNioBuffer() {
+        return false;
+      }
+
+      @Override
+      public ByteBuffer nioBuffer() {
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public boolean hasArray() {
+        return true;
+      }
+
+      @Override
+      public byte[] array() {
+        return bytes;
+      }
+
+      @Override
+      public int arrayOffset() {
+        return offset;
+      }
+
+      @Override
+      public int position() {
+        return position;
+      }
+
+      @Override
+      public AllocatedBuffer position(int position) {
+        if (position < 0 || position > length) {
+          throw new IllegalArgumentException("Invalid position: " + position);
+        }
+        this.position = position;
+        return this;
+      }
+
+      @Override
+      public int limit() {
+        // Relative to offset.
+        return length;
+      }
+
+      @Override
+      public int remaining() {
+        return length - position;
+      }
+    };
+  }
+}

+ 1076 - 0
java/core/src/main/java/com/google/protobuf/ArrayDecoders.java

@@ -0,0 +1,1076 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.MessageSchema.getMutableUnknownFields;
+
+import com.google.protobuf.Internal.ProtobufList;
+import java.io.IOException;
+
+/**
+ * Helper functions to decode protobuf wire format from a byte array.
+ *
+ * <p>Note that these functions don't do boundary check on the byte array but instead rely on Java
+ * VM to check it. That means parsing rountines utilizing these functions must catch
+ * IndexOutOfBoundsException and convert it to protobuf's InvalidProtocolBufferException when
+ * crossing protobuf public API boundaries.
+ */
+final class ArrayDecoders {
+  /**
+   * A helper used to return multiple values in a Java function. Java doesn't natively support
+   * returning multiple values in a function. Creating a new Object to hold the return values will
+   * be too expensive. Instead, we pass a Registers instance to functions that want to return
+   * multiple values and let the function set the return value in this Registers instance instead.
+   *
+   * <p>TODO(xiaofeng): This could be merged into CodedInputStream or CodedInputStreamReader which
+   * is already being passed through all the parsing rountines.
+   */
+  static final class Registers {
+    public int int1;
+    public long long1;
+    public Object object1;
+    public final ExtensionRegistryLite extensionRegistry;
+
+    Registers() {
+      this.extensionRegistry = ExtensionRegistryLite.getEmptyRegistry();
+    }
+
+    Registers(ExtensionRegistryLite extensionRegistry) {
+      if (extensionRegistry == null) {
+        throw new NullPointerException();
+      }
+      this.extensionRegistry = extensionRegistry;
+    }
+  }
+
+  /**
+   * Decodes a varint. Returns the position after the varint. The decoded varint is stored in
+   * registers.int1.
+   */
+  static int decodeVarint32(byte[] data, int position, Registers registers) {
+    int value = data[position++];
+    if (value >= 0) {
+      registers.int1 = value;
+      return position;
+    }
+    return decodeVarint32(value, data, position, registers);
+  }
+
+  /** Like decodeVarint32 except that the first byte is already read. */
+  static int decodeVarint32(int firstByte, byte[] data, int position, Registers registers) {
+    int value = firstByte & 0x7F;
+    final byte b2 = data[position++];
+    if (b2 >= 0) {
+      registers.int1 = value | ((int) b2 << 7);
+      return position;
+    }
+    value |= (b2 & 0x7F) << 7;
+
+    final byte b3 = data[position++];
+    if (b3 >= 0) {
+      registers.int1 = value | ((int) b3 << 14);
+      return position;
+    }
+    value |= (b3 & 0x7F) << 14;
+
+    final byte b4 = data[position++];
+    if (b4 >= 0) {
+      registers.int1 = value | ((int) b4 << 21);
+      return position;
+    }
+    value |= (b4 & 0x7F) << 21;
+
+    final byte b5 = data[position++];
+    if (b5 >= 0) {
+      registers.int1 = value | ((int) b5 << 28);
+      return position;
+    }
+    value |= (b5 & 0x7F) << 28;
+
+    while (data[position++] < 0) {}
+
+    registers.int1 = value;
+    return position;
+  }
+
+  /**
+   * Decodes a varint. Returns the position after the varint. The decoded varint is stored in
+   * registers.long1.
+   */
+  static int decodeVarint64(byte[] data, int position, Registers registers) {
+    long value = data[position++];
+    if (value >= 0) {
+      registers.long1 = value;
+      return position;
+    } else {
+      return decodeVarint64(value, data, position, registers);
+    }
+  }
+
+  /** Like decodeVarint64 except that the first byte is already read. */
+  static int decodeVarint64(long firstByte, byte[] data, int position, Registers registers) {
+    long value = firstByte & 0x7F;
+    byte next = data[position++];
+    int shift = 7;
+    value |= (long) (next & 0x7F) << 7;
+    while (next < 0) {
+      next = data[position++];
+      shift += 7;
+      value |= (long) (next & 0x7F) << shift;
+    }
+    registers.long1 = value;
+    return position;
+  }
+
+  /** Decodes and returns a fixed32 value. */
+  static int decodeFixed32(byte[] data, int position) {
+    return (data[position] & 0xff)
+        | ((data[position + 1] & 0xff) << 8)
+        | ((data[position + 2] & 0xff) << 16)
+        | ((data[position + 3] & 0xff) << 24);
+  }
+
+  /** Decodes and returns a fixed64 value. */
+  static long decodeFixed64(byte[] data, int position) {
+    return (data[position] & 0xffL)
+        | ((data[position + 1] & 0xffL) << 8)
+        | ((data[position + 2] & 0xffL) << 16)
+        | ((data[position + 3] & 0xffL) << 24)
+        | ((data[position + 4] & 0xffL) << 32)
+        | ((data[position + 5] & 0xffL) << 40)
+        | ((data[position + 6] & 0xffL) << 48)
+        | ((data[position + 7] & 0xffL) << 56);
+  }
+
+  /** Decodes and returns a double value. */
+  static double decodeDouble(byte[] data, int position) {
+    return Double.longBitsToDouble(decodeFixed64(data, position));
+  }
+
+  /** Decodes and returns a float value. */
+  static float decodeFloat(byte[] data, int position) {
+    return Float.intBitsToFloat(decodeFixed32(data, position));
+  }
+
+  /** Decodes a string value. */
+  static int decodeString(byte[] data, int position, Registers registers)
+      throws InvalidProtocolBufferException {
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length == 0) {
+      registers.object1 = "";
+      return position;
+    } else {
+      registers.object1 = new String(data, position, length, Internal.UTF_8);
+      return position + length;
+    }
+  }
+
+  /** Decodes a string value with utf8 check. */
+  static int decodeStringRequireUtf8(byte[] data, int position, Registers registers)
+      throws InvalidProtocolBufferException {
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length == 0) {
+      registers.object1 = "";
+      return position;
+    } else {
+      registers.object1 = Utf8.decodeUtf8(data, position, length);
+      return position + length;
+    }
+  }
+
+  /** Decodes a bytes value. */
+  static int decodeBytes(byte[] data, int position, Registers registers)
+      throws InvalidProtocolBufferException {
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length > data.length - position) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    } else if (length == 0) {
+      registers.object1 = ByteString.EMPTY;
+      return position;
+    } else {
+      registers.object1 = ByteString.copyFrom(data, position, length);
+      return position + length;
+    }
+  }
+
+  /** Decodes a message value. */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  static int decodeMessageField(
+      Schema schema, byte[] data, int position, int limit, Registers registers) throws IOException {
+    int length = data[position++];
+    if (length < 0) {
+      position = decodeVarint32(length, data, position, registers);
+      length = registers.int1;
+    }
+    if (length < 0 || length > limit - position) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    Object result = schema.newInstance();
+    schema.mergeFrom(result, data, position, position + length, registers);
+    schema.makeImmutable(result);
+    registers.object1 = result;
+    return position + length;
+  }
+
+  /** Decodes a group value. */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  static int decodeGroupField(
+      Schema schema, byte[] data, int position, int limit, int endGroup, Registers registers)
+      throws IOException {
+    // A group field must has a MessageSchema (the only other subclass of Schema is MessageSetSchema
+    // and it can't be used in group fields).
+    final MessageSchema messageSchema = (MessageSchema) schema;
+    Object result = messageSchema.newInstance();
+    // It's OK to directly use parseProto2Message since proto3 doesn't have group.
+    final int endPosition =
+        messageSchema.parseProto2Message(result, data, position, limit, endGroup, registers);
+    messageSchema.makeImmutable(result);
+    registers.object1 = result;
+    return endPosition;
+  }
+
+  /** Decodes a repeated 32-bit varint field. Returns the position after all read values. */
+  static int decodeVarint32List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final IntArrayList output = (IntArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    output.addInt(registers.int1);
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint32(data, nextPosition, registers);
+      output.addInt(registers.int1);
+    }
+    return position;
+  }
+
+  /** Decodes a repeated 64-bit varint field. Returns the position after all read values. */
+  static int decodeVarint64List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final LongArrayList output = (LongArrayList) list;
+    position = decodeVarint64(data, position, registers);
+    output.addLong(registers.long1);
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint64(data, nextPosition, registers);
+      output.addLong(registers.long1);
+    }
+    return position;
+  }
+
+  /** Decodes a repeated fixed32 field. Returns the position after all read values. */
+  static int decodeFixed32List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final IntArrayList output = (IntArrayList) list;
+    output.addInt(decodeFixed32(data, position));
+    position += 4;
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      output.addInt(decodeFixed32(data, nextPosition));
+      position = nextPosition + 4;
+    }
+    return position;
+  }
+
+  /** Decodes a repeated fixed64 field. Returns the position after all read values. */
+  static int decodeFixed64List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final LongArrayList output = (LongArrayList) list;
+    output.addLong(decodeFixed64(data, position));
+    position += 8;
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      output.addLong(decodeFixed64(data, nextPosition));
+      position = nextPosition + 8;
+    }
+    return position;
+  }
+
+  /** Decodes a repeated float field. Returns the position after all read values. */
+  static int decodeFloatList(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final FloatArrayList output = (FloatArrayList) list;
+    output.addFloat(decodeFloat(data, position));
+    position += 4;
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      output.addFloat(decodeFloat(data, nextPosition));
+      position = nextPosition + 4;
+    }
+    return position;
+  }
+
+  /** Decodes a repeated double field. Returns the position after all read values. */
+  static int decodeDoubleList(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final DoubleArrayList output = (DoubleArrayList) list;
+    output.addDouble(decodeDouble(data, position));
+    position += 8;
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      output.addDouble(decodeDouble(data, nextPosition));
+      position = nextPosition + 8;
+    }
+    return position;
+  }
+
+  /** Decodes a repeated boolean field. Returns the position after all read values. */
+  static int decodeBoolList(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final BooleanArrayList output = (BooleanArrayList) list;
+    position = decodeVarint64(data, position, registers);
+    output.addBoolean(registers.long1 != 0);
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint64(data, nextPosition, registers);
+      output.addBoolean(registers.long1 != 0);
+    }
+    return position;
+  }
+
+  /** Decodes a repeated sint32 field. Returns the position after all read values. */
+  static int decodeSInt32List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final IntArrayList output = (IntArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint32(data, nextPosition, registers);
+      output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
+    }
+    return position;
+  }
+
+  /** Decodes a repeated sint64 field. Returns the position after all read values. */
+  static int decodeSInt64List(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers) {
+    final LongArrayList output = (LongArrayList) list;
+    position = decodeVarint64(data, position, registers);
+    output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint64(data, nextPosition, registers);
+      output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
+    }
+    return position;
+  }
+
+  /** Decodes a packed 32-bit varint field. Returns the position after all read values. */
+  static int decodePackedVarint32List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final IntArrayList output = (IntArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      position = decodeVarint32(data, position, registers);
+      output.addInt(registers.int1);
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed 64-bit varint field. Returns the position after all read values. */
+  static int decodePackedVarint64List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final LongArrayList output = (LongArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      position = decodeVarint64(data, position, registers);
+      output.addLong(registers.long1);
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed fixed32 field. Returns the position after all read values. */
+  static int decodePackedFixed32List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final IntArrayList output = (IntArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      output.addInt(decodeFixed32(data, position));
+      position += 4;
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed fixed64 field. Returns the position after all read values. */
+  static int decodePackedFixed64List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final LongArrayList output = (LongArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      output.addLong(decodeFixed64(data, position));
+      position += 8;
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed float field. Returns the position after all read values. */
+  static int decodePackedFloatList(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final FloatArrayList output = (FloatArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      output.addFloat(decodeFloat(data, position));
+      position += 4;
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed double field. Returns the position after all read values. */
+  static int decodePackedDoubleList(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final DoubleArrayList output = (DoubleArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      output.addDouble(decodeDouble(data, position));
+      position += 8;
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed boolean field. Returns the position after all read values. */
+  static int decodePackedBoolList(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final BooleanArrayList output = (BooleanArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      position = decodeVarint64(data, position, registers);
+      output.addBoolean(registers.long1 != 0);
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed sint32 field. Returns the position after all read values. */
+  static int decodePackedSInt32List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final IntArrayList output = (IntArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      position = decodeVarint32(data, position, registers);
+      output.addInt(CodedInputStream.decodeZigZag32(registers.int1));
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a packed sint64 field. Returns the position after all read values. */
+  @SuppressWarnings("unchecked")
+  static int decodePackedSInt64List(
+      byte[] data, int position, ProtobufList<?> list, Registers registers) throws IOException {
+    final LongArrayList output = (LongArrayList) list;
+    position = decodeVarint32(data, position, registers);
+    final int fieldLimit = position + registers.int1;
+    while (position < fieldLimit) {
+      position = decodeVarint64(data, position, registers);
+      output.addLong(CodedInputStream.decodeZigZag64(registers.long1));
+    }
+    if (position != fieldLimit) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+    return position;
+  }
+
+  /** Decodes a repeated string field. Returns the position after all read values. */
+  @SuppressWarnings("unchecked")
+  static int decodeStringList(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
+      throws InvalidProtocolBufferException {
+    final ProtobufList<String> output = (ProtobufList<String>) list;
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length == 0) {
+      output.add("");
+    } else {
+      String value = new String(data, position, length, Internal.UTF_8);
+      output.add(value);
+      position += length;
+    }
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint32(data, nextPosition, registers);
+      final int nextLength = registers.int1;
+      if (nextLength < 0) {
+        throw InvalidProtocolBufferException.negativeSize();
+      } else if (nextLength == 0) {
+        output.add("");
+      } else {
+        String value = new String(data, position, nextLength, Internal.UTF_8);
+        output.add(value);
+        position += nextLength;
+      }
+    }
+    return position;
+  }
+
+  /**
+   * Decodes a repeated string field with utf8 check. Returns the position after all read values.
+   */
+  @SuppressWarnings("unchecked")
+  static int decodeStringListRequireUtf8(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
+      throws InvalidProtocolBufferException {
+    final ProtobufList<String> output = (ProtobufList<String>) list;
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length == 0) {
+      output.add("");
+    } else {
+      if (!Utf8.isValidUtf8(data, position, position + length)) {
+        throw InvalidProtocolBufferException.invalidUtf8();
+      }
+      String value = new String(data, position, length, Internal.UTF_8);
+      output.add(value);
+      position += length;
+    }
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint32(data, nextPosition, registers);
+      final int nextLength = registers.int1;
+      if (nextLength < 0) {
+        throw InvalidProtocolBufferException.negativeSize();
+      } else if (nextLength == 0) {
+        output.add("");
+      } else {
+        if (!Utf8.isValidUtf8(data, position, position + nextLength)) {
+          throw InvalidProtocolBufferException.invalidUtf8();
+        }
+        String value = new String(data, position, nextLength, Internal.UTF_8);
+        output.add(value);
+        position += nextLength;
+      }
+    }
+    return position;
+  }
+
+  /** Decodes a repeated bytes field. Returns the position after all read values. */
+  @SuppressWarnings("unchecked")
+  static int decodeBytesList(
+      int tag, byte[] data, int position, int limit, ProtobufList<?> list, Registers registers)
+      throws InvalidProtocolBufferException {
+    final ProtobufList<ByteString> output = (ProtobufList<ByteString>) list;
+    position = decodeVarint32(data, position, registers);
+    final int length = registers.int1;
+    if (length < 0) {
+      throw InvalidProtocolBufferException.negativeSize();
+    } else if (length > data.length - position) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    } else if (length == 0) {
+      output.add(ByteString.EMPTY);
+    } else {
+      output.add(ByteString.copyFrom(data, position, length));
+      position += length;
+    }
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeVarint32(data, nextPosition, registers);
+      final int nextLength = registers.int1;
+      if (nextLength < 0) {
+        throw InvalidProtocolBufferException.negativeSize();
+      } else if (nextLength > data.length - position) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      } else if (nextLength == 0) {
+        output.add(ByteString.EMPTY);
+      } else {
+        output.add(ByteString.copyFrom(data, position, nextLength));
+        position += nextLength;
+      }
+    }
+    return position;
+  }
+
+  /**
+   * Decodes a repeated message field
+   *
+   * @return The position of after read all messages
+   */
+  @SuppressWarnings({"unchecked"})
+  static int decodeMessageList(
+      Schema<?> schema,
+      int tag,
+      byte[] data,
+      int position,
+      int limit,
+      ProtobufList<?> list,
+      Registers registers)
+      throws IOException {
+    final ProtobufList<Object> output = (ProtobufList<Object>) list;
+    position = decodeMessageField(schema, data, position, limit, registers);
+    output.add(registers.object1);
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeMessageField(schema, data, nextPosition, limit, registers);
+      output.add(registers.object1);
+    }
+    return position;
+  }
+
+  /**
+   * Decodes a repeated group field
+   *
+   * @return The position of after read all groups
+   */
+  @SuppressWarnings({"unchecked", "rawtypes"})
+  static int decodeGroupList(
+      Schema schema,
+      int tag,
+      byte[] data,
+      int position,
+      int limit,
+      ProtobufList<?> list,
+      Registers registers)
+      throws IOException {
+    final ProtobufList<Object> output = (ProtobufList<Object>) list;
+    final int endgroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
+    position = decodeGroupField(schema, data, position, limit, endgroup, registers);
+    output.add(registers.object1);
+    while (position < limit) {
+      int nextPosition = decodeVarint32(data, position, registers);
+      if (tag != registers.int1) {
+        break;
+      }
+      position = decodeGroupField(schema, data, nextPosition, limit, endgroup, registers);
+      output.add(registers.object1);
+    }
+    return position;
+  }
+
+  static int decodeExtensionOrUnknownField(
+      int tag, byte[] data, int position, int limit,
+      Object message,
+      MessageLite defaultInstance,
+      UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite> unknownFieldSchema,
+      Registers registers)
+      throws IOException {
+    final int number = tag >>> 3;
+    GeneratedMessageLite.GeneratedExtension extension =
+        registers.extensionRegistry.findLiteExtensionByNumber(defaultInstance, number);
+    if (extension == null) {
+      return decodeUnknownField(
+          tag, data, position, limit, getMutableUnknownFields(message), registers);
+    } else  {
+      ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).ensureExtensionsAreMutable();
+      return decodeExtension(
+          tag, data, position, limit, (GeneratedMessageLite.ExtendableMessage) message,
+          extension, unknownFieldSchema, registers);
+    }
+  }
+
+  static int decodeExtension(
+      int tag,
+      byte[] data,
+      int position,
+      int limit,
+      GeneratedMessageLite.ExtendableMessage<?, ?> message,
+      GeneratedMessageLite.GeneratedExtension<?, ?> extension,
+      UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite> unknownFieldSchema,
+      Registers registers)
+      throws IOException {
+    final FieldSet<GeneratedMessageLite.ExtensionDescriptor> extensions = message.extensions;
+    final int fieldNumber = tag >>> 3;
+    if (extension.descriptor.isRepeated() && extension.descriptor.isPacked()) {
+      switch (extension.getLiteType()) {
+        case DOUBLE:
+        {
+          DoubleArrayList list = new DoubleArrayList();
+          position = decodePackedDoubleList(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case FLOAT:
+        {
+          FloatArrayList list = new FloatArrayList();
+          position = decodePackedFloatList(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case INT64:
+        case UINT64:
+        {
+          LongArrayList list = new LongArrayList();
+          position = decodePackedVarint64List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case INT32:
+        case UINT32:
+        {
+          IntArrayList list = new IntArrayList();
+          position = decodePackedVarint32List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case FIXED64:
+        case SFIXED64:
+        {
+          LongArrayList list = new LongArrayList();
+          position = decodePackedFixed64List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case FIXED32:
+        case SFIXED32:
+        {
+          IntArrayList list = new IntArrayList();
+          position = decodePackedFixed32List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case BOOL:
+        {
+          BooleanArrayList list = new BooleanArrayList();
+          position = decodePackedBoolList(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case SINT32:
+        {
+          IntArrayList list = new IntArrayList();
+          position = decodePackedSInt32List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case SINT64:
+        {
+          LongArrayList list = new LongArrayList();
+          position = decodePackedSInt64List(data, position, list, registers);
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        case ENUM:
+        {
+          IntArrayList list = new IntArrayList();
+          position = decodePackedVarint32List(data, position, list, registers);
+          UnknownFieldSetLite unknownFields = message.unknownFields;
+          if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
+            unknownFields = null;
+          }
+          unknownFields =
+              SchemaUtil.filterUnknownEnumList(
+                  fieldNumber,
+                  list,
+                  extension.descriptor.getEnumType(),
+                  unknownFields,
+                  unknownFieldSchema);
+          if (unknownFields != null) {
+            message.unknownFields = unknownFields;
+          }
+          extensions.setField(extension.descriptor, list);
+          break;
+        }
+        default:
+          throw new IllegalStateException(
+              "Type cannot be packed: " + extension.descriptor.getLiteType());
+      }
+    } else {
+      Object value = null;
+      // Enum is a special case becasue unknown enum values will be put into UnknownFieldSetLite.
+      if (extension.getLiteType() == WireFormat.FieldType.ENUM) {
+        position = decodeVarint32(data, position, registers);
+        Object enumValue = extension.descriptor.getEnumType().findValueByNumber(registers.int1);
+        if (enumValue == null) {
+          UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
+          if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
+            unknownFields = UnknownFieldSetLite.newInstance();
+            ((GeneratedMessageLite) message).unknownFields = unknownFields;
+          }
+          SchemaUtil.storeUnknownEnum(
+              fieldNumber, registers.int1, unknownFields, unknownFieldSchema);
+          return position;
+        }
+        // Note, we store the integer value instead of the actual enum object in FieldSet.
+        // This is also different from full-runtime where we store EnumValueDescriptor.
+        value = registers.int1;
+      } else {
+        switch (extension.getLiteType()) {
+          case DOUBLE:
+            value = decodeDouble(data, position);
+            position += 8;
+            break;
+          case FLOAT:
+            value = decodeFloat(data, position);
+            position += 4;
+            break;
+          case INT64:
+          case UINT64:
+            position = decodeVarint64(data, position, registers);
+            value = registers.long1;
+            break;
+          case INT32:
+          case UINT32:
+            position = decodeVarint32(data, position, registers);
+            value = registers.int1;
+            break;
+          case FIXED64:
+          case SFIXED64:
+            value = decodeFixed64(data, position);
+            position += 8;
+            break;
+          case FIXED32:
+          case SFIXED32:
+            value = decodeFixed32(data, position);
+            position += 4;
+            break;
+          case BOOL:
+            position = decodeVarint64(data, position, registers);
+            value = (registers.long1 != 0);
+            break;
+          case BYTES:
+            position = decodeBytes(data, position, registers);
+            value = registers.object1;
+            break;
+          case SINT32:
+            position = decodeVarint32(data, position, registers);
+            value = CodedInputStream.decodeZigZag32(registers.int1);
+            break;
+          case SINT64:
+            position = decodeVarint64(data, position, registers);
+            value = CodedInputStream.decodeZigZag64(registers.long1);
+            break;
+          case STRING:
+            position = decodeString(data, position, registers);
+            value = registers.object1;
+            break;
+          case GROUP:
+            final int endTag = (fieldNumber << 3) | WireFormat.WIRETYPE_END_GROUP;
+            position = decodeGroupField(
+                Protobuf.getInstance().schemaFor(extension.getMessageDefaultInstance().getClass()),
+                data, position, limit, endTag, registers);
+            value = registers.object1;
+            break;
+
+          case MESSAGE:
+            position = decodeMessageField(
+                Protobuf.getInstance().schemaFor(extension.getMessageDefaultInstance().getClass()),
+                data, position, limit, registers);
+            value = registers.object1;
+            break;
+
+          case ENUM:
+            throw new IllegalStateException("Shouldn't reach here.");
+        }
+      }
+      if (extension.isRepeated()) {
+        extensions.addRepeatedField(extension.descriptor, value);
+      } else {
+        switch (extension.getLiteType()) {
+          case MESSAGE:
+          case GROUP:
+            Object oldValue = extensions.getField(extension.descriptor);
+            if (oldValue != null) {
+              value = Internal.mergeMessage(oldValue, value);
+            }
+            break;
+          default:
+            break;
+        }
+        extensions.setField(extension.descriptor, value);
+      }
+    }
+    return position;
+  }
+
+  /** Decodes an unknown field. */
+  static int decodeUnknownField(
+      int tag,
+      byte[] data,
+      int position,
+      int limit,
+      UnknownFieldSetLite unknownFields,
+      Registers registers)
+      throws InvalidProtocolBufferException {
+    if (WireFormat.getTagFieldNumber(tag) == 0) {
+      throw InvalidProtocolBufferException.invalidTag();
+    }
+    switch (WireFormat.getTagWireType(tag)) {
+      case WireFormat.WIRETYPE_VARINT:
+        position = decodeVarint64(data, position, registers);
+        unknownFields.storeField(tag, registers.long1);
+        return position;
+      case WireFormat.WIRETYPE_FIXED32:
+        unknownFields.storeField(tag, decodeFixed32(data, position));
+        return position + 4;
+      case WireFormat.WIRETYPE_FIXED64:
+        unknownFields.storeField(tag, decodeFixed64(data, position));
+        return position + 8;
+      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+        position = decodeVarint32(data, position, registers);
+        final int length = registers.int1;
+        if (length < 0) {
+          throw InvalidProtocolBufferException.negativeSize();
+        } else if (length > data.length - position) {
+          throw InvalidProtocolBufferException.truncatedMessage();
+        } else if (length == 0) {
+          unknownFields.storeField(tag, ByteString.EMPTY);
+        } else {
+          unknownFields.storeField(tag, ByteString.copyFrom(data, position, length));
+        }
+        return position + length;
+      case WireFormat.WIRETYPE_START_GROUP:
+        final UnknownFieldSetLite child = UnknownFieldSetLite.newInstance();
+        final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
+        int lastTag = 0;
+        while (position < limit) {
+          position = decodeVarint32(data, position, registers);
+          lastTag = registers.int1;
+          if (lastTag == endGroup) {
+            break;
+          }
+          position = decodeUnknownField(lastTag, data, position, limit, child, registers);
+        }
+        if (position > limit || lastTag != endGroup) {
+          throw InvalidProtocolBufferException.parseFailure();
+        }
+        unknownFields.storeField(tag, child);
+        return position;
+      default:
+        throw InvalidProtocolBufferException.invalidTag();
+    }
+  }
+
+  /** Skips an unknown field. */
+  static int skipField(int tag, byte[] data, int position, int limit, Registers registers)
+      throws InvalidProtocolBufferException {
+    if (WireFormat.getTagFieldNumber(tag) == 0) {
+      throw InvalidProtocolBufferException.invalidTag();
+    }
+    switch (WireFormat.getTagWireType(tag)) {
+      case WireFormat.WIRETYPE_VARINT:
+        position = decodeVarint64(data, position, registers);
+        return position;
+      case WireFormat.WIRETYPE_FIXED32:
+        return position + 4;
+      case WireFormat.WIRETYPE_FIXED64:
+        return position + 8;
+      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+        position = decodeVarint32(data, position, registers);
+        return position + registers.int1;
+      case WireFormat.WIRETYPE_START_GROUP:
+        final int endGroup = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
+        int lastTag = 0;
+        while (position < limit) {
+          position = decodeVarint32(data, position, registers);
+          lastTag = registers.int1;
+          if (lastTag == endGroup) {
+            break;
+          }
+          position = skipField(lastTag, data, position, limit, registers);
+        }
+        if (position > limit || lastTag != endGroup) {
+          throw InvalidProtocolBufferException.parseFailure();
+        }
+        return position;
+      default:
+        throw InvalidProtocolBufferException.invalidTag();
+    }
+  }
+}

+ 1729 - 0
java/core/src/main/java/com/google/protobuf/BinaryReader.java

@@ -0,0 +1,1729 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.WireFormat.FIXED32_SIZE;
+import static com.google.protobuf.WireFormat.FIXED64_SIZE;
+import static com.google.protobuf.WireFormat.WIRETYPE_END_GROUP;
+import static com.google.protobuf.WireFormat.WIRETYPE_FIXED32;
+import static com.google.protobuf.WireFormat.WIRETYPE_FIXED64;
+import static com.google.protobuf.WireFormat.WIRETYPE_LENGTH_DELIMITED;
+import static com.google.protobuf.WireFormat.WIRETYPE_START_GROUP;
+import static com.google.protobuf.WireFormat.WIRETYPE_VARINT;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A {@link Reader} that reads from a buffer containing a message serialized with the binary
+ * protocol.
+ */
+@ExperimentalApi
+abstract class BinaryReader implements Reader {
+  private static final int FIXED32_MULTIPLE_MASK = FIXED32_SIZE - 1;
+  private static final int FIXED64_MULTIPLE_MASK = FIXED64_SIZE - 1;
+
+  /**
+   * Creates a new reader using the given {@code buffer} as input.
+   *
+   * @param buffer the input buffer. The buffer (including position, limit, etc.) will not be
+   *     modified. To increment the buffer position after the read completes, use the value returned
+   *     by {@link #getTotalBytesRead()}.
+   * @param bufferIsImmutable if {@code true} the reader assumes that the content of {@code buffer}
+   *     will never change and any allocated {@link ByteString} instances will by directly wrap
+   *     slices of {@code buffer}.
+   * @return the reader
+   */
+  public static BinaryReader newInstance(ByteBuffer buffer, boolean bufferIsImmutable) {
+    if (buffer.hasArray()) {
+      // TODO(nathanmittler): Add support for unsafe operations.
+      return new SafeHeapReader(buffer, bufferIsImmutable);
+    }
+    // TODO(nathanmittler): Add support for direct buffers
+    throw new IllegalArgumentException("Direct buffers not yet supported");
+  }
+
+  /** Only allow subclassing for inner classes. */
+  private BinaryReader() {}
+
+  /** Returns the total number of bytes read so far from the input buffer. */
+  public abstract int getTotalBytesRead();
+
+  @Override
+  public boolean shouldDiscardUnknownFields() {
+    return false;
+  }
+
+  /**
+   * A {@link BinaryReader} implementation that operates on a heap {@link ByteBuffer}. Uses only
+   * safe operations on the underlying array.
+   */
+  private static final class SafeHeapReader extends BinaryReader {
+    private final boolean bufferIsImmutable;
+    private final byte[] buffer;
+    private int pos;
+    private final int initialPos;
+    private int limit;
+    private int tag;
+    private int endGroupTag;
+
+    public SafeHeapReader(ByteBuffer bytebuf, boolean bufferIsImmutable) {
+      this.bufferIsImmutable = bufferIsImmutable;
+      buffer = bytebuf.array();
+      initialPos = pos = bytebuf.arrayOffset() + bytebuf.position();
+      limit = bytebuf.arrayOffset() + bytebuf.limit();
+    }
+
+    private boolean isAtEnd() {
+      return pos == limit;
+    }
+
+    @Override
+    public int getTotalBytesRead() {
+      return pos - initialPos;
+    }
+
+    @Override
+    public int getFieldNumber() throws IOException {
+      if (isAtEnd()) {
+        return Reader.READ_DONE;
+      }
+      tag = readVarint32();
+      if (tag == endGroupTag) {
+        return Reader.READ_DONE;
+      }
+      return WireFormat.getTagFieldNumber(tag);
+    }
+
+    @Override
+    public int getTag() {
+      return tag;
+    }
+
+    @Override
+    public boolean skipField() throws IOException {
+      if (isAtEnd() || tag == endGroupTag) {
+        return false;
+      }
+
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_VARINT:
+          skipVarint();
+          return true;
+        case WIRETYPE_FIXED64:
+          skipBytes(FIXED64_SIZE);
+          return true;
+        case WIRETYPE_LENGTH_DELIMITED:
+          skipBytes(readVarint32());
+          return true;
+        case WIRETYPE_FIXED32:
+          skipBytes(FIXED32_SIZE);
+          return true;
+        case WIRETYPE_START_GROUP:
+          skipGroup();
+          return true;
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+
+    @Override
+    public double readDouble() throws IOException {
+      requireWireType(WIRETYPE_FIXED64);
+      return Double.longBitsToDouble(readLittleEndian64());
+    }
+
+    @Override
+    public float readFloat() throws IOException {
+      requireWireType(WIRETYPE_FIXED32);
+      return Float.intBitsToFloat(readLittleEndian32());
+    }
+
+    @Override
+    public long readUInt64() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint64();
+    }
+
+    @Override
+    public long readInt64() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint64();
+    }
+
+    @Override
+    public int readInt32() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint32();
+    }
+
+    @Override
+    public long readFixed64() throws IOException {
+      requireWireType(WIRETYPE_FIXED64);
+      return readLittleEndian64();
+    }
+
+    @Override
+    public int readFixed32() throws IOException {
+      requireWireType(WIRETYPE_FIXED32);
+      return readLittleEndian32();
+    }
+
+    @Override
+    public boolean readBool() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint32() != 0;
+    }
+
+    @Override
+    public String readString() throws IOException {
+      return readStringInternal(false);
+    }
+
+    @Override
+    public String readStringRequireUtf8() throws IOException {
+      return readStringInternal(true);
+    }
+
+    public String readStringInternal(boolean requireUtf8) throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      final int size = readVarint32();
+      if (size == 0) {
+        return "";
+      }
+
+      requireBytes(size);
+      if (requireUtf8 && !Utf8.isValidUtf8(buffer, pos, pos + size)) {
+        throw InvalidProtocolBufferException.invalidUtf8();
+      }
+      String result = new String(buffer, pos, size, Internal.UTF_8);
+      pos += size;
+      return result;
+    }
+
+    @Override
+    public <T> T readMessage(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      return readMessage(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
+    }
+
+    @Override
+    public <T> T readMessageBySchemaWithCheck(
+        Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      return readMessage(schema, extensionRegistry);
+    }
+
+    private <T> T readMessage(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      int size = readVarint32();
+      requireBytes(size);
+
+      // Update the limit.
+      int prevLimit = limit;
+      int newLimit = pos + size;
+      limit = newLimit;
+
+      try {
+        // Allocate and read the message.
+        T message = schema.newInstance();
+        schema.mergeFrom(message, this, extensionRegistry);
+        schema.makeImmutable(message);
+
+        if (pos != newLimit) {
+          throw InvalidProtocolBufferException.parseFailure();
+        }
+        return message;
+      } finally {
+        // Restore the limit.
+        limit = prevLimit;
+      }
+    }
+
+    @Override
+    public <T> T readGroup(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      requireWireType(WIRETYPE_START_GROUP);
+      return readGroup(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
+    }
+
+    @Override
+    public <T> T readGroupBySchemaWithCheck(
+        Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException {
+      requireWireType(WIRETYPE_START_GROUP);
+      return readGroup(schema, extensionRegistry);
+    }
+
+    private <T> T readGroup(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      int prevEndGroupTag = endGroupTag;
+      endGroupTag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WIRETYPE_END_GROUP);
+
+      try {
+        // Allocate and read the message.
+        T message = schema.newInstance();
+        schema.mergeFrom(message, this, extensionRegistry);
+        schema.makeImmutable(message);
+
+        if (tag != endGroupTag) {
+          throw InvalidProtocolBufferException.parseFailure();
+        }
+        return message;
+      } finally {
+        // Restore the old end group tag.
+        endGroupTag = prevEndGroupTag;
+      }
+    }
+
+    @Override
+    public ByteString readBytes() throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      int size = readVarint32();
+      if (size == 0) {
+        return ByteString.EMPTY;
+      }
+
+      requireBytes(size);
+      ByteString bytes =
+          bufferIsImmutable
+              ? ByteString.wrap(buffer, pos, size)
+              : ByteString.copyFrom(buffer, pos, size);
+      pos += size;
+      return bytes;
+    }
+
+    @Override
+    public int readUInt32() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint32();
+    }
+
+    @Override
+    public int readEnum() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return readVarint32();
+    }
+
+    @Override
+    public int readSFixed32() throws IOException {
+      requireWireType(WIRETYPE_FIXED32);
+      return readLittleEndian32();
+    }
+
+    @Override
+    public long readSFixed64() throws IOException {
+      requireWireType(WIRETYPE_FIXED64);
+      return readLittleEndian64();
+    }
+
+    @Override
+    public int readSInt32() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return CodedInputStream.decodeZigZag32(readVarint32());
+    }
+
+    @Override
+    public long readSInt64() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return CodedInputStream.decodeZigZag64(readVarint64());
+    }
+
+    @Override
+    public void readDoubleList(List<Double> target) throws IOException {
+      if (target instanceof DoubleArrayList) {
+        DoubleArrayList plist = (DoubleArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addDouble(Double.longBitsToDouble(readLittleEndian64_NoCheck()));
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              plist.addDouble(readDouble());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(Double.longBitsToDouble(readLittleEndian64_NoCheck()));
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              target.add(readDouble());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readFloatList(List<Float> target) throws IOException {
+      if (target instanceof FloatArrayList) {
+        FloatArrayList plist = (FloatArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addFloat(Float.intBitsToFloat(readLittleEndian32_NoCheck()));
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              plist.addFloat(readFloat());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(Float.intBitsToFloat(readLittleEndian32_NoCheck()));
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              target.add(readFloat());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readUInt64List(List<Long> target) throws IOException {
+      if (target instanceof LongArrayList) {
+        LongArrayList plist = (LongArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readVarint64());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addLong(readUInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint64());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readUInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readInt64List(List<Long> target) throws IOException {
+      if (target instanceof LongArrayList) {
+        LongArrayList plist = (LongArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readVarint64());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addLong(readInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint64());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readInt32List(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(readVarint32());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addInt(readInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint32());
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readFixed64List(List<Long> target) throws IOException {
+      if (target instanceof LongArrayList) {
+        LongArrayList plist = (LongArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readLittleEndian64_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              plist.addLong(readFixed64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readLittleEndian64_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              target.add(readFixed64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readFixed32List(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(readLittleEndian32_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              plist.addInt(readFixed32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readLittleEndian32_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              target.add(readFixed32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readBoolList(List<Boolean> target) throws IOException {
+      if (target instanceof BooleanArrayList) {
+        BooleanArrayList plist = (BooleanArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addBoolean(readVarint32() != 0);
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addBoolean(readBool());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint32() != 0);
+            }
+            requirePosition(fieldEndPos);
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readBool());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readStringList(List<String> target) throws IOException {
+      readStringListInternal(target, false);
+    }
+
+    @Override
+    public void readStringListRequireUtf8(List<String> target) throws IOException {
+      readStringListInternal(target, true);
+    }
+
+    public void readStringListInternal(List<String> target, boolean requireUtf8)
+        throws IOException {
+      if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+
+      if (target instanceof LazyStringList && !requireUtf8) {
+        LazyStringList lazyList = (LazyStringList) target;
+        while (true) {
+          lazyList.add(readBytes());
+
+          if (isAtEnd()) {
+            return;
+          }
+          int prevPos = pos;
+          int nextTag = readVarint32();
+          if (nextTag != tag) {
+            // We've reached the end of the repeated field. Rewind the buffer position to before
+            // the new tag.
+            pos = prevPos;
+            return;
+          }
+        }
+      } else {
+        while (true) {
+          target.add(readStringInternal(requireUtf8));
+
+          if (isAtEnd()) {
+            return;
+          }
+          int prevPos = pos;
+          int nextTag = readVarint32();
+          if (nextTag != tag) {
+            // We've reached the end of the repeated field. Rewind the buffer position to before
+            // the new tag.
+            pos = prevPos;
+            return;
+          }
+        }
+      }
+    }
+
+    @Override
+    public <T> void readMessageList(
+        List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+      readMessageList(target, schema, extensionRegistry);
+    }
+
+    @Override
+    public <T> void readMessageList(
+        List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+      final int listTag = tag;
+      while (true) {
+        target.add(readMessage(schema, extensionRegistry));
+
+        if (isAtEnd()) {
+          return;
+        }
+        int prevPos = pos;
+        int nextTag = readVarint32();
+        if (nextTag != listTag) {
+          // We've reached the end of the repeated field. Rewind the buffer position to before
+          // the new tag.
+          pos = prevPos;
+          return;
+        }
+      }
+    }
+
+    @Override
+    public <T> void readGroupList(
+        List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+      readGroupList(target, schema, extensionRegistry);
+    }
+
+    @Override
+    public <T> void readGroupList(
+        List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      if (WireFormat.getTagWireType(tag) != WIRETYPE_START_GROUP) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+      final int listTag = tag;
+      while (true) {
+        target.add(readGroup(schema, extensionRegistry));
+
+        if (isAtEnd()) {
+          return;
+        }
+        int prevPos = pos;
+        int nextTag = readVarint32();
+        if (nextTag != listTag) {
+          // We've reached the end of the repeated field. Rewind the buffer position to before
+          // the new tag.
+          pos = prevPos;
+          return;
+        }
+      }
+    }
+
+    @Override
+    public void readBytesList(List<ByteString> target) throws IOException {
+      if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+
+      while (true) {
+        target.add(readBytes());
+
+        if (isAtEnd()) {
+          return;
+        }
+        int prevPos = pos;
+        int nextTag = readVarint32();
+        if (nextTag != tag) {
+          // We've reached the end of the repeated field. Rewind the buffer position to before
+          // the new tag.
+          pos = prevPos;
+          return;
+        }
+      }
+    }
+
+    @Override
+    public void readUInt32List(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(readVarint32());
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addInt(readUInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint32());
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readUInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readEnumList(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(readVarint32());
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addInt(readEnum());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint32());
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readEnum());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readSFixed32List(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(readLittleEndian32_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              plist.addInt(readSFixed32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed32Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readLittleEndian32_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED32:
+            while (true) {
+              target.add(readSFixed32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readSFixed64List(List<Long> target) throws IOException {
+      if (target instanceof LongArrayList) {
+        LongArrayList plist = (LongArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readLittleEndian64_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              plist.addLong(readSFixed64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            verifyPackedFixed64Length(bytes);
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readLittleEndian64_NoCheck());
+            }
+            break;
+          case WIRETYPE_FIXED64:
+            while (true) {
+              target.add(readSFixed64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readSInt32List(List<Integer> target) throws IOException {
+      if (target instanceof IntArrayList) {
+        IntArrayList plist = (IntArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(CodedInputStream.decodeZigZag32(readVarint32()));
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addInt(readSInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(CodedInputStream.decodeZigZag32(readVarint32()));
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readSInt32());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @Override
+    public void readSInt64List(List<Long> target) throws IOException {
+      if (target instanceof LongArrayList) {
+        LongArrayList plist = (LongArrayList) target;
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(CodedInputStream.decodeZigZag64(readVarint64()));
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              plist.addLong(readSInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      } else {
+        switch (WireFormat.getTagWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(CodedInputStream.decodeZigZag64(readVarint64()));
+            }
+            break;
+          case WIRETYPE_VARINT:
+            while (true) {
+              target.add(readSInt64());
+
+              if (isAtEnd()) {
+                return;
+              }
+              int prevPos = pos;
+              int nextTag = readVarint32();
+              if (nextTag != tag) {
+                // We've reached the end of the repeated field. Rewind the buffer position to before
+                // the new tag.
+                pos = prevPos;
+                return;
+              }
+            }
+          default:
+            throw InvalidProtocolBufferException.invalidWireType();
+        }
+      }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <K, V> void readMap(
+        Map<K, V> target,
+        MapEntryLite.Metadata<K, V> metadata,
+        ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      int size = readVarint32();
+      requireBytes(size);
+
+      // Update the limit.
+      int prevLimit = limit;
+      int newLimit = pos + size;
+      limit = newLimit;
+
+      try {
+        K key = metadata.defaultKey;
+        V value = metadata.defaultValue;
+        while (true) {
+          int number = getFieldNumber();
+          if (number == READ_DONE) {
+            break;
+          }
+          try {
+            switch (number) {
+              case 1:
+                key = (K) readField(metadata.keyType, null, null);
+                break;
+              case 2:
+                value =
+                    (V)
+                        readField(
+                            metadata.valueType,
+                            metadata.defaultValue.getClass(),
+                            extensionRegistry);
+                break;
+              default:
+                if (!skipField()) {
+                  throw new InvalidProtocolBufferException("Unable to parse map entry.");
+                }
+                break;
+            }
+          } catch (InvalidProtocolBufferException.InvalidWireTypeException ignore) {
+            // the type doesn't match, skip the field.
+            if (!skipField()) {
+              throw new InvalidProtocolBufferException("Unable to parse map entry.");
+            }
+          }
+        }
+        target.put(key, value);
+      } finally {
+        // Restore the limit.
+        limit = prevLimit;
+      }
+    }
+
+    private Object readField(
+        WireFormat.FieldType fieldType,
+        Class<?> messageType,
+        ExtensionRegistryLite extensionRegistry)
+        throws IOException {
+      switch (fieldType) {
+        case BOOL:
+          return readBool();
+        case BYTES:
+          return readBytes();
+        case DOUBLE:
+          return readDouble();
+        case ENUM:
+          return readEnum();
+        case FIXED32:
+          return readFixed32();
+        case FIXED64:
+          return readFixed64();
+        case FLOAT:
+          return readFloat();
+        case INT32:
+          return readInt32();
+        case INT64:
+          return readInt64();
+        case MESSAGE:
+          return readMessage(messageType, extensionRegistry);
+        case SFIXED32:
+          return readSFixed32();
+        case SFIXED64:
+          return readSFixed64();
+        case SINT32:
+          return readSInt32();
+        case SINT64:
+          return readSInt64();
+        case STRING:
+          return readStringRequireUtf8();
+        case UINT32:
+          return readUInt32();
+        case UINT64:
+          return readUInt64();
+        default:
+          throw new RuntimeException("unsupported field type.");
+      }
+    }
+
+    /** Read a raw Varint from the stream. If larger than 32 bits, discard the upper bits. */
+    private int readVarint32() throws IOException {
+      // See implementation notes for readRawVarint64
+      int i = pos;
+
+      if (limit == pos) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+
+      int x;
+      if ((x = buffer[i++]) >= 0) {
+        pos = i;
+        return x;
+      } else if (limit - i < 9) {
+        return (int) readVarint64SlowPath();
+      } else if ((x ^= (buffer[i++] << 7)) < 0) {
+        x ^= (~0 << 7);
+      } else if ((x ^= (buffer[i++] << 14)) >= 0) {
+        x ^= (~0 << 7) ^ (~0 << 14);
+      } else if ((x ^= (buffer[i++] << 21)) < 0) {
+        x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21);
+      } else {
+        int y = buffer[i++];
+        x ^= y << 28;
+        x ^= (~0 << 7) ^ (~0 << 14) ^ (~0 << 21) ^ (~0 << 28);
+        if (y < 0
+            && buffer[i++] < 0
+            && buffer[i++] < 0
+            && buffer[i++] < 0
+            && buffer[i++] < 0
+            && buffer[i++] < 0) {
+          throw InvalidProtocolBufferException.malformedVarint();
+        }
+      }
+      pos = i;
+      return x;
+    }
+
+    public long readVarint64() throws IOException {
+      // Implementation notes:
+      //
+      // Optimized for one-byte values, expected to be common.
+      // The particular code below was selected from various candidates
+      // empirically, by winning VarintBenchmark.
+      //
+      // Sign extension of (signed) Java bytes is usually a nuisance, but
+      // we exploit it here to more easily obtain the sign of bytes read.
+      // Instead of cleaning up the sign extension bits by masking eagerly,
+      // we delay until we find the final (positive) byte, when we clear all
+      // accumulated bits with one xor.  We depend on javac to constant fold.
+      int i = pos;
+
+      if (limit == i) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+
+      final byte[] buffer = this.buffer;
+      long x;
+      int y;
+      if ((y = buffer[i++]) >= 0) {
+        pos = i;
+        return y;
+      } else if (limit - i < 9) {
+        return readVarint64SlowPath();
+      } else if ((y ^= (buffer[i++] << 7)) < 0) {
+        x = y ^ (~0 << 7);
+      } else if ((y ^= (buffer[i++] << 14)) >= 0) {
+        x = y ^ ((~0 << 7) ^ (~0 << 14));
+      } else if ((y ^= (buffer[i++] << 21)) < 0) {
+        x = y ^ ((~0 << 7) ^ (~0 << 14) ^ (~0 << 21));
+      } else if ((x = y ^ ((long) buffer[i++] << 28)) >= 0L) {
+        x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28);
+      } else if ((x ^= ((long) buffer[i++] << 35)) < 0L) {
+        x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35);
+      } else if ((x ^= ((long) buffer[i++] << 42)) >= 0L) {
+        x ^= (~0L << 7) ^ (~0L << 14) ^ (~0L << 21) ^ (~0L << 28) ^ (~0L << 35) ^ (~0L << 42);
+      } else if ((x ^= ((long) buffer[i++] << 49)) < 0L) {
+        x ^=
+            (~0L << 7)
+                ^ (~0L << 14)
+                ^ (~0L << 21)
+                ^ (~0L << 28)
+                ^ (~0L << 35)
+                ^ (~0L << 42)
+                ^ (~0L << 49);
+      } else {
+        x ^= ((long) buffer[i++] << 56);
+        x ^=
+            (~0L << 7)
+                ^ (~0L << 14)
+                ^ (~0L << 21)
+                ^ (~0L << 28)
+                ^ (~0L << 35)
+                ^ (~0L << 42)
+                ^ (~0L << 49)
+                ^ (~0L << 56);
+        if (x < 0L) {
+          if (buffer[i++] < 0L) {
+            throw InvalidProtocolBufferException.malformedVarint();
+          }
+        }
+      }
+      pos = i;
+      return x;
+    }
+
+    private long readVarint64SlowPath() throws IOException {
+      long result = 0;
+      for (int shift = 0; shift < 64; shift += 7) {
+        final byte b = readByte();
+        result |= (long) (b & 0x7F) << shift;
+        if ((b & 0x80) == 0) {
+          return result;
+        }
+      }
+      throw InvalidProtocolBufferException.malformedVarint();
+    }
+
+    private byte readByte() throws IOException {
+      if (pos == limit) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+      return buffer[pos++];
+    }
+
+    private int readLittleEndian32() throws IOException {
+      requireBytes(FIXED32_SIZE);
+      return readLittleEndian32_NoCheck();
+    }
+
+    private long readLittleEndian64() throws IOException {
+      requireBytes(FIXED64_SIZE);
+      return readLittleEndian64_NoCheck();
+    }
+
+    private int readLittleEndian32_NoCheck() {
+      int p = pos;
+      final byte[] buffer = this.buffer;
+      pos = p + FIXED32_SIZE;
+      return (((buffer[p] & 0xff))
+          | ((buffer[p + 1] & 0xff) << 8)
+          | ((buffer[p + 2] & 0xff) << 16)
+          | ((buffer[p + 3] & 0xff) << 24));
+    }
+
+    private long readLittleEndian64_NoCheck() {
+      int p = pos;
+      final byte[] buffer = this.buffer;
+      pos = p + FIXED64_SIZE;
+      return (((buffer[p] & 0xffL))
+          | ((buffer[p + 1] & 0xffL) << 8)
+          | ((buffer[p + 2] & 0xffL) << 16)
+          | ((buffer[p + 3] & 0xffL) << 24)
+          | ((buffer[p + 4] & 0xffL) << 32)
+          | ((buffer[p + 5] & 0xffL) << 40)
+          | ((buffer[p + 6] & 0xffL) << 48)
+          | ((buffer[p + 7] & 0xffL) << 56));
+    }
+
+    private void skipVarint() throws IOException {
+      if (limit - pos >= 10) {
+        final byte[] buffer = this.buffer;
+        int p = pos;
+        for (int i = 0; i < 10; i++) {
+          if (buffer[p++] >= 0) {
+            pos = p;
+            return;
+          }
+        }
+      }
+      skipVarintSlowPath();
+    }
+
+    private void skipVarintSlowPath() throws IOException {
+      for (int i = 0; i < 10; i++) {
+        if (readByte() >= 0) {
+          return;
+        }
+      }
+      throw InvalidProtocolBufferException.malformedVarint();
+    }
+
+    private void skipBytes(final int size) throws IOException {
+      requireBytes(size);
+
+      pos += size;
+    }
+
+    private void skipGroup() throws IOException {
+      int prevEndGroupTag = endGroupTag;
+      endGroupTag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WIRETYPE_END_GROUP);
+      while (true) {
+        if (getFieldNumber() == READ_DONE || !skipField()) {
+          break;
+        }
+      }
+      if (tag != endGroupTag) {
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+      endGroupTag = prevEndGroupTag;
+    }
+
+    private void requireBytes(int size) throws IOException {
+      if (size < 0 || size > (limit - pos)) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+    }
+
+    private void requireWireType(int requiredWireType) throws IOException {
+      if (WireFormat.getTagWireType(tag) != requiredWireType) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+
+    private void verifyPackedFixed64Length(int bytes) throws IOException {
+      requireBytes(bytes);
+      if ((bytes & FIXED64_MULTIPLE_MASK) != 0) {
+        // Require that the number of bytes be a multiple of 8.
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+    }
+
+    private void verifyPackedFixed32Length(int bytes) throws IOException {
+      requireBytes(bytes);
+      if ((bytes & FIXED32_MULTIPLE_MASK) != 0) {
+        // Require that the number of bytes be a multiple of 4.
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+    }
+
+    private void requirePosition(int expectedPosition) throws IOException {
+      if (pos != expectedPosition) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+    }
+  }
+}

+ 3071 - 0
java/core/src/main/java/com/google/protobuf/BinaryWriter.java

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

+ 64 - 0
java/core/src/main/java/com/google/protobuf/BufferAllocator.java

@@ -0,0 +1,64 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.nio.ByteBuffer;
+
+/**
+ * An object responsible for allocation of buffers. This is an extension point to enable buffer
+ * pooling within an application.
+ */
+@ExperimentalApi
+abstract class BufferAllocator {
+  private static final BufferAllocator UNPOOLED =
+      new BufferAllocator() {
+        @Override
+        public AllocatedBuffer allocateHeapBuffer(int capacity) {
+          return AllocatedBuffer.wrap(new byte[capacity]);
+        }
+
+        @Override
+        public AllocatedBuffer allocateDirectBuffer(int capacity) {
+          return AllocatedBuffer.wrap(ByteBuffer.allocateDirect(capacity));
+        }
+      };
+
+  /** Returns an unpooled buffer allocator, which will create a new buffer for each request. */
+  public static BufferAllocator unpooled() {
+    return UNPOOLED;
+  }
+
+  /** Allocates a buffer with the given capacity that is backed by an array on the heap. */
+  public abstract AllocatedBuffer allocateHeapBuffer(int capacity);
+
+  /** Allocates a direct (i.e. non-heap) buffer with the given capacity. */
+  public abstract AllocatedBuffer allocateDirectBuffer(int capacity);
+}

+ 24 - 8
java/core/src/main/java/com/google/protobuf/ByteString.java

@@ -732,6 +732,16 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
    */
    */
   abstract void writeTo(ByteOutput byteOutput) throws IOException;
   abstract void writeTo(ByteOutput byteOutput) throws IOException;
 
 
+  /**
+   * This method behaves exactly the same as {@link #writeTo(ByteOutput)} unless the {@link
+   * ByteString} is a rope. For ropes, the leaf nodes are written in reverse order to the {@code
+   * byteOutput}.
+   *
+   * @param byteOutput the output target to receive the bytes
+   * @throws IOException if an I/O error occurs
+   * @see UnsafeByteOperations#unsafeWriteToReverse(ByteString, ByteOutput)
+   */
+  abstract void writeToReverse(ByteOutput byteOutput) throws IOException;
 
 
   /**
   /**
    * Constructs a read-only {@code java.nio.ByteBuffer} whose content is equal to the contents of
    * Constructs a read-only {@code java.nio.ByteBuffer} whose content is equal to the contents of
@@ -862,6 +872,10 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
       return true;
       return true;
     }
     }
 
 
+    @Override
+    void writeToReverse(ByteOutput byteOutput) throws IOException {
+      writeTo(byteOutput);
+    }
 
 
     /**
     /**
      * Check equality of the substring of given length of this object starting at zero with another
      * Check equality of the substring of given length of this object starting at zero with another
@@ -1438,14 +1452,16 @@ public abstract class ByteString implements Iterable<Byte>, Serializable {
         LiteralByteString lbsOther = (LiteralByteString) other;
         LiteralByteString lbsOther = (LiteralByteString) other;
         byte[] thisBytes = bytes;
         byte[] thisBytes = bytes;
         byte[] otherBytes = lbsOther.bytes;
         byte[] otherBytes = lbsOther.bytes;
-
-        return UnsafeUtil.mismatch(
-                thisBytes,
-                getOffsetIntoBytes(),
-                otherBytes,
-                lbsOther.getOffsetIntoBytes() + offset,
-                length)
-            == -1;
+        int thisLimit = getOffsetIntoBytes() + length;
+        for (int thisIndex = getOffsetIntoBytes(),
+                otherIndex = lbsOther.getOffsetIntoBytes() + offset;
+            (thisIndex < thisLimit);
+            ++thisIndex, ++otherIndex) {
+          if (thisBytes[thisIndex] != otherBytes[otherIndex]) {
+            return false;
+          }
+        }
+        return true;
       }
       }
 
 
       return other.substring(offset, offset + length).equals(substring(0, length));
       return other.substring(offset, offset + length).equals(substring(0, length));

+ 42 - 15
java/core/src/main/java/com/google/protobuf/CodedInputStream.java

@@ -72,6 +72,9 @@ public abstract class CodedInputStream {
   /** Visible for subclasses. See setSizeLimit() */
   /** Visible for subclasses. See setSizeLimit() */
   int sizeLimit = DEFAULT_SIZE_LIMIT;
   int sizeLimit = DEFAULT_SIZE_LIMIT;
 
 
+  /** Used to adapt to the experimental {@link Reader} interface. */
+  CodedInputStreamReader wrapper;
+
   /** Create a new CodedInputStream wrapping the given InputStream. */
   /** Create a new CodedInputStream wrapping the given InputStream. */
   public static CodedInputStream newInstance(final InputStream input) {
   public static CodedInputStream newInstance(final InputStream input) {
     return newInstance(input, DEFAULT_BUFFER_SIZE);
     return newInstance(input, DEFAULT_BUFFER_SIZE);
@@ -2263,7 +2266,7 @@ public abstract class CodedInputStream {
         return result;
         return result;
       }
       }
       // Slow path:  Build a byte array first then copy it.
       // Slow path:  Build a byte array first then copy it.
-      return new String(readRawBytesSlowPath(size), UTF_8);
+      return new String(readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ false), UTF_8);
     }
     }
 
 
     @Override
     @Override
@@ -2287,7 +2290,7 @@ public abstract class CodedInputStream {
         pos = tempPos + size;
         pos = tempPos + size;
       } else {
       } else {
         // Slow path:  Build a byte array first then copy it.
         // Slow path:  Build a byte array first then copy it.
-        bytes = readRawBytesSlowPath(size);
+        bytes = readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ false);
         tempPos = 0;
         tempPos = 0;
       }
       }
       return Utf8.decodeUtf8(bytes, tempPos, size);
       return Utf8.decodeUtf8(bytes, tempPos, size);
@@ -2392,7 +2395,8 @@ public abstract class CodedInputStream {
         return result;
         return result;
       } else {
       } else {
         // Slow path: Build a byte array first then copy it.
         // Slow path: Build a byte array first then copy it.
-        return readRawBytesSlowPath(size);
+        // TODO(dweis): Do we want to protect from malicious input streams here?
+        return readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ false);
       }
       }
     }
     }
 
 
@@ -2409,7 +2413,10 @@ public abstract class CodedInputStream {
         return Internal.EMPTY_BYTE_BUFFER;
         return Internal.EMPTY_BYTE_BUFFER;
       }
       }
       // Slow path: Build a byte array first then copy it.
       // Slow path: Build a byte array first then copy it.
-      return ByteBuffer.wrap(readRawBytesSlowPath(size));
+      
+      // We must copy as the byte array was handed off to the InputStream and a malicious
+      // implementation could retain a reference.
+      return ByteBuffer.wrap(readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ true));
     }
     }
 
 
     @Override
     @Override
@@ -2812,19 +2819,24 @@ public abstract class CodedInputStream {
         pos = tempPos + size;
         pos = tempPos + size;
         return Arrays.copyOfRange(buffer, tempPos, tempPos + size);
         return Arrays.copyOfRange(buffer, tempPos, tempPos + size);
       } else {
       } else {
-        return readRawBytesSlowPath(size);
+        // TODO(dweis): Do we want to protect from malicious input streams here?
+        return readRawBytesSlowPath(size, /* ensureNoLeakedReferences= */ false);
       }
       }
     }
     }
 
 
     /**
     /**
      * Exactly like readRawBytes, but caller must have already checked the fast path: (size <=
      * Exactly like readRawBytes, but caller must have already checked the fast path: (size <=
      * (bufferSize - pos) && size > 0)
      * (bufferSize - pos) && size > 0)
+     * 
+     * If ensureNoLeakedReferences is true, the value is guaranteed to have not escaped to
+     * untrusted code.
      */
      */
-    private byte[] readRawBytesSlowPath(final int size) throws IOException {
+    private byte[] readRawBytesSlowPath(
+        final int size, boolean ensureNoLeakedReferences) throws IOException {
       // Attempt to read the data in one byte array when it's safe to do.
       // Attempt to read the data in one byte array when it's safe to do.
       byte[] result = readRawBytesSlowPathOneChunk(size);
       byte[] result = readRawBytesSlowPathOneChunk(size);
       if (result != null) {
       if (result != null) {
-        return result;
+        return ensureNoLeakedReferences ? result.clone() : result;
       }
       }
 
 
       final int originalBufferPos = pos;
       final int originalBufferPos = pos;
@@ -2862,6 +2874,8 @@ public abstract class CodedInputStream {
     /**
     /**
      * Attempts to read the data in one byte array when it's safe to do. Returns null if the size to
      * Attempts to read the data in one byte array when it's safe to do. Returns null if the size to
      * read is too large and needs to be allocated in smaller chunks for security reasons.
      * read is too large and needs to be allocated in smaller chunks for security reasons.
+     * 
+     * Returns a byte[] that may have escaped to user code via InputStream APIs.
      */
      */
     private byte[] readRawBytesSlowPathOneChunk(final int size) throws IOException {
     private byte[] readRawBytesSlowPathOneChunk(final int size) throws IOException {
       if (size == 0) {
       if (size == 0) {
@@ -2916,7 +2930,11 @@ public abstract class CodedInputStream {
       return null;
       return null;
     }
     }
 
 
-    /** Reads the remaining data in small chunks from the input stream. */
+    /**
+     * Reads the remaining data in small chunks from the input stream.
+     * 
+     * Returns a byte[] that may have escaped to user code via InputStream APIs.
+     */
     private List<byte[]> readRawBytesSlowPathRemainingChunks(int sizeLeft) throws IOException {
     private List<byte[]> readRawBytesSlowPathRemainingChunks(int sizeLeft) throws IOException {
       // The size is very large.  For security reasons, we can't allocate the
       // The size is very large.  For security reasons, we can't allocate the
       // entire byte array yet.  The size comes directly from the input, so a
       // entire byte array yet.  The size comes directly from the input, so a
@@ -2953,7 +2971,9 @@ public abstract class CodedInputStream {
     private ByteString readBytesSlowPath(final int size) throws IOException {
     private ByteString readBytesSlowPath(final int size) throws IOException {
       final byte[] result = readRawBytesSlowPathOneChunk(size);
       final byte[] result = readRawBytesSlowPathOneChunk(size);
       if (result != null) {
       if (result != null) {
-        return ByteString.wrap(result);
+        // We must copy as the byte array was handed off to the InputStream and a malicious
+        // implementation could retain a reference.
+        return ByteString.copyFrom(result);
       }
       }
 
 
       final int originalBufferPos = pos;
       final int originalBufferPos = pos;
@@ -2971,13 +2991,20 @@ public abstract class CodedInputStream {
       // chunks.
       // chunks.
       List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft);
       List<byte[]> chunks = readRawBytesSlowPathRemainingChunks(sizeLeft);
 
 
-      // Wrap the byte arrays into a single ByteString.
-      List<ByteString> byteStrings = new ArrayList<ByteString>(1 + chunks.size());
-      byteStrings.add(ByteString.copyFrom(buffer, originalBufferPos, bufferedBytes));
-      for (byte[] chunk : chunks) {
-        byteStrings.add(ByteString.wrap(chunk));
+      // OK, got everything.  Now concatenate it all into one buffer.
+      final byte[] bytes = new byte[size];
+
+      // Start by copying the leftover bytes from this.buffer.
+      System.arraycopy(buffer, originalBufferPos, bytes, 0, bufferedBytes);
+
+      // And now all the chunks.
+      int tempPos = bufferedBytes;
+      for (final byte[] chunk : chunks) {
+        System.arraycopy(chunk, 0, bytes, tempPos, chunk.length);
+        tempPos += chunk.length;
       }
       }
-      return ByteString.copyFrom(byteStrings);
+      
+      return ByteString.wrap(bytes);
     }
     }
 
 
     @Override
     @Override

+ 1333 - 0
java/core/src/main/java/com/google/protobuf/CodedInputStreamReader.java

@@ -0,0 +1,1333 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.WireFormat.FIXED32_SIZE;
+import static com.google.protobuf.WireFormat.FIXED64_SIZE;
+import static com.google.protobuf.WireFormat.WIRETYPE_END_GROUP;
+import static com.google.protobuf.WireFormat.WIRETYPE_FIXED32;
+import static com.google.protobuf.WireFormat.WIRETYPE_FIXED64;
+import static com.google.protobuf.WireFormat.WIRETYPE_LENGTH_DELIMITED;
+import static com.google.protobuf.WireFormat.WIRETYPE_START_GROUP;
+import static com.google.protobuf.WireFormat.WIRETYPE_VARINT;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/** An adapter between the {@link Reader} interface and {@link CodedInputStream}. */
+@ExperimentalApi
+final class CodedInputStreamReader implements Reader {
+  private static final int FIXED32_MULTIPLE_MASK = FIXED32_SIZE - 1;
+  private static final int FIXED64_MULTIPLE_MASK = FIXED64_SIZE - 1;
+  private static final int NEXT_TAG_UNSET = 0;
+
+  private final CodedInputStream input;
+  private int tag;
+  private int endGroupTag;
+  private int nextTag = NEXT_TAG_UNSET;
+
+  public static CodedInputStreamReader forCodedInput(CodedInputStream input) {
+    if (input.wrapper != null) {
+      return input.wrapper;
+    }
+    return new CodedInputStreamReader(input);
+  }
+
+  private CodedInputStreamReader(CodedInputStream input) {
+    this.input = Internal.checkNotNull(input, "input");
+    this.input.wrapper = this;
+  }
+
+  @Override
+  public boolean shouldDiscardUnknownFields() {
+    return input.shouldDiscardUnknownFields();
+  }
+
+  @Override
+  public int getFieldNumber() throws IOException {
+    if (nextTag != NEXT_TAG_UNSET) {
+      tag = nextTag;
+      nextTag = NEXT_TAG_UNSET;
+    } else {
+      tag = input.readTag();
+    }
+    if (tag == 0 || tag == endGroupTag) {
+      return Reader.READ_DONE;
+    }
+    return WireFormat.getTagFieldNumber(tag);
+  }
+
+  @Override
+  public int getTag() {
+    return tag;
+  }
+
+  @Override
+  public boolean skipField() throws IOException {
+    if (input.isAtEnd() || tag == endGroupTag) {
+      return false;
+    }
+    return input.skipField(tag);
+  }
+
+  private void requireWireType(int requiredWireType) throws IOException {
+    if (WireFormat.getTagWireType(tag) != requiredWireType) {
+      throw InvalidProtocolBufferException.invalidWireType();
+    }
+  }
+
+  @Override
+  public double readDouble() throws IOException {
+    requireWireType(WIRETYPE_FIXED64);
+    return input.readDouble();
+  }
+
+  @Override
+  public float readFloat() throws IOException {
+    requireWireType(WIRETYPE_FIXED32);
+    return input.readFloat();
+  }
+
+  @Override
+  public long readUInt64() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readUInt64();
+  }
+
+  @Override
+  public long readInt64() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readInt64();
+  }
+
+  @Override
+  public int readInt32() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readInt32();
+  }
+
+  @Override
+  public long readFixed64() throws IOException {
+    requireWireType(WIRETYPE_FIXED64);
+    return input.readFixed64();
+  }
+
+  @Override
+  public int readFixed32() throws IOException {
+    requireWireType(WIRETYPE_FIXED32);
+    return input.readFixed32();
+  }
+
+  @Override
+  public boolean readBool() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readBool();
+  }
+
+  @Override
+  public String readString() throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    return input.readString();
+  }
+
+  @Override
+  public String readStringRequireUtf8() throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    return input.readStringRequireUtf8();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <T> T readMessage(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    return readMessage(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <T> T readMessageBySchemaWithCheck(
+      Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    return readMessage(schema, extensionRegistry);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <T> T readGroup(Class<T> clazz, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    requireWireType(WIRETYPE_START_GROUP);
+    return readGroup(Protobuf.getInstance().schemaFor(clazz), extensionRegistry);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <T> T readGroupBySchemaWithCheck(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    requireWireType(WIRETYPE_START_GROUP);
+    return readGroup(schema, extensionRegistry);
+  }
+
+  // Should have the same semantics of CodedInputStream#readMessage()
+  private <T> T readMessage(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    int size = input.readUInt32();
+    if (input.recursionDepth >= input.recursionLimit) {
+      throw InvalidProtocolBufferException.recursionLimitExceeded();
+    }
+
+    // Push the new limit.
+    final int prevLimit = input.pushLimit(size);
+    // Allocate and read the message.
+    T message = schema.newInstance();
+    ++input.recursionDepth;
+    schema.mergeFrom(message, this, extensionRegistry);
+    schema.makeImmutable(message);
+    input.checkLastTagWas(0);
+    --input.recursionDepth;
+    // Restore the previous limit.
+    input.popLimit(prevLimit);
+    return message;
+  }
+
+  private <T> T readGroup(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    int prevEndGroupTag = endGroupTag;
+    endGroupTag = WireFormat.makeTag(WireFormat.getTagFieldNumber(tag), WIRETYPE_END_GROUP);
+
+    try {
+      // Allocate and read the message.
+      T message = schema.newInstance();
+      schema.mergeFrom(message, this, extensionRegistry);
+      schema.makeImmutable(message);
+
+      if (tag != endGroupTag) {
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+      return message;
+    } finally {
+      // Restore the old end group tag.
+      endGroupTag = prevEndGroupTag;
+    }
+  }
+
+  @Override
+  public ByteString readBytes() throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    return input.readBytes();
+  }
+
+  @Override
+  public int readUInt32() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readUInt32();
+  }
+
+  @Override
+  public int readEnum() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readEnum();
+  }
+
+  @Override
+  public int readSFixed32() throws IOException {
+    requireWireType(WIRETYPE_FIXED32);
+    return input.readSFixed32();
+  }
+
+  @Override
+  public long readSFixed64() throws IOException {
+    requireWireType(WIRETYPE_FIXED64);
+    return input.readSFixed64();
+  }
+
+  @Override
+  public int readSInt32() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readSInt32();
+  }
+
+  @Override
+  public long readSInt64() throws IOException {
+    requireWireType(WIRETYPE_VARINT);
+    return input.readSInt64();
+  }
+
+  @Override
+  public void readDoubleList(List<Double> target) throws IOException {
+    if (target instanceof DoubleArrayList) {
+      DoubleArrayList plist = (DoubleArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addDouble(input.readDouble());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            plist.addDouble(input.readDouble());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readDouble());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            target.add(input.readDouble());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readFloatList(List<Float> target) throws IOException {
+    if (target instanceof FloatArrayList) {
+      FloatArrayList plist = (FloatArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addFloat(input.readFloat());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            plist.addFloat(input.readFloat());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readFloat());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            target.add(input.readFloat());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readUInt64List(List<Long> target) throws IOException {
+    if (target instanceof LongArrayList) {
+      LongArrayList plist = (LongArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addLong(input.readUInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addLong(input.readUInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readUInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readUInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readInt64List(List<Long> target) throws IOException {
+    if (target instanceof LongArrayList) {
+      LongArrayList plist = (LongArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addLong(input.readInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addLong(input.readInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readInt32List(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addInt(input.readInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readFixed64List(List<Long> target) throws IOException {
+    if (target instanceof LongArrayList) {
+      LongArrayList plist = (LongArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addLong(input.readFixed64());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            plist.addLong(input.readFixed64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readFixed64());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            target.add(input.readFixed64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readFixed32List(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readFixed32());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            plist.addInt(input.readFixed32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readFixed32());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            target.add(input.readFixed32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readBoolList(List<Boolean> target) throws IOException {
+    if (target instanceof BooleanArrayList) {
+      BooleanArrayList plist = (BooleanArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addBoolean(input.readBool());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addBoolean(input.readBool());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readBool());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readBool());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readStringList(List<String> target) throws IOException {
+    readStringListInternal(target, false);
+  }
+
+  @Override
+  public void readStringListRequireUtf8(List<String> target) throws IOException {
+    readStringListInternal(target, true);
+  }
+
+  public void readStringListInternal(List<String> target, boolean requireUtf8) throws IOException {
+    if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+      throw InvalidProtocolBufferException.invalidWireType();
+    }
+
+    if (target instanceof LazyStringList && !requireUtf8) {
+      LazyStringList lazyList = (LazyStringList) target;
+      while (true) {
+        lazyList.add(readBytes());
+        if (input.isAtEnd()) {
+          return;
+        }
+        int nextTag = input.readTag();
+        if (nextTag != tag) {
+          // We've reached the end of the repeated field. Save the next tag value.
+          this.nextTag = nextTag;
+          return;
+        }
+      }
+    } else {
+      while (true) {
+        target.add(requireUtf8 ? readStringRequireUtf8() : readString());
+        if (input.isAtEnd()) {
+          return;
+        }
+        int nextTag = input.readTag();
+        if (nextTag != tag) {
+          // We've reached the end of the repeated field. Save the next tag value.
+          this.nextTag = nextTag;
+          return;
+        }
+      }
+    }
+  }
+
+  @Override
+  public <T> void readMessageList(
+      List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+    readMessageList(target, schema, extensionRegistry);
+  }
+
+  @Override
+  public <T> void readMessageList(
+      List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+      throw InvalidProtocolBufferException.invalidWireType();
+    }
+    final int listTag = tag;
+    while (true) {
+      target.add(readMessage(schema, extensionRegistry));
+      if (input.isAtEnd() || nextTag != NEXT_TAG_UNSET) {
+        return;
+      }
+      int nextTag = input.readTag();
+      if (nextTag != listTag) {
+        // We've reached the end of the repeated field. Save the next tag value.
+        this.nextTag = nextTag;
+        return;
+      }
+    }
+  }
+
+  @Override
+  public <T> void readGroupList(
+      List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+    readGroupList(target, schema, extensionRegistry);
+  }
+
+  @Override
+  public <T> void readGroupList(
+      List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    if (WireFormat.getTagWireType(tag) != WIRETYPE_START_GROUP) {
+      throw InvalidProtocolBufferException.invalidWireType();
+    }
+    final int listTag = tag;
+    while (true) {
+      target.add(readGroup(schema, extensionRegistry));
+      if (input.isAtEnd() || nextTag != NEXT_TAG_UNSET) {
+        return;
+      }
+      int nextTag = input.readTag();
+      if (nextTag != listTag) {
+        // We've reached the end of the repeated field. Save the next tag value.
+        this.nextTag = nextTag;
+        return;
+      }
+    }
+  }
+
+  @Override
+  public void readBytesList(List<ByteString> target) throws IOException {
+    if (WireFormat.getTagWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+      throw InvalidProtocolBufferException.invalidWireType();
+    }
+
+    while (true) {
+      target.add(readBytes());
+      if (input.isAtEnd()) {
+        return;
+      }
+      int nextTag = input.readTag();
+      if (nextTag != tag) {
+        // We've reached the end of the repeated field. Save the next tag value.
+        this.nextTag = nextTag;
+        return;
+      }
+    }
+  }
+
+  @Override
+  public void readUInt32List(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readUInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addInt(input.readUInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readUInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readUInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readEnumList(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readEnum());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addInt(input.readEnum());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readEnum());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readEnum());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readSFixed32List(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readSFixed32());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            plist.addInt(input.readSFixed32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed32Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readSFixed32());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED32:
+          while (true) {
+            target.add(input.readSFixed32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readSFixed64List(List<Long> target) throws IOException {
+    if (target instanceof LongArrayList) {
+      LongArrayList plist = (LongArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addLong(input.readSFixed64());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            plist.addLong(input.readSFixed64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          verifyPackedFixed64Length(bytes);
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readSFixed64());
+          } while (input.getTotalBytesRead() < endPos);
+          break;
+        case WIRETYPE_FIXED64:
+          while (true) {
+            target.add(input.readSFixed64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readSInt32List(List<Integer> target) throws IOException {
+    if (target instanceof IntArrayList) {
+      IntArrayList plist = (IntArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addInt(input.readSInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addInt(input.readSInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readSInt32());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readSInt32());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  @Override
+  public void readSInt64List(List<Long> target) throws IOException {
+    if (target instanceof LongArrayList) {
+      LongArrayList plist = (LongArrayList) target;
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            plist.addLong(input.readSInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            plist.addLong(input.readSInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    } else {
+      switch (WireFormat.getTagWireType(tag)) {
+        case WIRETYPE_LENGTH_DELIMITED:
+          final int bytes = input.readUInt32();
+          int endPos = input.getTotalBytesRead() + bytes;
+          do {
+            target.add(input.readSInt64());
+          } while (input.getTotalBytesRead() < endPos);
+          requirePosition(endPos);
+          break;
+        case WIRETYPE_VARINT:
+          while (true) {
+            target.add(input.readSInt64());
+            if (input.isAtEnd()) {
+              return;
+            }
+            int nextTag = input.readTag();
+            if (nextTag != tag) {
+              // We've reached the end of the repeated field. Save the next tag value.
+              this.nextTag = nextTag;
+              return;
+            }
+          }
+        default:
+          throw InvalidProtocolBufferException.invalidWireType();
+      }
+    }
+  }
+
+  private void verifyPackedFixed64Length(int bytes) throws IOException {
+    if ((bytes & FIXED64_MULTIPLE_MASK) != 0) {
+      // Require that the number of bytes be a multiple of 8.
+      throw InvalidProtocolBufferException.parseFailure();
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public <K, V> void readMap(
+      Map<K, V> target,
+      MapEntryLite.Metadata<K, V> metadata,
+      ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    requireWireType(WIRETYPE_LENGTH_DELIMITED);
+    int size = input.readUInt32();
+    final int prevLimit = input.pushLimit(size);
+    K key = metadata.defaultKey;
+    V value = metadata.defaultValue;
+    try {
+      while (true) {
+        int number = getFieldNumber();
+        if (number == READ_DONE || input.isAtEnd()) {
+          break;
+        }
+        try {
+          switch (number) {
+            case 1:
+              key = (K) readField(metadata.keyType, null, null);
+              break;
+            case 2:
+              value =
+                  (V)
+                      readField(
+                          metadata.valueType, metadata.defaultValue.getClass(), extensionRegistry);
+              break;
+            default:
+              if (!skipField()) {
+                throw new InvalidProtocolBufferException("Unable to parse map entry.");
+              }
+              break;
+          }
+        } catch (InvalidProtocolBufferException.InvalidWireTypeException ignore) {
+          // the type doesn't match, skip the field.
+          if (!skipField()) {
+            throw new InvalidProtocolBufferException("Unable to parse map entry.");
+          }
+        }
+      }
+      target.put(key, value);
+    } finally {
+      // Restore the previous limit.
+      input.popLimit(prevLimit);
+    }
+  }
+
+  private Object readField(
+      WireFormat.FieldType fieldType, Class<?> messageType, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    switch (fieldType) {
+      case BOOL:
+        return readBool();
+      case BYTES:
+        return readBytes();
+      case DOUBLE:
+        return readDouble();
+      case ENUM:
+        return readEnum();
+      case FIXED32:
+        return readFixed32();
+      case FIXED64:
+        return readFixed64();
+      case FLOAT:
+        return readFloat();
+      case INT32:
+        return readInt32();
+      case INT64:
+        return readInt64();
+      case MESSAGE:
+        return readMessage(messageType, extensionRegistry);
+      case SFIXED32:
+        return readSFixed32();
+      case SFIXED64:
+        return readSFixed64();
+      case SINT32:
+        return readSInt32();
+      case SINT64:
+        return readSInt64();
+      case STRING:
+        return readStringRequireUtf8();
+      case UINT32:
+        return readUInt32();
+      case UINT64:
+        return readUInt64();
+      default:
+        throw new RuntimeException("unsupported field type.");
+    }
+  }
+
+  private void verifyPackedFixed32Length(int bytes) throws IOException {
+    if ((bytes & FIXED32_MULTIPLE_MASK) != 0) {
+      // Require that the number of bytes be a multiple of 4.
+      throw InvalidProtocolBufferException.parseFailure();
+    }
+  }
+
+  private void requirePosition(int expectedPosition) throws IOException {
+    if (input.getTotalBytesRead() != expectedPosition) {
+      throw InvalidProtocolBufferException.truncatedMessage();
+    }
+  }
+}

+ 113 - 0
java/core/src/main/java/com/google/protobuf/CodedOutputStream.java

@@ -60,6 +60,9 @@ public abstract class CodedOutputStream extends ByteOutput {
   private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
   private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
   private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations();
   private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations();
 
 
+  /** Used to adapt to the experimental {@link Writer} interface. */
+  CodedOutputStreamWriter wrapper;
+
   /** @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead. */
   /** @deprecated Use {@link #computeFixed32SizeNoTag(int)} instead. */
   @Deprecated public static final int LITTLE_ENDIAN_32_SIZE = FIXED32_SIZE;
   @Deprecated public static final int LITTLE_ENDIAN_32_SIZE = FIXED32_SIZE;
 
 
@@ -361,6 +364,10 @@ public abstract class CodedOutputStream extends ByteOutput {
   public abstract void writeMessage(final int fieldNumber, final MessageLite value)
   public abstract void writeMessage(final int fieldNumber, final MessageLite value)
       throws IOException;
       throws IOException;
 
 
+  /** Write an embedded message field, including tag, to the stream. */
+  // Abstract to avoid overhead of additional virtual method calls.
+  abstract void writeMessage(final int fieldNumber, final MessageLite value, Schema schema)
+      throws IOException;
 
 
   /**
   /**
    * Write a MessageSet extension field to the stream. For historical reasons, the wire format
    * Write a MessageSet extension field to the stream. For historical reasons, the wire format
@@ -466,6 +473,9 @@ public abstract class CodedOutputStream extends ByteOutput {
   // Abstract to avoid overhead of additional virtual method calls.
   // Abstract to avoid overhead of additional virtual method calls.
   public abstract void writeMessageNoTag(final MessageLite value) throws IOException;
   public abstract void writeMessageNoTag(final MessageLite value) throws IOException;
 
 
+  /** Write an embedded message field to the stream. */
+  // Abstract to avoid overhead of additional virtual method calls.
+  abstract void writeMessageNoTag(final MessageLite value, Schema schema) throws IOException;
 
 
   // =================================================================
   // =================================================================
 
 
@@ -651,6 +661,14 @@ public abstract class CodedOutputStream extends ByteOutput {
     return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value);
     return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value);
   }
   }
 
 
+  /**
+   * Compute the number of bytes that would be needed to encode an embedded message field, including
+   * tag.
+   */
+  static int computeMessageSize(
+      final int fieldNumber, final MessageLite value, final Schema schema) {
+    return computeTagSize(fieldNumber) + computeMessageSizeNoTag(value, schema);
+  }
 
 
   /**
   /**
    * Compute the number of bytes that would be needed to encode a MessageSet extension to the
    * Compute the number of bytes that would be needed to encode a MessageSet extension to the
@@ -859,6 +877,10 @@ public abstract class CodedOutputStream extends ByteOutput {
     return computeLengthDelimitedFieldSize(value.getSerializedSize());
     return computeLengthDelimitedFieldSize(value.getSerializedSize());
   }
   }
 
 
+  /** Compute the number of bytes that would be needed to encode an embedded message field. */
+  static int computeMessageSizeNoTag(final MessageLite value, final Schema schema) {
+    return computeLengthDelimitedFieldSize(((AbstractMessageLite) value).getSerializedSize(schema));
+  }
 
 
   static int computeLengthDelimitedFieldSize(int fieldLength) {
   static int computeLengthDelimitedFieldSize(int fieldLength) {
     return computeUInt32SizeNoTag(fieldLength) + fieldLength;
     return computeUInt32SizeNoTag(fieldLength) + fieldLength;
@@ -993,6 +1015,18 @@ public abstract class CodedOutputStream extends ByteOutput {
     writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
     writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
   }
   }
 
 
+  /**
+   * Write a {@code group} field, including tag, to the stream.
+   *
+   * @deprecated groups are deprecated.
+   */
+  @Deprecated
+  final void writeGroup(final int fieldNumber, final MessageLite value, Schema schema)
+      throws IOException {
+    writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP);
+    writeGroupNoTag(value, schema);
+    writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
+  }
 
 
   /**
   /**
    * Write a {@code group} field to the stream.
    * Write a {@code group} field to the stream.
@@ -1004,6 +1038,15 @@ public abstract class CodedOutputStream extends ByteOutput {
     value.writeTo(this);
     value.writeTo(this);
   }
   }
 
 
+  /**
+   * Write a {@code group} field to the stream.
+   *
+   * @deprecated groups are deprecated.
+   */
+  @Deprecated
+  final void writeGroupNoTag(final MessageLite value, Schema schema) throws IOException {
+    schema.writeTo(value, wrapper);
+  }
 
 
   /**
   /**
    * Compute the number of bytes that would be needed to encode a {@code group} field, including
    * Compute the number of bytes that would be needed to encode a {@code group} field, including
@@ -1016,6 +1059,16 @@ public abstract class CodedOutputStream extends ByteOutput {
     return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value);
     return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value);
   }
   }
 
 
+  /**
+   * Compute the number of bytes that would be needed to encode a {@code group} field, including
+   * tag.
+   *
+   * @deprecated groups are deprecated.
+   */
+  @Deprecated
+  static int computeGroupSize(final int fieldNumber, final MessageLite value, Schema schema) {
+    return computeTagSize(fieldNumber) * 2 + computeGroupSizeNoTag(value, schema);
+  }
 
 
   /** Compute the number of bytes that would be needed to encode a {@code group} field. */
   /** Compute the number of bytes that would be needed to encode a {@code group} field. */
   @Deprecated
   @Deprecated
@@ -1023,6 +1076,11 @@ public abstract class CodedOutputStream extends ByteOutput {
     return value.getSerializedSize();
     return value.getSerializedSize();
   }
   }
 
 
+  /** Compute the number of bytes that would be needed to encode a {@code group} field. */
+  @Deprecated
+  static int computeGroupSizeNoTag(final MessageLite value, Schema schema) {
+    return ((AbstractMessageLite) value).getSerializedSize(schema);
+  }
 
 
   /**
   /**
    * Encode and write a varint. {@code value} is treated as unsigned, so it won't be sign-extended
    * Encode and write a varint. {@code value} is treated as unsigned, so it won't be sign-extended
@@ -1216,6 +1274,13 @@ public abstract class CodedOutputStream extends ByteOutput {
       writeMessageNoTag(value);
       writeMessageNoTag(value);
     }
     }
 
 
+    @Override
+    final void writeMessage(final int fieldNumber, final MessageLite value, Schema schema)
+        throws IOException {
+      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public final void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
     public final void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
@@ -1241,6 +1306,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       value.writeTo(this);
       value.writeTo(this);
     }
     }
 
 
+    @Override
+    final void writeMessageNoTag(final MessageLite value, Schema schema) throws IOException {
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public final void write(byte value) throws IOException {
     public final void write(byte value) throws IOException {
@@ -1571,6 +1641,12 @@ public abstract class CodedOutputStream extends ByteOutput {
       writeMessageNoTag(value);
       writeMessageNoTag(value);
     }
     }
 
 
+    @Override
+    void writeMessage(final int fieldNumber, final MessageLite value, Schema schema)
+        throws IOException {
+      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+      writeMessageNoTag(value, schema);
+    }
 
 
     @Override
     @Override
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
@@ -1596,6 +1672,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       value.writeTo(this);
       value.writeTo(this);
     }
     }
 
 
+    @Override
+    void writeMessageNoTag(final MessageLite value, Schema schema) throws IOException {
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public void write(byte value) throws IOException {
     public void write(byte value) throws IOException {
@@ -1893,6 +1974,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       writeMessageNoTag(value);
       writeMessageNoTag(value);
     }
     }
 
 
+    @Override
+    void writeMessage(int fieldNumber, MessageLite value, Schema schema) throws IOException {
+      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+      writeMessageNoTag(value, schema);
+    }
 
 
     @Override
     @Override
     public void writeMessageSetExtension(int fieldNumber, MessageLite value) throws IOException {
     public void writeMessageSetExtension(int fieldNumber, MessageLite value) throws IOException {
@@ -1916,6 +2002,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       value.writeTo(this);
       value.writeTo(this);
     }
     }
 
 
+    @Override
+    void writeMessageNoTag(MessageLite value, Schema schema) throws IOException {
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public void write(byte value) throws IOException {
     public void write(byte value) throws IOException {
@@ -2419,6 +2510,12 @@ public abstract class CodedOutputStream extends ByteOutput {
       writeMessageNoTag(value);
       writeMessageNoTag(value);
     }
     }
 
 
+    @Override
+    void writeMessage(final int fieldNumber, final MessageLite value, Schema schema)
+        throws IOException {
+      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+      writeMessageNoTag(value, schema);
+    }
 
 
     @Override
     @Override
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
@@ -2444,6 +2541,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       value.writeTo(this);
       value.writeTo(this);
     }
     }
 
 
+    @Override
+    void writeMessageNoTag(final MessageLite value, Schema schema) throws IOException {
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public void write(byte value) throws IOException {
     public void write(byte value) throws IOException {
@@ -2722,6 +2824,12 @@ public abstract class CodedOutputStream extends ByteOutput {
       writeMessageNoTag(value);
       writeMessageNoTag(value);
     }
     }
 
 
+    @Override
+    void writeMessage(final int fieldNumber, final MessageLite value, Schema schema)
+        throws IOException {
+      writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+      writeMessageNoTag(value, schema);
+    }
 
 
     @Override
     @Override
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
     public void writeMessageSetExtension(final int fieldNumber, final MessageLite value)
@@ -2747,6 +2855,11 @@ public abstract class CodedOutputStream extends ByteOutput {
       value.writeTo(this);
       value.writeTo(this);
     }
     }
 
 
+    @Override
+    void writeMessageNoTag(final MessageLite value, Schema schema) throws IOException {
+      writeUInt32NoTag(((AbstractMessageLite) value).getSerializedSize(schema));
+      schema.writeTo(value, wrapper);
+    }
 
 
     @Override
     @Override
     public void write(byte value) throws IOException {
     public void write(byte value) throws IOException {

+ 691 - 0
java/core/src/main/java/com/google/protobuf/CodedOutputStreamWriter.java

@@ -0,0 +1,691 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+import static com.google.protobuf.WireFormat.WIRETYPE_LENGTH_DELIMITED;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/** An adapter between the {@link Writer} interface and {@link CodedOutputStream}. */
+@ExperimentalApi
+final class CodedOutputStreamWriter implements Writer {
+  private final CodedOutputStream output;
+
+  public static CodedOutputStreamWriter forCodedOutput(CodedOutputStream output) {
+    if (output.wrapper != null) {
+      return output.wrapper;
+    }
+    return new CodedOutputStreamWriter(output);
+  }
+
+  private CodedOutputStreamWriter(CodedOutputStream output) {
+    this.output = checkNotNull(output, "output");
+    this.output.wrapper = this;
+  }
+
+  @Override
+  public FieldOrder fieldOrder() {
+    return FieldOrder.ASCENDING;
+  }
+
+  public int getTotalBytesWritten() {
+    return output.getTotalBytesWritten();
+  }
+
+  @Override
+  public void writeSFixed32(int fieldNumber, int value) throws IOException {
+    output.writeSFixed32(fieldNumber, value);
+  }
+
+  @Override
+  public void writeInt64(int fieldNumber, long value) throws IOException {
+    output.writeInt64(fieldNumber, value);
+  }
+
+  @Override
+  public void writeSFixed64(int fieldNumber, long value) throws IOException {
+    output.writeSFixed64(fieldNumber, value);
+  }
+
+  @Override
+  public void writeFloat(int fieldNumber, float value) throws IOException {
+    output.writeFloat(fieldNumber, value);
+  }
+
+  @Override
+  public void writeDouble(int fieldNumber, double value) throws IOException {
+    output.writeDouble(fieldNumber, value);
+  }
+
+  @Override
+  public void writeEnum(int fieldNumber, int value) throws IOException {
+    output.writeEnum(fieldNumber, value);
+  }
+
+  @Override
+  public void writeUInt64(int fieldNumber, long value) throws IOException {
+    output.writeUInt64(fieldNumber, value);
+  }
+
+  @Override
+  public void writeInt32(int fieldNumber, int value) throws IOException {
+    output.writeInt32(fieldNumber, value);
+  }
+
+  @Override
+  public void writeFixed64(int fieldNumber, long value) throws IOException {
+    output.writeFixed64(fieldNumber, value);
+  }
+
+  @Override
+  public void writeFixed32(int fieldNumber, int value) throws IOException {
+    output.writeFixed32(fieldNumber, value);
+  }
+
+  @Override
+  public void writeBool(int fieldNumber, boolean value) throws IOException {
+    output.writeBool(fieldNumber, value);
+  }
+
+  @Override
+  public void writeString(int fieldNumber, String value) throws IOException {
+    output.writeString(fieldNumber, value);
+  }
+
+  @Override
+  public void writeBytes(int fieldNumber, ByteString value) throws IOException {
+    output.writeBytes(fieldNumber, value);
+  }
+
+  @Override
+  public void writeUInt32(int fieldNumber, int value) throws IOException {
+    output.writeUInt32(fieldNumber, value);
+  }
+
+  @Override
+  public void writeSInt32(int fieldNumber, int value) throws IOException {
+    output.writeSInt32(fieldNumber, value);
+  }
+
+  @Override
+  public void writeSInt64(int fieldNumber, long value) throws IOException {
+    output.writeSInt64(fieldNumber, value);
+  }
+
+  @Override
+  public void writeMessage(int fieldNumber, Object value) throws IOException {
+    output.writeMessage(fieldNumber, (MessageLite) value);
+  }
+
+  @Override
+  public void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException {
+    output.writeMessage(fieldNumber, (MessageLite) value, schema);
+  }
+
+  @Override
+  public void writeGroup(int fieldNumber, Object value) throws IOException {
+    output.writeGroup(fieldNumber, (MessageLite) value);
+  }
+
+  @Override
+  public void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException {
+    output.writeGroup(fieldNumber, (MessageLite) value, schema);
+  }
+
+  @Override
+  public void writeStartGroup(int fieldNumber) throws IOException {
+    output.writeTag(fieldNumber, WireFormat.WIRETYPE_START_GROUP);
+  }
+
+  @Override
+  public void writeEndGroup(int fieldNumber) throws IOException {
+    output.writeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
+  }
+
+  @Override
+  public final void writeMessageSetItem(int fieldNumber, Object value) throws IOException {
+    if (value instanceof ByteString) {
+      output.writeRawMessageSetExtension(fieldNumber, (ByteString) value);
+    } else {
+      output.writeMessageSetExtension(fieldNumber, (MessageLite) value);
+    }
+  }
+
+  @Override
+  public void writeInt32List(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeInt32SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeInt32NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeInt32(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeFixed32List(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeFixed32SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFixed32NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFixed32(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeInt64List(int fieldNumber, List<Long> value, boolean packed) throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeInt64SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeInt64NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeInt64(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeUInt64List(int fieldNumber, List<Long> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeUInt64SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeUInt64NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeUInt64(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeFixed64List(int fieldNumber, List<Long> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeFixed64SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFixed64NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFixed64(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeFloatList(int fieldNumber, List<Float> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeFloatSizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFloatNoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeFloat(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeDoubleList(int fieldNumber, List<Double> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeDoubleSizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeDoubleNoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeDouble(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeEnumList(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeEnumSizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeEnumNoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeEnum(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeBoolList(int fieldNumber, List<Boolean> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeBoolSizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeBoolNoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeBool(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeStringList(int fieldNumber, List<String> value) throws IOException {
+    if (value instanceof LazyStringList) {
+      final LazyStringList lazyList = (LazyStringList) value;
+      for (int i = 0; i < value.size(); ++i) {
+        writeLazyString(fieldNumber, lazyList.getRaw(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeString(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  private void writeLazyString(int fieldNumber, Object value) throws IOException {
+    if (value instanceof String) {
+      output.writeString(fieldNumber, (String) value);
+    } else {
+      output.writeBytes(fieldNumber, (ByteString) value);
+    }
+  }
+
+  @Override
+  public void writeBytesList(int fieldNumber, List<ByteString> value) throws IOException {
+    for (int i = 0; i < value.size(); ++i) {
+      output.writeBytes(fieldNumber, value.get(i));
+    }
+  }
+
+  @Override
+  public void writeUInt32List(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeUInt32SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeUInt32NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeUInt32(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeSFixed32List(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeSFixed32SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSFixed32NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSFixed32(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeSFixed64List(int fieldNumber, List<Long> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeSFixed64SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSFixed64NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSFixed64(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeSInt32List(int fieldNumber, List<Integer> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeSInt32SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSInt32NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSInt32(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeSInt64List(int fieldNumber, List<Long> value, boolean packed)
+      throws IOException {
+    if (packed) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+
+      // Compute and write the length of the data.
+      int dataSize = 0;
+      for (int i = 0; i < value.size(); ++i) {
+        dataSize += CodedOutputStream.computeSInt64SizeNoTag(value.get(i));
+      }
+      output.writeUInt32NoTag(dataSize);
+
+      // Write the data itself, without any tags.
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSInt64NoTag(value.get(i));
+      }
+    } else {
+      for (int i = 0; i < value.size(); ++i) {
+        output.writeSInt64(fieldNumber, value.get(i));
+      }
+    }
+  }
+
+  @Override
+  public void writeMessageList(int fieldNumber, List<?> value) throws IOException {
+    for (int i = 0; i < value.size(); ++i) {
+      writeMessage(fieldNumber, value.get(i));
+    }
+  }
+
+  @Override
+  public void writeMessageList(int fieldNumber, List<?> value, Schema schema) throws IOException {
+    for (int i = 0; i < value.size(); ++i) {
+      writeMessage(fieldNumber, value.get(i), schema);
+    }
+  }
+
+  @Override
+  public void writeGroupList(int fieldNumber, List<?> value) throws IOException {
+    for (int i = 0; i < value.size(); ++i) {
+      writeGroup(fieldNumber, value.get(i));
+    }
+  }
+
+  @Override
+  public void writeGroupList(int fieldNumber, List<?> value, Schema schema) throws IOException {
+    for (int i = 0; i < value.size(); ++i) {
+      writeGroup(fieldNumber, value.get(i), schema);
+    }
+  }
+
+  @Override
+  public <K, V> void writeMap(int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map)
+      throws IOException {
+    if (output.isSerializationDeterministic()) {
+      writeDeterministicMap(fieldNumber, metadata, map);
+      return;
+    }
+    for (Map.Entry<K, V> entry : map.entrySet()) {
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+      output.writeUInt32NoTag(
+          MapEntryLite.computeSerializedSize(metadata, entry.getKey(), entry.getValue()));
+      MapEntryLite.writeTo(output, metadata, entry.getKey(), entry.getValue());
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private <K, V> void writeDeterministicMap(
+      int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map) throws IOException {
+    switch (metadata.keyType) {
+      case BOOL:
+        V value;
+        if ((value = map.get(Boolean.FALSE)) != null) {
+          writeDeterministicBooleanMapEntry(
+              fieldNumber, /* key= */ false, value, (MapEntryLite.Metadata<Boolean, V>) metadata);
+        }
+        if ((value = map.get(Boolean.TRUE)) != null) {
+          writeDeterministicBooleanMapEntry(
+              fieldNumber, /* key= */ true, value, (MapEntryLite.Metadata<Boolean, V>) metadata);
+        }
+        break;
+      case FIXED32:
+      case INT32:
+      case SFIXED32:
+      case SINT32:
+      case UINT32:
+        writeDeterministicIntegerMap(
+            fieldNumber, (MapEntryLite.Metadata<Integer, V>) metadata, (Map<Integer, V>) map);
+        break;
+      case FIXED64:
+      case INT64:
+      case SFIXED64:
+      case SINT64:
+      case UINT64:
+        writeDeterministicLongMap(
+            fieldNumber, (MapEntryLite.Metadata<Long, V>) metadata, (Map<Long, V>) map);
+        break;
+      case STRING:
+        writeDeterministicStringMap(
+            fieldNumber, (MapEntryLite.Metadata<String, V>) metadata, (Map<String, V>) map);
+        break;
+      default:
+        throw new IllegalArgumentException("does not support key type: " + metadata.keyType);
+    }
+  }
+
+  private <V> void writeDeterministicBooleanMapEntry(
+      int fieldNumber, boolean key, V value, MapEntryLite.Metadata<Boolean, V> metadata)
+      throws IOException {
+    output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+    output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
+    MapEntryLite.writeTo(output, metadata, key, value);
+  }
+
+  private <V> void writeDeterministicIntegerMap(
+      int fieldNumber, MapEntryLite.Metadata<Integer, V> metadata, Map<Integer, V> map)
+      throws IOException {
+    int[] keys = new int[map.size()];
+    int index = 0;
+    for (int k : map.keySet()) {
+      keys[index++] = k;
+    }
+    Arrays.sort(keys);
+    for (int key : keys) {
+      V value = map.get(key);
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+      output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
+      MapEntryLite.writeTo(output, metadata, key, value);
+    }
+  }
+
+  private <V> void writeDeterministicLongMap(
+      int fieldNumber, MapEntryLite.Metadata<Long, V> metadata, Map<Long, V> map)
+      throws IOException {
+    long[] keys = new long[map.size()];
+    int index = 0;
+    for (long k : map.keySet()) {
+      keys[index++] = k;
+    }
+    Arrays.sort(keys);
+    for (long key : keys) {
+      V value = map.get(key);
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+      output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
+      MapEntryLite.writeTo(output, metadata, key, value);
+    }
+  }
+
+  private <V> void writeDeterministicStringMap(
+      int fieldNumber, MapEntryLite.Metadata<String, V> metadata, Map<String, V> map)
+      throws IOException {
+    String[] keys = new String[map.size()];
+    int index = 0;
+    for (String k : map.keySet()) {
+      keys[index++] = k;
+    }
+    Arrays.sort(keys);
+    for (String key : keys) {
+      V value = map.get(key);
+      output.writeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
+      output.writeUInt32NoTag(MapEntryLite.computeSerializedSize(metadata, key, value));
+      MapEntryLite.writeTo(output, metadata, key, value);
+    }
+  }
+}

+ 690 - 0
java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java

@@ -0,0 +1,690 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.FieldInfo.forField;
+import static com.google.protobuf.FieldInfo.forFieldWithEnumVerifier;
+import static com.google.protobuf.FieldInfo.forMapField;
+import static com.google.protobuf.FieldInfo.forOneofMemberField;
+import static com.google.protobuf.FieldInfo.forPackedField;
+import static com.google.protobuf.FieldInfo.forPackedFieldWithEnumVerifier;
+import static com.google.protobuf.FieldInfo.forProto2OptionalField;
+import static com.google.protobuf.FieldInfo.forProto2RequiredField;
+import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
+
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor.Type;
+import com.google.protobuf.Descriptors.OneofDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** A factory for message info based on protobuf descriptors for a {@link GeneratedMessageV3}. */
+@ExperimentalApi
+final class DescriptorMessageInfoFactory implements MessageInfoFactory {
+  private static final String GET_DEFAULT_INSTANCE_METHOD_NAME = "getDefaultInstance";
+  private static final DescriptorMessageInfoFactory instance = new DescriptorMessageInfoFactory();
+  private static final Set<String> specialFieldNames =
+      new HashSet<>(Arrays.asList("cached_size", "serialized_size", "class"));
+
+  // Disallow construction - it's a singleton.
+  private DescriptorMessageInfoFactory() {}
+
+  public static DescriptorMessageInfoFactory getInstance() {
+    return instance;
+  }
+
+  @Override
+  public boolean isSupported(Class<?> messageType) {
+    return GeneratedMessageV3.class.isAssignableFrom(messageType);
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> messageType) {
+    if (!GeneratedMessageV3.class.isAssignableFrom(messageType)) {
+      throw new IllegalArgumentException("Unsupported message type: " + messageType.getName());
+    }
+
+    return convert(messageType, descriptorForType(messageType));
+  }
+
+  private static Message getDefaultInstance(Class<?> messageType) {
+    try {
+      Method method = messageType.getDeclaredMethod(GET_DEFAULT_INSTANCE_METHOD_NAME);
+      return (Message) method.invoke(null);
+    } catch (Exception e) {
+      throw new IllegalArgumentException(
+          "Unable to get default instance for message class " + messageType.getName(), e);
+    }
+  }
+
+  private static Descriptor descriptorForType(Class<?> messageType) {
+    return getDefaultInstance(messageType).getDescriptorForType();
+  }
+
+  private static MessageInfo convert(Class<?> messageType, Descriptor messageDescriptor) {
+    switch (messageDescriptor.getFile().getSyntax()) {
+      case PROTO2:
+        return convertProto2(messageType, messageDescriptor);
+      case PROTO3:
+        return convertProto3(messageType, messageDescriptor);
+      default:
+        throw new IllegalArgumentException(
+            "Unsupported syntax: " + messageDescriptor.getFile().getSyntax());
+    }
+  }
+
+  /**
+   * A helper class to determine whether a message type needs to implement {@code isInitialized()}.
+   *
+   * <p>If a message type doesn't have any required fields or extensions (directly and
+   * transitively), it doesn't need to implement isInitialized() and can always return true there.
+   * It's a bit tricky to determine whether a type has transitive required fields because protobuf
+   * allows cycle references within the same .proto file (e.g., message Foo has a Bar field, and
+   * message Bar has a Foo field). For that we use Tarjan's strongly connected components algorithm
+   * to classify messages into strongly connected groups. Messages in the same group are
+   * transitively including each other, so they should either all have transitive required fields
+   * (or extensions), or none have.
+   *
+   * <p>This class is thread-safe.
+   */
+  static class IsInitializedCheckAnalyzer {
+
+    private final Map<Descriptor, Boolean> resultCache =
+        new ConcurrentHashMap<Descriptor, Boolean>();
+
+    // The following data members are part of Tarjan's SCC algorithm. See:
+    //   https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+    private int index = 0;
+    private final Stack<Node> stack = new Stack<Node>();
+    private final Map<Descriptor, Node> nodeCache = new HashMap<Descriptor, Node>();
+
+    public boolean needsIsInitializedCheck(Descriptor descriptor) {
+      Boolean cachedValue = resultCache.get(descriptor);
+      if (cachedValue != null) {
+        return cachedValue;
+      }
+      synchronized (this) {
+        // Double-check the cache because some other thread may have updated it while we
+        // were acquiring the lock.
+        cachedValue = resultCache.get(descriptor);
+        if (cachedValue != null) {
+          return cachedValue;
+        }
+        return dfs(descriptor).component.needsIsInitializedCheck;
+      }
+    }
+
+    private static class Node {
+      final Descriptor descriptor;
+      final int index;
+      int lowLink;
+      StronglyConnectedComponent component; // null if the node is still on stack.
+
+      Node(Descriptor descriptor, int index) {
+        this.descriptor = descriptor;
+        this.index = index;
+        this.lowLink = index;
+        this.component = null;
+      }
+    }
+
+    private static class StronglyConnectedComponent {
+      final List<Descriptor> messages = new ArrayList<Descriptor>();
+      boolean needsIsInitializedCheck = false;
+    }
+
+    private Node dfs(Descriptor descriptor) {
+      Node result = new Node(descriptor, index++);
+      stack.push(result);
+      nodeCache.put(descriptor, result);
+
+      // Recurse the fields / nodes in graph
+      for (FieldDescriptor field : descriptor.getFields()) {
+        if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+          Node child = nodeCache.get(field.getMessageType());
+          if (child == null) {
+            // Unexplored node
+            child = dfs(field.getMessageType());
+            result.lowLink = Math.min(result.lowLink, child.lowLink);
+          } else {
+            if (child.component == null) {
+              // Still in the stack so we found a back edge.
+              result.lowLink = Math.min(result.lowLink, child.lowLink);
+            }
+          }
+        }
+      }
+
+      if (result.index == result.lowLink) {
+        // This is the root of a strongly connected component.
+        StronglyConnectedComponent component = new StronglyConnectedComponent();
+        while (true) {
+          Node node = stack.pop();
+          node.component = component;
+          component.messages.add(node.descriptor);
+          if (node == result) {
+            break;
+          }
+        }
+
+        analyze(component);
+      }
+
+      return result;
+    }
+
+    // Determine whether messages in this SCC needs isInitialized check.
+    private void analyze(StronglyConnectedComponent component) {
+      boolean needsIsInitializedCheck = false;
+      loop:
+      for (Descriptor descriptor : component.messages) {
+        if (descriptor.isExtendable()) {
+          needsIsInitializedCheck = true;
+          break;
+        }
+
+        for (FieldDescriptor field : descriptor.getFields()) {
+          if (field.isRequired()) {
+            needsIsInitializedCheck = true;
+            break loop;
+          }
+
+          if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+            // Since we are analyzing the graph bottom-up, all referenced fields should either be
+            // in this same component or in a different already-analyzed component.
+            Node node = nodeCache.get(field.getMessageType());
+            if (node.component != component) {
+              if (node.component.needsIsInitializedCheck) {
+                needsIsInitializedCheck = true;
+                break loop;
+              }
+            }
+          }
+        }
+      }
+
+      component.needsIsInitializedCheck = needsIsInitializedCheck;
+
+      for (Descriptor descriptor : component.messages) {
+        resultCache.put(descriptor, component.needsIsInitializedCheck);
+      }
+    }
+  }
+
+  private static IsInitializedCheckAnalyzer isInitializedCheckAnalyzer =
+      new IsInitializedCheckAnalyzer();
+
+  private static boolean needsIsInitializedCheck(Descriptor descriptor) {
+    return isInitializedCheckAnalyzer.needsIsInitializedCheck(descriptor);
+  }
+
+  private static StructuralMessageInfo convertProto2(
+      Class<?> messageType, Descriptor messageDescriptor) {
+    List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields();
+    StructuralMessageInfo.Builder builder =
+        StructuralMessageInfo.newBuilder(fieldDescriptors.size());
+    builder.withDefaultInstance(getDefaultInstance(messageType));
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    builder.withMessageSetWireFormat(messageDescriptor.getOptions().getMessageSetWireFormat());
+
+    OneofState oneofState = new OneofState();
+    int bitFieldIndex = 0;
+    int presenceMask = 1;
+    Field bitField = null;
+
+    // Fields in the descriptor are ordered by the index position in which they appear in the
+    // proto file. This is the same order used to determine the presence mask used in the
+    // bitFields. So to determine the appropriate presence mask to be used for a field, we simply
+    // need to shift the presence mask whenever a presence-checked field is encountered.
+    for (int i = 0; i < fieldDescriptors.size(); ++i) {
+      final FieldDescriptor fd = fieldDescriptors.get(i);
+      boolean enforceUtf8 = fd.getFile().getOptions().getJavaStringCheckUtf8();
+      Internal.EnumVerifier enumVerifier = null;
+      if (fd.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) {
+        enumVerifier =
+            new Internal.EnumVerifier() {
+              @Override
+              public boolean isInRange(int number) {
+                return fd.getEnumType().findValueByNumber(number) != null;
+              }
+            };
+      }
+      if (fd.getContainingOneof() != null) {
+        // Build a oneof member field.
+        builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, enumVerifier));
+      } else {
+        Field field = field(messageType, fd);
+        int number = fd.getNumber();
+        FieldType type = getFieldType(fd);
+
+        if (fd.isMapField()) {
+          // Map field points to an auto-generated message entry type with the definition:
+          //   message MapEntry {
+          //     K key = 1;
+          //     V value = 2;
+          //   }
+          final FieldDescriptor valueField = fd.getMessageType().findFieldByNumber(2);
+          if (valueField.getJavaType() == Descriptors.FieldDescriptor.JavaType.ENUM) {
+            enumVerifier =
+                new Internal.EnumVerifier() {
+                  @Override
+                  public boolean isInRange(int number) {
+                    return valueField.getEnumType().findValueByNumber(number) != null;
+                  }
+                };
+          }
+          builder.withField(
+              forMapField(
+                  field,
+                  number,
+                  SchemaUtil.getMapDefaultEntry(messageType, fd.getName()),
+                  enumVerifier));
+          continue;
+        }
+
+        if (fd.isRepeated()) {
+          // Repeated fields are not presence-checked.
+          if (enumVerifier != null) {
+            if (fd.isPacked()) {
+              builder.withField(
+                  forPackedFieldWithEnumVerifier(
+                      field, number, type, enumVerifier, cachedSizeField(messageType, fd)));
+            } else {
+              builder.withField(forFieldWithEnumVerifier(field, number, type, enumVerifier));
+            }
+          } else if (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+            builder.withField(
+                forRepeatedMessageField(
+                    field, number, type, getTypeForRepeatedMessageField(messageType, fd)));
+          } else {
+            if (fd.isPacked()) {
+              builder.withField(
+                  forPackedField(field, number, type, cachedSizeField(messageType, fd)));
+            } else {
+              builder.withField(forField(field, number, type, enforceUtf8));
+            }
+          }
+          continue;
+        }
+
+        if (bitField == null) {
+          // Lazy-create the next bitfield since we know it must exist.
+          bitField = bitField(messageType, bitFieldIndex);
+        }
+
+        // It's a presence-checked field.
+        if (fd.isRequired()) {
+          builder.withField(
+              forProto2RequiredField(
+                  field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier));
+        } else {
+          builder.withField(
+              forProto2OptionalField(
+                  field, number, type, bitField, presenceMask, enforceUtf8, enumVerifier));
+        }
+      }
+
+      // Update the presence mask for the next iteration. If the shift clears out the mask, we will
+      // go to the next bitField.
+      presenceMask <<= 1;
+      if (presenceMask == 0) {
+        bitField = null;
+        presenceMask = 1;
+        bitFieldIndex++;
+      }
+    }
+
+    List<Integer> fieldsToCheckIsInitialized = new ArrayList<Integer>();
+    for (int i = 0; i < fieldDescriptors.size(); ++i) {
+      FieldDescriptor fd = fieldDescriptors.get(i);
+      if (fd.isRequired()
+          || (fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE
+              && needsIsInitializedCheck(fd.getMessageType()))) {
+        fieldsToCheckIsInitialized.add(fd.getNumber());
+      }
+    }
+    int[] numbers = new int[fieldsToCheckIsInitialized.size()];
+    for (int i = 0; i < fieldsToCheckIsInitialized.size(); i++) {
+      numbers[i] = fieldsToCheckIsInitialized.get(i);
+    }
+    builder.withCheckInitialized(numbers);
+
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo convertProto3(
+      Class<?> messageType, Descriptor messageDescriptor) {
+    List<FieldDescriptor> fieldDescriptors = messageDescriptor.getFields();
+    StructuralMessageInfo.Builder builder =
+        StructuralMessageInfo.newBuilder(fieldDescriptors.size());
+    builder.withDefaultInstance(getDefaultInstance(messageType));
+    builder.withSyntax(ProtoSyntax.PROTO3);
+
+    OneofState oneofState = new OneofState();
+    boolean enforceUtf8 = true;
+    for (int i = 0; i < fieldDescriptors.size(); ++i) {
+      FieldDescriptor fd = fieldDescriptors.get(i);
+      if (fd.getContainingOneof() != null) {
+        // Build a oneof member field.
+        builder.withField(buildOneofMember(messageType, fd, oneofState, enforceUtf8, null));
+        continue;
+      }
+      if (fd.isMapField()) {
+        builder.withField(
+            forMapField(
+                field(messageType, fd),
+                fd.getNumber(),
+                SchemaUtil.getMapDefaultEntry(messageType, fd.getName()),
+                null));
+        continue;
+      }
+      if (fd.isRepeated() && fd.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+        builder.withField(
+            forRepeatedMessageField(
+                field(messageType, fd),
+                fd.getNumber(),
+                getFieldType(fd),
+                getTypeForRepeatedMessageField(messageType, fd)));
+        continue;
+      }
+      if (fd.isPacked()) {
+        builder.withField(
+            forPackedField(
+                field(messageType, fd),
+                fd.getNumber(),
+                getFieldType(fd),
+                cachedSizeField(messageType, fd)));
+      } else {
+        builder.withField(
+            forField(field(messageType, fd), fd.getNumber(), getFieldType(fd), enforceUtf8));
+      }
+    }
+
+    return builder.build();
+  }
+
+  /** Builds info for a oneof member field. */
+  private static FieldInfo buildOneofMember(
+      Class<?> messageType,
+      FieldDescriptor fd,
+      OneofState oneofState,
+      boolean enforceUtf8,
+      Internal.EnumVerifier enumVerifier) {
+    OneofInfo oneof = oneofState.getOneof(messageType, fd.getContainingOneof());
+    FieldType type = getFieldType(fd);
+    Class<?> oneofStoredType = getOneofStoredType(messageType, fd, type);
+    return forOneofMemberField(
+        fd.getNumber(), type, oneof, oneofStoredType, enforceUtf8, enumVerifier);
+  }
+
+  private static Class<?> getOneofStoredType(
+      Class<?> messageType, FieldDescriptor fd, FieldType type) {
+    switch (type.getJavaType()) {
+      case BOOLEAN:
+        return Boolean.class;
+      case BYTE_STRING:
+        return ByteString.class;
+      case DOUBLE:
+        return Double.class;
+      case FLOAT:
+        return Float.class;
+      case ENUM:
+      case INT:
+        return Integer.class;
+      case LONG:
+        return Long.class;
+      case STRING:
+        return String.class;
+      case MESSAGE:
+        return getOneofStoredTypeForMessage(messageType, fd);
+      default:
+        throw new IllegalArgumentException("Invalid type for oneof: " + type);
+    }
+  }
+
+  private static FieldType getFieldType(FieldDescriptor fd) {
+    switch (fd.getType()) {
+      case BOOL:
+        if (!fd.isRepeated()) {
+          return FieldType.BOOL;
+        }
+        return fd.isPacked() ? FieldType.BOOL_LIST_PACKED : FieldType.BOOL_LIST;
+      case BYTES:
+        return fd.isRepeated() ? FieldType.BYTES_LIST : FieldType.BYTES;
+      case DOUBLE:
+        if (!fd.isRepeated()) {
+          return FieldType.DOUBLE;
+        }
+        return fd.isPacked() ? FieldType.DOUBLE_LIST_PACKED : FieldType.DOUBLE_LIST;
+      case ENUM:
+        if (!fd.isRepeated()) {
+          return FieldType.ENUM;
+        }
+        return fd.isPacked() ? FieldType.ENUM_LIST_PACKED : FieldType.ENUM_LIST;
+      case FIXED32:
+        if (!fd.isRepeated()) {
+          return FieldType.FIXED32;
+        }
+        return fd.isPacked() ? FieldType.FIXED32_LIST_PACKED : FieldType.FIXED32_LIST;
+      case FIXED64:
+        if (!fd.isRepeated()) {
+          return FieldType.FIXED64;
+        }
+        return fd.isPacked() ? FieldType.FIXED64_LIST_PACKED : FieldType.FIXED64_LIST;
+      case FLOAT:
+        if (!fd.isRepeated()) {
+          return FieldType.FLOAT;
+        }
+        return fd.isPacked() ? FieldType.FLOAT_LIST_PACKED : FieldType.FLOAT_LIST;
+      case GROUP:
+        return fd.isRepeated() ? FieldType.GROUP_LIST : FieldType.GROUP;
+      case INT32:
+        if (!fd.isRepeated()) {
+          return FieldType.INT32;
+        }
+        return fd.isPacked() ? FieldType.INT32_LIST_PACKED : FieldType.INT32_LIST;
+      case INT64:
+        if (!fd.isRepeated()) {
+          return FieldType.INT64;
+        }
+        return fd.isPacked() ? FieldType.INT64_LIST_PACKED : FieldType.INT64_LIST;
+      case MESSAGE:
+        if (fd.isMapField()) {
+          return FieldType.MAP;
+        }
+        return fd.isRepeated() ? FieldType.MESSAGE_LIST : FieldType.MESSAGE;
+      case SFIXED32:
+        if (!fd.isRepeated()) {
+          return FieldType.SFIXED32;
+        }
+        return fd.isPacked() ? FieldType.SFIXED32_LIST_PACKED : FieldType.SFIXED32_LIST;
+      case SFIXED64:
+        if (!fd.isRepeated()) {
+          return FieldType.SFIXED64;
+        }
+        return fd.isPacked() ? FieldType.SFIXED64_LIST_PACKED : FieldType.SFIXED64_LIST;
+      case SINT32:
+        if (!fd.isRepeated()) {
+          return FieldType.SINT32;
+        }
+        return fd.isPacked() ? FieldType.SINT32_LIST_PACKED : FieldType.SINT32_LIST;
+      case SINT64:
+        if (!fd.isRepeated()) {
+          return FieldType.SINT64;
+        }
+        return fd.isPacked() ? FieldType.SINT64_LIST_PACKED : FieldType.SINT64_LIST;
+      case STRING:
+        return fd.isRepeated() ? FieldType.STRING_LIST : FieldType.STRING;
+      case UINT32:
+        if (!fd.isRepeated()) {
+          return FieldType.UINT32;
+        }
+        return fd.isPacked() ? FieldType.UINT32_LIST_PACKED : FieldType.UINT32_LIST;
+      case UINT64:
+        if (!fd.isRepeated()) {
+          return FieldType.UINT64;
+        }
+        return fd.isPacked() ? FieldType.UINT64_LIST_PACKED : FieldType.UINT64_LIST;
+      default:
+        throw new IllegalArgumentException("Unsupported field type: " + fd.getType());
+    }
+  }
+
+  private static Field bitField(Class<?> messageType, int index) {
+    return field(messageType, "bitField" + index + "_");
+  }
+
+  private static Field field(Class<?> messageType, FieldDescriptor fd) {
+    return field(messageType, getFieldName(fd));
+  }
+
+  private static Field cachedSizeField(Class<?> messageType, FieldDescriptor fd) {
+    return field(messageType, getCachedSizeFieldName(fd));
+  }
+
+  private static Field field(Class<?> messageType, String fieldName) {
+    try {
+      return messageType.getDeclaredField(fieldName);
+    } catch (Exception e) {
+      throw new IllegalArgumentException(
+          "Unable to find field " + fieldName + " in message class " + messageType.getName());
+    }
+  }
+
+  static String getFieldName(FieldDescriptor fd) {
+    String name = (fd.getType() == FieldDescriptor.Type.GROUP)
+                  ? fd.getMessageType().getName()
+                  : fd.getName();
+    String suffix = specialFieldNames.contains(name) ? "__" : "_";
+    return snakeCaseToCamelCase(name) + suffix;
+  }
+
+  private static String getCachedSizeFieldName(FieldDescriptor fd) {
+    return snakeCaseToCamelCase(fd.getName()) + "MemoizedSerializedSize";
+  }
+
+  /**
+   * This method must match exactly with the corresponding function in protocol compiler. See:
+   * https://github.com/google/protobuf/blob/v3.0.0/src/google/protobuf/compiler/java/java_helpers.cc#L153
+   */
+  private static String snakeCaseToCamelCase(String snakeCase) {
+    StringBuilder sb = new StringBuilder(snakeCase.length() + 1);
+    boolean capNext = false;
+    for (int ctr = 0; ctr < snakeCase.length(); ctr++) {
+      char next = snakeCase.charAt(ctr);
+      if (next == '_') {
+        capNext = true;
+      } else if (Character.isDigit(next)) {
+        sb.append(next);
+        capNext = true;
+      } else if (capNext) {
+        sb.append(Character.toUpperCase(next));
+        capNext = false;
+      } else if (ctr == 0) {
+        sb.append(Character.toLowerCase(next));
+      } else {
+        sb.append(next);
+      }
+    }
+    return sb.toString();
+  }
+
+  /**
+   * Inspects the message to identify the stored type for a message field that is part of a oneof.
+   */
+  private static Class<?> getOneofStoredTypeForMessage(Class<?> messageType, FieldDescriptor fd) {
+    try {
+      String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName();
+      Method getter = messageType.getDeclaredMethod(getterForField(name));
+      return getter.getReturnType();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Inspects the message to identify the message type of a repeated message field. */
+  private static Class<?> getTypeForRepeatedMessageField(Class<?> messageType, FieldDescriptor fd) {
+    try {
+      String name = fd.getType() == Type.GROUP ? fd.getMessageType().getName() : fd.getName();
+      Method getter = messageType.getDeclaredMethod(getterForField(name), int.class);
+      return getter.getReturnType();
+    } catch (Exception e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Constructs the name of the get method for the given field in the proto. */
+  private static String getterForField(String snakeCase) {
+    String camelCase = snakeCaseToCamelCase(snakeCase);
+    StringBuilder builder = new StringBuilder("get");
+    // Capitalize the first character in the field name.
+    builder.append(Character.toUpperCase(camelCase.charAt(0)));
+    builder.append(camelCase.substring(1, camelCase.length()));
+    return builder.toString();
+  }
+
+  private static final class OneofState {
+    private OneofInfo[] oneofs = new OneofInfo[2];
+
+    OneofInfo getOneof(Class<?> messageType, OneofDescriptor desc) {
+      int index = desc.getIndex();
+      if (index >= oneofs.length) {
+        // Grow the array.
+        oneofs = Arrays.copyOf(oneofs, index * 2);
+      }
+      OneofInfo info = oneofs[index];
+      if (info == null) {
+        info = newInfo(messageType, desc);
+        oneofs[index] = info;
+      }
+      return info;
+    }
+
+    private static OneofInfo newInfo(Class<?> messageType, OneofDescriptor desc) {
+      String camelCase = snakeCaseToCamelCase(desc.getName());
+      String valueFieldName = camelCase + "_";
+      String caseFieldName = camelCase + "Case_";
+
+      return new OneofInfo(
+          desc.getIndex(), field(messageType, caseFieldName), field(messageType, valueFieldName));
+    }
+  }
+}

+ 12 - 1
java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java

@@ -110,12 +110,23 @@ public class ExtensionRegistryLite {
     return ExtensionRegistryFactory.create();
     return ExtensionRegistryFactory.create();
   }
   }
 
 
+  private static volatile ExtensionRegistryLite emptyRegistry;
+
   /**
   /**
    * Get the unmodifiable singleton empty instance of either ExtensionRegistryLite or {@code
    * Get the unmodifiable singleton empty instance of either ExtensionRegistryLite or {@code
    * ExtensionRegistry} (if the full (non-Lite) proto libraries are available).
    * ExtensionRegistry} (if the full (non-Lite) proto libraries are available).
    */
    */
   public static ExtensionRegistryLite getEmptyRegistry() {
   public static ExtensionRegistryLite getEmptyRegistry() {
-    return ExtensionRegistryFactory.createEmpty();
+    ExtensionRegistryLite result = emptyRegistry;
+    if (result == null) {
+      synchronized (ExtensionRegistryLite.class) {
+        result = emptyRegistry;
+        if (result == null) {
+          result = emptyRegistry = ExtensionRegistryFactory.createEmpty();
+        }
+      }
+    }
+    return result;
   }
   }
 
 
 
 

+ 98 - 0
java/core/src/main/java/com/google/protobuf/ExtensionSchema.java

@@ -0,0 +1,98 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+import java.util.Map;
+
+abstract class ExtensionSchema<T extends FieldSet.FieldDescriptorLite<T>> {
+
+  /** Returns true for messages that support extensions. */
+  abstract boolean hasExtensions(MessageLite prototype);
+
+  /** Returns the extension {@link FieldSet} for the message instance. */
+  abstract FieldSet<T> getExtensions(Object message);
+
+  /** Replaces the extension {@link FieldSet} for the message instance. */
+  abstract void setExtensions(Object message, FieldSet<T> extensions);
+
+  /** Returns the extension {@link FieldSet} and ensures it's mutable. */
+  abstract FieldSet<T> getMutableExtensions(Object message);
+
+  /** Marks the extension {@link FieldSet} as immutable. */
+  abstract void makeImmutable(Object message);
+
+  /**
+   * Parses an extension. Returns the passed-in unknownFields parameter if no unknown enum value is
+   * found or a modified unknownFields (a new instance if the passed-in unknownFields is null)
+   * containing unknown enum values found while parsing.
+   *
+   * @param <UT> The type used to store unknown fields. It's either UnknownFieldSet in full runtime
+   *     or UnknownFieldSetLite in lite runtime.
+   */
+  abstract <UT, UB> UB parseExtension(
+      Reader reader,
+      Object extension,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<T> extensions,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema)
+      throws IOException;
+
+  /** Gets the field number of an extension entry. */
+  abstract int extensionNumber(Map.Entry<?, ?> extension);
+
+  /** Serializes one extension entry. */
+  abstract void serializeExtension(Writer writer, Map.Entry<?, ?> extension) throws IOException;
+
+  /** Finds an extension by field number. */
+  abstract Object findExtensionByNumber(
+      ExtensionRegistryLite extensionRegistry, MessageLite defaultInstance, int number);
+
+  /** Parses a length-prefixed MessageSet item from the reader. */
+  abstract void parseLengthPrefixedMessageSetItem(
+      Reader reader,
+      Object extension,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<T> extensions)
+      throws IOException;
+
+  /**
+   * Parses the entire content of a {@link ByteString} as one MessageSet item. Unlike {@link
+   * #parseLengthPrefixedMessageSetItem}, there isn't a length-prefix.
+   */
+  abstract void parseMessageSetItem(
+      ByteString data,
+      Object extension,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<T> extensions)
+      throws IOException;
+}

+ 547 - 0
java/core/src/main/java/com/google/protobuf/ExtensionSchemaFull.java

@@ -0,0 +1,547 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.Descriptors.EnumValueDescriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("unchecked")
+final class ExtensionSchemaFull extends ExtensionSchema<FieldDescriptor> {
+
+  private static final long EXTENSION_FIELD_OFFSET = getExtensionsFieldOffset();
+
+  private static <T> long getExtensionsFieldOffset() {
+    try {
+      Field field = GeneratedMessageV3.ExtendableMessage.class.getDeclaredField("extensions");
+      return UnsafeUtil.objectFieldOffset(field);
+    } catch (Throwable e) {
+      throw new IllegalStateException("Unable to lookup extension field offset");
+    }
+  }
+
+  @Override
+  boolean hasExtensions(MessageLite prototype) {
+    return prototype instanceof GeneratedMessageV3.ExtendableMessage;
+  }
+
+  @Override
+  public FieldSet<FieldDescriptor> getExtensions(Object message) {
+    return (FieldSet<FieldDescriptor>) UnsafeUtil.getObject(message, EXTENSION_FIELD_OFFSET);
+  }
+
+  @Override
+  void setExtensions(Object message, FieldSet<FieldDescriptor> extensions) {
+    UnsafeUtil.putObject(message, EXTENSION_FIELD_OFFSET, extensions);
+  }
+
+  @Override
+  FieldSet<FieldDescriptor> getMutableExtensions(Object message) {
+    FieldSet<FieldDescriptor> extensions = getExtensions(message);
+    if (extensions.isImmutable()) {
+      extensions = extensions.clone();
+      setExtensions(message, extensions);
+    }
+    return extensions;
+  }
+
+  @Override
+  void makeImmutable(Object message) {
+    getExtensions(message).makeImmutable();
+  }
+
+  @Override
+  <UT, UB> UB parseExtension(
+      Reader reader,
+      Object extensionObject,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<FieldDescriptor> extensions,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema)
+      throws IOException {
+    ExtensionRegistry.ExtensionInfo extension = (ExtensionRegistry.ExtensionInfo) extensionObject;
+    int fieldNumber = extension.descriptor.getNumber();
+
+    if (extension.descriptor.isRepeated() && extension.descriptor.isPacked()) {
+      Object value = null;
+      switch (extension.descriptor.getLiteType()) {
+        case DOUBLE:
+          {
+            List<Double> list = new ArrayList<Double>();
+            reader.readDoubleList(list);
+            value = list;
+            break;
+          }
+        case FLOAT:
+          {
+            List<Float> list = new ArrayList<Float>();
+            reader.readFloatList(list);
+            value = list;
+            break;
+          }
+        case INT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readInt64List(list);
+            value = list;
+            break;
+          }
+        case UINT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readUInt64List(list);
+            value = list;
+            break;
+          }
+        case INT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readInt32List(list);
+            value = list;
+            break;
+          }
+        case FIXED64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readFixed64List(list);
+            value = list;
+            break;
+          }
+        case FIXED32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readFixed32List(list);
+            value = list;
+            break;
+          }
+        case BOOL:
+          {
+            List<Boolean> list = new ArrayList<Boolean>();
+            reader.readBoolList(list);
+            value = list;
+            break;
+          }
+        case UINT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readUInt32List(list);
+            value = list;
+            break;
+          }
+        case SFIXED32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readSFixed32List(list);
+            value = list;
+            break;
+          }
+        case SFIXED64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readSFixed64List(list);
+            value = list;
+            break;
+          }
+        case SINT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readSInt32List(list);
+            value = list;
+            break;
+          }
+        case SINT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readSInt64List(list);
+            value = list;
+            break;
+          }
+        case ENUM:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readEnumList(list);
+            List<EnumValueDescriptor> enumList = new ArrayList<EnumValueDescriptor>();
+            for (int number : list) {
+              EnumValueDescriptor enumDescriptor =
+                  extension.descriptor.getEnumType().findValueByNumber(number);
+              if (enumDescriptor != null) {
+                enumList.add(enumDescriptor);
+              } else {
+                unknownFields =
+                    SchemaUtil.storeUnknownEnum(
+                        fieldNumber, number, unknownFields, unknownFieldSchema);
+              }
+            }
+            value = enumList;
+            break;
+          }
+        default:
+          throw new IllegalStateException(
+              "Type cannot be packed: " + extension.descriptor.getLiteType());
+      }
+      extensions.setField(extension.descriptor, value);
+    } else {
+      Object value = null;
+      // Enum is a special case because unknown enum values will be put into UnknownFieldSetLite.
+      if (extension.descriptor.getLiteType() == WireFormat.FieldType.ENUM) {
+        int number = reader.readInt32();
+        Object enumValue = extension.descriptor.getEnumType().findValueByNumber(number);
+        if (enumValue == null) {
+          return SchemaUtil.storeUnknownEnum(
+              fieldNumber, number, unknownFields, unknownFieldSchema);
+        }
+        value = enumValue;
+      } else {
+        switch (extension.descriptor.getLiteType()) {
+          case DOUBLE:
+            value = reader.readDouble();
+            break;
+          case FLOAT:
+            value = reader.readFloat();
+            break;
+          case INT64:
+            value = reader.readInt64();
+            break;
+          case UINT64:
+            value = reader.readUInt64();
+            break;
+          case INT32:
+            value = reader.readInt32();
+            break;
+          case FIXED64:
+            value = reader.readFixed64();
+            break;
+          case FIXED32:
+            value = reader.readFixed32();
+            break;
+          case BOOL:
+            value = reader.readBool();
+            break;
+          case BYTES:
+            value = reader.readBytes();
+            break;
+          case UINT32:
+            value = reader.readUInt32();
+            break;
+          case SFIXED32:
+            value = reader.readSFixed32();
+            break;
+          case SFIXED64:
+            value = reader.readSFixed64();
+            break;
+          case SINT32:
+            value = reader.readSInt32();
+            break;
+          case SINT64:
+            value = reader.readSInt64();
+            break;
+
+          case STRING:
+            value = reader.readString();
+            break;
+          case GROUP:
+            value = reader.readGroup(extension.defaultInstance.getClass(), extensionRegistry);
+            break;
+
+          case MESSAGE:
+            value = reader.readMessage(extension.defaultInstance.getClass(), extensionRegistry);
+            break;
+
+          case ENUM:
+            throw new IllegalStateException("Shouldn't reach here.");
+        }
+      }
+      if (extension.descriptor.isRepeated()) {
+        extensions.addRepeatedField(extension.descriptor, value);
+      } else {
+        switch (extension.descriptor.getLiteType()) {
+          case MESSAGE:
+          case GROUP:
+            Object oldValue = extensions.getField(extension.descriptor);
+            if (oldValue != null) {
+              value = Internal.mergeMessage(oldValue, value);
+            }
+            break;
+          default:
+            break;
+        }
+        extensions.setField(extension.descriptor, value);
+      }
+    }
+    return unknownFields;
+  }
+
+  @Override
+  int extensionNumber(Map.Entry<?, ?> extension) {
+    FieldDescriptor descriptor = (FieldDescriptor) extension.getKey();
+    return descriptor.getNumber();
+  }
+
+  @Override
+  void serializeExtension(Writer writer, Map.Entry<?, ?> extension) throws IOException {
+    FieldDescriptor descriptor = (FieldDescriptor) extension.getKey();
+    if (descriptor.isRepeated()) {
+      switch (descriptor.getLiteType()) {
+        case DOUBLE:
+          SchemaUtil.writeDoubleList(
+              descriptor.getNumber(),
+              (List<Double>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FLOAT:
+          SchemaUtil.writeFloatList(
+              descriptor.getNumber(),
+              (List<Float>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case INT64:
+          SchemaUtil.writeInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case UINT64:
+          SchemaUtil.writeUInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case INT32:
+          SchemaUtil.writeInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FIXED64:
+          SchemaUtil.writeFixed64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FIXED32:
+          SchemaUtil.writeFixed32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case BOOL:
+          SchemaUtil.writeBoolList(
+              descriptor.getNumber(),
+              (List<Boolean>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case BYTES:
+          SchemaUtil.writeBytesList(
+              descriptor.getNumber(), (List<ByteString>) extension.getValue(), writer);
+          break;
+        case UINT32:
+          SchemaUtil.writeUInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SFIXED32:
+          SchemaUtil.writeSFixed32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SFIXED64:
+          SchemaUtil.writeSFixed64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SINT32:
+          SchemaUtil.writeSInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SINT64:
+          SchemaUtil.writeSInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case ENUM:
+          {
+            List<EnumValueDescriptor> enumList = (List<EnumValueDescriptor>) extension.getValue();
+            List<Integer> list = new ArrayList<Integer>();
+            for (EnumValueDescriptor d : enumList) {
+              list.add(d.getNumber());
+            }
+            SchemaUtil.writeInt32List(descriptor.getNumber(), list, writer, descriptor.isPacked());
+            break;
+          }
+        case STRING:
+          SchemaUtil.writeStringList(
+              descriptor.getNumber(), (List<String>) extension.getValue(), writer);
+          break;
+        case GROUP:
+          SchemaUtil.writeGroupList(descriptor.getNumber(), (List<?>) extension.getValue(), writer);
+          break;
+        case MESSAGE:
+          SchemaUtil.writeMessageList(
+              descriptor.getNumber(), (List<?>) extension.getValue(), writer);
+          break;
+      }
+    } else {
+      switch (descriptor.getLiteType()) {
+        case DOUBLE:
+          writer.writeDouble(descriptor.getNumber(), (Double) extension.getValue());
+          break;
+        case FLOAT:
+          writer.writeFloat(descriptor.getNumber(), (Float) extension.getValue());
+          break;
+        case INT64:
+          writer.writeInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case UINT64:
+          writer.writeUInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case INT32:
+          writer.writeInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case FIXED64:
+          writer.writeFixed64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case FIXED32:
+          writer.writeFixed32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case BOOL:
+          writer.writeBool(descriptor.getNumber(), (Boolean) extension.getValue());
+          break;
+        case BYTES:
+          writer.writeBytes(descriptor.getNumber(), (ByteString) extension.getValue());
+          break;
+        case UINT32:
+          writer.writeUInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SFIXED32:
+          writer.writeSFixed32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SFIXED64:
+          writer.writeSFixed64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case SINT32:
+          writer.writeSInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SINT64:
+          writer.writeSInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case ENUM:
+          writer.writeInt32(
+              descriptor.getNumber(), ((EnumValueDescriptor) extension.getValue()).getNumber());
+          break;
+        case STRING:
+          writer.writeString(descriptor.getNumber(), (String) extension.getValue());
+          break;
+        case GROUP:
+          writer.writeGroup(descriptor.getNumber(), extension.getValue());
+          break;
+        case MESSAGE:
+          writer.writeMessage(descriptor.getNumber(), extension.getValue());
+          break;
+      }
+    }
+  }
+
+  @Override
+  Object findExtensionByNumber(
+      ExtensionRegistryLite extensionRegistry, MessageLite defaultInstance, int number) {
+    return ((ExtensionRegistry) extensionRegistry)
+        .findExtensionByNumber(((Message) defaultInstance).getDescriptorForType(), number);
+  }
+
+  @Override
+  void parseLengthPrefixedMessageSetItem(
+      Reader reader,
+      Object extension,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<FieldDescriptor> extensions)
+      throws IOException {
+    ExtensionRegistry.ExtensionInfo extensionInfo = (ExtensionRegistry.ExtensionInfo) extension;
+
+    if (ExtensionRegistryLite.isEagerlyParseMessageSets()) {
+      Object value =
+          reader.readMessage(extensionInfo.defaultInstance.getClass(), extensionRegistry);
+      extensions.setField(extensionInfo.descriptor, value);
+    } else {
+      extensions.setField(
+          extensionInfo.descriptor,
+          new LazyField(extensionInfo.defaultInstance, extensionRegistry, reader.readBytes()));
+    }
+  }
+
+  @Override
+  void parseMessageSetItem(
+      ByteString data,
+      Object extension,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<FieldDescriptor> extensions)
+      throws IOException {
+    ExtensionRegistry.ExtensionInfo extensionInfo = (ExtensionRegistry.ExtensionInfo) extension;
+    Object value = extensionInfo.defaultInstance.newBuilderForType().buildPartial();
+
+    if (ExtensionRegistryLite.isEagerlyParseMessageSets()) {
+      Reader reader = BinaryReader.newInstance(ByteBuffer.wrap(data.toByteArray()), true);
+      Protobuf.getInstance().mergeFrom(value, reader, extensionRegistry);
+      extensions.setField(extensionInfo.descriptor, value);
+
+      if (reader.getFieldNumber() != Reader.READ_DONE) {
+        throw InvalidProtocolBufferException.invalidEndTag();
+      }
+    } else {
+      extensions.setField(
+          extensionInfo.descriptor,
+          new LazyField(extensionInfo.defaultInstance, extensionRegistry, data));
+    }
+  }
+}

+ 541 - 0
java/core/src/main/java/com/google/protobuf/ExtensionSchemaLite.java

@@ -0,0 +1,541 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.GeneratedMessageLite.ExtensionDescriptor;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+@SuppressWarnings("unchecked")
+final class ExtensionSchemaLite extends ExtensionSchema<ExtensionDescriptor> {
+
+  @Override
+  boolean hasExtensions(MessageLite prototype) {
+    return prototype instanceof GeneratedMessageLite.ExtendableMessage;
+  }
+
+  @Override
+  FieldSet<ExtensionDescriptor> getExtensions(Object message) {
+    return ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).extensions;
+  }
+
+  @Override
+  void setExtensions(Object message, FieldSet<ExtensionDescriptor> extensions) {
+    ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).extensions = extensions;
+  }
+
+  @Override
+  FieldSet<ExtensionDescriptor> getMutableExtensions(Object message) {
+    return ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).ensureExtensionsAreMutable();
+  }
+
+  @Override
+  void makeImmutable(Object message) {
+    getExtensions(message).makeImmutable();
+  }
+
+  @Override
+  <UT, UB> UB parseExtension(
+      Reader reader,
+      Object extensionObject,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<ExtensionDescriptor> extensions,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema)
+      throws IOException {
+    GeneratedMessageLite.GeneratedExtension<?, ?> extension =
+        (GeneratedMessageLite.GeneratedExtension<?, ?>) extensionObject;
+    int fieldNumber = extension.getNumber();
+
+    if (extension.descriptor.isRepeated() && extension.descriptor.isPacked()) {
+      Object value = null;
+      switch (extension.getLiteType()) {
+        case DOUBLE:
+          {
+            List<Double> list = new ArrayList<Double>();
+            reader.readDoubleList(list);
+            value = list;
+            break;
+          }
+        case FLOAT:
+          {
+            List<Float> list = new ArrayList<Float>();
+            reader.readFloatList(list);
+            value = list;
+            break;
+          }
+        case INT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readInt64List(list);
+            value = list;
+            break;
+          }
+        case UINT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readUInt64List(list);
+            value = list;
+            break;
+          }
+        case INT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readInt32List(list);
+            value = list;
+            break;
+          }
+        case FIXED64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readFixed64List(list);
+            value = list;
+            break;
+          }
+        case FIXED32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readFixed32List(list);
+            value = list;
+            break;
+          }
+        case BOOL:
+          {
+            List<Boolean> list = new ArrayList<Boolean>();
+            reader.readBoolList(list);
+            value = list;
+            break;
+          }
+        case UINT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readUInt32List(list);
+            value = list;
+            break;
+          }
+        case SFIXED32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readSFixed32List(list);
+            value = list;
+            break;
+          }
+        case SFIXED64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readSFixed64List(list);
+            value = list;
+            break;
+          }
+        case SINT32:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readSInt32List(list);
+            value = list;
+            break;
+          }
+        case SINT64:
+          {
+            List<Long> list = new ArrayList<Long>();
+            reader.readSInt64List(list);
+            value = list;
+            break;
+          }
+        case ENUM:
+          {
+            List<Integer> list = new ArrayList<Integer>();
+            reader.readEnumList(list);
+            unknownFields =
+                SchemaUtil.filterUnknownEnumList(
+                    fieldNumber,
+                    list,
+                    extension.descriptor.getEnumType(),
+                    unknownFields,
+                    unknownFieldSchema);
+            value = list;
+            break;
+          }
+        default:
+          throw new IllegalStateException(
+              "Type cannot be packed: " + extension.descriptor.getLiteType());
+      }
+      extensions.setField(extension.descriptor, value);
+    } else {
+      Object value = null;
+      // Enum is a special case becasue unknown enum values will be put into UnknownFieldSetLite.
+      if (extension.getLiteType() == WireFormat.FieldType.ENUM) {
+        int number = reader.readInt32();
+        Object enumValue = extension.descriptor.getEnumType().findValueByNumber(number);
+        if (enumValue == null) {
+          return SchemaUtil.storeUnknownEnum(
+              fieldNumber, number, unknownFields, unknownFieldSchema);
+        }
+        // Note, we store the integer value instead of the actual enum object in FieldSet.
+        // This is also different from full-runtime where we store EnumValueDescriptor.
+        value = number;
+      } else {
+        switch (extension.getLiteType()) {
+          case DOUBLE:
+            value = reader.readDouble();
+            break;
+          case FLOAT:
+            value = reader.readFloat();
+            break;
+          case INT64:
+            value = reader.readInt64();
+            break;
+          case UINT64:
+            value = reader.readUInt64();
+            break;
+          case INT32:
+            value = reader.readInt32();
+            break;
+          case FIXED64:
+            value = reader.readFixed64();
+            break;
+          case FIXED32:
+            value = reader.readFixed32();
+            break;
+          case BOOL:
+            value = reader.readBool();
+            break;
+          case BYTES:
+            value = reader.readBytes();
+            break;
+          case UINT32:
+            value = reader.readUInt32();
+            break;
+          case SFIXED32:
+            value = reader.readSFixed32();
+            break;
+          case SFIXED64:
+            value = reader.readSFixed64();
+            break;
+          case SINT32:
+            value = reader.readSInt32();
+            break;
+          case SINT64:
+            value = reader.readSInt64();
+            break;
+
+          case STRING:
+            value = reader.readString();
+            break;
+          case GROUP:
+            value =
+                reader.readGroup(
+                    extension.getMessageDefaultInstance().getClass(), extensionRegistry);
+            break;
+
+          case MESSAGE:
+            value =
+                reader.readMessage(
+                    extension.getMessageDefaultInstance().getClass(), extensionRegistry);
+            break;
+
+          case ENUM:
+            throw new IllegalStateException("Shouldn't reach here.");
+        }
+      }
+      if (extension.isRepeated()) {
+        extensions.addRepeatedField(extension.descriptor, value);
+      } else {
+        switch (extension.getLiteType()) {
+          case MESSAGE:
+          case GROUP:
+            Object oldValue = extensions.getField(extension.descriptor);
+            if (oldValue != null) {
+              value = Internal.mergeMessage(oldValue, value);
+            }
+            break;
+          default:
+            break;
+        }
+        extensions.setField(extension.descriptor, value);
+      }
+    }
+    return unknownFields;
+  }
+
+  @Override
+  int extensionNumber(Map.Entry<?, ?> extension) {
+    GeneratedMessageLite.ExtensionDescriptor descriptor =
+        (GeneratedMessageLite.ExtensionDescriptor) extension.getKey();
+    return descriptor.getNumber();
+  }
+
+  @Override
+  void serializeExtension(Writer writer, Map.Entry<?, ?> extension) throws IOException {
+    GeneratedMessageLite.ExtensionDescriptor descriptor =
+        (GeneratedMessageLite.ExtensionDescriptor) extension.getKey();
+    if (descriptor.isRepeated()) {
+      switch (descriptor.getLiteType()) {
+        case DOUBLE:
+          SchemaUtil.writeDoubleList(
+              descriptor.getNumber(),
+              (List<Double>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FLOAT:
+          SchemaUtil.writeFloatList(
+              descriptor.getNumber(),
+              (List<Float>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case INT64:
+          SchemaUtil.writeInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case UINT64:
+          SchemaUtil.writeUInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case INT32:
+          SchemaUtil.writeInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FIXED64:
+          SchemaUtil.writeFixed64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case FIXED32:
+          SchemaUtil.writeFixed32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case BOOL:
+          SchemaUtil.writeBoolList(
+              descriptor.getNumber(),
+              (List<Boolean>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case BYTES:
+          SchemaUtil.writeBytesList(
+              descriptor.getNumber(), (List<ByteString>) extension.getValue(), writer);
+          break;
+        case UINT32:
+          SchemaUtil.writeUInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SFIXED32:
+          SchemaUtil.writeSFixed32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SFIXED64:
+          SchemaUtil.writeSFixed64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SINT32:
+          SchemaUtil.writeSInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case SINT64:
+          SchemaUtil.writeSInt64List(
+              descriptor.getNumber(),
+              (List<Long>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case ENUM:
+          SchemaUtil.writeInt32List(
+              descriptor.getNumber(),
+              (List<Integer>) extension.getValue(),
+              writer,
+              descriptor.isPacked());
+          break;
+        case STRING:
+          SchemaUtil.writeStringList(
+              descriptor.getNumber(), (List<String>) extension.getValue(), writer);
+          break;
+        case GROUP:
+          {
+            List<?> data = (List<?>) extension.getValue();
+            if (data != null && !data.isEmpty()) {
+              SchemaUtil.writeGroupList(
+                  descriptor.getNumber(),
+                  (List<?>) extension.getValue(),
+                  writer,
+                  Protobuf.getInstance().schemaFor(data.get(0).getClass()));
+            }
+          }
+          break;
+        case MESSAGE:
+          {
+            List<?> data = (List<?>) extension.getValue();
+            if (data != null && !data.isEmpty()) {
+              SchemaUtil.writeMessageList(
+                  descriptor.getNumber(),
+                  (List<?>) extension.getValue(),
+                  writer,
+                  Protobuf.getInstance().schemaFor(data.get(0).getClass()));
+            }
+          }
+          break;
+      }
+    } else {
+      switch (descriptor.getLiteType()) {
+        case DOUBLE:
+          writer.writeDouble(descriptor.getNumber(), (Double) extension.getValue());
+          break;
+        case FLOAT:
+          writer.writeFloat(descriptor.getNumber(), (Float) extension.getValue());
+          break;
+        case INT64:
+          writer.writeInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case UINT64:
+          writer.writeUInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case INT32:
+          writer.writeInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case FIXED64:
+          writer.writeFixed64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case FIXED32:
+          writer.writeFixed32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case BOOL:
+          writer.writeBool(descriptor.getNumber(), (Boolean) extension.getValue());
+          break;
+        case BYTES:
+          writer.writeBytes(descriptor.getNumber(), (ByteString) extension.getValue());
+          break;
+        case UINT32:
+          writer.writeUInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SFIXED32:
+          writer.writeSFixed32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SFIXED64:
+          writer.writeSFixed64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case SINT32:
+          writer.writeSInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case SINT64:
+          writer.writeSInt64(descriptor.getNumber(), (Long) extension.getValue());
+          break;
+        case ENUM:
+          writer.writeInt32(descriptor.getNumber(), (Integer) extension.getValue());
+          break;
+        case STRING:
+          writer.writeString(descriptor.getNumber(), (String) extension.getValue());
+          break;
+        case GROUP:
+          writer.writeGroup(
+              descriptor.getNumber(),
+              extension.getValue(),
+              Protobuf.getInstance().schemaFor(extension.getValue().getClass()));
+          break;
+        case MESSAGE:
+          writer.writeMessage(
+              descriptor.getNumber(),
+              extension.getValue(),
+              Protobuf.getInstance().schemaFor(extension.getValue().getClass()));
+          break;
+      }
+    }
+  }
+
+  @Override
+  Object findExtensionByNumber(
+      ExtensionRegistryLite extensionRegistry, MessageLite defaultInstance, int number) {
+    return extensionRegistry.findLiteExtensionByNumber(defaultInstance, number);
+  }
+
+  @Override
+  void parseLengthPrefixedMessageSetItem(
+      Reader reader,
+      Object extensionObject,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<ExtensionDescriptor> extensions)
+      throws IOException {
+    GeneratedMessageLite.GeneratedExtension<?, ?> extension =
+        (GeneratedMessageLite.GeneratedExtension<?, ?>) extensionObject;
+    Object value =
+        reader.readMessage(extension.getMessageDefaultInstance().getClass(), extensionRegistry);
+    extensions.setField(extension.descriptor, value);
+  }
+
+  @Override
+  void parseMessageSetItem(
+      ByteString data,
+      Object extensionObject,
+      ExtensionRegistryLite extensionRegistry,
+      FieldSet<ExtensionDescriptor> extensions)
+      throws IOException {
+    GeneratedMessageLite.GeneratedExtension<?, ?> extension =
+        (GeneratedMessageLite.GeneratedExtension<?, ?>) extensionObject;
+    Object value = extension.getMessageDefaultInstance().newBuilderForType().buildPartial();
+
+    Reader reader = BinaryReader.newInstance(ByteBuffer.wrap(data.toByteArray()), true);
+
+    Protobuf.getInstance().mergeFrom(value, reader, extensionRegistry);
+    extensions.setField(extension.descriptor, value);
+
+    if (reader.getFieldNumber() != Reader.READ_DONE) {
+      throw InvalidProtocolBufferException.invalidEndTag();
+    }
+  }
+}

+ 56 - 0
java/core/src/main/java/com/google/protobuf/ExtensionSchemas.java

@@ -0,0 +1,56 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+final class ExtensionSchemas {
+  private static final ExtensionSchema<?> LITE_SCHEMA = new ExtensionSchemaLite();
+  private static final ExtensionSchema<?> FULL_SCHEMA = loadSchemaForFullRuntime();
+
+  private static ExtensionSchema<?> loadSchemaForFullRuntime() {
+    try {
+      Class<?> clazz = Class.forName("com.google.protobuf.ExtensionSchemaFull");
+      return (ExtensionSchema) clazz.getDeclaredConstructor().newInstance();
+    } catch (Exception e) {
+      return null;
+    }
+  }
+
+  static ExtensionSchema<?> lite() {
+    return LITE_SCHEMA;
+  }
+
+  static ExtensionSchema<?> full() {
+    if (FULL_SCHEMA == null) {
+      throw new IllegalStateException("Protobuf runtime is not correctly loaded.");
+    }
+    return FULL_SCHEMA;
+  }
+}

+ 577 - 0
java/core/src/main/java/com/google/protobuf/FieldInfo.java

@@ -0,0 +1,577 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+import com.google.protobuf.Internal.EnumVerifier;
+import java.lang.reflect.Field;
+
+/** Information for a single field in a protobuf message class. */
+@ExperimentalApi
+final class FieldInfo implements Comparable<FieldInfo> {
+  private final Field field;
+  private final FieldType type;
+  private final Class<?> messageClass; // The message type for repeated message fields.
+  private final int fieldNumber;
+  private final Field presenceField;
+  private final int presenceMask;
+  private final boolean required;
+  private final boolean enforceUtf8;
+  private final OneofInfo oneof;
+  private final Field cachedSizeField;
+  /**
+   * The actual type stored in the oneof value for this field. Since the oneof value is an {@link
+   * Object}, primitives will store their boxed type. Only valid in conjunction with {@link #oneof}
+   * (both must be either null or non-null.
+   */
+  private final Class<?> oneofStoredType;
+
+  // TODO(liujisi): make map default entry lazy?
+  private final Object mapDefaultEntry;
+
+  private final EnumVerifier enumVerifier;
+
+  /** Constructs a new descriptor for a field. */
+  public static FieldInfo forField(
+      Field field, int fieldNumber, FieldType fieldType, boolean enforceUtf8) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    checkNotNull(fieldType, "fieldType");
+    if (fieldType == FieldType.MESSAGE_LIST || fieldType == FieldType.GROUP_LIST) {
+      throw new IllegalStateException("Shouldn't be called for repeated message fields.");
+    }
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        enforceUtf8,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        /* enumVerifier= */ null,
+        /* cachedSizeField= */ null);
+  }
+
+  /** Constructs a new descriptor for a packed field. */
+  public static FieldInfo forPackedField(
+      Field field, int fieldNumber, FieldType fieldType, Field cachedSizeField) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    checkNotNull(fieldType, "fieldType");
+    if (fieldType == FieldType.MESSAGE_LIST || fieldType == FieldType.GROUP_LIST) {
+      throw new IllegalStateException("Shouldn't be called for repeated message fields.");
+    }
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        /* enforceUtf8= */ false,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        /* enumVerifier= */ null,
+        cachedSizeField);
+  }
+
+  /** Constructs a new descriptor for a repeated message field. */
+  public static FieldInfo forRepeatedMessageField(
+      Field field, int fieldNumber, FieldType fieldType, Class<?> messageClass) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    checkNotNull(fieldType, "fieldType");
+    checkNotNull(messageClass, "messageClass");
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        messageClass,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        /* enforceUtf8= */ false,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        /* enumVerifier= */ null,
+        /* cachedSizeField= */ null);
+  }
+
+  public static FieldInfo forFieldWithEnumVerifier(
+      Field field, int fieldNumber, FieldType fieldType, EnumVerifier enumVerifier) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        /* enforceUtf8= */ false,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        enumVerifier,
+        /* cachedSizeField= */ null);
+  }
+
+  public static FieldInfo forPackedFieldWithEnumVerifier(
+      Field field,
+      int fieldNumber,
+      FieldType fieldType,
+      EnumVerifier enumVerifier,
+      Field cachedSizeField) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        /* enforceUtf8= */ false,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        enumVerifier,
+        cachedSizeField);
+  }
+
+  /** Constructor for a proto2 optional field. */
+  public static FieldInfo forProto2OptionalField(
+      Field field,
+      int fieldNumber,
+      FieldType fieldType,
+      Field presenceField,
+      int presenceMask,
+      boolean enforceUtf8,
+      EnumVerifier enumVerifier) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    checkNotNull(fieldType, "fieldType");
+    checkNotNull(presenceField, "presenceField");
+    if (presenceField != null && !isExactlyOneBitSet(presenceMask)) {
+      throw new IllegalArgumentException(
+          "presenceMask must have exactly one bit set: " + presenceMask);
+    }
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        presenceField,
+        presenceMask,
+        /* required= */ false,
+        enforceUtf8,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        enumVerifier,
+        /* cachedSizeField= */ null);
+  }
+
+  /**
+   * Constructor for a field that is part of a oneof.
+   *
+   * @param fieldNumber the unique field number for this field within the message.
+   * @param fieldType the type of the field (must be non-null).
+   * @param oneof the oneof for which this field is associated (must be non-null).
+   * @param oneofStoredType the actual type stored in the oneof value for this field. Since the
+   *     oneof value is an {@link Object}, primitives will store their boxed type. Must be non-null.
+   * @param enforceUtf8 Only used for string fields. If {@code true}, will enforce UTF-8 on a string
+   *     field.
+   * @return the {@link FieldInfo} describing this field.
+   */
+  public static FieldInfo forOneofMemberField(
+      int fieldNumber,
+      FieldType fieldType,
+      OneofInfo oneof,
+      Class<?> oneofStoredType,
+      boolean enforceUtf8,
+      EnumVerifier enumVerifier) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(fieldType, "fieldType");
+    checkNotNull(oneof, "oneof");
+    checkNotNull(oneofStoredType, "oneofStoredType");
+    if (!fieldType.isScalar()) {
+      throw new IllegalArgumentException(
+          "Oneof is only supported for scalar fields. Field "
+              + fieldNumber
+              + " is of type "
+              + fieldType);
+    }
+    return new FieldInfo(
+        /* field= */ null,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        enforceUtf8,
+        oneof,
+        oneofStoredType,
+        /* mapDefaultEntry= */ null,
+        enumVerifier,
+        /* cachedSizeField= */ null);
+  }
+
+  private static void checkFieldNumber(int fieldNumber) {
+    if (fieldNumber <= 0) {
+      throw new IllegalArgumentException("fieldNumber must be positive: " + fieldNumber);
+    }
+  }
+
+  /** Constructor for a proto2 required field. */
+  public static FieldInfo forProto2RequiredField(
+      Field field,
+      int fieldNumber,
+      FieldType fieldType,
+      Field presenceField,
+      int presenceMask,
+      boolean enforceUtf8,
+      EnumVerifier enumVerifier) {
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    checkNotNull(fieldType, "fieldType");
+    checkNotNull(presenceField, "presenceField");
+    if (presenceField != null && !isExactlyOneBitSet(presenceMask)) {
+      throw new IllegalArgumentException(
+          "presenceMask must have exactly one bit set: " + presenceMask);
+    }
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        fieldType,
+        /* messageClass= */ null,
+        presenceField,
+        presenceMask,
+        /* required= */ true,
+        enforceUtf8,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        /* mapDefaultEntry= */ null,
+        /* enumVerifier= */ enumVerifier,
+        /* cachedSizeField= */ null);
+  }
+
+  public static FieldInfo forMapField(
+      Field field, int fieldNumber, Object mapDefaultEntry, EnumVerifier enumVerifier) {
+    checkNotNull(mapDefaultEntry, "mapDefaultEntry");
+    checkFieldNumber(fieldNumber);
+    checkNotNull(field, "field");
+    return new FieldInfo(
+        field,
+        fieldNumber,
+        FieldType.MAP,
+        /* messageClass= */ null,
+        /* presenceField= */ null,
+        /* presenceMask= */ 0,
+        /* required= */ false,
+        /* enforceUtf8= */ true,
+        /* oneof= */ null,
+        /* oneofStoredType= */ null,
+        mapDefaultEntry,
+        enumVerifier,
+        /* cachedSizeField= */ null);
+  }
+
+  private FieldInfo(
+      Field field,
+      int fieldNumber,
+      FieldType type,
+      Class<?> messageClass,
+      Field presenceField,
+      int presenceMask,
+      boolean required,
+      boolean enforceUtf8,
+      OneofInfo oneof,
+      Class<?> oneofStoredType,
+      Object mapDefaultEntry,
+      EnumVerifier enumVerifier,
+      Field cachedSizeField) {
+    this.field = field;
+    this.type = type;
+    this.messageClass = messageClass;
+    this.fieldNumber = fieldNumber;
+    this.presenceField = presenceField;
+    this.presenceMask = presenceMask;
+    this.required = required;
+    this.enforceUtf8 = enforceUtf8;
+    this.oneof = oneof;
+    this.oneofStoredType = oneofStoredType;
+    this.mapDefaultEntry = mapDefaultEntry;
+    this.enumVerifier = enumVerifier;
+    this.cachedSizeField = cachedSizeField;
+  }
+
+  /** Gets the field number for the field. */
+  public int getFieldNumber() {
+    return fieldNumber;
+  }
+
+  /** Gets the subject {@link Field} of this descriptor. */
+  public Field getField() {
+    return field;
+  }
+
+  /** Gets the type information for the field. */
+  public FieldType getType() {
+    return type;
+  }
+
+  /** Gets the oneof for which this field is a member, or {@code null} if not part of a oneof. */
+  public OneofInfo getOneof() {
+    return oneof;
+  }
+
+  /**
+   * Gets the actual type stored in the oneof value by this field. Since the oneof value is an
+   * {@link Object}, primitives will store their boxed type. For non-oneof fields, this will always
+   * be {@code null}.
+   */
+  public Class<?> getOneofStoredType() {
+    return oneofStoredType;
+  }
+
+  /** Gets the {@code EnumVerifier} if the field is an enum field. */
+  public EnumVerifier getEnumVerifier() {
+    return enumVerifier;
+  }
+
+  @Override
+  public int compareTo(FieldInfo o) {
+    return fieldNumber - o.fieldNumber;
+  }
+
+  /**
+   * For repeated message fields, returns the message type of the field. For other fields, returns
+   * {@code null}.
+   */
+  public Class<?> getListElementType() {
+    return messageClass;
+  }
+
+  /** Gets the presence bit field. Only valid for unary fields. For lists, returns {@code null}. */
+  public Field getPresenceField() {
+    return presenceField;
+  }
+
+  public Object getMapDefaultEntry() {
+    return mapDefaultEntry;
+  }
+
+  /**
+   * If {@link #getPresenceField()} is non-{@code null}, returns the mask used to identify the
+   * presence bit for this field in the message.
+   */
+  public int getPresenceMask() {
+    return presenceMask;
+  }
+
+  /** Whether this is a required field. */
+  public boolean isRequired() {
+    return required;
+  }
+
+  /**
+   * Whether a UTF-8 should be enforced on string fields. Only applies to strings and string lists.
+   */
+  public boolean isEnforceUtf8() {
+    return enforceUtf8;
+  }
+
+  public Field getCachedSizeField() {
+    return cachedSizeField;
+  }
+
+  /**
+   * For singular or repeated message fields, returns the message type. For other fields, returns
+   * {@code null}.
+   */
+  public Class<?> getMessageFieldClass() {
+    switch (type) {
+      case MESSAGE:
+      case GROUP:
+        return field != null ? field.getType() : oneofStoredType;
+      case MESSAGE_LIST:
+      case GROUP_LIST:
+        return messageClass;
+      default:
+        return null;
+    }
+  }
+
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /** A builder for {@link FieldInfo} instances. */
+  public static final class Builder {
+    private Field field;
+    private FieldType type;
+    private int fieldNumber;
+    private Field presenceField;
+    private int presenceMask;
+    private boolean required;
+    private boolean enforceUtf8;
+    private OneofInfo oneof;
+    private Class<?> oneofStoredType;
+    private Object mapDefaultEntry;
+    private EnumVerifier enumVerifier;
+    private Field cachedSizeField;
+
+    private Builder() {}
+
+    /**
+     * Specifies the actual field on the message represented by this field. This should not be
+     * called for oneof member fields.
+     */
+    public Builder withField(Field field) {
+      if (oneof != null) {
+        throw new IllegalStateException("Cannot set field when building a oneof.");
+      }
+      this.field = field;
+      return this;
+    }
+
+    /** Specifies the type of this field. */
+    public Builder withType(FieldType type) {
+      this.type = type;
+      return this;
+    }
+
+    /** Specifies the unique field number for this field within the message. */
+    public Builder withFieldNumber(int fieldNumber) {
+      this.fieldNumber = fieldNumber;
+      return this;
+    }
+
+    /** Specifies proto2 presence information. This should not be called for oneof fields. */
+    public Builder withPresence(Field presenceField, int presenceMask) {
+      this.presenceField = checkNotNull(presenceField, "presenceField");
+      this.presenceMask = presenceMask;
+      return this;
+    }
+
+    /**
+     * Sets the information for building a oneof member field. This is incompatible with {@link
+     * #withField(Field)} and {@link #withPresence(Field, int)}.
+     *
+     * @param oneof the oneof for which this field is associated.
+     * @param oneofStoredType the actual type stored in the oneof value for this field. Since the
+     *     oneof value is an {@link Object}, primitives will store their boxed type.
+     */
+    public Builder withOneof(OneofInfo oneof, Class<?> oneofStoredType) {
+      if (field != null || presenceField != null) {
+        throw new IllegalStateException(
+            "Cannot set oneof when field or presenceField have been provided");
+      }
+      this.oneof = oneof;
+      this.oneofStoredType = oneofStoredType;
+      return this;
+    }
+
+    public Builder withRequired(boolean required) {
+      this.required = required;
+      return this;
+    }
+
+    public Builder withMapDefaultEntry(Object mapDefaultEntry) {
+      this.mapDefaultEntry = mapDefaultEntry;
+      return this;
+    }
+
+    public Builder withEnforceUtf8(boolean enforceUtf8) {
+      this.enforceUtf8 = enforceUtf8;
+      return this;
+    }
+
+    public Builder withEnumVerifier(EnumVerifier enumVerifier) {
+      this.enumVerifier = enumVerifier;
+      return this;
+    }
+
+    public Builder withCachedSizeField(Field cachedSizeField) {
+      this.cachedSizeField = cachedSizeField;
+      return this;
+    }
+
+    public FieldInfo build() {
+      if (oneof != null) {
+        return forOneofMemberField(
+            fieldNumber, type, oneof, oneofStoredType, enforceUtf8, enumVerifier);
+      }
+      if (mapDefaultEntry != null) {
+        return forMapField(field, fieldNumber, mapDefaultEntry, enumVerifier);
+      }
+      if (presenceField != null) {
+        if (required) {
+          return forProto2RequiredField(
+              field, fieldNumber, type, presenceField, presenceMask, enforceUtf8, enumVerifier);
+        } else {
+          return forProto2OptionalField(
+              field, fieldNumber, type, presenceField, presenceMask, enforceUtf8, enumVerifier);
+        }
+      }
+      if (enumVerifier != null) {
+        if (cachedSizeField == null) {
+          return forFieldWithEnumVerifier(field, fieldNumber, type, enumVerifier);
+        } else {
+          return forPackedFieldWithEnumVerifier(
+              field, fieldNumber, type, enumVerifier, cachedSizeField);
+        }
+      } else {
+        if (cachedSizeField == null) {
+          return forField(field, fieldNumber, type, enforceUtf8);
+        } else {
+          return forPackedField(field, fieldNumber, type, cachedSizeField);
+        }
+      }
+    }
+  }
+
+  private static boolean isExactlyOneBitSet(int value) {
+    return value != 0 && (value & (value - 1)) == 0;
+  }
+}

+ 11 - 0
java/core/src/main/java/com/google/protobuf/FieldSet.java

@@ -218,6 +218,17 @@ final class FieldSet<
     return fields.entrySet().iterator();
     return fields.entrySet().iterator();
   }
   }
 
 
+  /**
+   * Get an iterator over the fields in the map in descending (i.e. reverse) order. This iterator
+   * should not be leaked out of the protobuf library as it is not protected from mutation when
+   * fields is not immutable.
+   */
+  Iterator<Map.Entry<FieldDescriptorType, Object>> descendingIterator() {
+    if (hasLazyField) {
+      return new LazyIterator<FieldDescriptorType>(fields.descendingEntrySet().iterator());
+    }
+    return fields.descendingEntrySet().iterator();
+  }
 
 
   /** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */
   /** Useful for implementing {@link Message#hasField(Descriptors.FieldDescriptor)}. */
   public boolean hasField(final FieldDescriptorType descriptor) {
   public boolean hasField(final FieldDescriptorType descriptor) {

+ 346 - 0
java/core/src/main/java/com/google/protobuf/FieldType.java

@@ -0,0 +1,346 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+
+/** Enumeration identifying all relevant type information for a protobuf field. */
+@ExperimentalApi
+public enum FieldType {
+  DOUBLE(0, Collection.SCALAR, JavaType.DOUBLE),
+  FLOAT(1, Collection.SCALAR, JavaType.FLOAT),
+  INT64(2, Collection.SCALAR, JavaType.LONG),
+  UINT64(3, Collection.SCALAR, JavaType.LONG),
+  INT32(4, Collection.SCALAR, JavaType.INT),
+  FIXED64(5, Collection.SCALAR, JavaType.LONG),
+  FIXED32(6, Collection.SCALAR, JavaType.INT),
+  BOOL(7, Collection.SCALAR, JavaType.BOOLEAN),
+  STRING(8, Collection.SCALAR, JavaType.STRING),
+  MESSAGE(9, Collection.SCALAR, JavaType.MESSAGE),
+  BYTES(10, Collection.SCALAR, JavaType.BYTE_STRING),
+  UINT32(11, Collection.SCALAR, JavaType.INT),
+  ENUM(12, Collection.SCALAR, JavaType.ENUM),
+  SFIXED32(13, Collection.SCALAR, JavaType.INT),
+  SFIXED64(14, Collection.SCALAR, JavaType.LONG),
+  SINT32(15, Collection.SCALAR, JavaType.INT),
+  SINT64(16, Collection.SCALAR, JavaType.LONG),
+  GROUP(17, Collection.SCALAR, JavaType.MESSAGE),
+  DOUBLE_LIST(18, Collection.VECTOR, JavaType.DOUBLE),
+  FLOAT_LIST(19, Collection.VECTOR, JavaType.FLOAT),
+  INT64_LIST(20, Collection.VECTOR, JavaType.LONG),
+  UINT64_LIST(21, Collection.VECTOR, JavaType.LONG),
+  INT32_LIST(22, Collection.VECTOR, JavaType.INT),
+  FIXED64_LIST(23, Collection.VECTOR, JavaType.LONG),
+  FIXED32_LIST(24, Collection.VECTOR, JavaType.INT),
+  BOOL_LIST(25, Collection.VECTOR, JavaType.BOOLEAN),
+  STRING_LIST(26, Collection.VECTOR, JavaType.STRING),
+  MESSAGE_LIST(27, Collection.VECTOR, JavaType.MESSAGE),
+  BYTES_LIST(28, Collection.VECTOR, JavaType.BYTE_STRING),
+  UINT32_LIST(29, Collection.VECTOR, JavaType.INT),
+  ENUM_LIST(30, Collection.VECTOR, JavaType.ENUM),
+  SFIXED32_LIST(31, Collection.VECTOR, JavaType.INT),
+  SFIXED64_LIST(32, Collection.VECTOR, JavaType.LONG),
+  SINT32_LIST(33, Collection.VECTOR, JavaType.INT),
+  SINT64_LIST(34, Collection.VECTOR, JavaType.LONG),
+  DOUBLE_LIST_PACKED(35, Collection.PACKED_VECTOR, JavaType.DOUBLE),
+  FLOAT_LIST_PACKED(36, Collection.PACKED_VECTOR, JavaType.FLOAT),
+  INT64_LIST_PACKED(37, Collection.PACKED_VECTOR, JavaType.LONG),
+  UINT64_LIST_PACKED(38, Collection.PACKED_VECTOR, JavaType.LONG),
+  INT32_LIST_PACKED(39, Collection.PACKED_VECTOR, JavaType.INT),
+  FIXED64_LIST_PACKED(40, Collection.PACKED_VECTOR, JavaType.LONG),
+  FIXED32_LIST_PACKED(41, Collection.PACKED_VECTOR, JavaType.INT),
+  BOOL_LIST_PACKED(42, Collection.PACKED_VECTOR, JavaType.BOOLEAN),
+  UINT32_LIST_PACKED(43, Collection.PACKED_VECTOR, JavaType.INT),
+  ENUM_LIST_PACKED(44, Collection.PACKED_VECTOR, JavaType.ENUM),
+  SFIXED32_LIST_PACKED(45, Collection.PACKED_VECTOR, JavaType.INT),
+  SFIXED64_LIST_PACKED(46, Collection.PACKED_VECTOR, JavaType.LONG),
+  SINT32_LIST_PACKED(47, Collection.PACKED_VECTOR, JavaType.INT),
+  SINT64_LIST_PACKED(48, Collection.PACKED_VECTOR, JavaType.LONG),
+  GROUP_LIST(49, Collection.VECTOR, JavaType.MESSAGE),
+  MAP(50, Collection.MAP, JavaType.VOID);
+
+  private final JavaType javaType;
+  private final int id;
+  private final Collection collection;
+  private final Class<?> elementType;
+  private final boolean primitiveScalar;
+
+  FieldType(int id, Collection collection, JavaType javaType) {
+    this.id = id;
+    this.collection = collection;
+    this.javaType = javaType;
+
+    switch (collection) {
+      case MAP:
+        elementType = javaType.getBoxedType();
+        break;
+      case VECTOR:
+        elementType = javaType.getBoxedType();
+        break;
+      case SCALAR:
+      default:
+        elementType = null;
+        break;
+    }
+
+    boolean primitiveScalar = false;
+    if (collection == Collection.SCALAR) {
+      switch (javaType) {
+        case BYTE_STRING:
+        case MESSAGE:
+        case STRING:
+          break;
+        default:
+          primitiveScalar = true;
+          break;
+      }
+    }
+    this.primitiveScalar = primitiveScalar;
+  }
+
+  /** A reliable unique identifier for this type. */
+  public int id() {
+    return id;
+  }
+
+  /**
+   * Gets the {@link JavaType} for this field. For lists, this identifies the type of the elements
+   * contained within the list.
+   */
+  public JavaType getJavaType() {
+    return javaType;
+  }
+
+  /** Indicates whether a list field should be represented on the wire in packed form. */
+  public boolean isPacked() {
+    return Collection.PACKED_VECTOR.equals(collection);
+  }
+
+  /**
+   * Indicates whether this field type represents a primitive scalar value. If this is {@code true},
+   * then {@link #isScalar()} will also be {@code true}.
+   */
+  public boolean isPrimitiveScalar() {
+    return primitiveScalar;
+  }
+
+  /** Indicates whether this field type represents a scalar value. */
+  public boolean isScalar() {
+    return collection == Collection.SCALAR;
+  }
+
+  /** Indicates whether this field represents a list of values. */
+  public boolean isList() {
+    return collection.isList();
+  }
+
+  /** Indicates whether this field represents a map. */
+  public boolean isMap() {
+    return collection == Collection.MAP;
+  }
+
+  /** Indicates whether or not this {@link FieldType} can be applied to the given {@link Field}. */
+  public boolean isValidForField(Field field) {
+    if (Collection.VECTOR.equals(collection)) {
+      return isValidForList(field);
+    } else {
+      return javaType.getType().isAssignableFrom(field.getType());
+    }
+  }
+
+  private boolean isValidForList(Field field) {
+    Class<?> clazz = field.getType();
+    if (!javaType.getType().isAssignableFrom(clazz)) {
+      // The field isn't a List type.
+      return false;
+    }
+    Type[] types = EMPTY_TYPES;
+    Type genericType = field.getGenericType();
+    if (genericType instanceof ParameterizedType) {
+      types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
+    }
+    Type listParameter = getListParameter(clazz, types);
+    if (!(listParameter instanceof Class)) {
+      // It's a wildcard, we should allow anything in the list.
+      return true;
+    }
+    return elementType.isAssignableFrom((Class<?>) listParameter);
+  }
+
+  /**
+   * Looks up the appropriate {@link FieldType} by it's identifier.
+   *
+   * @return the {@link FieldType} or {@code null} if not found.
+   */
+  /* @Nullable */
+  public static FieldType forId(int id) {
+    if (id < 0 || id >= VALUES.length) {
+      return null;
+    }
+    return VALUES[id];
+  }
+
+  private static final FieldType[] VALUES;
+  private static final Type[] EMPTY_TYPES = new Type[0];
+
+  static {
+    FieldType[] values = values();
+    VALUES = new FieldType[values.length];
+    for (FieldType type : values) {
+      VALUES[type.id] = type;
+    }
+  }
+
+  /**
+   * Given a class, finds a generic super class or interface that extends {@link List}.
+   *
+   * @return the generic super class/interface, or {@code null} if not found.
+   */
+  /* @Nullable */
+  private static Type getGenericSuperList(Class<?> clazz) {
+    // First look at interfaces.
+    Type[] genericInterfaces = clazz.getGenericInterfaces();
+    for (Type genericInterface : genericInterfaces) {
+      if (genericInterface instanceof ParameterizedType) {
+        ParameterizedType parameterizedType = (ParameterizedType) genericInterface;
+        Class<?> rawType = (Class<?>) parameterizedType.getRawType();
+        if (List.class.isAssignableFrom(rawType)) {
+          return genericInterface;
+        }
+      }
+    }
+
+    // Try the subclass
+    Type type = clazz.getGenericSuperclass();
+    if (type instanceof ParameterizedType) {
+      ParameterizedType parameterizedType = (ParameterizedType) type;
+      Class<?> rawType = (Class<?>) parameterizedType.getRawType();
+      if (List.class.isAssignableFrom(rawType)) {
+        return type;
+      }
+    }
+
+    // No super class/interface extends List.
+    return null;
+  }
+
+  /**
+   * Inspects the inheritance hierarchy for the given class and finds the generic type parameter for
+   * {@link List}.
+   *
+   * @param clazz the class to begin the search.
+   * @param realTypes the array of actual type parameters for {@code clazz}. These will be used to
+   *     substitute generic parameters up the inheritance hierarchy. If {@code clazz} does not have
+   *     any generic parameters, this list should be empty.
+   * @return the {@link List} parameter.
+   */
+  private static Type getListParameter(Class<?> clazz, Type[] realTypes) {
+    top:
+    while (clazz != List.class) {
+      // First look at generic subclass and interfaces.
+      Type genericType = getGenericSuperList(clazz);
+      if (genericType instanceof ParameterizedType) {
+        // Replace any generic parameters with the real values.
+        ParameterizedType parameterizedType = (ParameterizedType) genericType;
+        Type[] superArgs = parameterizedType.getActualTypeArguments();
+        for (int i = 0; i < superArgs.length; ++i) {
+          Type superArg = superArgs[i];
+          if (superArg instanceof TypeVariable) {
+            // Get the type variables for this class so that we can match them to the variables
+            // used on the super class.
+            TypeVariable<?>[] clazzParams = clazz.getTypeParameters();
+            if (realTypes.length != clazzParams.length) {
+              throw new RuntimeException("Type array mismatch");
+            }
+
+            // Replace the variable parameter with the real type.
+            boolean foundReplacement = false;
+            for (int j = 0; j < clazzParams.length; ++j) {
+              if (superArg == clazzParams[j]) {
+                Type realType = realTypes[j];
+                superArgs[i] = realType;
+                foundReplacement = true;
+                break;
+              }
+            }
+            if (!foundReplacement) {
+              throw new RuntimeException("Unable to find replacement for " + superArg);
+            }
+          }
+        }
+
+        Class<?> parent = (Class<?>) parameterizedType.getRawType();
+
+        realTypes = superArgs;
+        clazz = parent;
+        continue;
+      }
+
+      // None of the parameterized types inherit List. Just continue up the inheritance hierarchy
+      // toward the List interface until we can identify the parameters.
+      realTypes = EMPTY_TYPES;
+      for (Class<?> iface : clazz.getInterfaces()) {
+        if (List.class.isAssignableFrom(iface)) {
+          clazz = iface;
+          continue top;
+        }
+      }
+      clazz = clazz.getSuperclass();
+    }
+
+    if (realTypes.length != 1) {
+      throw new RuntimeException("Unable to identify parameter type for List<T>");
+    }
+    return realTypes[0];
+  }
+
+  enum Collection {
+    SCALAR(false),
+    VECTOR(true),
+    PACKED_VECTOR(true),
+    MAP(false);
+
+    private final boolean isList;
+
+    Collection(boolean isList) {
+      this.isList = isList;
+    }
+
+    /** @return the isList */
+    public boolean isList() {
+      return isList;
+    }
+  }
+}

+ 65 - 0
java/core/src/main/java/com/google/protobuf/GeneratedMessageInfoFactory.java

@@ -0,0 +1,65 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** A factory for message info that is generated into the message itself. */
+@ExperimentalApi
+class GeneratedMessageInfoFactory implements MessageInfoFactory {
+
+  private static final GeneratedMessageInfoFactory instance = new GeneratedMessageInfoFactory();
+
+  // Disallow construction - it's a singleton.
+  private GeneratedMessageInfoFactory() {}
+
+  public static GeneratedMessageInfoFactory getInstance() {
+    return instance;
+  }
+
+  @Override
+  public boolean isSupported(Class<?> messageType) {
+    return GeneratedMessageLite.class.isAssignableFrom(messageType);
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> messageType) {
+    if (!GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      throw new IllegalArgumentException("Unsupported message type: " + messageType.getName());
+    }
+
+    try {
+      return (MessageInfo) GeneratedMessageLite.getDefaultInstance(
+          messageType.asSubclass(GeneratedMessageLite.class))
+          .buildMessageInfo();
+    } catch (Exception e) {
+      throw new RuntimeException("Unable to get message info for " + messageType.getName(), e);
+    }
+  }
+}

+ 102 - 893
java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java

@@ -61,12 +61,6 @@ public abstract class GeneratedMessageLite<
         MessageType extends GeneratedMessageLite<MessageType, BuilderType>,
         MessageType extends GeneratedMessageLite<MessageType, BuilderType>,
         BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>>
         BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>>
     extends AbstractMessageLite<MessageType, BuilderType> {
     extends AbstractMessageLite<MessageType, BuilderType> {
-  // BEGIN REGULAR
-  static final boolean ENABLE_EXPERIMENTAL_RUNTIME_AT_BUILD_TIME = false;
-  // END REGULAR
-  // BEGIN EXPERIMENTAL
-  // static final boolean ENABLE_EXPERIMENTAL_RUNTIME_AT_BUILD_TIME = true;
-  // END EXPERIMENTAL
 
 
   /** For use by generated code only. Lazily initialized to reduce allocations. */
   /** For use by generated code only. Lazily initialized to reduce allocations. */
   protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance();
   protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance();
@@ -115,32 +109,10 @@ public abstract class GeneratedMessageLite<
     if (memoizedHashCode != 0) {
     if (memoizedHashCode != 0) {
       return memoizedHashCode;
       return memoizedHashCode;
     }
     }
-    // BEGIN EXPERIMENTAL
-    // memoizedHashCode = Protobuf.getInstance().schemaFor(this).hashCode(this);
-    // return memoizedHashCode;
-    // END EXPERIMENTAL
-    // BEGIN REGULAR
-    HashCodeVisitor visitor = new HashCodeVisitor();
-    visit(visitor, (MessageType) this);
-    memoizedHashCode = visitor.hashCode;
+    memoizedHashCode = Protobuf.getInstance().schemaFor(this).hashCode(this);
     return memoizedHashCode;
     return memoizedHashCode;
-    // END REGULAR
   }
   }
 
 
-  // BEGIN REGULAR
-  @SuppressWarnings("unchecked") // Guaranteed by runtime
-  int hashCode(HashCodeVisitor visitor) {
-    if (memoizedHashCode == 0) {
-      int inProgressHashCode = visitor.hashCode;
-      visitor.hashCode = 0;
-      visit(visitor, (MessageType) this);
-      memoizedHashCode = visitor.hashCode;
-      visitor.hashCode = inProgressHashCode;
-    }
-    return memoizedHashCode;
-  }
-  // END REGULAR
-
   @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime
   @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime
   @Override
   @Override
   public boolean equals(Object other) {
   public boolean equals(Object other) {
@@ -152,36 +124,8 @@ public abstract class GeneratedMessageLite<
       return false;
       return false;
     }
     }
 
 
-    // BEGIN EXPERIMENTAL
-    // return Protobuf.getInstance().schemaFor(this).equals(this, (MessageType) other);
-    // END EXPERIMENTAL
-    // BEGIN REGULAR
-
-    try {
-      visit(EqualsVisitor.INSTANCE, (MessageType) other);
-    } catch (EqualsVisitor.NotEqualsException e) {
-      return false;
-    }
-    return true;
-    // END REGULAR
-  }
-
-  // BEGIN REGULAR
-  /** Same as {@link #equals(Object)} but throws {@code NotEqualsException}. */
-  @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime
-  boolean equals(EqualsVisitor visitor, MessageLite other) {
-    if (this == other) {
-      return true;
-    }
-
-    if (!getDefaultInstanceForType().getClass().isInstance(other)) {
-      return false;
-    }
-
-    visit(visitor, (MessageType) other);
-    return true;
+    return Protobuf.getInstance().schemaFor(this).equals(this, (MessageType) other);
   }
   }
-  // END REGULAR
 
 
   // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as
   // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as
   // mutable during the parsing constructor and immutable after. This allows us to avoid
   // mutable during the parsing constructor and immutable after. This allows us to avoid
@@ -223,13 +167,7 @@ public abstract class GeneratedMessageLite<
 
 
   /** Called by subclasses to complete parsing. For use by generated code only. */
   /** Called by subclasses to complete parsing. For use by generated code only. */
   protected void makeImmutable() {
   protected void makeImmutable() {
-    // BEGIN REGULAR
-    dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE);
-    unknownFields.makeImmutable();
-    // END REGULAR
-    // BEGIN EXPERIMENTAL
-    // Protobuf.getInstance().schemaFor(this).makeImmutable(this);
-    // END EXPERIMENTAL
+    Protobuf.getInstance().schemaFor(this).makeImmutable(this);
   }
   }
 
 
   protected final <
   protected final <
@@ -266,17 +204,12 @@ public abstract class GeneratedMessageLite<
    * <p>For use by generated code only.
    * <p>For use by generated code only.
    */
    */
   public static enum MethodToInvoke {
   public static enum MethodToInvoke {
-    // BEGIN REGULAR
-    IS_INITIALIZED,
-    VISIT,
-    MERGE_FROM_STREAM,
-    MAKE_IMMUTABLE,
-    // END REGULAR
     // Rely on/modify instance state
     // Rely on/modify instance state
     GET_MEMOIZED_IS_INITIALIZED,
     GET_MEMOIZED_IS_INITIALIZED,
     SET_MEMOIZED_IS_INITIALIZED,
     SET_MEMOIZED_IS_INITIALIZED,
 
 
     // Rely on static state
     // Rely on static state
+    BUILD_MESSAGE_INFO,
     NEW_MUTABLE_INSTANCE,
     NEW_MUTABLE_INSTANCE,
     NEW_BUILDER,
     NEW_BUILDER,
     GET_DEFAULT_INSTANCE,
     GET_DEFAULT_INSTANCE,
@@ -290,10 +223,6 @@ public abstract class GeneratedMessageLite<
    * count.
    * count.
    *
    *
    * <ul>
    * <ul>
-   *   <li>{@code MERGE_FROM_STREAM} is parameterized with an {@link CodedInputStream} and {@link
-   *       ExtensionRegistryLite}. It consumes the input stream, parsing the contents into the
-   *       returned protocol buffer. If parsing throws an {@link InvalidProtocolBufferException},
-   *       the implementation wraps it in a RuntimeException.
    *   <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer that has not yet been
    *   <li>{@code NEW_INSTANCE} returns a new instance of the protocol buffer that has not yet been
    *       made immutable. See {@code MAKE_IMMUTABLE}.
    *       made immutable. See {@code MAKE_IMMUTABLE}.
    *   <li>{@code IS_INITIALIZED} returns {@code null} for false and the default instance for true.
    *   <li>{@code IS_INITIALIZED} returns {@code null} for false and the default instance for true.
@@ -303,9 +232,6 @@ public abstract class GeneratedMessageLite<
    *   <li>{@code SET_MEMOIZED_IS_INITIALIZED} sets the memoized {@code isInitilaized} byte value to
    *   <li>{@code SET_MEMOIZED_IS_INITIALIZED} sets the memoized {@code isInitilaized} byte value to
    *       1 if the first parameter is not null, or to 0 if the first parameter is null.
    *       1 if the first parameter is not null, or to 0 if the first parameter is null.
    *   <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance.
    *   <li>{@code NEW_BUILDER} returns a {@code BuilderType} instance.
-   *   <li>{@code VISIT} is parameterized with a {@code Visitor} and a {@code MessageType} and
-   *       recursively iterates through the fields side by side between this and the instance.
-   *   <li>{@code MAKE_IMMUTABLE} sets all internal fields to an immutable state.
    * </ul>
    * </ul>
    *
    *
    * This method, plus the implementation of the Builder, enables the Builder class to be proguarded
    * This method, plus the implementation of the Builder, enables the Builder class to be proguarded
@@ -325,13 +251,6 @@ public abstract class GeneratedMessageLite<
     return dynamicMethod(method, null, null);
     return dynamicMethod(method, null, null);
   }
   }
 
 
-  // BEGIN REGULAR
-  void visit(Visitor visitor, MessageType other) {
-    dynamicMethod(MethodToInvoke.VISIT, visitor, other);
-    unknownFields = visitor.visitUnknownFields(unknownFields, other.unknownFields);
-  }
-  // END REGULAR
-
   @Override
   @Override
   int getMemoizedSerializedSize() {
   int getMemoizedSerializedSize() {
     return memoizedSerializedSize;
     return memoizedSerializedSize;
@@ -342,7 +261,60 @@ public abstract class GeneratedMessageLite<
     memoizedSerializedSize = size;
     memoizedSerializedSize = size;
   }
   }
 
 
+  public void writeTo(CodedOutputStream output) throws IOException {
+    writeToInternal(output);
+  }
+
+  public int getSerializedSize() {
+    if (memoizedSerializedSize == -1) {
+      memoizedSerializedSize = getSerializedSizeInternal();
+    }
+    return memoizedSerializedSize;
+  }
+
+  /** Constructs a {@link MessageInfo} for this message type. */
+  Object buildMessageInfo() throws Exception {
+    return dynamicMethod(MethodToInvoke.BUILD_MESSAGE_INFO);
+  }
+
+  private static Map<Object, GeneratedMessageLite<?, ?>> defaultInstanceMap =
+      new ConcurrentHashMap<Object, GeneratedMessageLite<?, ?>>();
+
+  @SuppressWarnings("unchecked")
+  static <T extends GeneratedMessageLite<?, ?>> T getDefaultInstance(Class<T> clazz) {
+    T result = (T) defaultInstanceMap.get(clazz);
+    if (result == null) {
+      // Foo.class does not initialize the class so we need to force the initialization in order to
+      // get the default instance registered.
+      try {
+        Class.forName(clazz.getName(), true, clazz.getClassLoader());
+      } catch (ClassNotFoundException e) {
+        throw new IllegalStateException("Class initialization cannot fail.", e);
+      }
+      result = (T) defaultInstanceMap.get(clazz);
+    }
+    if (result == null) {
+      // On some Samsung devices, this still doesn't return a valid value for some reason. We add a
+      // reflective fallback to keep the device running. See b/114675342.
+      result = (T) UnsafeUtil.allocateInstance(clazz).getDefaultInstanceForType();
+      // A sanity check to ensure that <clinit> was actually invoked.
+      if (result == null) {
+        throw new IllegalStateException();
+      }
+      defaultInstanceMap.put(clazz, result);
+    }
+    return result;
+  }
+
+  protected static <T extends GeneratedMessageLite<?, ?>> void registerDefaultInstance(
+      Class<T> clazz, T defaultInstance) {
+    defaultInstanceMap.put(clazz, defaultInstance);
+  }
 
 
+  protected static Object newMessageInfo(
+      MessageLite defaultInstance, String info, Object[] objects) {
+    return new RawMessageInfo(defaultInstance, info, objects);
+  }
 
 
   /**
   /**
    * Merge some unknown fields into the {@link UnknownFieldSetLite} for this message.
    * Merge some unknown fields into the {@link UnknownFieldSetLite} for this message.
@@ -437,12 +409,7 @@ public abstract class GeneratedMessageLite<
     }
     }
 
 
     private void mergeFromInstance(MessageType dest, MessageType src) {
     private void mergeFromInstance(MessageType dest, MessageType src) {
-      // BEGIN EXPERIMENTAL
-      // Protobuf.getInstance().schemaFor(dest).mergeFrom(dest, src);
-      // END EXPERIMENTAL
-      // BEGIN REGULAR
-      dest.visit(MergeFromVisitor.INSTANCE, src);
-      // END REGULAR
+      Protobuf.getInstance().schemaFor(dest).mergeFrom(dest, src);
     }
     }
 
 
     @Override
     @Override
@@ -454,36 +421,26 @@ public abstract class GeneratedMessageLite<
     public BuilderType mergeFrom(
     public BuilderType mergeFrom(
         byte[] input, int offset, int length, ExtensionRegistryLite extensionRegistry)
         byte[] input, int offset, int length, ExtensionRegistryLite extensionRegistry)
         throws InvalidProtocolBufferException {
         throws InvalidProtocolBufferException {
-      // BEGIN REGULAR
-      return super.mergeFrom(input, offset, length, extensionRegistry);
-      // END REGULAR
-      // BEGIN EXPERIMENTAL
-      // copyOnWrite();
-      // try {
-      //   Protobuf.getInstance().schemaFor(instance).mergeFrom(
-      //       instance, input, offset, offset + length,
-      //       new ArrayDecoders.Registers(extensionRegistry));
-      // } catch (InvalidProtocolBufferException e) {
-      //   throw e;
-      // } catch (IndexOutOfBoundsException e) {
-      //   throw InvalidProtocolBufferException.truncatedMessage();
-      // } catch (IOException e) {
-      //   throw new RuntimeException("Reading from byte array should not throw IOException.", e);
-      // }
-      // return (BuilderType) this;
-      // END EXPERIMENTAL
+      copyOnWrite();
+      try {
+        Protobuf.getInstance().schemaFor(instance).mergeFrom(
+            instance, input, offset, offset + length,
+            new ArrayDecoders.Registers(extensionRegistry));
+      } catch (InvalidProtocolBufferException e) {
+        throw e;
+      } catch (IndexOutOfBoundsException e) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      } catch (IOException e) {
+        throw new RuntimeException("Reading from byte array should not throw IOException.", e);
+      }
+      return (BuilderType) this;
     }
     }
 
 
     @Override
     @Override
     public BuilderType mergeFrom(
     public BuilderType mergeFrom(
         byte[] input, int offset, int length)
         byte[] input, int offset, int length)
         throws InvalidProtocolBufferException {
         throws InvalidProtocolBufferException {
-      // BEGIN REGULAR
-      return super.mergeFrom(input, offset, length);
-      // END REGULAR
-      // BEGIN EXPERIMENTAL
-      // return mergeFrom(input, offset, length, ExtensionRegistryLite.getEmptyRegistry());
-      // END EXPERIMENTAL
+      return mergeFrom(input, offset, length, ExtensionRegistryLite.getEmptyRegistry());
     }
     }
 
 
     @Override
     @Override
@@ -493,15 +450,10 @@ public abstract class GeneratedMessageLite<
         throws IOException {
         throws IOException {
       copyOnWrite();
       copyOnWrite();
       try {
       try {
-        // BEGIN REGULAR
-        instance.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry);
-        // END REGULAR
-        // BEGIN EXPERIMENTAL
-        // // TODO(yilunchong): Try to make input with type CodedInpuStream.ArrayDecoder use
-        // // fast path.
-        // Protobuf.getInstance().schemaFor(instance).mergeFrom(
-        //     instance, CodedInputStreamReader.forCodedInput(input), extensionRegistry);
-        // END EXPERIMENTAL
+        // TODO(yilunchong): Try to make input with type CodedInpuStream.ArrayDecoder use
+        // fast path.
+        Protobuf.getInstance().schemaFor(instance).mergeFrom(
+            instance, CodedInputStreamReader.forCodedInput(input), extensionRegistry);
       } catch (RuntimeException e) {
       } catch (RuntimeException e) {
         if (e.getCause() instanceof IOException) {
         if (e.getCause() instanceof IOException) {
           throw (IOException) e.getCause();
           throw (IOException) e.getCause();
@@ -553,14 +505,6 @@ public abstract class GeneratedMessageLite<
       extensions.mergeFrom(((ExtendableMessage) other).extensions);
       extensions.mergeFrom(((ExtendableMessage) other).extensions);
     }
     }
 
 
-    // BEGIN REGULAR
-    @Override
-    final void visit(Visitor visitor, MessageType other) {
-      super.visit(visitor, other);
-      extensions = visitor.visitExtensions(extensions, other.extensions);
-    }
-    // END REGULAR
-
     /**
     /**
      * Parse an unknown field or an extension. For use by generated code only.
      * Parse an unknown field or an extension. For use by generated code only.
      *
      *
@@ -898,14 +842,6 @@ public abstract class GeneratedMessageLite<
       return extensions.isInitialized();
       return extensions.isInitialized();
     }
     }
 
 
-    @Override
-    protected final void makeImmutable() {
-      super.makeImmutable();
-      // BEGIN REGULAR
-      extensions.makeImmutable();
-      // END REGULAR
-    }
-
     /**
     /**
      * Used by subclasses to serialize extensions. Extension ranges may be interleaved with field
      * Used by subclasses to serialize extensions. Extension ranges may be interleaved with field
      * numbers, but we must write them in canonical (sorted by field number) order. ExtensionWriter
      * numbers, but we must write them in canonical (sorted by field number) order. ExtensionWriter
@@ -1479,13 +1415,7 @@ public abstract class GeneratedMessageLite<
     if (memoizedIsInitialized == 0) {
     if (memoizedIsInitialized == 0) {
       return false;
       return false;
     }
     }
-    // BEGIN EXPERIMENTAL
-    // boolean isInitialized = Protobuf.getInstance().schemaFor(message).isInitialized(message);
-    // END EXPERIMENTAL
-    // BEGIN REGULAR
-    boolean isInitialized =
-        message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, Boolean.FALSE) != null;
-    // END REGULAR
+    boolean isInitialized = Protobuf.getInstance().schemaFor(message).isInitialized(message);
     if (shouldMemoize) {
     if (shouldMemoize) {
       message.dynamicMethod(
       message.dynamicMethod(
           MethodToInvoke.SET_MEMOIZED_IS_INITIALIZED, isInitialized ? message : null);
           MethodToInvoke.SET_MEMOIZED_IS_INITIALIZED, isInitialized ? message : null);
@@ -1593,23 +1523,16 @@ public abstract class GeneratedMessageLite<
     @SuppressWarnings("unchecked") // Guaranteed by protoc
     @SuppressWarnings("unchecked") // Guaranteed by protoc
     T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
     T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
     try {
     try {
-      // BEGIN REGULAR
-      result.dynamicMethod(MethodToInvoke.MERGE_FROM_STREAM, input, extensionRegistry);
-      // END REGULAR
-      // BEGIN EXPERIMENTAL
-      // // TODO(yilunchong): Try to make input with type CodedInpuStream.ArrayDecoder use
-      // // fast path.
-      // Protobuf.getInstance().schemaFor(result).mergeFrom(
-      //     result, CodedInputStreamReader.forCodedInput(input), extensionRegistry);
-      // END EXPERIMENTAL
+      // TODO(yilunchong): Try to make input with type CodedInpuStream.ArrayDecoder use
+      // fast path.
+      Protobuf.getInstance().schemaFor(result).mergeFrom(
+          result, CodedInputStreamReader.forCodedInput(input), extensionRegistry);
       result.makeImmutable();
       result.makeImmutable();
-      // BEGIN EXPERIMENTAL
-      // } catch (IOException e) {
-      //   if (e.getCause() instanceof InvalidProtocolBufferException) {
-      //     throw (InvalidProtocolBufferException) e.getCause();
-      //   }
-      //   throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result);
-      // END EXPERIMENTAL
+    } catch (IOException e) {
+      if (e.getCause() instanceof InvalidProtocolBufferException) {
+        throw (InvalidProtocolBufferException) e.getCause();
+      }
+      throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result);
     } catch (RuntimeException e) {
     } catch (RuntimeException e) {
       if (e.getCause() instanceof InvalidProtocolBufferException) {
       if (e.getCause() instanceof InvalidProtocolBufferException) {
         throw (InvalidProtocolBufferException) e.getCause();
         throw (InvalidProtocolBufferException) e.getCause();
@@ -1623,42 +1546,25 @@ public abstract class GeneratedMessageLite<
   static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
   static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
       T instance, byte[] input, int offset, int length, ExtensionRegistryLite extensionRegistry)
       T instance, byte[] input, int offset, int length, ExtensionRegistryLite extensionRegistry)
       throws InvalidProtocolBufferException {
       throws InvalidProtocolBufferException {
-    // BEGIN REGULAR
-    T message;
+    @SuppressWarnings("unchecked") // Guaranteed by protoc
+    T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
     try {
     try {
-      CodedInputStream cis = CodedInputStream.newInstance(input, offset, length);
-      message = parsePartialFrom(instance, cis, extensionRegistry);
-      try {
-        cis.checkLastTagWas(0);
-      } catch (InvalidProtocolBufferException e) {
-        throw e.setUnfinishedMessage(message);
+      Protobuf.getInstance().schemaFor(result).mergeFrom(
+          result, input, offset, offset + length,
+          new ArrayDecoders.Registers(extensionRegistry));
+      result.makeImmutable();
+      if (result.memoizedHashCode != 0) {
+        throw new RuntimeException();
       }
       }
-      return message;
-    } catch (InvalidProtocolBufferException e) {
-      throw e;
+    } catch (IOException e) {
+      if (e.getCause() instanceof InvalidProtocolBufferException) {
+        throw (InvalidProtocolBufferException) e.getCause();
+      }
+      throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result);
+    } catch (IndexOutOfBoundsException e) {
+      throw InvalidProtocolBufferException.truncatedMessage().setUnfinishedMessage(result);
     }
     }
-    // END REGULAR
-    // BEGIN EXPERIMENTAL
-    // @SuppressWarnings("unchecked") // Guaranteed by protoc
-    // T result = (T) instance.dynamicMethod(MethodToInvoke.NEW_MUTABLE_INSTANCE);
-    // try {
-    //   Protobuf.getInstance().schemaFor(result).mergeFrom(
-    //       result, input, offset, offset + length,
-    //       new ArrayDecoders.Registers(extensionRegistry));
-    //   result.makeImmutable();
-    //   if (result.memoizedHashCode != 0) {
-    //     throw new RuntimeException();
-    //   }
-    // } catch (IOException e) {
-    //   if (e.getCause() instanceof InvalidProtocolBufferException) {
-    //     throw (InvalidProtocolBufferException) e.getCause();
-    //   }
-    //   throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(result);
-    // } catch (IndexOutOfBoundsException e) {
-    //   throw InvalidProtocolBufferException.truncatedMessage().setUnfinishedMessage(result);
-    // }
-    // return result;
-    // END EXPERIMENTAL
+    return result;
   }
   }
 
 
   protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
   protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
@@ -1825,701 +1731,4 @@ public abstract class GeneratedMessageLite<
     }
     }
     return message;
     return message;
   }
   }
-
-  // BEGIN REGULAR
-  /**
-   * An abstract visitor that the generated code calls into that we use to implement various
-   * features. Fields that are not members of oneofs are always visited. Members of a oneof are only
-   * visited when they are the set oneof case value on the "other" proto. The visitOneofNotSet
-   * method is invoked if other's oneof case is not set.
-   */
-  protected interface Visitor {
-    boolean visitBoolean(boolean minePresent, boolean mine, boolean otherPresent, boolean other);
-
-    int visitInt(boolean minePresent, int mine, boolean otherPresent, int other);
-
-    double visitDouble(boolean minePresent, double mine, boolean otherPresent, double other);
-
-    float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other);
-
-    long visitLong(boolean minePresent, long mine, boolean otherPresent, long other);
-
-    String visitString(boolean minePresent, String mine, boolean otherPresent, String other);
-
-    ByteString visitByteString(
-        boolean minePresent, ByteString mine, boolean otherPresent, ByteString other);
-
-    Object visitOneofBoolean(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofInt(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofDouble(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofFloat(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofLong(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofString(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofByteString(boolean minePresent, Object mine, Object other);
-
-    Object visitOneofMessage(boolean minePresent, Object mine, Object other);
-
-    void visitOneofNotSet(boolean minePresent);
-
-    /** Message fields use null sentinals. */
-    <T extends MessageLite> T visitMessage(T mine, T other);
-
-    <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other);
-
-    BooleanList visitBooleanList(BooleanList mine, BooleanList other);
-
-    IntList visitIntList(IntList mine, IntList other);
-
-    DoubleList visitDoubleList(DoubleList mine, DoubleList other);
-
-    FloatList visitFloatList(FloatList mine, FloatList other);
-
-    LongList visitLongList(LongList mine, LongList other);
-
-    FieldSet<ExtensionDescriptor> visitExtensions(
-        FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other);
-
-    UnknownFieldSetLite visitUnknownFields(UnknownFieldSetLite mine, UnknownFieldSetLite other);
-
-    <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other);
-  }
-
-  /** Implements equals. Throws a {@link NotEqualsException} when not equal. */
-  static class EqualsVisitor implements Visitor {
-
-    static final class NotEqualsException extends RuntimeException {}
-
-    static final EqualsVisitor INSTANCE = new EqualsVisitor();
-
-    static final NotEqualsException NOT_EQUALS = new NotEqualsException();
-
-    private EqualsVisitor() {}
-
-    @Override
-    public boolean visitBoolean(
-        boolean minePresent, boolean mine, boolean otherPresent, boolean other) {
-      if (minePresent != otherPresent || mine != other) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) {
-      if (minePresent != otherPresent || mine != other) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public double visitDouble(
-        boolean minePresent, double mine, boolean otherPresent, double other) {
-      if (minePresent != otherPresent || mine != other) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) {
-      if (minePresent != otherPresent || mine != other) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) {
-      if (minePresent != otherPresent || mine != other) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public String visitString(
-        boolean minePresent, String mine, boolean otherPresent, String other) {
-      if (minePresent != otherPresent || !mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public ByteString visitByteString(
-        boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) {
-      if (minePresent != otherPresent || !mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofInt(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofDouble(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofFloat(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofLong(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofString(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofByteString(boolean minePresent, Object mine, Object other) {
-      if (minePresent && mine.equals(other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
-      if (minePresent && ((GeneratedMessageLite<?, ?>) mine).equals(this, (MessageLite) other)) {
-        return mine;
-      }
-      throw NOT_EQUALS;
-    }
-
-    @Override
-    public void visitOneofNotSet(boolean minePresent) {
-      if (minePresent) {
-        throw NOT_EQUALS;
-      }
-    }
-
-    @Override
-    public <T extends MessageLite> T visitMessage(T mine, T other) {
-      if (mine == null && other == null) {
-        return null;
-      }
-
-      if (mine == null || other == null) {
-        throw NOT_EQUALS;
-      }
-
-      ((GeneratedMessageLite<?, ?>) mine).equals(this, other);
-
-      return mine;
-    }
-
-    @Override
-    public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public BooleanList visitBooleanList(BooleanList mine, BooleanList other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public IntList visitIntList(IntList mine, IntList other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public DoubleList visitDoubleList(DoubleList mine, DoubleList other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public FloatList visitFloatList(FloatList mine, FloatList other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public LongList visitLongList(LongList mine, LongList other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public FieldSet<ExtensionDescriptor> visitExtensions(
-        FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public UnknownFieldSetLite visitUnknownFields(
-        UnknownFieldSetLite mine, UnknownFieldSetLite other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-
-    @Override
-    public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) {
-      if (!mine.equals(other)) {
-        throw NOT_EQUALS;
-      }
-      return mine;
-    }
-  }
-
-  /** Implements hashCode by accumulating state. */
-  static class HashCodeVisitor implements Visitor {
-
-    // The caller must ensure that the visitor is invoked parameterized with this and this such that
-    // other is this. This is required due to how oneof cases are handled. See the class comment
-    // on Visitor for more information.
-
-    int hashCode = 0;
-
-    @Override
-    public boolean visitBoolean(
-        boolean minePresent, boolean mine, boolean otherPresent, boolean other) {
-      hashCode = (53 * hashCode) + Internal.hashBoolean(mine);
-      return mine;
-    }
-
-    @Override
-    public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) {
-      hashCode = (53 * hashCode) + mine;
-      return mine;
-    }
-
-    @Override
-    public double visitDouble(
-        boolean minePresent, double mine, boolean otherPresent, double other) {
-      hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits(mine));
-      return mine;
-    }
-
-    @Override
-    public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) {
-      hashCode = (53 * hashCode) + Float.floatToIntBits(mine);
-      return mine;
-    }
-
-    @Override
-    public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) {
-      hashCode = (53 * hashCode) + Internal.hashLong(mine);
-      return mine;
-    }
-
-    @Override
-    public String visitString(
-        boolean minePresent, String mine, boolean otherPresent, String other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public ByteString visitByteString(
-        boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + Internal.hashBoolean(((Boolean) mine));
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofInt(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + (Integer) mine;
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofDouble(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + Internal.hashLong(Double.doubleToLongBits((Double) mine));
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofFloat(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + Float.floatToIntBits((Float) mine);
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofLong(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + Internal.hashLong((Long) mine);
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofString(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofByteString(boolean minePresent, Object mine, Object other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
-      return visitMessage((MessageLite) mine, (MessageLite) other);
-    }
-
-    @Override
-    public void visitOneofNotSet(boolean minePresent) {
-      if (minePresent) {
-        throw new IllegalStateException(); // Can't happen if other == this.
-      }
-    }
-
-    @Override
-    public <T extends MessageLite> T visitMessage(T mine, T other) {
-      final int protoHash;
-      if (mine != null) {
-        if (mine instanceof GeneratedMessageLite) {
-          protoHash = ((GeneratedMessageLite) mine).hashCode(this);
-        } else {
-          protoHash = mine.hashCode();
-        }
-      } else {
-        protoHash = 37;
-      }
-      hashCode = (53 * hashCode) + protoHash;
-      return mine;
-    }
-
-    @Override
-    public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public BooleanList visitBooleanList(BooleanList mine, BooleanList other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public IntList visitIntList(IntList mine, IntList other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public DoubleList visitDoubleList(DoubleList mine, DoubleList other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public FloatList visitFloatList(FloatList mine, FloatList other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public LongList visitLongList(LongList mine, LongList other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public FieldSet<ExtensionDescriptor> visitExtensions(
-        FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public UnknownFieldSetLite visitUnknownFields(
-        UnknownFieldSetLite mine, UnknownFieldSetLite other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-
-    @Override
-    public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
-      return mine;
-    }
-  }
-
-  /** Implements field merging semantics over the visitor interface. */
-  protected static class MergeFromVisitor implements Visitor {
-
-    public static final MergeFromVisitor INSTANCE = new MergeFromVisitor();
-
-    private MergeFromVisitor() {}
-
-    @Override
-    public boolean visitBoolean(
-        boolean minePresent, boolean mine, boolean otherPresent, boolean other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public int visitInt(boolean minePresent, int mine, boolean otherPresent, int other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public double visitDouble(
-        boolean minePresent, double mine, boolean otherPresent, double other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public float visitFloat(boolean minePresent, float mine, boolean otherPresent, float other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public long visitLong(boolean minePresent, long mine, boolean otherPresent, long other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public String visitString(
-        boolean minePresent, String mine, boolean otherPresent, String other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public ByteString visitByteString(
-        boolean minePresent, ByteString mine, boolean otherPresent, ByteString other) {
-      return otherPresent ? other : mine;
-    }
-
-    @Override
-    public Object visitOneofBoolean(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofInt(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofDouble(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofFloat(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofLong(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofString(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofByteString(boolean minePresent, Object mine, Object other) {
-      return other;
-    }
-
-    @Override
-    public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
-      if (minePresent) {
-        return visitMessage((MessageLite) mine, (MessageLite) other);
-      }
-      return other;
-    }
-
-    @Override
-    public void visitOneofNotSet(boolean minePresent) {
-      return;
-    }
-
-    @SuppressWarnings("unchecked") // Guaranteed by runtime.
-    @Override
-    public <T extends MessageLite> T visitMessage(T mine, T other) {
-      if (mine != null && other != null) {
-        return (T) mine.toBuilder().mergeFrom(other).build();
-      }
-
-      return mine != null ? mine : other;
-    }
-
-    @Override
-    public <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public BooleanList visitBooleanList(BooleanList mine, BooleanList other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public IntList visitIntList(IntList mine, IntList other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public DoubleList visitDoubleList(DoubleList mine, DoubleList other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public FloatList visitFloatList(FloatList mine, FloatList other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public LongList visitLongList(LongList mine, LongList other) {
-      int size = mine.size();
-      int otherSize = other.size();
-      if (size > 0 && otherSize > 0) {
-        if (!mine.isModifiable()) {
-          mine = mine.mutableCopyWithCapacity(size + otherSize);
-        }
-        mine.addAll(other);
-      }
-
-      return size > 0 ? mine : other;
-    }
-
-    @Override
-    public FieldSet<ExtensionDescriptor> visitExtensions(
-        FieldSet<ExtensionDescriptor> mine, FieldSet<ExtensionDescriptor> other) {
-      if (mine.isImmutable()) {
-        mine = mine.clone();
-      }
-      mine.mergeFrom(other);
-      return mine;
-    }
-
-    @Override
-    public UnknownFieldSetLite visitUnknownFields(
-        UnknownFieldSetLite mine, UnknownFieldSetLite other) {
-      return other == UnknownFieldSetLite.getDefaultInstance()
-          ? mine
-          : UnknownFieldSetLite.mutableCopyOf(mine, other);
-    }
-
-    @Override
-    public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) {
-      if (!other.isEmpty()) {
-        if (!mine.isMutable()) {
-          mine = mine.mutableCopy();
-        }
-        mine.mergeFrom(other);
-      }
-      return mine;
-    }
-  }
-  // END REGULAR
 }
 }

+ 23 - 0
java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java

@@ -458,6 +458,29 @@ public abstract class GeneratedMessageV3 extends AbstractMessage
 
 
 
 
 
 
+  /**
+   * This class is used to make a generated protected method inaccessible from user's code (e.g.,
+   * the {@link #newInstance} method below). When this class is used as a parameter's type in a
+   * generated protected method, the method is visible to user's code in the same package, but
+   * since the constructor of this class is private to protobuf runtime, user's code can't obtain
+   * an instance of this class and as such can't actually make a method call on the protected
+   * method.
+   */
+  protected static final class UnusedPrivateParameter {
+    static final UnusedPrivateParameter INSTANCE = new UnusedPrivateParameter();
+
+    private UnusedPrivateParameter() {
+    }
+  }
+
+  /**
+   * Creates a new instance of this message type. Overridden in the generated code.
+   */
+  @SuppressWarnings({"unused"})
+  protected Object newInstance(UnusedPrivateParameter unused) {
+    throw new UnsupportedOperationException("This method must be overridden by the subclass.");
+  }
+
   /**
   /**
    * Used by parsing constructors in generated classes.
    * Used by parsing constructors in generated classes.
    */
    */

+ 18 - 0
java/core/src/main/java/com/google/protobuf/Internal.java

@@ -540,6 +540,24 @@ public final class Internal {
         }
         }
         return valueConverter.doForward(oldValue);
         return valueConverter.doForward(oldValue);
       }
       }
+
+      @Override
+      public boolean equals(Object o) {
+        if (o == this) {
+          return true;
+        }
+        if (!(o instanceof Map.Entry)) {
+          return false;
+        }
+        @SuppressWarnings("unchecked")
+        Map.Entry<?, ?> other = (Map.Entry<?, ?>) o;
+        return getKey().equals(other.getKey()) && getValue().equals(getValue());
+      }
+
+      @Override
+      public int hashCode() {
+        return realEntry.hashCode();
+      }
     }
     }
   }
   }
 
 

+ 76 - 0
java/core/src/main/java/com/google/protobuf/JavaType.java

@@ -0,0 +1,76 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** Enum that identifies the Java types required to store protobuf fields. */
+@ExperimentalApi
+public enum JavaType {
+  VOID(Void.class, Void.class, null),
+  INT(int.class, Integer.class, 0),
+  LONG(long.class, Long.class, 0L),
+  FLOAT(float.class, Float.class, 0F),
+  DOUBLE(double.class, Double.class, 0D),
+  BOOLEAN(boolean.class, Boolean.class, false),
+  STRING(String.class, String.class, ""),
+  BYTE_STRING(ByteString.class, ByteString.class, ByteString.EMPTY),
+  ENUM(int.class, Integer.class, null),
+  MESSAGE(Object.class, Object.class, null);
+
+  private final Class<?> type;
+  private final Class<?> boxedType;
+  private final Object defaultDefault;
+
+  JavaType(Class<?> type, Class<?> boxedType, Object defaultDefault) {
+    this.type = type;
+    this.boxedType = boxedType;
+    this.defaultDefault = defaultDefault;
+  }
+
+  /** The default default value for fields of this type, if it's a primitive type. */
+  public Object getDefaultDefault() {
+    return defaultDefault;
+  }
+
+  /** Gets the required type for a field that would hold a value of this type. */
+  public Class<?> getType() {
+    return type;
+  }
+
+  /** @return the boxedType */
+  public Class<?> getBoxedType() {
+    return boxedType;
+  }
+
+  /** Indicates whether or not this {@link JavaType} can be applied to a field of the given type. */
+  public boolean isValidType(Class<?> t) {
+    return type.isAssignableFrom(t);
+  }
+}

+ 12 - 0
java/core/src/main/java/com/google/protobuf/LazyFieldLite.java

@@ -388,6 +388,18 @@ public class LazyFieldLite {
     }
     }
   }
   }
 
 
+  /** Writes this lazy field into a {@link Writer}. */
+  void writeTo(Writer writer, int fieldNumber) throws IOException {
+    if (memoizedBytes != null) {
+      writer.writeBytes(fieldNumber, memoizedBytes);
+    } else if (delayedBytes != null) {
+      writer.writeBytes(fieldNumber, delayedBytes);
+    } else if (value != null) {
+      writer.writeMessage(fieldNumber, value);
+    } else {
+      writer.writeBytes(fieldNumber, ByteString.EMPTY);
+    }
+  }
 
 
   /** Might lazily parse the bytes that were previously passed in. Is thread-safe. */
   /** Might lazily parse the bytes that were previously passed in. Is thread-safe. */
   protected void ensureInitialized(MessageLite defaultInstance) {
   protected void ensureInitialized(MessageLite defaultInstance) {

+ 190 - 0
java/core/src/main/java/com/google/protobuf/ListFieldSchema.java

@@ -0,0 +1,190 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.Internal.ProtobufList;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class that aids in properly manipulating list fields for either the lite or full runtime.
+ */
+abstract class ListFieldSchema {
+  // Disallow construction.
+  private ListFieldSchema() {}
+
+  private static final ListFieldSchema FULL_INSTANCE = new ListFieldSchemaFull();
+  private static final ListFieldSchema LITE_INSTANCE = new ListFieldSchemaLite();
+
+  abstract <L> List<L> mutableListAt(Object msg, long offset);
+
+  abstract void makeImmutableListAt(Object msg, long offset);
+
+  abstract <L> void mergeListsAt(Object msg, Object otherMsg, long offset);
+
+  static ListFieldSchema full() {
+    return FULL_INSTANCE;
+  }
+
+  static ListFieldSchema lite() {
+    return LITE_INSTANCE;
+  }
+
+  /** Implementation for the full runtime. */
+  private static final class ListFieldSchemaFull extends ListFieldSchema {
+
+    private static final Class<?> UNMODIFIABLE_LIST_CLASS =
+        Collections.unmodifiableList(Collections.emptyList()).getClass();
+
+    @Override
+    <L> List<L> mutableListAt(Object message, long offset) {
+      return mutableListAt(message, offset, AbstractProtobufList.DEFAULT_CAPACITY);
+    }
+
+    @Override
+    void makeImmutableListAt(Object message, long offset) {
+      List<?> list = (List<?>) UnsafeUtil.getObject(message, offset);
+      Object immutable = null;
+      if (list instanceof LazyStringList) {
+        immutable = ((LazyStringList) list).getUnmodifiableView();
+      } else if (UNMODIFIABLE_LIST_CLASS.isAssignableFrom(list.getClass())) {
+        // already immutable
+        return;
+      } else if (list instanceof PrimitiveNonBoxingCollection && list instanceof ProtobufList) {
+        if (((ProtobufList<?>) list).isModifiable()) {
+          ((ProtobufList<?>) list).makeImmutable();
+        }
+        return;
+      } else {
+        immutable = Collections.unmodifiableList((List<?>) list);
+      }
+      UnsafeUtil.putObject(message, offset, immutable);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static <L> List<L> mutableListAt(Object message, long offset, int additionalCapacity) {
+      List<L> list = getList(message, offset);
+      if (list.isEmpty()) {
+        if (list instanceof LazyStringList) {
+          list = (List<L>) new LazyStringArrayList(additionalCapacity);
+        } else if (list instanceof PrimitiveNonBoxingCollection && list instanceof ProtobufList) {
+          list = ((ProtobufList<L>) list).mutableCopyWithCapacity(additionalCapacity);
+        } else {
+          list = new ArrayList<L>(additionalCapacity);
+        }
+        UnsafeUtil.putObject(message, offset, list);
+      } else if (UNMODIFIABLE_LIST_CLASS.isAssignableFrom(list.getClass())) {
+        ArrayList<L> newList = new ArrayList<L>(list.size() + additionalCapacity);
+        newList.addAll(list);
+        list = newList;
+        UnsafeUtil.putObject(message, offset, list);
+      } else if (list instanceof UnmodifiableLazyStringList) {
+        LazyStringArrayList newList = new LazyStringArrayList(list.size() + additionalCapacity);
+        newList.addAll((UnmodifiableLazyStringList) list);
+        list = (List<L>) newList;
+        UnsafeUtil.putObject(message, offset, list);
+      } else if (list instanceof PrimitiveNonBoxingCollection
+          && list instanceof ProtobufList
+          && !((ProtobufList<L>) list).isModifiable()) {
+        list = ((ProtobufList<L>) list).mutableCopyWithCapacity(list.size() + additionalCapacity);
+        UnsafeUtil.putObject(message, offset, list);
+      }
+      return list;
+    }
+
+    @Override
+    <E> void mergeListsAt(Object msg, Object otherMsg, long offset) {
+      List<E> other = getList(otherMsg, offset);
+      List<E> mine = mutableListAt(msg, offset, other.size());
+
+      int size = mine.size();
+      int otherSize = other.size();
+      if (size > 0 && otherSize > 0) {
+        mine.addAll(other);
+      }
+
+      List<E> merged = size > 0 ? mine : other;
+      UnsafeUtil.putObject(msg, offset, merged);
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E> List<E> getList(Object message, long offset) {
+      return (List<E>) UnsafeUtil.getObject(message, offset);
+    }
+  }
+
+  /** Implementation for the lite runtime. */
+  private static final class ListFieldSchemaLite extends ListFieldSchema {
+
+    @Override
+    <L> List<L> mutableListAt(Object message, long offset) {
+      ProtobufList<L> list = getProtobufList(message, offset);
+      if (!list.isModifiable()) {
+        int size = list.size();
+        list =
+            list.mutableCopyWithCapacity(
+                size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
+        UnsafeUtil.putObject(message, offset, list);
+      }
+      return list;
+    }
+
+    @Override
+    void makeImmutableListAt(Object message, long offset) {
+      ProtobufList<?> list = getProtobufList(message, offset);
+      list.makeImmutable();
+    }
+
+    @Override
+    <E> void mergeListsAt(Object msg, Object otherMsg, long offset) {
+      ProtobufList<E> mine = getProtobufList(msg, offset);
+      ProtobufList<E> other = getProtobufList(otherMsg, offset);
+
+      int size = mine.size();
+      int otherSize = other.size();
+      if (size > 0 && otherSize > 0) {
+        if (!mine.isModifiable()) {
+          mine = mine.mutableCopyWithCapacity(size + otherSize);
+        }
+        mine.addAll(other);
+      }
+
+      ProtobufList<E> merged = size > 0 ? mine : other;
+      UnsafeUtil.putObject(msg, offset, merged);
+    }
+
+    @SuppressWarnings("unchecked")
+    static <E> ProtobufList<E> getProtobufList(Object message, long offset) {
+      return (ProtobufList<E>) UnsafeUtil.getObject(message, offset);
+    }
+  }
+}

+ 172 - 0
java/core/src/main/java/com/google/protobuf/ManifestSchemaFactory.java

@@ -0,0 +1,172 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+/**
+ * Dynamically generates a manifest-based (i.e. table-based) schema for a given protobuf message.
+ */
+@ExperimentalApi
+final class ManifestSchemaFactory implements SchemaFactory {
+
+  private final MessageInfoFactory messageInfoFactory;
+
+  public ManifestSchemaFactory() {
+    this(getDefaultMessageInfoFactory());
+  }
+
+  private ManifestSchemaFactory(MessageInfoFactory messageInfoFactory) {
+    this.messageInfoFactory = checkNotNull(messageInfoFactory, "messageInfoFactory");
+  }
+
+  @Override
+  public <T> Schema<T> createSchema(Class<T> messageType) {
+    SchemaUtil.requireGeneratedMessage(messageType);
+
+    MessageInfo messageInfo = messageInfoFactory.messageInfoFor(messageType);
+
+    // MessageSet has a special schema.
+    if (messageInfo.isMessageSetWireFormat()) {
+      if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+        return MessageSetSchema.newSchema(
+            SchemaUtil.unknownFieldSetLiteSchema(),
+            ExtensionSchemas.lite(),
+            messageInfo.getDefaultInstance());
+      }
+      return MessageSetSchema.newSchema(
+          SchemaUtil.proto2UnknownFieldSetSchema(),
+          ExtensionSchemas.full(),
+          messageInfo.getDefaultInstance());
+    }
+
+    return newSchema(messageType, messageInfo);
+  }
+
+  private static <T> Schema<T> newSchema(Class<T> messageType, MessageInfo messageInfo) {
+    if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      return isProto2(messageInfo)
+          ? MessageSchema.newSchema(
+              messageType,
+              messageInfo,
+              NewInstanceSchemas.lite(),
+              ListFieldSchema.lite(),
+              SchemaUtil.unknownFieldSetLiteSchema(),
+              ExtensionSchemas.lite(),
+              MapFieldSchemas.lite())
+          : MessageSchema.newSchema(
+              messageType,
+              messageInfo,
+              NewInstanceSchemas.lite(),
+              ListFieldSchema.lite(),
+              SchemaUtil.unknownFieldSetLiteSchema(),
+              /* extensionSchema= */ null,
+              MapFieldSchemas.lite());
+    }
+    return isProto2(messageInfo)
+        ? MessageSchema.newSchema(
+            messageType,
+            messageInfo,
+            NewInstanceSchemas.full(),
+            ListFieldSchema.full(),
+            SchemaUtil.proto2UnknownFieldSetSchema(),
+            ExtensionSchemas.full(),
+            MapFieldSchemas.full())
+        : MessageSchema.newSchema(
+            messageType,
+            messageInfo,
+            NewInstanceSchemas.full(),
+            ListFieldSchema.full(),
+            SchemaUtil.proto3UnknownFieldSetSchema(),
+            /* extensionSchema= */ null,
+            MapFieldSchemas.full());
+  }
+
+  private static boolean isProto2(MessageInfo messageInfo) {
+    return messageInfo.getSyntax() == ProtoSyntax.PROTO2;
+  }
+
+  private static MessageInfoFactory getDefaultMessageInfoFactory() {
+    return new CompositeMessageInfoFactory(
+        GeneratedMessageInfoFactory.getInstance(), getDescriptorMessageInfoFactory());
+  }
+
+  private static class CompositeMessageInfoFactory implements MessageInfoFactory {
+    private MessageInfoFactory[] factories;
+
+    CompositeMessageInfoFactory(MessageInfoFactory... factories) {
+      this.factories = factories;
+    }
+
+    @Override
+    public boolean isSupported(Class<?> clazz) {
+      for (MessageInfoFactory factory : factories) {
+        if (factory.isSupported(clazz)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    @Override
+    public MessageInfo messageInfoFor(Class<?> clazz) {
+      for (MessageInfoFactory factory : factories) {
+        if (factory.isSupported(clazz)) {
+          return factory.messageInfoFor(clazz);
+        }
+      }
+      throw new UnsupportedOperationException(
+          "No factory is available for message type: " + clazz.getName());
+    }
+  }
+
+  private static final MessageInfoFactory EMPTY_FACTORY =
+      new MessageInfoFactory() {
+        @Override
+        public boolean isSupported(Class<?> clazz) {
+          return false;
+        }
+
+        @Override
+        public MessageInfo messageInfoFor(Class<?> clazz) {
+          throw new IllegalStateException("This should never be called.");
+        }
+      };
+
+  private static MessageInfoFactory getDescriptorMessageInfoFactory() {
+    try {
+      Class<?> clazz = Class.forName("com.google.protobuf.DescriptorMessageInfoFactory");
+      return (MessageInfoFactory) clazz.getDeclaredMethod("getInstance").invoke(null);
+    } catch (Exception e) {
+      return EMPTY_FACTORY;
+    }
+  }
+}

+ 63 - 0
java/core/src/main/java/com/google/protobuf/MapFieldSchema.java

@@ -0,0 +1,63 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.Map;
+
+interface MapFieldSchema {
+  /** Returns the map data for mutation. */
+  Map<?, ?> forMutableMapData(Object mapField);
+
+  /** Returns the map data for read. */
+  Map<?, ?> forMapData(Object mapField);
+
+  /** Whether toImmutable() has been called on this map field. */
+  boolean isImmutable(Object mapField);
+
+  /**
+   * Returns an immutable instance of the map field. It may make the parameter immutable and return
+   * the parameter, or create an immutable copy. The status of the parameter after the call is
+   * undefined.
+   */
+  Object toImmutable(Object mapField);
+
+  /** Returns a new instance of the map field given a map default entry. */
+  Object newMapField(Object mapDefaultEntry);
+
+  /** Returns the metadata from a default entry. */
+  MapEntryLite.Metadata<?, ?> forMapMetadata(Object mapDefaultEntry);
+
+  /** Merges {@code srcMapField} into {@code destMapField}, and returns the merged instance. */
+  Object mergeFrom(Object destMapField, Object srcMapField);
+
+  /** Compute the serialized size for the map with a given field number. */
+  int getSerializedSize(int fieldNumber, Object mapField, Object mapDefaultEntry);
+}

+ 112 - 0
java/core/src/main/java/com/google/protobuf/MapFieldSchemaFull.java

@@ -0,0 +1,112 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.MapEntryLite.Metadata;
+import java.util.Map;
+
+class MapFieldSchemaFull implements MapFieldSchema {
+  @Override
+  public Map<?, ?> forMutableMapData(Object mapField) {
+    return ((MapField<?, ?>) mapField).getMutableMap();
+  }
+
+  @Override
+  public Map<?, ?> forMapData(Object mapField) {
+    return ((MapField<?, ?>) mapField).getMap();
+  }
+
+  @Override
+  public boolean isImmutable(Object mapField) {
+    return !((MapField<?, ?>) mapField).isMutable();
+  }
+
+  @Override
+  public Object toImmutable(Object mapField) {
+    ((MapField<?, ?>) mapField).makeImmutable();
+    return mapField;
+  }
+
+  @Override
+  public Object newMapField(Object mapDefaultEntry) {
+    return MapField.newMapField((MapEntry<?, ?>) mapDefaultEntry);
+  }
+
+  @Override
+  public Metadata<?, ?> forMapMetadata(Object mapDefaultEntry) {
+    return ((MapEntry<?, ?>) mapDefaultEntry).getMetadata();
+  }
+
+  @Override
+  public Object mergeFrom(Object destMapField, Object srcMapField) {
+    return mergeFromFull(destMapField, srcMapField);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> Object mergeFromFull(Object destMapField, Object srcMapField) {
+    MapField<K, V> mine = (MapField<K, V>) destMapField;
+    MapField<K, V> other = (MapField<K, V>) srcMapField;
+    if (!mine.isMutable()) {
+      mine.copy();
+    }
+    mine.mergeFrom(other);
+    return mine;
+  }
+
+  @Override
+  public int getSerializedSize(int number, Object mapField, Object mapDefaultEntry) {
+    return getSerializedSizeFull(number, mapField, mapDefaultEntry);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> int getSerializedSizeFull(
+      int number, Object mapField, Object defaultEntryObject) {
+    // Full runtime allocates map fields lazily.
+    if (mapField == null) {
+      return 0;
+    }
+
+    Map<K, V> map = ((MapField<K, V>) mapField).getMap();
+    MapEntry<K, V> defaultEntry = (MapEntry<K, V>) defaultEntryObject;
+    if (map.isEmpty()) {
+      return 0;
+    }
+    int size = 0;
+    for (Map.Entry<K, V> entry : map.entrySet()) {
+      size +=
+          CodedOutputStream.computeTagSize(number)
+              + CodedOutputStream.computeLengthDelimitedFieldSize(
+                  MapEntryLite.computeSerializedSize(
+                      defaultEntry.getMetadata(), entry.getKey(), entry.getValue()));
+    }
+    return size;
+  }
+}

+ 107 - 0
java/core/src/main/java/com/google/protobuf/MapFieldSchemaLite.java

@@ -0,0 +1,107 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.MapEntryLite.Metadata;
+import java.util.Map;
+
+class MapFieldSchemaLite implements MapFieldSchema {
+
+  @Override
+  public Map<?, ?> forMutableMapData(Object mapField) {
+    return (MapFieldLite<?, ?>) mapField;
+  }
+
+  @Override
+  public Metadata<?, ?> forMapMetadata(Object mapDefaultEntry) {
+    return ((MapEntryLite<?, ?>) mapDefaultEntry).getMetadata();
+  }
+
+  @Override
+  public Map<?, ?> forMapData(Object mapField) {
+    return (MapFieldLite<?, ?>) mapField;
+  }
+
+  @Override
+  public boolean isImmutable(Object mapField) {
+    return !((MapFieldLite<?, ?>) mapField).isMutable();
+  }
+
+  @Override
+  public Object toImmutable(Object mapField) {
+    ((MapFieldLite<?, ?>) mapField).makeImmutable();
+    return mapField;
+  }
+
+  @Override
+  public Object newMapField(Object unused) {
+    return MapFieldLite.emptyMapField().mutableCopy();
+  }
+
+  @Override
+  public Object mergeFrom(Object destMapField, Object srcMapField) {
+    return mergeFromLite(destMapField, srcMapField);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> MapFieldLite<K, V> mergeFromLite(Object destMapField, Object srcMapField) {
+    MapFieldLite<K, V> mine = (MapFieldLite<K, V>) destMapField;
+    MapFieldLite<K, V> other = (MapFieldLite<K, V>) srcMapField;
+    if (!other.isEmpty()) {
+      if (!mine.isMutable()) {
+        mine = mine.mutableCopy();
+      }
+      mine.mergeFrom(other);
+    }
+    return mine;
+  }
+
+  @Override
+  public int getSerializedSize(int fieldNumber, Object mapField, Object mapDefaultEntry) {
+    return getSerializedSizeLite(fieldNumber, mapField, mapDefaultEntry);
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <K, V> int getSerializedSizeLite(
+      int fieldNumber, Object mapField, Object defaultEntry) {
+    MapFieldLite<K, V> mapFieldLite = (MapFieldLite<K, V>) mapField;
+    MapEntryLite<K, V> defaultEntryLite = (MapEntryLite<K, V>) defaultEntry;
+
+    if (mapFieldLite.isEmpty()) {
+      return 0;
+    }
+    int size = 0;
+    for (Map.Entry<K, V> entry : mapFieldLite.entrySet()) {
+      size += defaultEntryLite.computeMessageSize(fieldNumber, entry.getKey(), entry.getValue());
+    }
+    return size;
+  }
+}

+ 53 - 0
java/core/src/main/java/com/google/protobuf/MapFieldSchemas.java

@@ -0,0 +1,53 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+final class MapFieldSchemas {
+  private static final MapFieldSchema FULL_SCHEMA = loadSchemaForFullRuntime();
+  private static final MapFieldSchema LITE_SCHEMA = new MapFieldSchemaLite();
+
+  static MapFieldSchema full() {
+    return FULL_SCHEMA;
+  }
+
+  static MapFieldSchema lite() {
+    return LITE_SCHEMA;
+  }
+
+  private static MapFieldSchema loadSchemaForFullRuntime() {
+    try {
+      Class<?> clazz = Class.forName("com.google.protobuf.MapFieldSchemaFull");
+      return (MapFieldSchema) clazz.getDeclaredConstructor().newInstance();
+    } catch (Exception e) {
+      return null;
+    }
+  }
+}

+ 43 - 0
java/core/src/main/java/com/google/protobuf/MessageInfo.java

@@ -0,0 +1,43 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** A MesageInfo object describes a proto message type. */
+interface MessageInfo {
+  /** Gets syntax for this type. */
+  ProtoSyntax getSyntax();
+
+  /** Whether this type is MessageSet. */
+  boolean isMessageSetWireFormat();
+
+  /** Gets the default instance of this type. */
+  MessageLite getDefaultInstance();
+}

+ 41 - 0
java/core/src/main/java/com/google/protobuf/MessageInfoFactory.java

@@ -0,0 +1,41 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** A factory that creates {@link MessageInfo} instances for message types. */
+@ExperimentalApi
+interface MessageInfoFactory {
+  /** Whether the message class is supported by this factory. */
+  boolean isSupported(Class<?> clazz);
+
+  /** Returns a information of the message class. */
+  MessageInfo messageInfoFor(Class<?> clazz);
+}

+ 5884 - 0
java/core/src/main/java/com/google/protobuf/MessageSchema.java

@@ -0,0 +1,5884 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.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.decodeGroupField;
+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.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. */
+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[] EMPTY_INT_ARRAY = new int[0];
+
+  /** 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>
+   * [ 0 -    3] unused
+   * [ 4 -   31] field number
+   * [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
+   * [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>
+   */
+  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 boolean proto3;
+  // TODO(xiaofeng): 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,
+      boolean proto3,
+      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.proto3 = proto3;
+    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) {
+    final boolean isProto3 = messageInfo.getSyntax() == ProtoSyntax.PROTO3;
+
+    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 flags = 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 & 0x400) != 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 */) {
+          // proto2
+          if ((flags & 0x1) == 0x1) {
+            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 */) {
+          if ((flags & 0x1) == 0x1) {
+            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 & 0x800) != 0) {
+            objects[bufferIndex / INTS_PER_FIELD * 2 + 1] = messageInfoObjects[objectsPosition++];
+          }
+        }
+
+        fieldOffset = (int) unsafe.objectFieldOffset(field);
+        if ((flags & 0x1) == 0x1 && 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 = 0;
+          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 & 0x200) != 0 ? ENFORCE_UTF8_MASK : 0)
+              | ((fieldTypeWithExtraBits & 0x100) != 0 ? REQUIRED_MASK : 0)
+              | (fieldType << OFFSET_BITS)
+              | fieldOffset;
+      buffer[bufferIndex++] = (presenceMaskShift << OFFSET_BITS) | presenceFieldOffset;
+    }
+
+    return new MessageSchema<T>(
+        buffer,
+        objects,
+        minFieldNumber,
+        maxFieldNumber,
+        messageInfo.getDefaultInstance(),
+        isProto3,
+        /* 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) {
+    final boolean isProto3 = messageInfo.getSyntax() == ProtoSyntax.PROTO3;
+    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, isProto3, 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(),
+        isProto3,
+        /* useCachedSizeField= */ true,
+        combined,
+        checkInitialized.length,
+        checkInitialized.length + mapFieldPositions.length,
+        newInstanceSchema,
+        listFieldSchema,
+        unknownFieldSchema,
+        extensionSchema,
+        mapFieldSchema);
+  }
+
+  private static void storeFieldData(
+      FieldInfo fi, int[] buffer, int bufferIndex, boolean proto3, 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 (!proto3 && !type.isList() && !type.isMap()) {
+        presenceFieldOffset = (int) UnsafeUtil.objectFieldOffset(fi.getPresenceField());
+        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) {
+    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);
+    }
+
+    if (!proto3) {
+      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 message, T other, int pos) {
+    final int typeAndOffset = typeAndOffsetAt(pos);
+    final long offset = offset(typeAndOffset);
+
+    if (!isFieldPresent(other, pos)) {
+      return;
+    }
+
+    Object mine = UnsafeUtil.getObject(message, offset);
+    Object theirs = UnsafeUtil.getObject(other, offset);
+    if (mine != null && theirs != null) {
+      Object merged = Internal.mergeMessage(mine, theirs);
+      UnsafeUtil.putObject(message, offset, merged);
+      setFieldPresent(message, pos);
+    } else if (theirs != null) {
+      UnsafeUtil.putObject(message, offset, theirs);
+      setFieldPresent(message, pos);
+    }
+  }
+
+  private void mergeOneofMessage(T message, T other, int pos) {
+    int typeAndOffset = typeAndOffsetAt(pos);
+    int number = numberAt(pos);
+    long offset = offset(typeAndOffset);
+
+    if (!isOneofPresent(other, number, pos)) {
+      return;
+    }
+
+    Object mine = UnsafeUtil.getObject(message, offset);
+    Object theirs = UnsafeUtil.getObject(other, offset);
+    if (mine != null && theirs != null) {
+      Object merged = Internal.mergeMessage(mine, theirs);
+      UnsafeUtil.putObject(message, offset, merged);
+      setOneofPresent(message, number, pos);
+    } else if (theirs != null) {
+      UnsafeUtil.putObject(message, offset, theirs);
+      setOneofPresent(message, number, pos);
+    }
+  }
+
+  @Override
+  public int getSerializedSize(T message) {
+    return proto3 ? getSerializedSizeProto3(message) : getSerializedSizeProto2(message);
+  }
+  
+  @SuppressWarnings("unchecked")
+  private int getSerializedSizeProto2(T message) {
+    int size = 0;
+
+    final sun.misc.Unsafe unsafe = UNSAFE;
+    int currentPresenceFieldOffset = -1;
+    int currentPresenceField = 0;
+    for (int i = 0; i < buffer.length; i += INTS_PER_FIELD) {
+      final int typeAndOffset = typeAndOffsetAt(i);
+      final int number = numberAt(i);
+
+      int fieldType = type(typeAndOffset);
+      int presenceMaskAndOffset = 0;
+      int presenceMask = 0;
+      if (fieldType <= 17) {
+        presenceMaskAndOffset = buffer[i + 2];
+        final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
+        presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
+        if (presenceFieldOffset != currentPresenceFieldOffset) {
+          currentPresenceFieldOffset = presenceFieldOffset;
+          currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
+        }
+      } else if (useCachedSizeField
+          && fieldType >= FieldType.DOUBLE_LIST_PACKED.id()
+          && fieldType <= FieldType.SINT64_LIST_PACKED.id()) {
+        presenceMaskAndOffset = buffer[i + 2] & OFFSET_MASK;
+      }
+
+      final long offset = offset(typeAndOffset);
+
+      switch (fieldType) {
+        case 0: // DOUBLE:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeDoubleSize(number, 0);
+          }
+          break;
+        case 1: // FLOAT:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeFloatSize(number, 0);
+          }
+          break;
+        case 2: // INT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeInt64Size(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 3: // UINT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeUInt64Size(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 4: // INT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeInt32Size(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 5: // FIXED64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeFixed64Size(number, 0);
+          }
+          break;
+        case 6: // FIXED32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeFixed32Size(number, 0);
+          }
+          break;
+        case 7: // BOOL:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeBoolSize(number, true);
+          }
+          break;
+        case 8: // STRING:
+          if ((currentPresenceField & presenceMask) != 0) {
+            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 ((currentPresenceField & presenceMask) != 0) {
+            Object value = unsafe.getObject(message, offset);
+            size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
+          }
+          break;
+        case 10: // BYTES:
+          if ((currentPresenceField & presenceMask) != 0) {
+            ByteString value = (ByteString) unsafe.getObject(message, offset);
+            size += CodedOutputStream.computeBytesSize(number, value);
+          }
+          break;
+        case 11: // UINT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeUInt32Size(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 12: // ENUM:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeEnumSize(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 13: // SFIXED32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeSFixed32Size(number, 0);
+          }
+          break;
+        case 14: // SFIXED64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeSFixed64Size(number, 0);
+          }
+          break;
+        case 15: // SINT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeSInt32Size(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 16: // SINT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            size += CodedOutputStream.computeSInt64Size(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 17: // GROUP:
+          if ((currentPresenceField & presenceMask) != 0) {
+            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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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) presenceMaskAndOffset, 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(dweis): 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 int getSerializedSizeProto3(T message) {
+    final sun.misc.Unsafe unsafe = UNSAFE;
+    int size = 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);
+
+      final long offset = offset(typeAndOffset);
+      final int cachedSizeOffset =
+          fieldType >= FieldType.DOUBLE_LIST_PACKED.id()
+                  && fieldType <= FieldType.SINT64_LIST_PACKED.id()
+              ? buffer[i + 2] & OFFSET_MASK
+              : 0;
+
+      switch (fieldType) {
+        case 0: // DOUBLE:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeDoubleSize(number, 0);
+          }
+          break;
+        case 1: // FLOAT:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeFloatSize(number, 0);
+          }
+          break;
+        case 2: // INT64:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeInt64Size(number, UnsafeUtil.getLong(message, offset));
+          }
+          break;
+        case 3: // UINT64:
+          if (isFieldPresent(message, i)) {
+            size +=
+                CodedOutputStream.computeUInt64Size(number, UnsafeUtil.getLong(message, offset));
+          }
+          break;
+        case 4: // INT32:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeInt32Size(number, UnsafeUtil.getInt(message, offset));
+          }
+          break;
+        case 5: // FIXED64:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeFixed64Size(number, 0);
+          }
+          break;
+        case 6: // FIXED32:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeFixed32Size(number, 0);
+          }
+          break;
+        case 7: // BOOL:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeBoolSize(number, true);
+          }
+          break;
+        case 8: // STRING:
+          if (isFieldPresent(message, i)) {
+            Object value = UnsafeUtil.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)) {
+            Object value = UnsafeUtil.getObject(message, offset);
+            size += SchemaUtil.computeSizeMessage(number, value, getMessageFieldSchema(i));
+          }
+          break;
+        case 10: // BYTES:
+          if (isFieldPresent(message, i)) {
+            ByteString value = (ByteString) UnsafeUtil.getObject(message, offset);
+            size += CodedOutputStream.computeBytesSize(number, value);
+          }
+          break;
+        case 11: // UINT32:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeUInt32Size(number, UnsafeUtil.getInt(message, offset));
+          }
+          break;
+        case 12: // ENUM:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeEnumSize(number, UnsafeUtil.getInt(message, offset));
+          }
+          break;
+        case 13: // SFIXED32:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeSFixed32Size(number, 0);
+          }
+          break;
+        case 14: // SFIXED64:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeSFixed64Size(number, 0);
+          }
+          break;
+        case 15: // SINT32:
+          if (isFieldPresent(message, i)) {
+            size += CodedOutputStream.computeSInt32Size(number, UnsafeUtil.getInt(message, offset));
+          }
+          break;
+        case 16: // SINT64:
+          if (isFieldPresent(message, i)) {
+            size +=
+                CodedOutputStream.computeSInt64Size(number, UnsafeUtil.getLong(message, offset));
+          }
+          break;
+        case 17: // GROUP:
+          if (isFieldPresent(message, i)) {
+            size +=
+                CodedOutputStream.computeGroupSize(
+                    number,
+                    (MessageLite) UnsafeUtil.getObject(message, offset),
+                    getMessageFieldSchema(i));
+          }
+          break;
+        case 18: // DOUBLE_LIST:
+          size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
+          break;
+        case 19: // FLOAT_LIST:
+          size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
+          break;
+        case 20: // INT64_LIST:
+          size +=
+              SchemaUtil.computeSizeInt64List(number, (List<Long>) listAt(message, offset), false);
+          break;
+        case 21: // UINT64_LIST:
+          size +=
+              SchemaUtil.computeSizeUInt64List(number, (List<Long>) listAt(message, offset), false);
+          break;
+        case 22: // INT32_LIST:
+          size +=
+              SchemaUtil.computeSizeInt32List(
+                  number, (List<Integer>) listAt(message, offset), false);
+          break;
+        case 23: // FIXED64_LIST:
+          size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
+          break;
+        case 24: // FIXED32_LIST:
+          size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
+          break;
+        case 25: // BOOL_LIST:
+          size += SchemaUtil.computeSizeBoolList(number, listAt(message, offset), false);
+          break;
+        case 26: // STRING_LIST:
+          size += SchemaUtil.computeSizeStringList(number, listAt(message, offset));
+          break;
+        case 27: // MESSAGE_LIST:
+          size +=
+              SchemaUtil.computeSizeMessageList(
+                  number, listAt(message, offset), getMessageFieldSchema(i));
+          break;
+        case 28: // BYTES_LIST:
+          size +=
+              SchemaUtil.computeSizeByteStringList(
+                  number, (List<ByteString>) listAt(message, offset));
+          break;
+        case 29: // UINT32_LIST:
+          size +=
+              SchemaUtil.computeSizeUInt32List(
+                  number, (List<Integer>) listAt(message, offset), false);
+          break;
+        case 30: // ENUM_LIST:
+          size +=
+              SchemaUtil.computeSizeEnumList(
+                  number, (List<Integer>) listAt(message, offset), false);
+          break;
+        case 31: // SFIXED32_LIST:
+          size += SchemaUtil.computeSizeFixed32List(number, listAt(message, offset), false);
+          break;
+        case 32: // SFIXED64_LIST:
+          size += SchemaUtil.computeSizeFixed64List(number, listAt(message, offset), false);
+          break;
+        case 33: // SINT32_LIST:
+          size +=
+              SchemaUtil.computeSizeSInt32List(
+                  number, (List<Integer>) listAt(message, offset), false);
+          break;
+        case 34: // SINT64_LIST:
+          size +=
+              SchemaUtil.computeSizeSInt64List(number, (List<Long>) listAt(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>) listAt(message, offset), getMessageFieldSchema(i));
+          break;
+        case 50: // MAP:
+          // TODO(dweis): Use schema cache.
+          size +=
+              mapFieldSchema.getSerializedSize(
+                  number, UnsafeUtil.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 = UnsafeUtil.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 = UnsafeUtil.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) UnsafeUtil.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) UnsafeUtil.getObject(message, offset),
+                    getMessageFieldSchema(i));
+          }
+          break;
+        default:
+          // Assume it's an empty entry.
+      }
+    }
+
+    size += getUnknownFieldsSerializedSize(unknownFieldSchema, message);
+
+    return size;
+  }
+
+  private <UT, UB> int getUnknownFieldsSerializedSize(
+      UnknownFieldSchema<UT, UB> schema, T message) {
+    UT unknowns = schema.getFromMessage(message);
+    return schema.getSerializedSize(unknowns);
+  }
+
+  private static List<?> listAt(Object message, long offset) {
+    return (List<?>) UnsafeUtil.getObject(message, offset);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  // TODO(nathanmittler): 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 {
+      if (proto3) {
+        writeFieldsInAscendingOrderProto3(message, writer);
+      } else {
+        writeFieldsInAscendingOrderProto2(message, writer);
+      }
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  private void writeFieldsInAscendingOrderProto2(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 = -1;
+    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 presenceMaskAndOffset = 0;
+      int presenceMask = 0;
+      if (!proto3 && fieldType <= 17) {
+        presenceMaskAndOffset = buffer[pos + 2];
+        final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
+        if (presenceFieldOffset != currentPresenceFieldOffset) {
+          currentPresenceFieldOffset = presenceFieldOffset;
+          currentPresenceField = unsafe.getInt(message, (long) presenceFieldOffset);
+        }
+        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 ((currentPresenceField & presenceMask) != 0) {
+            writer.writeDouble(number, doubleAt(message, offset));
+          }
+          break;
+        case 1: // FLOAT:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeFloat(number, floatAt(message, offset));
+          }
+          break;
+        case 2: // INT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeInt64(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 3: // UINT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeUInt64(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 4: // INT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeInt32(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 5: // FIXED64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeFixed64(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 6: // FIXED32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeFixed32(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 7: // BOOL:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeBool(number, booleanAt(message, offset));
+          }
+          break;
+        case 8: // STRING:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writeString(number, unsafe.getObject(message, offset), writer);
+          }
+          break;
+        case 9: // MESSAGE:
+          if ((currentPresenceField & presenceMask) != 0) {
+            Object value = unsafe.getObject(message, offset);
+            writer.writeMessage(number, value, getMessageFieldSchema(pos));
+          }
+          break;
+        case 10: // BYTES:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeBytes(number, (ByteString) unsafe.getObject(message, offset));
+          }
+          break;
+        case 11: // UINT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeUInt32(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 12: // ENUM:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeEnum(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 13: // SFIXED32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeSFixed32(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 14: // SFIXED64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeSFixed64(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 15: // SINT32:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeSInt32(number, unsafe.getInt(message, offset));
+          }
+          break;
+        case 16: // SINT64:
+          if ((currentPresenceField & presenceMask) != 0) {
+            writer.writeSInt64(number, unsafe.getLong(message, offset));
+          }
+          break;
+        case 17: // GROUP:
+          if ((currentPresenceField & presenceMask) != 0) {
+            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(xiaofeng): 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(dweis): 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 writeFieldsInAscendingOrderProto3(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();
+      }
+    }
+
+    final int bufferLength = buffer.length;
+    for (int pos = 0; pos < bufferLength; 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:
+          // TODO(xiaofeng): Make use of cached field size to speed up serialization.
+          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(dweis): 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:
+          // 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(dweis): 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();
+    }
+    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(
+                    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)) {
+              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:
+                if (isFieldPresent(message, pos)) {
+                  Object mergedResult =
+                      Internal.mergeMessage(
+                          UnsafeUtil.getObject(message, offset(typeAndOffset)),
+                          reader.readMessageBySchemaWithCheck(
+                              (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
+                  UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
+                } else {
+                  UnsafeUtil.putObject(
+                      message,
+                      offset(typeAndOffset),
+                      reader.readMessageBySchemaWithCheck(
+                          (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
+                  setFieldPresent(message, pos);
+                }
+                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(
+                          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:
+                if (isFieldPresent(message, pos)) {
+                  Object mergedResult =
+                      Internal.mergeMessage(
+                          UnsafeUtil.getObject(message, offset(typeAndOffset)),
+                          reader.readGroupBySchemaWithCheck(
+                              (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
+                  UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
+                } else {
+                  UnsafeUtil.putObject(
+                      message,
+                      offset(typeAndOffset),
+                      reader.readGroupBySchemaWithCheck(
+                          (Schema<T>) getMessageFieldSchema(pos), extensionRegistry));
+                  setFieldPresent(message, pos);
+                }
+                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(
+                        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(
+                        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:
+              if (isOneofPresent(message, number, pos)) {
+                Object mergedResult =
+                    Internal.mergeMessage(
+                        UnsafeUtil.getObject(message, offset(typeAndOffset)),
+                        reader.readMessageBySchemaWithCheck(
+                            getMessageFieldSchema(pos), extensionRegistry));
+                UnsafeUtil.putObject(message, offset(typeAndOffset), mergedResult);
+              } else {
+                UnsafeUtil.putObject(
+                    message,
+                    offset(typeAndOffset),
+                    reader.readMessageBySchemaWithCheck(
+                        getMessageFieldSchema(pos), extensionRegistry));
+                setFieldPresent(message, pos);
+              }
+              setOneofPresent(message, number, pos);
+              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(
+                          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:
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readGroupBySchemaWithCheck(getMessageFieldSchema(pos), extensionRegistry));
+              setOneofPresent(message, number, pos);
+              break;
+            default:
+              // Assume we've landed on an empty entry. Treat it as an unknown field.
+              if (unknownFields == null) {
+                unknownFields = unknownFieldSchema.newBuilder();
+              }
+              if (!unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader)) {
+                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)) {
+              return;
+            }
+          }
+        }
+      }
+    } finally {
+      for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
+        unknownFields =
+            filterMapUnknownEnumValues(message, intArray[i], unknownFields, unknownFieldSchema);
+      }
+      if (unknownFields != null) {
+        unknownFieldSchema.setBuilderToMessage(message, unknownFields);
+      }
+    }
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  static UnknownFieldSetLite getMutableUnknownFields(Object message) {
+    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;
+        }
+        UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
+        if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
+          // filterUnknownEnumList() expects the unknownFields parameter to be mutable or null.
+          // Since we don't know yet whether there exist unknown enum values, we'd better pass
+          // null to it instead of allocating a mutable instance. This is also needed to be
+          // consistent with the behavior of generated parser/builder.
+          unknownFields = null;
+        }
+        unknownFields =
+            SchemaUtil.filterUnknownEnumList(
+                number,
+                (ProtobufList<Integer>) list,
+                getEnumFieldVerifier(bufferPosition),
+                unknownFields,
+                (UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema);
+        if (unknownFields != null) {
+          ((GeneratedMessageLite) message).unknownFields = unknownFields;
+        }
+        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) {
+          position =
+              decodeMessageField(
+                  getMessageFieldSchema(bufferPosition), data, position, limit, registers);
+          final Object oldValue =
+              unsafe.getInt(message, oneofCaseOffset) == number
+                  ? unsafe.getObject(message, fieldOffset)
+                  : null;
+          if (oldValue == null) {
+            unsafe.putObject(message, fieldOffset, registers.object1);
+          } else {
+            unsafe.putObject(
+                message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
+          }
+          unsafe.putInt(message, oneofCaseOffset, number);
+        }
+        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 int endTag = (tag & ~0x7) | WireFormat.WIRETYPE_END_GROUP;
+          position =
+              decodeGroupField(
+                  getMessageFieldSchema(bufferPosition), data, position, limit, endTag, registers);
+          final Object oldValue =
+              unsafe.getInt(message, oneofCaseOffset) == number
+                  ? unsafe.getObject(message, fieldOffset)
+                  : null;
+          if (oldValue == null) {
+            unsafe.putObject(message, fieldOffset, registers.object1);
+          } else {
+            unsafe.putObject(
+                message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
+          }
+          unsafe.putInt(message, oneofCaseOffset, number);
+        }
+        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 proto2 message or group and returns the position after the message/group. If it's
+   * parsing a message (endGroup == 0), returns limit if parsing is successful; It it's parsing a
+   * group (endGroup != 0), parsing ends when a tag == endGroup is encountered and the position
+   * after that tag is returned.
+   */
+  int parseProto2Message(
+      T message, byte[] data, int position, int limit, int endGroup, Registers registers)
+      throws IOException {
+    final sun.misc.Unsafe unsafe = UNSAFE;
+    int currentPresenceFieldOffset = -1;
+    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) {
+          // Proto2 optional fields 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 has-bits integer value and only write it back when parsing a field
+          // using a different has-bits integer.
+          if (presenceFieldOffset != currentPresenceFieldOffset) {
+            if (currentPresenceFieldOffset != -1) {
+              unsafe.putInt(message, (long) currentPresenceFieldOffset, currentPresenceField);
+            }
+            currentPresenceFieldOffset = presenceFieldOffset;
+            currentPresenceField = 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 ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
+                  position = decodeString(data, position, registers);
+                } else {
+                  position = decodeStringRequireUtf8(data, position, registers);
+                }
+                unsafe.putObject(message, fieldOffset, registers.object1);
+                currentPresenceField |= presenceMask;
+                continue;
+              }
+              break;
+            case 9: // MESSAGE
+              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+                position =
+                    decodeMessageField(
+                        getMessageFieldSchema(pos), data, position, limit, registers);
+                if ((currentPresenceField & presenceMask) == 0) {
+                  unsafe.putObject(message, fieldOffset, registers.object1);
+                } else {
+                  unsafe.putObject(
+                      message,
+                      fieldOffset,
+                      Internal.mergeMessage(
+                          unsafe.getObject(message, fieldOffset), registers.object1));
+                }
+                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 (enumVerifier == null || enumVerifier.isInRange(enumValue)) {
+                  unsafe.putInt(message, fieldOffset, enumValue);
+                  currentPresenceField |= presenceMask;
+                } else {
+                  // 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 int endTag = (number << 3) | WireFormat.WIRETYPE_END_GROUP;
+                position =
+                    decodeGroupField(
+                        getMessageFieldSchema(pos), data, position, limit, endTag, registers);
+                if ((currentPresenceField & presenceMask) == 0) {
+                  unsafe.putObject(message, fieldOffset, registers.object1);
+                } else {
+                  unsafe.putObject(
+                      message,
+                      fieldOffset,
+                      Internal.mergeMessage(
+                          unsafe.getObject(message, fieldOffset), registers.object1));
+                }
+
+                currentPresenceField |= presenceMask;
+                continue;
+              }
+              break;
+            default:
+              break;
+          }
+        } else if (fieldType == 27) {
+          // Handle repeated message fields.
+          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 == endGroup && endGroup != 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 != -1) {
+      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);
+    }
+    if (unknownFields != null) {
+      ((UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite>) unknownFieldSchema)
+          .setBuilderToMessage(message, unknownFields);
+    }
+    if (endGroup == 0) {
+      if (position != limit) {
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+    } else {
+      if (position > limit || tag != endGroup) {
+        throw InvalidProtocolBufferException.parseFailure();
+      }
+    }
+    return position;
+  }
+
+  /** Parses a proto3 message and returns the limit if parsing is successful. */
+  private int parseProto3Message(
+      T message, byte[] data, int position, int limit, Registers registers) throws IOException {
+    final sun.misc.Unsafe unsafe = UNSAFE;
+    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) {
+          switch (fieldType) {
+            case 0: // DOUBLE:
+              if (wireType == WireFormat.WIRETYPE_FIXED64) {
+                UnsafeUtil.putDouble(message, fieldOffset, decodeDouble(data, position));
+                position += 8;
+                continue;
+              }
+              break;
+            case 1: // FLOAT:
+              if (wireType == WireFormat.WIRETYPE_FIXED32) {
+                UnsafeUtil.putFloat(message, fieldOffset, decodeFloat(data, position));
+                position += 4;
+                continue;
+              }
+              break;
+            case 2: // INT64:
+            case 3: // UINT64:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint64(data, position, registers);
+                unsafe.putLong(message, fieldOffset, registers.long1);
+                continue;
+              }
+              break;
+            case 4: // INT32:
+            case 11: // UINT32:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint32(data, position, registers);
+                unsafe.putInt(message, fieldOffset, registers.int1);
+                continue;
+              }
+              break;
+            case 5: // FIXED64:
+            case 14: // SFIXED64:
+              if (wireType == WireFormat.WIRETYPE_FIXED64) {
+                unsafe.putLong(message, fieldOffset, decodeFixed64(data, position));
+                position += 8;
+                continue;
+              }
+              break;
+            case 6: // FIXED32:
+            case 13: // SFIXED32:
+              if (wireType == WireFormat.WIRETYPE_FIXED32) {
+                unsafe.putInt(message, fieldOffset, decodeFixed32(data, position));
+                position += 4;
+                continue;
+              }
+              break;
+            case 7: // BOOL:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint64(data, position, registers);
+                UnsafeUtil.putBoolean(message, fieldOffset, registers.long1 != 0);
+                continue;
+              }
+              break;
+            case 8: // STRING:
+              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+                if ((typeAndOffset & ENFORCE_UTF8_MASK) == 0) {
+                  position = decodeString(data, position, registers);
+                } else {
+                  position = decodeStringRequireUtf8(data, position, registers);
+                }
+                unsafe.putObject(message, fieldOffset, registers.object1);
+                continue;
+              }
+              break;
+            case 9: // MESSAGE:
+              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+                position =
+                    decodeMessageField(
+                        getMessageFieldSchema(pos), data, position, limit, registers);
+                final Object oldValue = unsafe.getObject(message, fieldOffset);
+                if (oldValue == null) {
+                  unsafe.putObject(message, fieldOffset, registers.object1);
+                } else {
+                  unsafe.putObject(
+                      message, fieldOffset, Internal.mergeMessage(oldValue, registers.object1));
+                }
+                continue;
+              }
+              break;
+            case 10: // BYTES:
+              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+                position = decodeBytes(data, position, registers);
+                unsafe.putObject(message, fieldOffset, registers.object1);
+                continue;
+              }
+              break;
+            case 12: // ENUM:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint32(data, position, registers);
+                unsafe.putInt(message, fieldOffset, registers.int1);
+                continue;
+              }
+              break;
+            case 15: // SINT32:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint32(data, position, registers);
+                unsafe.putInt(
+                    message, fieldOffset, CodedInputStream.decodeZigZag32(registers.int1));
+                continue;
+              }
+              break;
+            case 16: // SINT64:
+              if (wireType == WireFormat.WIRETYPE_VARINT) {
+                position = decodeVarint64(data, position, registers);
+                unsafe.putLong(
+                    message, fieldOffset, CodedInputStream.decodeZigZag64(registers.long1));
+                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;
+          }
+        }
+      }
+      position = decodeUnknownField(
+          tag, data, position, limit, getMutableUnknownFields(message), registers);
+    }
+    if (position != limit) {
+      throw InvalidProtocolBufferException.parseFailure();
+    }
+    return position;
+  }
+
+  @Override
+  public void mergeFrom(T message, byte[] data, int position, int limit, Registers registers)
+      throws IOException {
+    if (proto3) {
+      parseProto3Message(message, data, position, limit, registers);
+    } else {
+      parseProto2Message(message, data, position, limit, 0, registers);
+    }
+  }
+
+  @Override
+  public void makeImmutable(T message) {
+    // Make all repeated/map fields immutable.
+    for (int i = checkInitializedCount; i < repeatedFieldOffsetStart; i++) {
+      long offset = offset(typeAndOffsetAt(intArray[i]));
+      Object mapField = UnsafeUtil.getObject(message, offset);
+      if (mapField == null) {
+        continue;
+      }
+      UnsafeUtil.putObject(message, offset, mapFieldSchema.toImmutable(mapField));
+    }
+    final int length = intArray.length;
+    for (int i = repeatedFieldOffsetStart; i < length; i++) {
+      listFieldSchema.makeImmutableListAt(message, intArray[i]);
+    }
+    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(xiaofeng): 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 final <UT, UB> UB filterMapUnknownEnumValues(
+      Object message, int pos, UB unknownFields, UnknownFieldSchema<UT, UB> unknownFieldSchema) {
+    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);
+    return unknownFields;
+  }
+
+  @SuppressWarnings("unchecked")
+  private final <K, V, UT, UB> UB filterUnknownEnumMap(
+      int pos,
+      int number,
+      Map<K, V> mapData,
+      EnumVerifier enumVerifier,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema) {
+    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.newBuilder();
+        }
+        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 = -1;
+    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 = 0;
+      int presenceMask = 0;
+      if (!proto3) {
+        presenceMaskAndOffset = buffer[pos + 2];
+        final int presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
+        presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
+        if (presenceFieldOffset != currentPresenceFieldOffset) {
+          currentPresenceFieldOffset = presenceFieldOffset;
+          currentPresenceField = UNSAFE.getInt(message, (long) presenceFieldOffset);
+        }
+      }
+
+      if (isRequired(typeAndOffset)) {
+        if (!isFieldPresent(message, pos, currentPresenceField, presenceMask)) {
+          return false;
+        }
+        // If a required message field is set but has no required fields of it's 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, 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(dweis): 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 long offset(int value) {
+    return value & OFFSET_MASK;
+  }
+
+  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 presenceField, int presenceMask) {
+    if (proto3) {
+      return isFieldPresent(message, pos);
+    } else {
+      return (presenceField & presenceMask) != 0;
+    }
+  }
+
+  private boolean isFieldPresent(T message, int pos) {
+    if (proto3) {
+      final int typeAndOffset = typeAndOffsetAt(pos);
+      final long offset = offset(typeAndOffset);
+      switch (type(typeAndOffset)) {
+        case 0: // DOUBLE:
+          return UnsafeUtil.getDouble(message, offset) != 0D;
+        case 1: // FLOAT:
+          return UnsafeUtil.getFloat(message, offset) != 0F;
+        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 {
+      int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
+      final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
+      return (UnsafeUtil.getInt(message, presenceMaskAndOffset & OFFSET_MASK) & presenceMask) != 0;
+    }
+  }
+
+  private void setFieldPresent(T message, int pos) {
+    if (proto3) {
+      // Proto3 doesn't have presence fields
+      return;
+    }
+    int presenceMaskAndOffset = presenceMaskAndOffsetAt(pos);
+    final int presenceMask = 1 << (presenceMaskAndOffset >>> OFFSET_BITS);
+    final long presenceFieldOffset = presenceMaskAndOffset & OFFSET_MASK;
+    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;
+  }
+}

+ 392 - 0
java/core/src/main/java/com/google/protobuf/MessageSetSchema.java

@@ -0,0 +1,392 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map.Entry;
+
+/** Schema used for proto2 messages using message_set_wireformat. */
+final class MessageSetSchema<T> implements Schema<T> {
+  private final MessageLite defaultInstance;
+  private final UnknownFieldSchema<?, ?> unknownFieldSchema;
+  private final boolean hasExtensions;
+  private final ExtensionSchema<?> extensionSchema;
+
+  private MessageSetSchema(
+      UnknownFieldSchema<?, ?> unknownFieldSchema,
+      ExtensionSchema<?> extensionSchema,
+      MessageLite defaultInstance) {
+    this.unknownFieldSchema = unknownFieldSchema;
+    this.hasExtensions = extensionSchema.hasExtensions(defaultInstance);
+    this.extensionSchema = extensionSchema;
+    this.defaultInstance = defaultInstance;
+  }
+
+  static <T> MessageSetSchema<T> newSchema(
+      UnknownFieldSchema<?, ?> unknownFieldSchema,
+      ExtensionSchema<?> extensionSchema,
+      MessageLite defaultInstance) {
+    return new MessageSetSchema<T>(unknownFieldSchema, extensionSchema, defaultInstance);
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public T newInstance() {
+    return (T) defaultInstance.newBuilderForType().buildPartial();
+  }
+
+  @Override
+  public boolean equals(T message, T other) {
+    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;
+  }
+
+  @Override
+  public int hashCode(T message) {
+    int hashCode = unknownFieldSchema.getFromMessage(message).hashCode();
+    if (hasExtensions) {
+      FieldSet<?> extensions = extensionSchema.getExtensions(message);
+      hashCode = (hashCode * 53) + extensions.hashCode();
+    }
+    return hashCode;
+  }
+
+  @Override
+  public void mergeFrom(T message, T other) {
+    SchemaUtil.mergeUnknownFields(unknownFieldSchema, message, other);
+    if (hasExtensions) {
+      SchemaUtil.mergeExtensions(extensionSchema, message, other);
+    }
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public void writeTo(T message, Writer writer) throws IOException {
+    FieldSet<?> extensions = extensionSchema.getExtensions(message);
+    Iterator<?> iterator = extensions.iterator();
+    while (iterator.hasNext()) {
+      Entry<?, ?> extension = (Entry<?, ?>) iterator.next();
+      FieldSet.FieldDescriptorLite<?> fd = (FieldSet.FieldDescriptorLite<?>) extension.getKey();
+      if (fd.getLiteJavaType() != WireFormat.JavaType.MESSAGE || fd.isRepeated() || fd.isPacked()) {
+        throw new IllegalStateException("Found invalid MessageSet item.");
+      }
+      if (extension instanceof LazyField.LazyEntry) {
+        writer.writeMessageSetItem(
+            fd.getNumber(), ((LazyField.LazyEntry) extension).getField().toByteString());
+      } else {
+        writer.writeMessageSetItem(fd.getNumber(), extension.getValue());
+      }
+    }
+    writeUnknownFieldsHelper(unknownFieldSchema, message, writer);
+  }
+
+  /**
+   * A helper method for wildcard capture of {@code unknownFieldSchema}. See:
+   * https://docs.oracle.com/javase/tutorial/java/generics/capture.html
+   */
+  private <UT, UB> void writeUnknownFieldsHelper(
+      UnknownFieldSchema<UT, UB> unknownFieldSchema, T message, Writer writer) throws IOException {
+    unknownFieldSchema.writeAsMessageSetTo(unknownFieldSchema.getFromMessage(message), writer);
+  }
+
+  @SuppressWarnings("ReferenceEquality")
+  @Override
+  public void mergeFrom(
+      T message, byte[] data, int position, int limit, ArrayDecoders.Registers registers)
+      throws IOException {
+    UnknownFieldSetLite unknownFields = ((GeneratedMessageLite) message).unknownFields;
+    if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
+      unknownFields = UnknownFieldSetLite.newInstance();
+      ((GeneratedMessageLite) message).unknownFields = unknownFields;
+    }
+    final FieldSet<GeneratedMessageLite.ExtensionDescriptor> extensions =
+        ((GeneratedMessageLite.ExtendableMessage<?, ?>) message).ensureExtensionsAreMutable();
+    GeneratedMessageLite.GeneratedExtension<?, ?> extension = null;
+    while (position < limit) {
+      position = ArrayDecoders.decodeVarint32(data, position, registers);
+      final int startTag = registers.int1;
+      if (startTag != WireFormat.MESSAGE_SET_ITEM_TAG) {
+        if (WireFormat.getTagWireType(startTag) == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+          extension =
+              (GeneratedMessageLite.GeneratedExtension<?, ?>) extensionSchema.findExtensionByNumber(
+                  registers.extensionRegistry, defaultInstance,
+                  WireFormat.getTagFieldNumber(startTag));
+          if (extension != null) {
+            position =
+                ArrayDecoders.decodeMessageField(
+                    Protobuf.getInstance().schemaFor(
+                        extension.getMessageDefaultInstance().getClass()),
+                    data, position, limit, registers);
+            extensions.setField(extension.descriptor, registers.object1);
+          } else {
+            position =
+                ArrayDecoders.decodeUnknownField(
+                    startTag, data, position, limit, unknownFields, registers);
+          }
+        } else {
+          position = ArrayDecoders.skipField(startTag, data, position, limit, registers);
+        }
+        continue;
+      }
+
+      int typeId = 0;
+      ByteString rawBytes = null;
+
+      while (position < limit) {
+        position = ArrayDecoders.decodeVarint32(data, position, registers);
+        final int tag = registers.int1;
+        final int number = WireFormat.getTagFieldNumber(tag);
+        final int wireType = WireFormat.getTagWireType(tag);
+        switch (number) {
+          case WireFormat.MESSAGE_SET_TYPE_ID:
+            if (wireType == WireFormat.WIRETYPE_VARINT) {
+              position = ArrayDecoders.decodeVarint32(data, position, registers);
+              typeId = registers.int1;
+              extension =
+                  (GeneratedMessageLite.GeneratedExtension<?, ?>) extensionSchema
+                      .findExtensionByNumber(registers.extensionRegistry, defaultInstance, typeId);
+              continue;
+            }
+            break;
+          case WireFormat.MESSAGE_SET_MESSAGE:
+            if (extension != null) {
+              position = ArrayDecoders.decodeMessageField(
+                  Protobuf.getInstance().schemaFor(
+                      extension.getMessageDefaultInstance().getClass()),
+                  data, position, limit, registers);
+              extensions.setField(extension.descriptor, registers.object1);
+              continue;
+            } else {
+              if (wireType == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+                position = ArrayDecoders.decodeBytes(data, position, registers);
+                rawBytes = (ByteString) registers.object1;
+                continue;
+              }
+              break;
+            }
+          default:
+            break;
+        }
+        if (tag == WireFormat.MESSAGE_SET_ITEM_END_TAG) {
+          break;
+        }
+        position = ArrayDecoders.skipField(tag, data, position, limit, registers);
+      }
+
+      if (rawBytes != null) {
+        unknownFields.storeField(
+            WireFormat.makeTag(typeId, WireFormat.WIRETYPE_LENGTH_DELIMITED), rawBytes);
+      }
+    }
+    if (position != limit) {
+      throw InvalidProtocolBufferException.parseFailure();
+    }
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    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
+   */
+  @SuppressWarnings("unchecked")
+  private <UT, UB, ET extends FieldSet.FieldDescriptorLite<ET>> void mergeFromHelper(
+      UnknownFieldSchema<UT, UB> unknownFieldSchema,
+      ExtensionSchema<ET> extensionSchema,
+      T message,
+      Reader reader,
+      ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    UB unknownFields = unknownFieldSchema.getBuilderFromMessage(message);
+    FieldSet<ET> extensions = extensionSchema.getMutableExtensions(message);
+    try {
+      while (true) {
+        final int number = reader.getFieldNumber();
+        if (number == Reader.READ_DONE) {
+          return;
+        }
+        if (parseMessageSetItemOrUnknownField(
+            reader,
+            extensionRegistry,
+            extensionSchema,
+            extensions,
+            unknownFieldSchema,
+            unknownFields)) {
+          continue;
+        }
+        // Done reading.
+        return;
+      }
+    } finally {
+      unknownFieldSchema.setBuilderToMessage(message, unknownFields);
+    }
+  }
+
+  @Override
+  public void makeImmutable(T message) {
+    unknownFieldSchema.makeImmutable(message);
+    extensionSchema.makeImmutable(message);
+  }
+
+  private <UT, UB, ET extends FieldSet.FieldDescriptorLite<ET>>
+      boolean parseMessageSetItemOrUnknownField(
+          Reader reader,
+          ExtensionRegistryLite extensionRegistry,
+          ExtensionSchema<ET> extensionSchema,
+          FieldSet<ET> extensions,
+          UnknownFieldSchema<UT, UB> unknownFieldSchema,
+          UB unknownFields)
+          throws IOException {
+    int startTag = reader.getTag();
+    if (startTag != WireFormat.MESSAGE_SET_ITEM_TAG) {
+      if (WireFormat.getTagWireType(startTag) == WireFormat.WIRETYPE_LENGTH_DELIMITED) {
+        Object extension =
+            extensionSchema.findExtensionByNumber(
+                extensionRegistry, defaultInstance, WireFormat.getTagFieldNumber(startTag));
+        if (extension != null) {
+          extensionSchema.parseLengthPrefixedMessageSetItem(
+              reader, extension, extensionRegistry, extensions);
+          return true;
+        } else {
+          return unknownFieldSchema.mergeOneFieldFrom(unknownFields, reader);
+        }
+      } else {
+        return reader.skipField();
+      }
+    }
+
+    // The wire format for MessageSet is:
+    //   message MessageSet {
+    //     repeated group Item = 1 {
+    //       required int32 typeId = 2;
+    //       required bytes message = 3;
+    //     }
+    //   }
+    // "typeId" is the extension's field number.  The extension can only be
+    // a message type, where "message" contains the encoded bytes of that
+    // message.
+    //
+    // In practice, we will probably never see a MessageSet item in which
+    // the message appears before the type ID, or where either field does not
+    // appear exactly once.  However, in theory such cases are valid, so we
+    // should be prepared to accept them.
+
+    int typeId = 0;
+    ByteString rawBytes = null; // If we encounter "message" before "typeId"
+    Object extension = null;
+
+    // Read bytes from input, if we get it's type first then parse it eagerly,
+    // otherwise we store the raw bytes in a local variable.
+    loop:
+    while (true) {
+      final int number = reader.getFieldNumber();
+      if (number == Reader.READ_DONE) {
+        break;
+      }
+
+      final int tag = reader.getTag();
+      if (tag == WireFormat.MESSAGE_SET_TYPE_ID_TAG) {
+        typeId = reader.readUInt32();
+        extension =
+            extensionSchema.findExtensionByNumber(extensionRegistry, defaultInstance, typeId);
+        continue;
+      } else if (tag == WireFormat.MESSAGE_SET_MESSAGE_TAG) {
+        if (extension != null) {
+          extensionSchema.parseLengthPrefixedMessageSetItem(
+              reader, extension, extensionRegistry, extensions);
+          continue;
+        }
+        // We haven't seen a type ID yet or we want parse message lazily.
+        rawBytes = reader.readBytes();
+        continue;
+      } else {
+        if (!reader.skipField()) {
+          break loop; // End of group
+        }
+      }
+    }
+
+    if (reader.getTag() != WireFormat.MESSAGE_SET_ITEM_END_TAG) {
+      throw InvalidProtocolBufferException.invalidEndTag();
+    }
+
+    // If there are any rawBytes left, it means the message content appears before the type ID.
+    if (rawBytes != null) {
+      if (extension != null) { // We known the type
+        // TODO(xiaofeng): Instead of reading into a temporary ByteString, maybe there is a way
+        // to read directly from Reader to the submessage?
+        extensionSchema.parseMessageSetItem(rawBytes, extension, extensionRegistry, extensions);
+      } else {
+        unknownFieldSchema.addLengthDelimited(unknownFields, typeId, rawBytes);
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public final boolean isInitialized(T message) {
+    FieldSet<?> extensions = extensionSchema.getExtensions(message);
+    return extensions.isInitialized();
+  }
+
+  @Override
+  public int getSerializedSize(T message) {
+    int size = 0;
+
+    size += getUnknownFieldsSerializedSize(unknownFieldSchema, message);
+
+    if (hasExtensions) {
+      size += extensionSchema.getExtensions(message).getMessageSetSerializedSize();
+    }
+
+    return size;
+  }
+
+  private <UT, UB> int getUnknownFieldsSerializedSize(
+      UnknownFieldSchema<UT, UB> schema, T message) {
+    UT unknowns = schema.getFromMessage(message);
+    return schema.getSerializedSizeAsMessageSet(unknowns);
+  }
+}

+ 36 - 0
java/core/src/main/java/com/google/protobuf/NewInstanceSchema.java

@@ -0,0 +1,36 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+interface NewInstanceSchema {
+  /** Create a new message instance given the default instance of the message type. */
+  Object newInstance(Object defaultInstance);
+}

+ 39 - 0
java/core/src/main/java/com/google/protobuf/NewInstanceSchemaFull.java

@@ -0,0 +1,39 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+final class NewInstanceSchemaFull implements NewInstanceSchema {
+  @Override
+  public Object newInstance(Object defaultInstance) {
+    return ((GeneratedMessageV3) defaultInstance)
+        .newInstance(GeneratedMessageV3.UnusedPrivateParameter.INSTANCE);
+  }
+}

+ 39 - 0
java/core/src/main/java/com/google/protobuf/NewInstanceSchemaLite.java

@@ -0,0 +1,39 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+final class NewInstanceSchemaLite implements NewInstanceSchema {
+  @Override
+  public Object newInstance(Object defaultInstance) {
+    return ((GeneratedMessageLite) defaultInstance)
+        .dynamicMethod(GeneratedMessageLite.MethodToInvoke.NEW_MUTABLE_INSTANCE);
+  }
+}

+ 53 - 0
java/core/src/main/java/com/google/protobuf/NewInstanceSchemas.java

@@ -0,0 +1,53 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+final class NewInstanceSchemas {
+  private static final NewInstanceSchema FULL_SCHEMA = loadSchemaForFullRuntime();
+  private static final NewInstanceSchema LITE_SCHEMA = new NewInstanceSchemaLite();
+
+  static NewInstanceSchema full() {
+    return FULL_SCHEMA;
+  }
+
+  static NewInstanceSchema lite() {
+    return LITE_SCHEMA;
+  }
+
+  private static NewInstanceSchema loadSchemaForFullRuntime() {
+    try {
+      Class<?> clazz = Class.forName("com.google.protobuf.NewInstanceSchemaFull");
+      return (NewInstanceSchema) clazz.getDeclaredConstructor().newInstance();
+    } catch (Exception e) {
+      return null;
+    }
+  }
+}

+ 66 - 0
java/core/src/main/java/com/google/protobuf/OneofInfo.java

@@ -0,0 +1,66 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.lang.reflect.Field;
+
+/** Information for a oneof within a protobuf message. */
+// TODO(nathanmittler): make this private once all of experimental code is migrated to protobuf.
+@ExperimentalApi
+final class OneofInfo {
+  private final int id;
+  private final Field caseField;
+  private final Field valueField;
+
+  public OneofInfo(int id, Field caseField, Field valueField) {
+    this.id = id;
+    this.caseField = caseField;
+    this.valueField = valueField;
+  }
+
+  /**
+   * Returns the unique identifier of the oneof within the message. This is really just an index
+   * starting at zero.
+   */
+  public int getId() {
+    return id;
+  }
+
+  /** The {@code int} field containing the field number of the currently active member. */
+  public Field getCaseField() {
+    return caseField;
+  }
+
+  /** The {@link Object} field containing the value of the currently active member. */
+  public Field getValueField() {
+    return valueField;
+  }
+}

+ 38 - 0
java/core/src/main/java/com/google/protobuf/ProtoSyntax.java

@@ -0,0 +1,38 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** Represents the syntax version of the message. */
+@ExperimentalApi
+public enum ProtoSyntax {
+  PROTO2,
+  PROTO3;
+}

+ 152 - 0
java/core/src/main/java/com/google/protobuf/Protobuf.java

@@ -0,0 +1,152 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+import java.io.IOException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Main runtime interface for protobuf. Applications should interact with this interface (rather
+ * than directly accessing internal APIs) in order to perform operations on protobuf messages.
+ */
+@ExperimentalApi
+final class Protobuf {
+  private static final Protobuf INSTANCE = new Protobuf();
+
+  private final SchemaFactory schemaFactory;
+
+  // TODO(nathanmittler): Consider using ClassValue instead.
+  private final ConcurrentMap<Class<?>, Schema<?>> schemaCache =
+      new ConcurrentHashMap<Class<?>, Schema<?>>();
+
+  /** Gets the singleton instance of the Protobuf runtime. */
+  public static Protobuf getInstance() {
+    return INSTANCE;
+  }
+
+  /** Writes the given message to the target {@link Writer}. */
+  public <T> void writeTo(T message, Writer writer) throws IOException {
+    schemaFor(message).writeTo(message, writer);
+  }
+
+  /** Reads fields from the given {@link Reader} and merges them into the message. */
+  public <T> void mergeFrom(T message, Reader reader) throws IOException {
+    mergeFrom(message, reader, ExtensionRegistryLite.getEmptyRegistry());
+  }
+
+  /** Reads fields from the given {@link Reader} and merges them into the message. */
+  public <T> void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    schemaFor(message).mergeFrom(message, reader, extensionRegistry);
+  }
+
+  /** Marks repeated/map/extension/unknown fields as immutable. */
+  public <T> void makeImmutable(T message) {
+    schemaFor(message).makeImmutable(message);
+  }
+
+  /**
+   * Checks if all required fields are set. TODO(xiaofeng): Make this package private when the tests
+   * are moved to protobuf package.
+   */
+  public <T> boolean isInitialized(T message) {
+    return schemaFor(message).isInitialized(message);
+  }
+
+  /** Gets the schema for the given message type. */
+  public <T> Schema<T> schemaFor(Class<T> messageType) {
+    checkNotNull(messageType, "messageType");
+    @SuppressWarnings("unchecked")
+    Schema<T> schema = (Schema<T>) schemaCache.get(messageType);
+    if (schema == null) {
+      schema = schemaFactory.createSchema(messageType);
+      @SuppressWarnings("unchecked")
+      Schema<T> previous = (Schema<T>) registerSchema(messageType, schema);
+      if (previous != null) {
+        // A new schema was registered by another thread.
+        schema = previous;
+      }
+    }
+    return schema;
+  }
+
+  /** Gets the schema for the given message. */
+  @SuppressWarnings("unchecked")
+  public <T> Schema<T> schemaFor(T message) {
+    return schemaFor((Class<T>) message.getClass());
+  }
+
+  /**
+   * Registers the given schema for the message type only if a schema was not already registered.
+   *
+   * @param messageType the type of message on which the schema operates.
+   * @param schema the schema for the message type.
+   * @return the previously registered schema, or {@code null} if the given schema was successfully
+   *     registered.
+   */
+  public Schema<?> registerSchema(Class<?> messageType, Schema<?> schema) {
+    checkNotNull(messageType, "messageType");
+    checkNotNull(schema, "schema");
+    return schemaCache.putIfAbsent(messageType, schema);
+  }
+
+  /**
+   * Visible for testing only. Registers the given schema for the message type. If a schema was
+   * previously registered, it will be replaced by the provided schema.
+   *
+   * @param messageType the type of message on which the schema operates.
+   * @param schema the schema for the message type.
+   * @return the previously registered schema, or {@code null} if no schema was registered
+   *     previously.
+   */
+  public Schema<?> registerSchemaOverride(Class<?> messageType, Schema<?> schema) {
+    checkNotNull(messageType, "messageType");
+    checkNotNull(schema, "schema");
+    return schemaCache.put(messageType, schema);
+  }
+
+  private Protobuf() {
+    schemaFactory = new ManifestSchemaFactory();
+  }
+
+  int getTotalSchemaSize() {
+    int result = 0;
+    for (Schema<?> schema : schemaCache.values()) {
+      if (schema instanceof MessageSchema) {
+        result += ((MessageSchema) schema).getSchemaSize();
+      }
+    }
+    return result;
+  }
+}

+ 94 - 0
java/core/src/main/java/com/google/protobuf/ProtobufLists.java

@@ -0,0 +1,94 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.Internal.BooleanList;
+import com.google.protobuf.Internal.DoubleList;
+import com.google.protobuf.Internal.FloatList;
+import com.google.protobuf.Internal.IntList;
+import com.google.protobuf.Internal.LongList;
+import com.google.protobuf.Internal.ProtobufList;
+
+/** Utility class for construction of lists that extend {@link ProtobufList}. */
+@ExperimentalApi
+final class ProtobufLists {
+  private ProtobufLists() {}
+
+  public static <E> ProtobufList<E> emptyProtobufList() {
+    return ProtobufArrayList.emptyList();
+  }
+
+  public static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) {
+    int size = list.size();
+    return list.mutableCopyWithCapacity(
+        size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
+  }
+
+  public static BooleanList emptyBooleanList() {
+    return BooleanArrayList.emptyList();
+  }
+
+  public static BooleanList newBooleanList() {
+    return new BooleanArrayList();
+  }
+
+  public static IntList emptyIntList() {
+    return IntArrayList.emptyList();
+  }
+
+  public static IntList newIntList() {
+    return new IntArrayList();
+  }
+
+  public static LongList emptyLongList() {
+    return LongArrayList.emptyList();
+  }
+
+  public static LongList newLongList() {
+    return new LongArrayList();
+  }
+
+  public static FloatList emptyFloatList() {
+    return FloatArrayList.emptyList();
+  }
+
+  public static FloatList newFloatList() {
+    return new FloatArrayList();
+  }
+
+  public static DoubleList emptyDoubleList() {
+    return DoubleArrayList.emptyList();
+  }
+
+  public static DoubleList newDoubleList() {
+    return new DoubleArrayList();
+  }
+}

+ 220 - 0
java/core/src/main/java/com/google/protobuf/RawMessageInfo.java

@@ -0,0 +1,220 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/**
+ * RawMessageInfo stores the same amount of information as {@link MessageInfo} but in a more compact
+ * format.
+ */
+final class RawMessageInfo implements MessageInfo {
+
+  private final MessageLite defaultInstance;
+
+  /**
+   * The compact format packs everything in a String object and a Object[] array. The String object
+   * is encoded with field number, field type, hasbits offset, oneof index, etc., whereas the
+   * Object[] array contains field references, class references, instance references, etc.
+   *
+   * <p>The String object encodes a sequence of integers into UTF-16 characters. For each int, it
+   * will be encoding into 1 to 3 UTF-16 characters depending on its unsigned value:
+   *
+   * <ul>
+   *   <li>1 char: [c1: 0x0000 - 0xD7FF] = int of the same value.
+   *   <li>2 chars: [c1: 0xE000 - 0xFFFF], [c2: 0x0000 - 0xD7FF] = (c2 << 13) | (c1 & 0x1FFF)
+   *   <li>3 chars: [c1: 0xE000 - 0xFFFF], [c2: 0xE000 - 0xFFFF], [c3: 0x0000 - 0xD7FF] = (c3 << 26)
+   *       | ((c2 & 0x1FFF) << 13) | (c1 & 0x1FFF)
+   * </ul>
+   *
+   * <p>Note that we don't use UTF-16 surrogate pairs [0xD800 - 0xDFFF] because they have to come in
+   * pairs to form a valid UTF-16char sequence and don't help us encode values more efficiently.
+   *
+   * <p>The integer sequence encoded in the String object has the following layout:
+   *
+   * <ul>
+   *   <li>[0]: flags, flags & 0x1 = is proto2?, flags & 0x2 = is message?.
+   *   <li>[1]: field count, if 0, this is the end of the integer sequence and the corresponding
+   *       Object[] array should be null.
+   *   <li>[2]: oneof count
+   *   <li>[3]: hasbits count, how many hasbits integers are generated.
+   *   <li>[4]: min field number
+   *   <li>[5]: max field number
+   *   <li>[6]: total number of entries need to allocate
+   *   <li>[7]: map field count
+   *   <li>[8]: repeated field count, this doesn't include map fields.
+   *   <li>[9]: size of checkInitialized array
+   *   <li>[...]: field entries
+   * </ul>
+   *
+   * <p>Each field entry starts with a field number and the field type:
+   *
+   * <ul>
+   *   <li>[0]: field number
+   *   <li>[1]: field type with extra bits:
+   *       <ul>
+   *         <li>v & 0xFF = field type as defined in the FieldType class
+   *         <li>v & 0x100 = is required?
+   *         <li>v & 0x200 = is checkUtf8?
+   *         <li>v & 0x400 = needs isInitialized check?
+   *         <li>v & 0x800 = is map field with proto2 enum value?
+   *       </ul>
+   * </ul>
+   *
+   * If the file is proto2 and this is a singular field:
+   *
+   * <ul>
+   *   <li>[2]: hasbits offset
+   * </ul>
+   *
+   * If the field is in an oneof:
+   *
+   * <ul>
+   *   <li>[2]: oenof index
+   * </ul>
+   *
+   * For other types, the field entry only has field number and field type.
+   *
+   * <p>The Object[] array has 3 sections:
+   *
+   * <ul>
+   *   <li>---- oneof section ----
+   *       <ul>
+   *         <li>[0]: value field for oneof 1.
+   *         <li>[1]: case field for oneof 1.
+   *         <li>...
+   *         <li>[.]: value field for oneof n.
+   *         <li>[.]: case field for oneof n.
+   *       </ul>
+   *   <li>---- hasbits section ----
+   *       <ul>
+   *         <li>[.]: hasbits field 1
+   *         <li>[.]: hasbits field 2
+   *         <li>...
+   *         <li>[.]: hasbits field n
+   *       </ul>
+   *   <li>---- field section ----
+   *       <ul>
+   *         <li>[...]: field entries
+   *       </ul>
+   * </ul>
+   *
+   * <p>In the Object[] array, field entries are ordered in the same way as field entries in the
+   * String object. The size of each entry is determined by the field type.
+   *
+   * <ul>
+   *   <li>Oneof field:
+   *       <ul>
+   *         <li>Oneof message field:
+   *             <ul>
+   *               <li>[0]: message class reference.
+   *             </ul>
+   *         <li>Oneof enum fieldin proto2:
+   *             <ul>
+   *               <li>[0]: EnumLiteMap
+   *             </ul>
+   *         <li>For all other oneof fields, field entry in the Object[] array is empty.
+   *       </ul>
+   *   <li>Repeated message field:
+   *       <ul>
+   *         <li>[0]: field reference
+   *         <li>[1]: message class reference
+   *       </ul>
+   *   <li>Proto2 singular/repeated enum field:
+   *       <ul>
+   *         <li>[0]: field reference
+   *         <li>[1]: EnumLiteMap
+   *       </ul>
+   *   <li>Map field with a proto2 enum value:
+   *       <ul>
+   *         <li>[0]: field reference
+   *         <li>[1]: map default entry instance
+   *         <li>[2]: EnumLiteMap
+   *       </ul>
+   *   <li>Map field with other value types:
+   *       <ul>
+   *         <li>[0]: field reference
+   *         <li>[1]: map default entry instance
+   *       </ul>
+   *   <li>All other field type:
+   *       <ul>
+   *         <li>[0]: field reference
+   *       </ul>
+   * </ul>
+   *
+   * <p>In order to read the field info from this compact format, a reader needs to progress through
+   * the String object and the Object[] array simultaneously.
+   */
+  private final String info;
+
+  private final Object[] objects;
+  private final int flags;
+
+  RawMessageInfo(MessageLite defaultInstance, String info, Object[] objects) {
+    this.defaultInstance = defaultInstance;
+    this.info = info;
+    this.objects = objects;
+    int position = 0;
+    int value = (int) info.charAt(position++);
+    if (value < 0xD800) {
+      flags = value;
+    } else {
+      int result = value & 0x1FFF;
+      int shift = 13;
+      while ((value = info.charAt(position++)) >= 0xD800) {
+        result |= (value & 0x1FFF) << shift;
+        shift += 13;
+      }
+      flags = result | (value << shift);
+    }
+  }
+
+  String getStringInfo() {
+    return info;
+  }
+
+  Object[] getObjects() {
+    return objects;
+  }
+
+  @Override
+  public MessageLite getDefaultInstance() {
+    return defaultInstance;
+  }
+
+  @Override
+  public ProtoSyntax getSyntax() {
+    return (flags & 0x1) == 0x1 ? ProtoSyntax.PROTO2 : ProtoSyntax.PROTO3;
+  }
+
+  @Override
+  public boolean isMessageSetWireFormat() {
+    return (flags & 0x2) == 0x2;
+  }
+}

+ 379 - 0
java/core/src/main/java/com/google/protobuf/Reader.java

@@ -0,0 +1,379 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/** A reader of fields from a serialized protobuf message. */
+// TODO(nathanmittler): Refactor to allow the reader to allocate properly sized lists.
+@ExperimentalApi
+interface Reader {
+  /** Value used to indicate that the end of input has been reached. */
+  int READ_DONE = Integer.MAX_VALUE;
+
+  /** Value used to indicate that the reader does not know the tag about the field. */
+  int TAG_UNKNOWN = 0;
+
+  boolean shouldDiscardUnknownFields();
+
+  /**
+   * Gets the field number for the current field being read.
+   *
+   * <p>TODO(liujisi): Rename it to make it more explicit about the side effect on the underlying
+   * buffer.
+   *
+   * @return the current field number or {@link #READ_DONE} if the end of input has been reached.
+   */
+  int getFieldNumber() throws IOException;
+
+  /**
+   * Gets the wire tag of the current field.
+   *
+   * @return the current wire tag or {@link #TAG_UNKNOWN} if the reader does not know the tag of the
+   *     current field.
+   */
+  int getTag();
+
+  /**
+   * Skips the current field and advances the reader to the next field.
+   *
+   * @return {@code true} if there are more fields or {@code false} if the end of input has been
+   *     reached.
+   */
+  boolean skipField() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code DOUBLE} and advances the reader to the next
+   * field.
+   */
+  double readDouble() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code FLOAT} and advances the reader to the next
+   * field.
+   */
+  float readFloat() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code UINT64} and advances the reader to the next
+   * field.
+   */
+  long readUInt64() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code INT64} and advances the reader to the next
+   * field.
+   */
+  long readInt64() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code INT32} and advances the reader to the next
+   * field.
+   */
+  int readInt32() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code FIXED64} and advances the reader to the next
+   * field.
+   */
+  long readFixed64() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code FIXED32} and advances the reader to the next
+   * field.
+   */
+  int readFixed32() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code BOOL} and advances the reader to the next
+   * field.
+   */
+  boolean readBool() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code STRING} and advances the reader to the next
+   * field. If the stream contains malformed UTF-8, replace the offending bytes with the standard
+   * UTF-8 replacement character.
+   */
+  String readString() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code STRING} and advances the reader to the next
+   * field. If the stream contains malformed UTF-8, throw exception {@link
+   * InvalidProtocolBufferException}.
+   */
+  String readStringRequireUtf8() throws IOException;
+
+  // TODO(yilunchong): the lack of other opinions for whether to expose this on the interface
+  <T> T readMessageBySchemaWithCheck(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code MESSAGE} and advances the reader to the next
+   * field.
+   */
+  <T> T readMessage(Class<T> clazz, ExtensionRegistryLite extensionRegistry) throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code GROUP} and advances the reader to the next
+   * field.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  <T> T readGroup(Class<T> clazz, ExtensionRegistryLite extensionRegistry) throws IOException;
+
+  // TODO(yilunchong): the lack of other opinions for whether to expose this on the interface
+  @Deprecated
+  <T> T readGroupBySchemaWithCheck(Schema<T> schema, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code BYTES} and advances the reader to the next
+   * field.
+   */
+  ByteString readBytes() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code UINT32} and advances the reader to the next
+   * field.
+   */
+  int readUInt32() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code ENUM} and advances the reader to the next
+   * field.
+   */
+  int readEnum() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code SFIXED32} and advances the reader to the next
+   * field.
+   */
+  int readSFixed32() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code SFIXED64} and advances the reader to the next
+   * field.
+   */
+  long readSFixed64() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code SINT32} and advances the reader to the next
+   * field.
+   */
+  int readSInt32() throws IOException;
+
+  /**
+   * Reads and returns the next field of type {@code SINT64} and advances the reader to the next
+   * field.
+   */
+  long readSInt64() throws IOException;
+
+  /**
+   * Reads the next field of type {@code DOUBLE_LIST} or {@code DOUBLE_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readDoubleList(List<Double> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code FLOAT_LIST} or {@code FLOAT_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readFloatList(List<Float> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code UINT64_LIST} or {@code UINT64_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readUInt64List(List<Long> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code INT64_LIST} or {@code INT64_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readInt64List(List<Long> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code INT32_LIST} or {@code INT32_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readInt32List(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code FIXED64_LIST} or {@code FIXED64_LIST_PACKED} and advances
+   * the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readFixed64List(List<Long> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code FIXED32_LIST} or {@code FIXED32_LIST_PACKED} and advances
+   * the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readFixed32List(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code BOOL_LIST} or {@code BOOL_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readBoolList(List<Boolean> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code STRING_LIST} and advances the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readStringList(List<String> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code STRING_LIST} and advances the reader to the next field. If
+   * the stream contains malformed UTF-8, throw exception {@link InvalidProtocolBufferException}.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readStringListRequireUtf8(List<String> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code MESSAGE_LIST} and advances the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   * @param targetType the type of the elements stored in the {@code target} list.
+   */
+  <T> void readMessageList(
+      List<T> target, Schema<T> schema, ExtensionRegistryLite extensionRegistry) throws IOException;
+
+  <T> void readMessageList(
+      List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  /**
+   * Reads the next field of type {@code GROUP_LIST} and advances the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   * @param targetType the type of the elements stored in the {@code target} list.
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  <T> void readGroupList(
+      List<T> target, Class<T> targetType, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  @Deprecated
+  <T> void readGroupList(
+      List<T> target, Schema<T> targetType, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  /**
+   * Reads the next field of type {@code BYTES_LIST} and advances the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readBytesList(List<ByteString> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code UINT32_LIST} or {@code UINT32_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readUInt32List(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code ENUM_LIST} or {@code ENUM_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readEnumList(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code SFIXED32_LIST} or {@code SFIXED32_LIST_PACKED} and advances
+   * the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readSFixed32List(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code SFIXED64_LIST} or {@code SFIXED64_LIST_PACKED} and advances
+   * the reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readSFixed64List(List<Long> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code SINT32_LIST} or {@code SINT32_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readSInt32List(List<Integer> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code SINT64_LIST} or {@code SINT64_LIST_PACKED} and advances the
+   * reader to the next field.
+   *
+   * @param target the list that will receive the read values.
+   */
+  void readSInt64List(List<Long> target) throws IOException;
+
+  /**
+   * Reads the next field of type {@code MAP} and advances the reader to the next field.
+   *
+   * @param target the mutable map that will receive the read values.
+   * @param mapDefaultEntry the default entry of the map field.
+   * @param extensionRegistry the extension registry for parsing message value fields.
+   */
+  <K, V> void readMap(
+      Map<K, V> target,
+      MapEntryLite.Metadata<K, V> mapDefaultEntry,
+      ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+}

+ 5 - 0
java/core/src/main/java/com/google/protobuf/RopeByteString.java

@@ -445,6 +445,11 @@ final class RopeByteString extends ByteString {
     right.writeTo(output);
     right.writeTo(output);
   }
   }
 
 
+  @Override
+  void writeToReverse(ByteOutput output) throws IOException {
+    right.writeToReverse(output);
+    left.writeToReverse(output);
+  }
 
 
   @Override
   @Override
   protected String toStringInternal(Charset charset) {
   protected String toStringInternal(Charset charset) {

+ 85 - 0
java/core/src/main/java/com/google/protobuf/Schema.java

@@ -0,0 +1,85 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.ArrayDecoders.Registers;
+import java.io.IOException;
+
+/**
+ * A runtime schema for a single protobuf message. A schema provides operations on message instances
+ * such as serialization/deserialization.
+ */
+@ExperimentalApi
+interface Schema<T> {
+  /** Writes the given message to the target {@link Writer}. */
+  void writeTo(T message, Writer writer) throws IOException;
+
+  /**
+   * Reads fields from the given {@link Reader} and merges them into the message. It doesn't make
+   * the message immutable after parsing is done. To make the message immutable, use {@link
+   * #makeImmutable}.
+   */
+  void mergeFrom(T message, Reader reader, ExtensionRegistryLite extensionRegistry)
+      throws IOException;
+
+  /**
+   * Like the above but parses from a byte[] without extensions. Entry point of fast path. Note that
+   * this method may throw IndexOutOfBoundsException if the input data is not valid protobuf wire
+   * format. Protobuf public API methods should catch and convert that exception to
+   * InvalidProtocolBufferException.
+   */
+  void mergeFrom(T message, byte[] data, int position, int limit, Registers registers)
+      throws IOException;
+
+  /** Marks repeated/map/extension/unknown fields as immutable. */
+  void makeImmutable(T message);
+
+  /** Checks whether all required fields are set. */
+  boolean isInitialized(T message);
+
+  /** Creates a new instance of the message class. */
+  T newInstance();
+
+  /** Determine of the two messages are equal. */
+  boolean equals(T message, T other);
+
+  /** Compute a hashCode for the message. */
+  int hashCode(T message);
+
+  /**
+   * Merge values from {@code other} into {@code message}. This method doesn't make the message
+   * immutable. To make the message immutable after merging, use {@link #makeImmutable}.
+   */
+  void mergeFrom(T message, T other);
+
+  /** Compute the serialized size of the message. */
+  int getSerializedSize(T message);
+}

+ 38 - 0
java/core/src/main/java/com/google/protobuf/SchemaFactory.java

@@ -0,0 +1,38 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** A factory that manufactures {@link Schema} instances for protobuf messages. */
+@ExperimentalApi
+interface SchemaFactory {
+  /** Creates a schema instance for the given protobuf message type. */
+  <T> Schema<T> createSchema(Class<T> messageType);
+}

+ 991 - 0
java/core/src/main/java/com/google/protobuf/SchemaUtil.java

@@ -0,0 +1,991 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.FieldSet.FieldDescriptorLite;
+import com.google.protobuf.Internal.EnumLiteMap;
+import com.google.protobuf.Internal.EnumVerifier;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.Iterator;
+import java.util.List;
+import java.util.RandomAccess;
+
+/** Helper methods used by schemas. */
+@ExperimentalApi
+final class SchemaUtil {
+  private static final Class<?> GENERATED_MESSAGE_CLASS = getGeneratedMessageClass();
+  private static final UnknownFieldSchema<?, ?> PROTO2_UNKNOWN_FIELD_SET_SCHEMA =
+      getUnknownFieldSetSchema(false);
+  private static final UnknownFieldSchema<?, ?> PROTO3_UNKNOWN_FIELD_SET_SCHEMA =
+      getUnknownFieldSetSchema(true);
+  private static final UnknownFieldSchema<?, ?> UNKNOWN_FIELD_SET_LITE_SCHEMA =
+      new UnknownFieldSetLiteSchema();
+
+  private static final int DEFAULT_LOOK_UP_START_NUMBER = 40;
+
+  private SchemaUtil() {}
+
+  /**
+   * Requires that the given message extend {@link com.google.protobuf.GeneratedMessageV3} or {@link
+   * GeneratedMessageLite}.
+   */
+  public static void requireGeneratedMessage(Class<?> messageType) {
+    if (!GeneratedMessageLite.class.isAssignableFrom(messageType)
+        && GENERATED_MESSAGE_CLASS != null
+        && !GENERATED_MESSAGE_CLASS.isAssignableFrom(messageType)) {
+      throw new IllegalArgumentException(
+          "Message classes must extend GeneratedMessage or GeneratedMessageLite");
+    }
+  }
+
+  public static void writeDouble(int fieldNumber, double value, Writer writer) throws IOException {
+    if (Double.compare(value, 0.0) != 0) {
+      writer.writeDouble(fieldNumber, value);
+    }
+  }
+
+  public static void writeFloat(int fieldNumber, float value, Writer writer) throws IOException {
+    if (Float.compare(value, 0.0f) != 0) {
+      writer.writeFloat(fieldNumber, value);
+    }
+  }
+
+  public static void writeInt64(int fieldNumber, long value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeUInt64(int fieldNumber, long value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeUInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeSInt64(int fieldNumber, long value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeSInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeFixed64(int fieldNumber, long value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeFixed64(fieldNumber, value);
+    }
+  }
+
+  public static void writeSFixed64(int fieldNumber, long value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeSFixed64(fieldNumber, value);
+    }
+  }
+
+  public static void writeInt32(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeUInt32(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeUInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeSInt32(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeSInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeFixed32(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeFixed32(fieldNumber, value);
+    }
+  }
+
+  public static void writeSFixed32(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeSFixed32(fieldNumber, value);
+    }
+  }
+
+  public static void writeEnum(int fieldNumber, int value, Writer writer) throws IOException {
+    if (value != 0) {
+      writer.writeEnum(fieldNumber, value);
+    }
+  }
+
+  public static void writeBool(int fieldNumber, boolean value, Writer writer) throws IOException {
+    if (value) {
+      writer.writeBool(fieldNumber, true);
+    }
+  }
+
+  public static void writeString(int fieldNumber, Object value, Writer writer) throws IOException {
+    if (value instanceof String) {
+      writeStringInternal(fieldNumber, (String) value, writer);
+    } else {
+      writeBytes(fieldNumber, (ByteString) value, writer);
+    }
+  }
+
+  private static void writeStringInternal(int fieldNumber, String value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeString(fieldNumber, value);
+    }
+  }
+
+  public static void writeBytes(int fieldNumber, ByteString value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBytes(fieldNumber, value);
+    }
+  }
+
+  public static void writeMessage(int fieldNumber, Object value, Writer writer) throws IOException {
+    if (value != null) {
+      writer.writeMessage(fieldNumber, value);
+    }
+  }
+
+  public static void writeDoubleList(
+      int fieldNumber, List<Double> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeDoubleList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFloatList(
+      int fieldNumber, List<Float> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFloatList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeUInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeUInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFixed64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFixed64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSFixed64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSFixed64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeUInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeUInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFixed32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFixed32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSFixed32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSFixed32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeEnumList(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeEnumList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeBoolList(
+      int fieldNumber, List<Boolean> value, Writer writer, boolean packed) throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBoolList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeStringList(int fieldNumber, List<String> value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeStringList(fieldNumber, value);
+    }
+  }
+
+  public static void writeBytesList(int fieldNumber, List<ByteString> value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBytesList(fieldNumber, value);
+    }
+  }
+
+  public static void writeMessageList(int fieldNumber, List<?> value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeMessageList(fieldNumber, value);
+    }
+  }
+
+  public static void writeMessageList(int fieldNumber, List<?> value, Writer writer, Schema schema)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeMessageList(fieldNumber, value, schema);
+    }
+  }
+
+  public static void writeLazyFieldList(int fieldNumber, List<?> value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      for (Object item : value) {
+        ((LazyFieldLite) item).writeTo(writer, fieldNumber);
+      }
+    }
+  }
+
+  public static void writeGroupList(int fieldNumber, List<?> value, Writer writer)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeGroupList(fieldNumber, value);
+    }
+  }
+
+  public static void writeGroupList(int fieldNumber, List<?> value, Writer writer, Schema schema)
+      throws IOException {
+    if (value != null && !value.isEmpty()) {
+      writer.writeGroupList(fieldNumber, value, schema);
+    }
+  }
+
+  static int computeSizeInt64ListNoTag(List<Long> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof LongArrayList) {
+      final LongArrayList primitiveList = (LongArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeInt64SizeNoTag(primitiveList.getLong(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeInt64SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeInt64List(int fieldNumber, List<Long> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeInt64ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (list.size() * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeUInt64ListNoTag(List<Long> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof LongArrayList) {
+      final LongArrayList primitiveList = (LongArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeUInt64SizeNoTag(primitiveList.getLong(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeUInt64SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeUInt64List(int fieldNumber, List<Long> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeUInt64ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeSInt64ListNoTag(List<Long> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof LongArrayList) {
+      final LongArrayList primitiveList = (LongArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeSInt64SizeNoTag(primitiveList.getLong(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeSInt64SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeSInt64List(int fieldNumber, List<Long> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeSInt64ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeEnumListNoTag(List<Integer> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof IntArrayList) {
+      final IntArrayList primitiveList = (IntArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeEnumSizeNoTag(primitiveList.getInt(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeEnumSizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeEnumList(int fieldNumber, List<Integer> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeEnumListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeInt32ListNoTag(List<Integer> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof IntArrayList) {
+      final IntArrayList primitiveList = (IntArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeInt32SizeNoTag(primitiveList.getInt(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeInt32SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeInt32List(int fieldNumber, List<Integer> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeInt32ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeUInt32ListNoTag(List<Integer> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof IntArrayList) {
+      final IntArrayList primitiveList = (IntArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeUInt32SizeNoTag(primitiveList.getInt(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeUInt32SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeUInt32List(int fieldNumber, List<Integer> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = computeSizeUInt32ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeSInt32ListNoTag(List<Integer> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = 0;
+
+    if (list instanceof IntArrayList) {
+      final IntArrayList primitiveList = (IntArrayList) list;
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeSInt32SizeNoTag(primitiveList.getInt(i));
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        size += CodedOutputStream.computeSInt32SizeNoTag(list.get(i));
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeSInt32List(int fieldNumber, List<Integer> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+
+    int size = computeSizeSInt32ListNoTag(list);
+
+    if (packed) {
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(size);
+    } else {
+      return size + (length * CodedOutputStream.computeTagSize(fieldNumber));
+    }
+  }
+
+  static int computeSizeFixed32ListNoTag(List<?> list) {
+    return list.size() * WireFormat.FIXED32_SIZE;
+  }
+
+  static int computeSizeFixed32List(int fieldNumber, List<?> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    if (packed) {
+      int dataSize = length * WireFormat.FIXED32_SIZE;
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(dataSize);
+    } else {
+      return length * CodedOutputStream.computeFixed32Size(fieldNumber, 0);
+    }
+  }
+
+  static int computeSizeFixed64ListNoTag(List<?> list) {
+    return list.size() * WireFormat.FIXED64_SIZE;
+  }
+
+  static int computeSizeFixed64List(int fieldNumber, List<?> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    if (packed) {
+      final int dataSize = length * WireFormat.FIXED64_SIZE;
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(dataSize);
+    } else {
+      return length * CodedOutputStream.computeFixed64Size(fieldNumber, 0);
+    }
+  }
+
+  static int computeSizeBoolListNoTag(List<?> list) {
+    // bools are 1 byte varints
+    return list.size();
+  }
+
+  static int computeSizeBoolList(int fieldNumber, List<?> list, boolean packed) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    if (packed) {
+      // bools are 1 byte varints
+      return CodedOutputStream.computeTagSize(fieldNumber)
+          + CodedOutputStream.computeLengthDelimitedFieldSize(length);
+    } else {
+      return length * CodedOutputStream.computeBoolSize(fieldNumber, true);
+    }
+  }
+
+  static int computeSizeStringList(int fieldNumber, List<?> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = length * CodedOutputStream.computeTagSize(fieldNumber);
+    if (list instanceof LazyStringList) {
+      LazyStringList lazyList = ((LazyStringList) list);
+      for (int i = 0; i < length; i++) {
+        Object value = lazyList.getRaw(i);
+        if (value instanceof ByteString) {
+          size += CodedOutputStream.computeBytesSizeNoTag((ByteString) value);
+        } else {
+          size += CodedOutputStream.computeStringSizeNoTag((String) value);
+        }
+      }
+    } else {
+      for (int i = 0; i < length; i++) {
+        Object value = list.get(i);
+        if (value instanceof ByteString) {
+          size += CodedOutputStream.computeBytesSizeNoTag((ByteString) value);
+        } else {
+          size += CodedOutputStream.computeStringSizeNoTag((String) value);
+        }
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeMessage(int fieldNumber, Object value, Schema schema) {
+    if (value instanceof LazyFieldLite) {
+      return CodedOutputStream.computeLazyFieldSize(fieldNumber, (LazyFieldLite) value);
+    } else {
+      return CodedOutputStream.computeMessageSize(fieldNumber, (MessageLite) value, schema);
+    }
+  }
+
+  static int computeSizeMessageList(int fieldNumber, List<?> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = length * CodedOutputStream.computeTagSize(fieldNumber);
+    for (int i = 0; i < length; i++) {
+      Object value = list.get(i);
+      if (value instanceof LazyFieldLite) {
+        size += CodedOutputStream.computeLazyFieldSizeNoTag((LazyFieldLite) value);
+      } else {
+        size += CodedOutputStream.computeMessageSizeNoTag((MessageLite) value);
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeMessageList(int fieldNumber, List<?> list, Schema schema) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = length * CodedOutputStream.computeTagSize(fieldNumber);
+    for (int i = 0; i < length; i++) {
+      Object value = list.get(i);
+      if (value instanceof LazyFieldLite) {
+        size += CodedOutputStream.computeLazyFieldSizeNoTag((LazyFieldLite) value);
+      } else {
+        size += CodedOutputStream.computeMessageSizeNoTag((MessageLite) value, schema);
+      }
+    }
+    return size;
+  }
+
+  static int computeSizeByteStringList(int fieldNumber, List<ByteString> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = length * CodedOutputStream.computeTagSize(fieldNumber);
+    for (int i = 0; i < list.size(); i++) {
+      size += CodedOutputStream.computeBytesSizeNoTag(list.get(i));
+    }
+    return size;
+  }
+
+  static int computeSizeGroupList(int fieldNumber, List<MessageLite> list) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = 0;
+    for (int i = 0; i < length; i++) {
+      size += CodedOutputStream.computeGroupSize(fieldNumber, list.get(i));
+    }
+    return size;
+  }
+
+  static int computeSizeGroupList(int fieldNumber, List<MessageLite> list, Schema schema) {
+    final int length = list.size();
+    if (length == 0) {
+      return 0;
+    }
+    int size = 0;
+    for (int i = 0; i < length; i++) {
+      size += CodedOutputStream.computeGroupSize(fieldNumber, list.get(i), schema);
+    }
+    return size;
+  }
+
+  /**
+   * Determines whether to issue tableswitch or lookupswitch for the mergeFrom method.
+   *
+   * @see #shouldUseTableSwitch(int, int, int)
+   */
+  public static boolean shouldUseTableSwitch(FieldInfo[] fields) {
+    // Determine whether to issue a tableswitch or a lookupswitch
+    // instruction.
+    if (fields.length == 0) {
+      return false;
+    }
+
+    int lo = fields[0].getFieldNumber();
+    int hi = fields[fields.length - 1].getFieldNumber();
+    return shouldUseTableSwitch(lo, hi, fields.length);
+  }
+
+  /**
+   * Determines whether to issue tableswitch or lookupswitch for the mergeFrom method. This is based
+   * on the <a href=
+   * "http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/30db5e0aaf83/src/share/classes/com/sun/tools/javac/jvm/Gen.java#l1159">
+   * logic in the JDK</a>.
+   *
+   * @param lo the lowest fieldNumber contained within the message.
+   * @param hi the higest fieldNumber contained within the message.
+   * @param numFields the total number of fields in the message.
+   * @return {@code true} if tableswitch should be used, rather than lookupswitch.
+   */
+  public static boolean shouldUseTableSwitch(int lo, int hi, int numFields) {
+    if (hi < DEFAULT_LOOK_UP_START_NUMBER) {
+      return true;
+    }
+    long tableSpaceCost = ((long) hi - lo + 1); // words
+    long tableTimeCost = 3; // comparisons
+    long lookupSpaceCost = 3 + 2 * (long) numFields;
+    long lookupTimeCost = 3 + (long) numFields;
+    return tableSpaceCost + 3 * tableTimeCost <= lookupSpaceCost + 3 * lookupTimeCost;
+  }
+
+  public static UnknownFieldSchema<?, ?> proto2UnknownFieldSetSchema() {
+    return PROTO2_UNKNOWN_FIELD_SET_SCHEMA;
+  }
+
+  public static UnknownFieldSchema<?, ?> proto3UnknownFieldSetSchema() {
+    return PROTO3_UNKNOWN_FIELD_SET_SCHEMA;
+  }
+
+  public static UnknownFieldSchema<?, ?> unknownFieldSetLiteSchema() {
+    return UNKNOWN_FIELD_SET_LITE_SCHEMA;
+  }
+
+  private static UnknownFieldSchema<?, ?> getUnknownFieldSetSchema(boolean proto3) {
+    try {
+      Class<?> clz = getUnknownFieldSetSchemaClass();
+      if (clz == null) {
+        return null;
+      }
+      return (UnknownFieldSchema) clz.getConstructor(boolean.class).newInstance(proto3);
+    } catch (Throwable t) {
+      return null;
+    }
+  }
+
+  private static Class<?> getGeneratedMessageClass() {
+    try {
+      return Class.forName("com.google.protobuf.GeneratedMessageV3");
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  private static Class<?> getUnknownFieldSetSchemaClass() {
+    try {
+      return Class.forName("com.google.protobuf.UnknownFieldSetSchema");
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  static Object getMapDefaultEntry(Class<?> clazz, String name) {
+    try {
+      Class<?> holder =
+          Class.forName(clazz.getName() + "$" + toCamelCase(name, true) + "DefaultEntryHolder");
+      Field[] fields = holder.getDeclaredFields();
+      if (fields.length != 1) {
+        throw new IllegalStateException(
+            "Unable to look up map field default entry holder class for "
+                + name
+                + " in "
+                + clazz.getName());
+      }
+      return UnsafeUtil.getStaticObject(fields[0]);
+    } catch (Throwable t) {
+      throw new RuntimeException(t);
+    }
+  }
+
+  static String toCamelCase(String name, boolean capNext) {
+    StringBuilder sb = new StringBuilder();
+    for (int i = 0; i < name.length(); ++i) {
+      char c = name.charAt(i);
+      // Matches protoc field name function:
+      if ('a' <= c && c <= 'z') {
+        if (capNext) {
+          sb.append((char) (c + ('A' - 'a')));
+        } else {
+          sb.append(c);
+        }
+        capNext = false;
+      } else if ('A' <= c && c <= 'Z') {
+        if (i == 0 && !capNext) {
+          // Force first letter to lower-case unless explicitly told to capitalize it.
+          sb.append((char) (c - ('A' - 'a')));
+        } else {
+          sb.append(c);
+        }
+        capNext = false;
+      } else if ('0' <= c && c <= '9') {
+        sb.append(c);
+        capNext = true;
+      } else {
+        capNext = true;
+      }
+    }
+    return sb.toString();
+  }
+
+  /** Returns true if both are null or both are {@link Object#equals}. */
+  static boolean safeEquals(Object a, Object b) {
+    return a == b || (a != null && a.equals(b));
+  }
+
+  static <T> void mergeMap(MapFieldSchema mapFieldSchema, T message, T o, long offset) {
+    Object merged =
+        mapFieldSchema.mergeFrom(
+            UnsafeUtil.getObject(message, offset), UnsafeUtil.getObject(o, offset));
+    UnsafeUtil.putObject(message, offset, merged);
+  }
+
+  static <T, FT extends FieldDescriptorLite<FT>> void mergeExtensions(
+      ExtensionSchema<FT> schema, T message, T other) {
+    FieldSet<FT> otherExtensions = schema.getExtensions(other);
+    if (!otherExtensions.isEmpty()) {
+      FieldSet<FT> messageExtensions = schema.getMutableExtensions(message);
+      messageExtensions.mergeFrom(otherExtensions);
+    }
+  }
+
+  static <T, UT, UB> void mergeUnknownFields(
+      UnknownFieldSchema<UT, UB> schema, T message, T other) {
+    UT messageUnknowns = schema.getFromMessage(message);
+    UT otherUnknowns = schema.getFromMessage(other);
+    UT merged = schema.merge(messageUnknowns, otherUnknowns);
+    schema.setToMessage(message, merged);
+  }
+
+  /** Filters unrecognized enum values in a list. */
+  static <UT, UB> UB filterUnknownEnumList(
+      int number,
+      List<Integer> enumList,
+      EnumLiteMap<?> enumMap,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema) {
+    if (enumMap == null) {
+      return unknownFields;
+    }
+    // TODO(dweis): Specialize for IntArrayList to avoid boxing.
+    if (enumList instanceof RandomAccess) {
+      int writePos = 0;
+      int size = enumList.size();
+      for (int readPos = 0; readPos < size; ++readPos) {
+        int enumValue = enumList.get(readPos);
+        if (enumMap.findValueByNumber(enumValue) != null) {
+          if (readPos != writePos) {
+            enumList.set(writePos, enumValue);
+          }
+          ++writePos;
+        } else {
+          unknownFields = storeUnknownEnum(number, enumValue, unknownFields, unknownFieldSchema);
+        }
+      }
+      if (writePos != size) {
+        enumList.subList(writePos, size).clear();
+      }
+    } else {
+      for (Iterator<Integer> it = enumList.iterator(); it.hasNext(); ) {
+        int enumValue = it.next();
+        if (enumMap.findValueByNumber(enumValue) == null) {
+          unknownFields = storeUnknownEnum(number, enumValue, unknownFields, unknownFieldSchema);
+          it.remove();
+        }
+      }
+    }
+    return unknownFields;
+  }
+
+  /** Filters unrecognized enum values in a list. */
+  static <UT, UB> UB filterUnknownEnumList(
+      int number,
+      List<Integer> enumList,
+      EnumVerifier enumVerifier,
+      UB unknownFields,
+      UnknownFieldSchema<UT, UB> unknownFieldSchema) {
+    if (enumVerifier == null) {
+      return unknownFields;
+    }
+    // TODO(dweis): Specialize for IntArrayList to avoid boxing.
+    if (enumList instanceof RandomAccess) {
+      int writePos = 0;
+      int size = enumList.size();
+      for (int readPos = 0; readPos < size; ++readPos) {
+        int enumValue = enumList.get(readPos);
+        if (enumVerifier.isInRange(enumValue)) {
+          if (readPos != writePos) {
+            enumList.set(writePos, enumValue);
+          }
+          ++writePos;
+        } else {
+          unknownFields = storeUnknownEnum(number, enumValue, unknownFields, unknownFieldSchema);
+        }
+      }
+      if (writePos != size) {
+        enumList.subList(writePos, size).clear();
+      }
+    } else {
+      for (Iterator<Integer> it = enumList.iterator(); it.hasNext(); ) {
+        int enumValue = it.next();
+        if (!enumVerifier.isInRange(enumValue)) {
+          unknownFields = storeUnknownEnum(number, enumValue, unknownFields, unknownFieldSchema);
+          it.remove();
+        }
+      }
+    }
+    return unknownFields;
+  }
+
+  /** Stores an unrecognized enum value as an unknown value. */
+  static <UT, UB> UB storeUnknownEnum(
+      int number, int enumValue, UB unknownFields, UnknownFieldSchema<UT, UB> unknownFieldSchema) {
+    if (unknownFields == null) {
+      unknownFields = unknownFieldSchema.newBuilder();
+    }
+    unknownFieldSchema.addVarint(unknownFields, number, enumValue);
+    return unknownFields;
+  }
+}

+ 65 - 0
java/core/src/main/java/com/google/protobuf/SmallSortedMap.java

@@ -136,6 +136,8 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
   // The EntrySet is a stateless view of the Map. It's initialized the first
   // The EntrySet is a stateless view of the Map. It's initialized the first
   // time it is requested and reused henceforth.
   // time it is requested and reused henceforth.
   private volatile EntrySet lazyEntrySet;
   private volatile EntrySet lazyEntrySet;
+  private Map<K, V> overflowEntriesDescending;
+  private volatile DescendingEntrySet lazyDescendingEntrySet;
 
 
   /**
   /**
    * @code arraySize Size of the array in which the lexicographically smallest mappings are stored.
    * @code arraySize Size of the array in which the lexicographically smallest mappings are stored.
@@ -145,6 +147,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
     this.maxArraySize = arraySize;
     this.maxArraySize = arraySize;
     this.entryList = Collections.emptyList();
     this.entryList = Collections.emptyList();
     this.overflowEntries = Collections.emptyMap();
     this.overflowEntries = Collections.emptyMap();
+    this.overflowEntriesDescending = Collections.emptyMap();
   }
   }
 
 
   /** Make this map immutable from this point forward. */
   /** Make this map immutable from this point forward. */
@@ -158,6 +161,10 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
           overflowEntries.isEmpty()
           overflowEntries.isEmpty()
               ? Collections.<K, V>emptyMap()
               ? Collections.<K, V>emptyMap()
               : Collections.unmodifiableMap(overflowEntries);
               : Collections.unmodifiableMap(overflowEntries);
+      overflowEntriesDescending =
+          overflowEntriesDescending.isEmpty()
+              ? Collections.<K, V>emptyMap()
+              : Collections.unmodifiableMap(overflowEntriesDescending);
       isImmutable = true;
       isImmutable = true;
     }
     }
   }
   }
@@ -189,6 +196,11 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
         : overflowEntries.entrySet();
         : overflowEntries.entrySet();
   }
   }
 
 
+  Iterable<Map.Entry<K, V>> getOverflowEntriesDescending() {
+    return overflowEntriesDescending.isEmpty()
+        ? EmptySet.<Map.Entry<K, V>>iterable()
+        : overflowEntriesDescending.entrySet();
+  }
 
 
   @Override
   @Override
   public int size() {
   public int size() {
@@ -344,6 +356,12 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
     return lazyEntrySet;
     return lazyEntrySet;
   }
   }
 
 
+  Set<Map.Entry<K, V>> descendingEntrySet() {
+    if (lazyDescendingEntrySet == null) {
+      lazyDescendingEntrySet = new DescendingEntrySet();
+    }
+    return lazyDescendingEntrySet;
+  }
 
 
   /** @throws UnsupportedOperationException if {@link #makeImmutable()} has has been called. */
   /** @throws UnsupportedOperationException if {@link #makeImmutable()} has has been called. */
   private void checkMutable() {
   private void checkMutable() {
@@ -361,6 +379,7 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
     checkMutable();
     checkMutable();
     if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) {
     if (overflowEntries.isEmpty() && !(overflowEntries instanceof TreeMap)) {
       overflowEntries = new TreeMap<K, V>();
       overflowEntries = new TreeMap<K, V>();
+      overflowEntriesDescending = ((TreeMap<K, V>) overflowEntries).descendingMap();
     }
     }
     return (SortedMap<K, V>) overflowEntries;
     return (SortedMap<K, V>) overflowEntries;
   }
   }
@@ -501,6 +520,12 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
     }
     }
   }
   }
 
 
+  private class DescendingEntrySet extends EntrySet {
+    @Override
+    public Iterator<java.util.Map.Entry<K, V>> iterator() {
+      return new DescendingEntryIterator();
+    }
+  }
 
 
   /**
   /**
    * Iterator implementation that switches from the entry array to the overflow entries
    * Iterator implementation that switches from the entry array to the overflow entries
@@ -557,6 +582,46 @@ class SmallSortedMap<K extends Comparable<K>, V> extends AbstractMap<K, V> {
     }
     }
   }
   }
 
 
+  /**
+   * Reverse Iterator implementation that switches from the entry array to the overflow entries
+   * appropriately.
+   */
+  private class DescendingEntryIterator implements Iterator<Map.Entry<K, V>> {
+
+    private int pos = entryList.size();
+    private Iterator<Map.Entry<K, V>> lazyOverflowIterator;
+
+    @Override
+    public boolean hasNext() {
+      return (pos > 0 && pos <= entryList.size()) || getOverflowIterator().hasNext();
+    }
+
+    @Override
+    public Map.Entry<K, V> next() {
+      if (getOverflowIterator().hasNext()) {
+        return getOverflowIterator().next();
+      }
+      return entryList.get(--pos);
+    }
+
+    @Override
+    public void remove() {
+      throw new UnsupportedOperationException();
+    }
+
+    /**
+     * It is important to create the overflow iterator only after the array entries have been
+     * iterated over because the overflow entry set changes when the client calls remove() on the
+     * array entries, which invalidates any existing iterators.
+     */
+    private Iterator<Map.Entry<K, V>> getOverflowIterator() {
+      if (lazyOverflowIterator == null) {
+        lazyOverflowIterator = overflowEntriesDescending.entrySet().iterator();
+      }
+      return lazyOverflowIterator;
+    }
+  }
+
   /**
   /**
    * Helper class that holds immutable instances of an Iterable/Iterator that we return when the
    * Helper class that holds immutable instances of an Iterable/Iterator that we return when the
    * overflow entries is empty. This eliminates the creation of an Iterator object when there is
    * overflow entries is empty. This eliminates the creation of an Iterator object when there is

+ 167 - 0
java/core/src/main/java/com/google/protobuf/StructuralMessageInfo.java

@@ -0,0 +1,167 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.Internal.checkNotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Information for the layout of a protobuf message class. This describes all of the fields
+ * contained within a message.
+ */
+@ExperimentalApi
+final class StructuralMessageInfo implements MessageInfo {
+  private final ProtoSyntax syntax;
+  private final boolean messageSetWireFormat;
+  private final int[] checkInitialized;
+  private final FieldInfo[] fields;
+  private final MessageLite defaultInstance;
+
+  /**
+   * Constructor.
+   *
+   * @param checkInitialized fields to check in isInitialized().
+   * @param fields the set of fields for the message, in field number order.
+   */
+  StructuralMessageInfo(
+      ProtoSyntax syntax,
+      boolean messageSetWireFormat,
+      int[] checkInitialized,
+      FieldInfo[] fields,
+      Object defaultInstance) {
+    this.syntax = syntax;
+    this.messageSetWireFormat = messageSetWireFormat;
+    this.checkInitialized = checkInitialized;
+    this.fields = fields;
+    this.defaultInstance = (MessageLite) checkNotNull(defaultInstance, "defaultInstance");
+  }
+
+  /** Gets the syntax for the message (e.g. PROTO2, PROTO3). */
+  @Override
+  public ProtoSyntax getSyntax() {
+    return syntax;
+  }
+
+  /** Indicates whether or not the message should be represented with message set wire format. */
+  @Override
+  public boolean isMessageSetWireFormat() {
+    return messageSetWireFormat;
+  }
+
+  /** An array of field numbers that need to be checked for isInitialized(). */
+  public int[] getCheckInitialized() {
+    return checkInitialized;
+  }
+
+  /**
+   * Gets the information for all fields within this message, sorted in ascending order by their
+   * field number.
+   */
+  public FieldInfo[] getFields() {
+    return fields;
+  }
+
+  @Override
+  public MessageLite getDefaultInstance() {
+    return defaultInstance;
+  }
+
+  /** Helper method for creating a new builder for {@link MessageInfo}. */
+  public static Builder newBuilder() {
+    return new Builder();
+  }
+
+  /** Helper method for creating a new builder for {@link MessageInfo}. */
+  public static Builder newBuilder(int numFields) {
+    return new Builder(numFields);
+  }
+
+  /** A builder of {@link MessageInfo} instances. */
+  public static final class Builder {
+    private final List<FieldInfo> fields;
+    private ProtoSyntax syntax;
+    private boolean wasBuilt;
+    private boolean messageSetWireFormat;
+    private int[] checkInitialized = null;
+    private Object defaultInstance;
+
+    public Builder() {
+      fields = new ArrayList<FieldInfo>();
+    }
+
+    public Builder(int numFields) {
+      fields = new ArrayList<FieldInfo>(numFields);
+    }
+
+    public void withDefaultInstance(Object defaultInstance) {
+      this.defaultInstance = defaultInstance;
+    }
+
+    public void withSyntax(ProtoSyntax syntax) {
+      this.syntax = checkNotNull(syntax, "syntax");
+    }
+
+    public void withMessageSetWireFormat(boolean messageSetWireFormat) {
+      this.messageSetWireFormat = messageSetWireFormat;
+    }
+
+    public void withCheckInitialized(int[] checkInitialized) {
+      this.checkInitialized = checkInitialized;
+    }
+
+    public void withField(FieldInfo field) {
+      if (wasBuilt) {
+        throw new IllegalStateException("Builder can only build once");
+      }
+      fields.add(field);
+    }
+
+    public StructuralMessageInfo build() {
+      if (wasBuilt) {
+        throw new IllegalStateException("Builder can only build once");
+      }
+      if (syntax == null) {
+        throw new IllegalStateException("Must specify a proto syntax");
+      }
+      wasBuilt = true;
+      Collections.sort(fields);
+      return new StructuralMessageInfo(
+          syntax,
+          messageSetWireFormat,
+          checkInitialized,
+          fields.toArray(new FieldInfo[0]),
+          defaultInstance);
+    }
+  }
+}

+ 31 - 23
java/core/src/main/java/com/google/protobuf/TextFormat.java

@@ -1247,6 +1247,18 @@ public final class TextFormat {
           SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES;
           SingularOverwritePolicy.ALLOW_SINGULAR_OVERWRITES;
       private TextFormatParseInfoTree.Builder parseInfoTreeBuilder = null;
       private TextFormatParseInfoTree.Builder parseInfoTreeBuilder = null;
 
 
+      /**
+       * Set whether this parser will allow unknown fields. By default, an exception is thrown if an
+       * unknown field is encountered. If this is set, the parser will only log a warning. Allow
+       * unknown fields will also allow unknown extensions.
+       *
+       * <p>Use of this parameter is discouraged which may hide some errors (e.g.
+       * spelling error on field name).
+       */
+      public Builder setAllowUnknownFields(boolean allowUnknownFields) {
+        this.allowUnknownFields = allowUnknownFields;
+        return this;
+      }
 
 
       /**
       /**
        * Set whether this parser will allow unknown extensions. By default, an
        * Set whether this parser will allow unknown extensions. By default, an
@@ -1448,14 +1460,15 @@ public final class TextFormat {
         extension = target.findExtensionByName(extensionRegistry, name.toString());
         extension = target.findExtensionByName(extensionRegistry, name.toString());
 
 
         if (extension == null) {
         if (extension == null) {
-          String message = (tokenizer.getPreviousLine() + 1)
-                           + ":"
-                           + (tokenizer.getPreviousColumn() + 1)
-                           + ":\t"
-                           + type.getFullName()
-                           + ".["
-                           + name
-                           + "]";
+          String message =
+              (tokenizer.getPreviousLine() + 1)
+                  + ":"
+                  + (tokenizer.getPreviousColumn() + 1)
+                  + ":\t"
+                  + type.getFullName()
+                  + ".["
+                  + name
+                  + "]";
           unknownFields.add(new UnknownField(message, UnknownField.Type.EXTENSION));
           unknownFields.add(new UnknownField(message, UnknownField.Type.EXTENSION));
         } else {
         } else {
           if (extension.descriptor.getContainingType() != type) {
           if (extension.descriptor.getContainingType() != type) {
@@ -1653,24 +1666,18 @@ public final class TextFormat {
           endToken = "}";
           endToken = "}";
         }
         }
 
 
-        Message defaultInstance = (extension == null) ? null : extension.defaultInstance;
-        MessageReflection.MergeTarget subField =
-            target.newMergeTargetForField(field, defaultInstance);
+          Message defaultInstance = (extension == null) ? null : extension.defaultInstance;
+          MessageReflection.MergeTarget subField =
+              target.newMergeTargetForField(field, defaultInstance);
 
 
-        while (!tokenizer.tryConsume(endToken)) {
-          if (tokenizer.atEnd()) {
-            throw tokenizer.parseException("Expected \"" + endToken + "\".");
+          while (!tokenizer.tryConsume(endToken)) {
+            if (tokenizer.atEnd()) {
+              throw tokenizer.parseException("Expected \"" + endToken + "\".");
+            }
+            mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder, unknownFields);
           }
           }
-          mergeField(
-              tokenizer,
-              extensionRegistry,
-              subField,
-              parseTreeBuilder,
-              unknownFields);
-        }
-
-        value = subField.finish();
 
 
+          value = subField.finish();
       } else {
       } else {
         switch (field.getType()) {
         switch (field.getType()) {
           case INT32:
           case INT32:
@@ -1776,6 +1783,7 @@ public final class TextFormat {
       }
       }
     }
     }
 
 
+
     /** Skips the next field including the field's name and value. */
     /** Skips the next field including the field's name and value. */
     private void skipField(Tokenizer tokenizer) throws ParseException {
     private void skipField(Tokenizer tokenizer) throws ParseException {
       if (tokenizer.tryConsume("[")) {
       if (tokenizer.tryConsume("[")) {

+ 133 - 0
java/core/src/main/java/com/google/protobuf/UnknownFieldSchema.java

@@ -0,0 +1,133 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+
+@ExperimentalApi
+abstract class UnknownFieldSchema<T, B> {
+
+  /** Whether unknown fields should be dropped. */
+  abstract boolean shouldDiscardUnknownFields(Reader reader);
+
+  /** Adds a varint value to the unknown fields. */
+  abstract void addVarint(B fields, int number, long value);
+
+  /** Adds a fixed32 value to the unknown fields. */
+  abstract void addFixed32(B fields, int number, int value);
+
+  /** Adds a fixed64 value to the unknown fields. */
+  abstract void addFixed64(B fields, int number, long value);
+
+  /** Adds a length delimited value to the unknown fields. */
+  abstract void addLengthDelimited(B fields, int number, ByteString value);
+
+  /** Adds a group value to the unknown fields. */
+  abstract void addGroup(B fields, int number, T subFieldSet);
+
+  /** Create a new builder for unknown fields. */
+  abstract B newBuilder();
+
+  /** Returns an immutable instance of the field container. */
+  abstract T toImmutable(B fields);
+
+  /**
+   * Sets the unknown fields into the message. Caller must take care of the mutability of the
+   * fields.
+   */
+  abstract void setToMessage(Object message, T fields);
+
+  /** Get the unknown fields from the message. */
+  abstract T getFromMessage(Object message);
+
+  /** Returns a builder for unknown fields in the message. */
+  abstract B getBuilderFromMessage(Object message);
+
+  /** Sets an unknown field builder into the message. */
+  abstract void setBuilderToMessage(Object message, B builder);
+
+  /** Marks unknown fields as immutable. */
+  abstract void makeImmutable(Object message);
+
+  /** Merges one field into the unknown fields. */
+  final boolean mergeOneFieldFrom(B unknownFields, Reader reader) throws IOException {
+    int tag = reader.getTag();
+    int fieldNumber = WireFormat.getTagFieldNumber(tag);
+    switch (WireFormat.getTagWireType(tag)) {
+      case WireFormat.WIRETYPE_VARINT:
+        addVarint(unknownFields, fieldNumber, reader.readInt64());
+        return true;
+      case WireFormat.WIRETYPE_FIXED32:
+        addFixed32(unknownFields, fieldNumber, reader.readFixed32());
+        return true;
+      case WireFormat.WIRETYPE_FIXED64:
+        addFixed64(unknownFields, fieldNumber, reader.readFixed64());
+        return true;
+      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+        addLengthDelimited(unknownFields, fieldNumber, reader.readBytes());
+        return true;
+      case WireFormat.WIRETYPE_START_GROUP:
+        final B subFields = newBuilder();
+        int endGroupTag = WireFormat.makeTag(fieldNumber, WireFormat.WIRETYPE_END_GROUP);
+        mergeFrom(subFields, reader);
+        if (endGroupTag != reader.getTag()) {
+          throw InvalidProtocolBufferException.invalidEndTag();
+        }
+        addGroup(unknownFields, fieldNumber, toImmutable(subFields));
+        return true;
+      case WireFormat.WIRETYPE_END_GROUP:
+        return false;
+      default:
+        throw InvalidProtocolBufferException.invalidWireType();
+    }
+  }
+
+  final void mergeFrom(B unknownFields, Reader reader) throws IOException {
+    while (true) {
+      if (reader.getFieldNumber() == Reader.READ_DONE
+          || !mergeOneFieldFrom(unknownFields, reader)) {
+        break;
+      }
+    }
+  }
+
+  abstract void writeTo(T unknownFields, Writer writer) throws IOException;
+
+  abstract void writeAsMessageSetTo(T unknownFields, Writer writer) throws IOException;
+
+  /** Merges {@code source} into {@code destination} and returns the merged instance. */
+  abstract T merge(T destination, T source);
+
+  /** Get the serialized size for message set serialization. */
+  abstract int getSerializedSizeAsMessageSet(T message);
+
+  abstract int getSerializedSize(T unknowns);
+}

+ 79 - 0
java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java

@@ -59,6 +59,7 @@ public final class UnknownFieldSet implements MessageLite {
 
 
   private UnknownFieldSet() {
   private UnknownFieldSet() {
     fields = null;
     fields = null;
+    fieldsDescending = null;
   }
   }
 
 
   /** Create a new {@link Builder}. */
   /** Create a new {@link Builder}. */
@@ -90,10 +91,14 @@ public final class UnknownFieldSet implements MessageLite {
    */
    */
   UnknownFieldSet(final Map<Integer, Field> fields, final Map<Integer, Field> fieldsDescending) {
   UnknownFieldSet(final Map<Integer, Field> fields, final Map<Integer, Field> fieldsDescending) {
     this.fields = fields;
     this.fields = fields;
+    this.fieldsDescending = fieldsDescending;
   }
   }
 
 
   private final Map<Integer, Field> fields;
   private final Map<Integer, Field> fields;
 
 
+  /** A copy of {@link #fields} who's iterator order is reversed. */
+  private final Map<Integer, Field> fieldsDescending;
+
 
 
   @Override
   @Override
   public boolean equals(final Object other) {
   public boolean equals(final Object other) {
@@ -212,6 +217,35 @@ public final class UnknownFieldSet implements MessageLite {
     }
     }
   }
   }
 
 
+  /** Serializes the set and writes it to {@code writer}. */
+  void writeTo(Writer writer) throws IOException {
+    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
+      // Write fields in descending order.
+      for (Map.Entry<Integer, Field> entry : fieldsDescending.entrySet()) {
+        entry.getValue().writeTo(entry.getKey(), writer);
+      }
+    } else {
+      // Write fields in ascending order.
+      for (Map.Entry<Integer, Field> entry : fields.entrySet()) {
+        entry.getValue().writeTo(entry.getKey(), writer);
+      }
+    }
+  }
+
+  /** Serializes the set and writes it to {@code writer} using {@code MessageSet} wire format. */
+  void writeAsMessageSetTo(final Writer writer) throws IOException {
+    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
+      // Write fields in descending order.
+      for (final Map.Entry<Integer, Field> entry : fieldsDescending.entrySet()) {
+        entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
+      }
+    } else {
+      // Write fields in ascending order.
+      for (final Map.Entry<Integer, Field> entry : fields.entrySet()) {
+        entry.getValue().writeAsMessageSetExtensionTo(entry.getKey(), writer);
+      }
+    }
+  }
 
 
   /** Get the number of bytes required to encode this set using {@code MessageSet} wire format. */
   /** Get the number of bytes required to encode this set using {@code MessageSet} wire format. */
   public int getSerializedSizeAsMessageSet() {
   public int getSerializedSizeAsMessageSet() {
@@ -328,6 +362,8 @@ public final class UnknownFieldSet implements MessageLite {
         result = getDefaultInstance();
         result = getDefaultInstance();
       } else {
       } else {
         Map<Integer, Field> descendingFields = null;
         Map<Integer, Field> descendingFields = null;
+        descendingFields =
+            Collections.unmodifiableMap(((TreeMap<Integer, Field>) fields).descendingMap());
         result = new UnknownFieldSet(Collections.unmodifiableMap(fields), descendingFields);
         result = new UnknownFieldSet(Collections.unmodifiableMap(fields), descendingFields);
       }
       }
       fields = null;
       fields = null;
@@ -344,6 +380,8 @@ public final class UnknownFieldSet implements MessageLite {
     public Builder clone() {
     public Builder clone() {
       getFieldBuilder(0); // Force lastField to be built.
       getFieldBuilder(0); // Force lastField to be built.
       Map<Integer, Field> descendingFields = null;
       Map<Integer, Field> descendingFields = null;
+      descendingFields =
+          Collections.unmodifiableMap(((TreeMap<Integer, Field>) fields).descendingMap());
       return UnknownFieldSet.newBuilder().mergeFrom(new UnknownFieldSet(fields, descendingFields));
       return UnknownFieldSet.newBuilder().mergeFrom(new UnknownFieldSet(fields, descendingFields));
     }
     }
 
 
@@ -808,6 +846,47 @@ public final class UnknownFieldSet implements MessageLite {
       }
       }
     }
     }
 
 
+    /** Serializes the field, including field number, and writes it to {@code writer}. */
+    void writeTo(final int fieldNumber, final Writer writer) throws IOException {
+      writer.writeInt64List(fieldNumber, varint, false);
+      writer.writeFixed32List(fieldNumber, fixed32, false);
+      writer.writeFixed64List(fieldNumber, fixed64, false);
+      writer.writeBytesList(fieldNumber, lengthDelimited);
+
+      if (writer.fieldOrder() == Writer.FieldOrder.ASCENDING) {
+        for (int i = 0; i < group.size(); i++) {
+          writer.writeStartGroup(fieldNumber);
+          group.get(i).writeTo(writer);
+          writer.writeEndGroup(fieldNumber);
+        }
+      } else {
+        for (int i = group.size() - 1; i >= 0; i--) {
+          writer.writeEndGroup(fieldNumber);
+          group.get(i).writeTo(writer);
+          writer.writeStartGroup(fieldNumber);
+        }
+      }
+    }
+
+    /**
+     * Serializes the field, including field number, and writes it to {@code writer}, using {@code
+     * MessageSet} wire format.
+     */
+    private void writeAsMessageSetExtensionTo(final int fieldNumber, final Writer writer)
+        throws IOException {
+      if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
+        // Write in descending field order.
+        ListIterator<ByteString> iter = lengthDelimited.listIterator(lengthDelimited.size());
+        while (iter.hasPrevious()) {
+          writer.writeMessageSetItem(fieldNumber, iter.previous());
+        }
+      } else {
+        // Write in ascending field order.
+        for (final ByteString value : lengthDelimited) {
+          writer.writeMessageSetItem(fieldNumber, value);
+        }
+      }
+    }
 
 
     /**
     /**
      * Get the number of bytes required to encode this field, including field number, using {@code
      * Get the number of bytes required to encode this field, including field number, using {@code

+ 66 - 0
java/core/src/main/java/com/google/protobuf/UnknownFieldSetLite.java

@@ -168,6 +168,72 @@ public final class UnknownFieldSetLite {
     }
     }
   }
   }
 
 
+  /** Serializes the set and writes it to {@code writer} using {@code MessageSet} wire format. */
+  void writeAsMessageSetTo(Writer writer) throws IOException {
+    if (writer.fieldOrder() == Writer.FieldOrder.DESCENDING) {
+      // Write fields in descending order.
+      for (int i = count - 1; i >= 0; i--) {
+        int fieldNumber = WireFormat.getTagFieldNumber(tags[i]);
+        writer.writeMessageSetItem(fieldNumber, objects[i]);
+      }
+    } else {
+      // Write fields in ascending order.
+      for (int i = 0; i < count; i++) {
+        int fieldNumber = WireFormat.getTagFieldNumber(tags[i]);
+        writer.writeMessageSetItem(fieldNumber, objects[i]);
+      }
+    }
+  }
+
+  /** Serializes the set and writes it to {@code writer}. */
+  public void writeTo(Writer writer) throws IOException {
+    if (count == 0) {
+      return;
+    }
+
+    // TODO: tags are not sorted, so there's no write order guarantees
+    if (writer.fieldOrder() == Writer.FieldOrder.ASCENDING) {
+      for (int i = 0; i < count; ++i) {
+        writeField(tags[i], objects[i], writer);
+      }
+    } else {
+      for (int i = count - 1; i >= 0; --i) {
+        writeField(tags[i], objects[i], writer);
+      }
+    }
+  }
+
+  private static void writeField(int tag, Object object, Writer writer) throws IOException {
+    int fieldNumber = WireFormat.getTagFieldNumber(tag);
+    switch (WireFormat.getTagWireType(tag)) {
+      case WireFormat.WIRETYPE_VARINT:
+        writer.writeInt64(fieldNumber, (Long) object);
+        break;
+      case WireFormat.WIRETYPE_FIXED32:
+        writer.writeFixed32(fieldNumber, (Integer) object);
+        break;
+      case WireFormat.WIRETYPE_FIXED64:
+        writer.writeFixed64(fieldNumber, (Long) object);
+        break;
+      case WireFormat.WIRETYPE_LENGTH_DELIMITED:
+        writer.writeBytes(fieldNumber, (ByteString) object);
+        break;
+      case WireFormat.WIRETYPE_START_GROUP:
+        if (writer.fieldOrder() == Writer.FieldOrder.ASCENDING) {
+          writer.writeStartGroup(fieldNumber);
+          ((UnknownFieldSetLite) object).writeTo(writer);
+          writer.writeEndGroup(fieldNumber);
+        } else {
+          writer.writeEndGroup(fieldNumber);
+          ((UnknownFieldSetLite) object).writeTo(writer);
+          writer.writeStartGroup(fieldNumber);
+        }
+        break;
+      default:
+        // TODO(liujisi): Change writeTo to throw IOException?
+        throw new RuntimeException(InvalidProtocolBufferException.invalidWireType());
+    }
+  }
 
 
   /**
   /**
    * Get the number of bytes required to encode this field, including field number, using {@code
    * Get the number of bytes required to encode this field, including field number, using {@code

+ 140 - 0
java/core/src/main/java/com/google/protobuf/UnknownFieldSetLiteSchema.java

@@ -0,0 +1,140 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+
+class UnknownFieldSetLiteSchema
+    extends UnknownFieldSchema<UnknownFieldSetLite, UnknownFieldSetLite> {
+
+  UnknownFieldSetLiteSchema() {}
+
+  @Override
+  boolean shouldDiscardUnknownFields(Reader reader) {
+    // We never drop unknown fields in lite.
+    return false;
+  }
+
+  @Override
+  UnknownFieldSetLite newBuilder() {
+    return UnknownFieldSetLite.newInstance();
+  }
+
+  @Override
+  void addVarint(UnknownFieldSetLite fields, int number, long value) {
+    fields.storeField(WireFormat.makeTag(number, WireFormat.WIRETYPE_VARINT), value);
+  }
+
+  @Override
+  void addFixed32(UnknownFieldSetLite fields, int number, int value) {
+    fields.storeField(WireFormat.makeTag(number, WireFormat.WIRETYPE_FIXED32), value);
+  }
+
+  @Override
+  void addFixed64(UnknownFieldSetLite fields, int number, long value) {
+    fields.storeField(WireFormat.makeTag(number, WireFormat.WIRETYPE_FIXED64), value);
+  }
+
+  @Override
+  void addLengthDelimited(UnknownFieldSetLite fields, int number, ByteString value) {
+    fields.storeField(WireFormat.makeTag(number, WireFormat.WIRETYPE_LENGTH_DELIMITED), value);
+  }
+
+  @Override
+  void addGroup(UnknownFieldSetLite fields, int number, UnknownFieldSetLite subFieldSet) {
+    fields.storeField(WireFormat.makeTag(number, WireFormat.WIRETYPE_START_GROUP), subFieldSet);
+  }
+
+  @Override
+  UnknownFieldSetLite toImmutable(UnknownFieldSetLite fields) {
+    fields.makeImmutable();
+    return fields;
+  }
+
+  @Override
+  void setToMessage(Object message, UnknownFieldSetLite fields) {
+    ((GeneratedMessageLite<?, ?>) message).unknownFields = fields;
+  }
+
+  @Override
+  UnknownFieldSetLite getFromMessage(Object message) {
+    return ((GeneratedMessageLite<?, ?>) message).unknownFields;
+  }
+
+  @Override
+  UnknownFieldSetLite getBuilderFromMessage(Object message) {
+    UnknownFieldSetLite unknownFields = getFromMessage(message);
+    // When parsing into a lite message object, its UnknownFieldSet is either the default instance
+    // or mutable. It can't be in a state where it's immutable but not default instance.
+    if (unknownFields == UnknownFieldSetLite.getDefaultInstance()) {
+      unknownFields = UnknownFieldSetLite.newInstance();
+      setToMessage(message, unknownFields);
+    }
+    return unknownFields;
+  }
+
+  @Override
+  void setBuilderToMessage(Object message, UnknownFieldSetLite fields) {
+    setToMessage(message, fields);
+  }
+
+  @Override
+  void makeImmutable(Object message) {
+    getFromMessage(message).makeImmutable();
+  }
+
+  @Override
+  void writeTo(UnknownFieldSetLite fields, Writer writer) throws IOException {
+    fields.writeTo(writer);
+  }
+
+  @Override
+  void writeAsMessageSetTo(UnknownFieldSetLite fields, Writer writer) throws IOException {
+    fields.writeAsMessageSetTo(writer);
+  }
+
+  @Override
+  UnknownFieldSetLite merge(UnknownFieldSetLite message, UnknownFieldSetLite other) {
+    return other.equals(UnknownFieldSetLite.getDefaultInstance())
+        ? message
+        : UnknownFieldSetLite.mutableCopyOf(message, other);
+  }
+
+  @Override
+  int getSerializedSize(UnknownFieldSetLite unknowns) {
+    return unknowns.getSerializedSize();
+  }
+
+  @Override
+  int getSerializedSizeAsMessageSet(UnknownFieldSetLite unknowns) {
+    return unknowns.getSerializedSizeAsMessageSet();
+  }
+}

+ 132 - 0
java/core/src/main/java/com/google/protobuf/UnknownFieldSetSchema.java

@@ -0,0 +1,132 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+
+class UnknownFieldSetSchema extends UnknownFieldSchema<UnknownFieldSet, UnknownFieldSet.Builder> {
+
+  private final boolean proto3;
+
+  public UnknownFieldSetSchema(boolean proto3) {
+    this.proto3 = proto3;
+  }
+
+  @Override
+  boolean shouldDiscardUnknownFields(Reader reader) {
+    return reader.shouldDiscardUnknownFields();
+  }
+
+  @Override
+  UnknownFieldSet.Builder newBuilder() {
+    return UnknownFieldSet.newBuilder();
+  }
+
+  @Override
+  void addVarint(UnknownFieldSet.Builder fields, int number, long value) {
+    fields.mergeField(number, UnknownFieldSet.Field.newBuilder().addVarint(value).build());
+  }
+
+  @Override
+  void addFixed32(UnknownFieldSet.Builder fields, int number, int value) {
+    fields.mergeField(number, UnknownFieldSet.Field.newBuilder().addFixed32(value).build());
+  }
+
+  @Override
+  void addFixed64(UnknownFieldSet.Builder fields, int number, long value) {
+    fields.mergeField(number, UnknownFieldSet.Field.newBuilder().addFixed64(value).build());
+  }
+
+  @Override
+  void addLengthDelimited(UnknownFieldSet.Builder fields, int number, ByteString value) {
+    fields.mergeField(number, UnknownFieldSet.Field.newBuilder().addLengthDelimited(value).build());
+  }
+
+  @Override
+  void addGroup(UnknownFieldSet.Builder fields, int number, UnknownFieldSet subFieldSet) {
+    fields.mergeField(number, UnknownFieldSet.Field.newBuilder().addGroup(subFieldSet).build());
+  }
+
+  @Override
+  UnknownFieldSet toImmutable(UnknownFieldSet.Builder fields) {
+    return fields.build();
+  }
+
+  @Override
+  void writeTo(UnknownFieldSet message, Writer writer) throws IOException {
+    message.writeTo(writer);
+  }
+
+  @Override
+  void writeAsMessageSetTo(UnknownFieldSet message, Writer writer) throws IOException {
+    message.writeAsMessageSetTo(writer);
+  }
+
+  @Override
+  UnknownFieldSet getFromMessage(Object message) {
+    return ((GeneratedMessageV3) message).unknownFields;
+  }
+
+  @Override
+  void setToMessage(Object message, UnknownFieldSet fields) {
+    ((GeneratedMessageV3) message).unknownFields = fields;
+  }
+
+  @Override
+  UnknownFieldSet.Builder getBuilderFromMessage(Object message) {
+    return ((GeneratedMessageV3) message).unknownFields.toBuilder();
+  }
+
+  @Override
+  void setBuilderToMessage(Object message, UnknownFieldSet.Builder builder) {
+    ((GeneratedMessageV3) message).unknownFields = builder.build();
+  }
+
+  @Override
+  void makeImmutable(Object message) {
+    // Already immutable.
+  }
+
+  @Override
+  UnknownFieldSet merge(UnknownFieldSet message, UnknownFieldSet other) {
+    return message.toBuilder().mergeFrom(other).build();
+  }
+
+  @Override
+  int getSerializedSize(UnknownFieldSet message) {
+    return message.getSerializedSize();
+  }
+
+  @Override
+  int getSerializedSizeAsMessageSet(UnknownFieldSet unknowns) {
+    return unknowns.getSerializedSizeAsMessageSet();
+  }
+}

+ 4 - 4
java/core/src/main/java/com/google/protobuf/Utf8.java

@@ -794,7 +794,7 @@ final class Utf8 {
       // if it occurs.
       // if it occurs.
       try {
       try {
         // Designed to take advantage of
         // Designed to take advantage of
-        // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+        // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
         for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) {
         for (char c; inIx < inLength && (c = in.charAt(inIx)) < 0x80; ++inIx) {
           out.put(outIx + inIx, (byte) c);
           out.put(outIx + inIx, (byte) c);
         }
         }
@@ -1041,7 +1041,7 @@ final class Utf8 {
       int i = 0;
       int i = 0;
       int limit = offset + length;
       int limit = offset + length;
       // Designed to take advantage of
       // Designed to take advantage of
-      // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+      // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
       for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) {
       for (char c; i < utf16Length && i + j < limit && (c = in.charAt(i)) < 0x80; i++) {
         out[j + i] = (byte) c;
         out[j + i] = (byte) c;
       }
       }
@@ -1527,7 +1527,7 @@ final class Utf8 {
       }
       }
 
 
       // Designed to take advantage of
       // Designed to take advantage of
-      // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+      // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
       int inIx = 0;
       int inIx = 0;
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
         UnsafeUtil.putByte(out, outIx++, (byte) c);
         UnsafeUtil.putByte(out, outIx++, (byte) c);
@@ -1589,7 +1589,7 @@ final class Utf8 {
       }
       }
 
 
       // Designed to take advantage of
       // Designed to take advantage of
-      // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+      // https://wiki.openjdk.java.net/display/HotSpotInternals/RangeCheckElimination
       int inIx = 0;
       int inIx = 0;
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
         UnsafeUtil.putByte(outIx++, (byte) c);
         UnsafeUtil.putByte(outIx++, (byte) c);

+ 219 - 0
java/core/src/main/java/com/google/protobuf/Writer.java

@@ -0,0 +1,219 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/** A writer that performs serialization of protobuf message fields. */
+@ExperimentalApi
+interface Writer {
+
+  /** The order in which the fields are written by a {@link Writer}. */
+  enum FieldOrder {
+    /** Fields are written in ascending order by field number. */
+    ASCENDING,
+
+    /** Fields are written in descending order by field number. */
+    DESCENDING
+  }
+
+  /** Indicates the order in which the fields are written by this {@link Writer}. */
+  FieldOrder fieldOrder();
+
+  /** Writes a field of type {@link FieldType#SFIXED32}. */
+  void writeSFixed32(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#INT64}. */
+  void writeInt64(int fieldNumber, long value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#SFIXED64}. */
+  void writeSFixed64(int fieldNumber, long value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#FLOAT}. */
+  void writeFloat(int fieldNumber, float value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#DOUBLE}. */
+  void writeDouble(int fieldNumber, double value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#ENUM}. */
+  void writeEnum(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#UINT64}. */
+  void writeUInt64(int fieldNumber, long value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#INT32}. */
+  void writeInt32(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#FIXED64}. */
+  void writeFixed64(int fieldNumber, long value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#FIXED32}. */
+  void writeFixed32(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#BOOL}. */
+  void writeBool(int fieldNumber, boolean value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#STRING}. */
+  void writeString(int fieldNumber, String value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#BYTES}. */
+  void writeBytes(int fieldNumber, ByteString value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#UINT32}. */
+  void writeUInt32(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#SINT32}. */
+  void writeSInt32(int fieldNumber, int value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#SINT64}. */
+  void writeSInt64(int fieldNumber, long value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#MESSAGE}. */
+  void writeMessage(int fieldNumber, Object value) throws IOException;
+
+  /** Writes a field of type {@link FieldType#MESSAGE}. */
+  void writeMessage(int fieldNumber, Object value, Schema schema) throws IOException;
+
+  /**
+   * Writes a field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroup(int fieldNumber, Object value) throws IOException;
+
+  /**
+   * Writes a field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroup(int fieldNumber, Object value, Schema schema) throws IOException;
+
+  /**
+   * Writes a single start group tag.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeStartGroup(int fieldNumber) throws IOException;
+
+  /**
+   * Writes a single end group tag.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeEndGroup(int fieldNumber) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#INT32}. */
+  void writeInt32List(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#FIXED32}. */
+  void writeFixed32List(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#INT64}. */
+  void writeInt64List(int fieldNumber, List<Long> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#UINT64}. */
+  void writeUInt64List(int fieldNumber, List<Long> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#FIXED64}. */
+  void writeFixed64List(int fieldNumber, List<Long> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#FLOAT}. */
+  void writeFloatList(int fieldNumber, List<Float> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#DOUBLE}. */
+  void writeDoubleList(int fieldNumber, List<Double> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#ENUM}. */
+  void writeEnumList(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#BOOL}. */
+  void writeBoolList(int fieldNumber, List<Boolean> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#STRING}. */
+  void writeStringList(int fieldNumber, List<String> value) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#BYTES}. */
+  void writeBytesList(int fieldNumber, List<ByteString> value) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#UINT32}. */
+  void writeUInt32List(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#SFIXED32}. */
+  void writeSFixed32List(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#SFIXED64}. */
+  void writeSFixed64List(int fieldNumber, List<Long> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#SINT32}. */
+  void writeSInt32List(int fieldNumber, List<Integer> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#SINT64}. */
+  void writeSInt64List(int fieldNumber, List<Long> value, boolean packed) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#MESSAGE}. */
+  void writeMessageList(int fieldNumber, List<?> value) throws IOException;
+
+  /** Writes a list field of type {@link FieldType#MESSAGE}. */
+  void writeMessageList(int fieldNumber, List<?> value, Schema schema) throws IOException;
+
+  /**
+   * Writes a list field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroupList(int fieldNumber, List<?> value) throws IOException;
+
+  /**
+   * Writes a list field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroupList(int fieldNumber, List<?> value, Schema schema) throws IOException;
+
+  /**
+   * Writes a message field in {@code MessageSet} wire-format.
+   *
+   * @param value A message instance or an opaque {@link ByteString} for an unknown field.
+   */
+  void writeMessageSetItem(int fieldNumber, Object value) throws IOException;
+
+  /** Writes a map field. */
+  <K, V> void writeMap(int fieldNumber, MapEntryLite.Metadata<K, V> metadata, Map<K, V> map)
+      throws IOException;
+}

+ 199 - 0
java/core/src/test/java/com/google/protobuf/AbstractProto2LiteSchemaTest.java

@@ -0,0 +1,199 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import com.google.protobuf.testing.Proto2TestingLite.Proto2EmptyLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLite.TestEnum;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLiteWithMaps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+
+/** Base class for tests using {@link Proto2MessageLite}. */
+public abstract class AbstractProto2LiteSchemaTest extends AbstractSchemaTest<Proto2MessageLite> {
+
+  @Override
+  protected Proto2MessageLiteFactory messageFactory() {
+    return new Proto2MessageLiteFactory(10, 20, 2, 2);
+  }
+
+  @Test
+  public void mergeOptionalMessageFields() throws Exception {
+    Proto2MessageLite message1 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(123).clearFieldInt325().build())
+            .build();
+    Proto2MessageLite message2 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().clearFieldInt643().setFieldInt325(456).build())
+            .build();
+    Proto2MessageLite message3 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(789).clearFieldInt325().build())
+            .build();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    message1.writeTo(output);
+    message2.writeTo(output);
+    message3.writeTo(output);
+    byte[] data = output.toByteArray();
+
+    Proto2MessageLite merged =
+        ExperimentalSerializationUtil.fromByteArray(data, Proto2MessageLite.class);
+    assertEquals(789, merged.getFieldMessage10().getFieldInt643());
+    assertEquals(456, merged.getFieldMessage10().getFieldInt325());
+  }
+
+  @Test
+  public void oneofFieldsShouldRoundtrip() throws IOException {
+    roundtrip("Field 53", newBuilder().setFieldDouble53(100).build());
+    roundtrip("Field 54", newBuilder().setFieldFloat54(100).build());
+    roundtrip("Field 55", newBuilder().setFieldInt6455(100).build());
+    roundtrip("Field 56", newBuilder().setFieldUint6456(100L).build());
+    roundtrip("Field 57", newBuilder().setFieldInt3257(100).build());
+    roundtrip("Field 58", newBuilder().setFieldFixed6458(100).build());
+    roundtrip("Field 59", newBuilder().setFieldFixed3259(100).build());
+    roundtrip("Field 60", newBuilder().setFieldBool60(true).build());
+    roundtrip("Field 61", newBuilder().setFieldString61(data().getString()).build());
+    roundtrip(
+        "Field 62", newBuilder().setFieldMessage62(newBuilder().setFieldDouble1(100)).build());
+    roundtrip("Field 63", newBuilder().setFieldBytes63(data().getBytes()).build());
+    roundtrip("Field 64", newBuilder().setFieldUint3264(100).build());
+    roundtrip("Field 65", newBuilder().setFieldSfixed3265(100).build());
+    roundtrip("Field 66", newBuilder().setFieldSfixed6466(100).build());
+    roundtrip("Field 67", newBuilder().setFieldSint3267(100).build());
+    roundtrip("Field 68", newBuilder().setFieldSint6468(100).build());
+    roundtrip(
+        "Field 69",
+        newBuilder()
+            .setFieldGroup69(
+                Proto2MessageLite.FieldGroup69.newBuilder().setFieldInt3270(data().getInt()))
+            .build());
+  }
+
+  private Proto2MessageLite.Builder newBuilder() {
+    return messageFactory().newMessage().toBuilder();
+  }
+
+  @Override
+  protected List<Proto2MessageLite> newMessagesMissingRequiredFields() {
+    return messageFactory().newMessagesMissingRequiredFields();
+  }
+
+  @Test
+  public void mapsShouldRoundtrip() throws IOException {
+    roundtrip(
+        "Proto2MessageLiteWithMaps",
+        new Proto2MessageLiteFactory(2, 10, 2, 2).newMessageWithMaps(),
+        Protobuf.getInstance().schemaFor(Proto2MessageLiteWithMaps.class));
+  }
+
+  @Test
+  public void unknownFieldsUnrecognized() throws Exception {
+    Proto2MessageLite expectedMessage = messageFactory().newMessage();
+    byte[] serializedBytes = expectedMessage.toByteArray();
+    Proto2EmptyLite empty =
+        ExperimentalSerializationUtil.fromByteArray(serializedBytes, Proto2EmptyLite.class);
+
+    // Merge serialized bytes into an empty message, then reserialize and merge it to a new
+    // Proto2Message. Make sure the two messages equal.
+    byte[] roundtripBytes = ExperimentalSerializationUtil.toByteArray(empty);
+    Proto2MessageLite roundtripMessage =
+        ExperimentalSerializationUtil.fromByteArray(roundtripBytes, Proto2MessageLite.class);
+    assertEquals(expectedMessage, roundtripMessage);
+  }
+
+  @Test
+  public void unknownEnum() throws IOException {
+    // Use unknown fields to hold invalid enum values.
+    UnknownFieldSetLite unknowns = UnknownFieldSetLite.newInstance();
+    final int outOfRange = 1000;
+    assertNull(TestEnum.forNumber(outOfRange));
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2MessageLite.FIELD_ENUM_13_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2MessageLite.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.ONE_VALUE);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2MessageLite.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2MessageLite.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.TWO_VALUE);
+
+    {
+      // Construct a packed enum list.
+      int packedSize =
+          CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE)
+              + CodedOutputStream.computeUInt32SizeNoTag(outOfRange)
+              + CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE);
+      ByteString.CodedBuilder packedBuilder = ByteString.newCodedBuilder(packedSize);
+      CodedOutputStream packedOut = packedBuilder.getCodedOutput();
+      packedOut.writeEnumNoTag(TestEnum.ONE_VALUE);
+      packedOut.writeEnumNoTag(outOfRange);
+      packedOut.writeEnumNoTag(TestEnum.TWO_VALUE);
+      unknowns.storeField(
+          WireFormat.makeTag(
+              Proto2MessageLite.FIELD_ENUM_LIST_PACKED_44_FIELD_NUMBER,
+              WireFormat.WIRETYPE_LENGTH_DELIMITED),
+          packedBuilder.build());
+    }
+    int size = unknowns.getSerializedSize();
+    byte[] output = new byte[size];
+    CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
+    unknowns.writeTo(codedOutput);
+    codedOutput.flush();
+
+    Proto2MessageLite parsed =
+        ExperimentalSerializationUtil.fromByteArray(output, Proto2MessageLite.class);
+    assertFalse("out-of-range singular enum should not be in message", parsed.hasFieldEnum13());
+    assertEquals(
+        "out-of-range repeated enum should not be in message", 2, parsed.getFieldEnumList30Count());
+    assertEquals(TestEnum.ONE, parsed.getFieldEnumList30(0));
+    assertEquals(TestEnum.TWO, parsed.getFieldEnumList30(1));
+    assertEquals(
+        "out-of-range packed repeated enum should not be in message",
+        2,
+        parsed.getFieldEnumListPacked44Count());
+    assertEquals(TestEnum.ONE, parsed.getFieldEnumListPacked44(0));
+    assertEquals(TestEnum.TWO, parsed.getFieldEnumListPacked44(1));
+  }
+}

+ 224 - 0
java/core/src/test/java/com/google/protobuf/AbstractProto2SchemaTest.java

@@ -0,0 +1,224 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2Empty;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.TestEnum;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithMaps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import org.junit.Test;
+
+/** Base class for tests using {@link Proto2Message}. */
+public abstract class AbstractProto2SchemaTest extends AbstractSchemaTest<Proto2Message> {
+
+  @Override
+  protected Proto2MessageFactory messageFactory() {
+    return new Proto2MessageFactory(10, 20, 2, 2);
+  }
+
+  @Test
+  public void mergeOptionalMessageFields() throws Exception {
+    Proto2Message message1 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(123).clearFieldInt325().build())
+            .build();
+    Proto2Message message2 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().clearFieldInt643().setFieldInt325(456).build())
+            .build();
+    Proto2Message message3 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(789).clearFieldInt325().build())
+            .build();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    message1.writeTo(output);
+    message2.writeTo(output);
+    message3.writeTo(output);
+    byte[] data = output.toByteArray();
+
+    Proto2Message merged = ExperimentalSerializationUtil.fromByteArray(data, Proto2Message.class);
+    assertEquals(789, merged.getFieldMessage10().getFieldInt643());
+    assertEquals(456, merged.getFieldMessage10().getFieldInt325());
+  }
+
+  @Test
+  public void oneofFieldsShouldRoundtrip() throws IOException {
+    roundtrip("Field 53", newBuilder().setFieldDouble53(100).build());
+    roundtrip("Field 54", newBuilder().setFieldFloat54(100).build());
+    roundtrip("Field 55", newBuilder().setFieldInt6455(100).build());
+    roundtrip("Field 56", newBuilder().setFieldUint6456(100L).build());
+    roundtrip("Field 57", newBuilder().setFieldInt3257(100).build());
+    roundtrip("Field 58", newBuilder().setFieldFixed6458(100).build());
+    roundtrip("Field 59", newBuilder().setFieldFixed3259(100).build());
+    roundtrip("Field 60", newBuilder().setFieldBool60(true).build());
+    roundtrip("Field 61", newBuilder().setFieldString61(data().getString()).build());
+    roundtrip(
+        "Field 62", newBuilder().setFieldMessage62(newBuilder().setFieldDouble1(100)).build());
+    roundtrip("Field 63", newBuilder().setFieldBytes63(data().getBytes()).build());
+    roundtrip("Field 64", newBuilder().setFieldUint3264(100).build());
+    roundtrip("Field 65", newBuilder().setFieldSfixed3265(100).build());
+    roundtrip("Field 66", newBuilder().setFieldSfixed6466(100).build());
+    roundtrip("Field 67", newBuilder().setFieldSint3267(100).build());
+    roundtrip("Field 68", newBuilder().setFieldSint6468(100).build());
+    roundtrip(
+        "Field 69",
+        newBuilder()
+            .setFieldGroup69(
+                Proto2Message.FieldGroup69.newBuilder().setFieldInt3270(data().getInt()))
+            .build());
+  }
+
+  private Proto2Message.Builder newBuilder() {
+    return messageFactory().newMessage().toBuilder();
+  }
+
+  @Test
+  public void mapsShouldRoundtrip() throws IOException {
+    roundtrip(
+        "Proto2MessageWithMaps",
+        new Proto2MessageFactory(2, 10, 2, 2).newMessageWithMaps(),
+        Protobuf.getInstance().schemaFor(Proto2MessageWithMaps.class));
+  }
+
+  @Test
+  public void unknownFieldsUnrecognized() throws Exception {
+    Proto2Message expectedMessage = messageFactory().newMessage();
+    byte[] serializedBytes = expectedMessage.toByteArray();
+    Proto2Empty empty =
+        ExperimentalSerializationUtil.fromByteArray(serializedBytes, Proto2Empty.class);
+
+    // Merge serialized bytes into an empty message, then reserialize and merge it to a new
+    // Proto2Message. Make sure the two messages equal.
+    byte[] roundtripBytes = ExperimentalSerializationUtil.toByteArray(empty);
+    assertEquals(serializedBytes.length, roundtripBytes.length);
+    Proto2Message roundtripMessage =
+        ExperimentalSerializationUtil.fromByteArray(roundtripBytes, Proto2Message.class);
+    assertEquals(expectedMessage, roundtripMessage);
+  }
+
+  @Test
+  public void unknownEnum() throws IOException {
+    // Use unknown fields to hold invalid enum values.
+    UnknownFieldSetLite unknowns = UnknownFieldSetLite.newInstance();
+    final int outOfRange = 1000;
+    assertNull(TestEnum.forNumber(outOfRange));
+    unknowns.storeField(
+        WireFormat.makeTag(Proto2Message.FIELD_ENUM_13_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.ONE_VALUE);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.TWO_VALUE);
+
+    {
+      // Construct a packed enum list.
+      int packedSize =
+          CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE)
+              + CodedOutputStream.computeUInt32SizeNoTag(outOfRange)
+              + CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE);
+      ByteString.CodedBuilder packedBuilder = ByteString.newCodedBuilder(packedSize);
+      CodedOutputStream packedOut = packedBuilder.getCodedOutput();
+      packedOut.writeEnumNoTag(TestEnum.ONE_VALUE);
+      packedOut.writeEnumNoTag(outOfRange);
+      packedOut.writeEnumNoTag(TestEnum.TWO_VALUE);
+      unknowns.storeField(
+          WireFormat.makeTag(
+              Proto2Message.FIELD_ENUM_LIST_PACKED_44_FIELD_NUMBER,
+              WireFormat.WIRETYPE_LENGTH_DELIMITED),
+          packedBuilder.build());
+    }
+    int size = unknowns.getSerializedSize();
+    byte[] output = new byte[size];
+    CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
+    unknowns.writeTo(codedOutput);
+    codedOutput.flush();
+
+    Proto2Message parsed = ExperimentalSerializationUtil.fromByteArray(output, Proto2Message.class);
+    assertFalse("out-of-range singular enum should not be in message", parsed.hasFieldEnum13());
+    {
+      List<Long> singularEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_13_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, singularEnum.size());
+      assertEquals((Long) (long) outOfRange, singularEnum.get(0));
+    }
+    {
+      List<Long> repeatedEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, repeatedEnum.size());
+      assertEquals((Long) (long) outOfRange, repeatedEnum.get(0));
+    }
+    {
+      List<Long> packedRepeatedEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_LIST_PACKED_44_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, packedRepeatedEnum.size());
+      assertEquals((Long) (long) outOfRange, packedRepeatedEnum.get(0));
+    }
+    assertEquals(
+        "out-of-range repeated enum should not be in message", 2, parsed.getFieldEnumList30Count());
+    assertEquals(TestEnum.ONE, parsed.getFieldEnumList30(0));
+    assertEquals(TestEnum.TWO, parsed.getFieldEnumList30(1));
+    assertEquals(
+        "out-of-range packed repeated enum should not be in message",
+        2,
+        parsed.getFieldEnumListPacked44Count());
+    assertEquals(TestEnum.ONE, parsed.getFieldEnumListPacked44(0));
+    assertEquals(TestEnum.TWO, parsed.getFieldEnumListPacked44(1));
+  }
+
+  @Override
+  protected List<Proto2Message> newMessagesMissingRequiredFields() {
+    return messageFactory().newMessagesMissingRequiredFields();
+  }
+}

+ 143 - 0
java/core/src/test/java/com/google/protobuf/AbstractProto3LiteSchemaTest.java

@@ -0,0 +1,143 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.protobuf.testing.Proto3TestingLite.Proto3EmptyLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLiteWithMaps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+/** Base class for tests using {@link Proto3MessageLite}. */
+public abstract class AbstractProto3LiteSchemaTest extends AbstractSchemaTest<Proto3MessageLite> {
+  @Override
+  protected Proto3MessageLiteFactory messageFactory() {
+    return new Proto3MessageLiteFactory(10, 20, 2, 2);
+  }
+
+  @Override
+  protected List<ByteBuffer> serializedBytesWithInvalidUtf8() throws IOException {
+    List<ByteBuffer> invalidBytes = new ArrayList<>();
+    byte[] invalid = new byte[] {(byte) 0x80};
+    {
+      ByteBuffer buffer = ByteBuffer.allocate(100);
+      CodedOutputStream codedOutput = CodedOutputStream.newInstance(buffer);
+      codedOutput.writeByteArray(Proto3MessageLite.FIELD_STRING_9_FIELD_NUMBER, invalid);
+      codedOutput.flush();
+      buffer.flip();
+      invalidBytes.add(buffer);
+    }
+    {
+      ByteBuffer buffer = ByteBuffer.allocate(100);
+      CodedOutputStream codedOutput = CodedOutputStream.newInstance(buffer);
+      codedOutput.writeByteArray(Proto3MessageLite.FIELD_STRING_LIST_26_FIELD_NUMBER, invalid);
+      codedOutput.flush();
+      buffer.flip();
+      invalidBytes.add(buffer);
+    }
+    return invalidBytes;
+  }
+
+  @Test
+  public void mergeOptionalMessageFields() throws Exception {
+    Proto3MessageLite message1 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(123).clearFieldInt325().build())
+            .build();
+    Proto3MessageLite message2 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().clearFieldInt643().setFieldInt325(456).build())
+            .build();
+    Proto3MessageLite message3 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(789).clearFieldInt325().build())
+            .build();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    message1.writeTo(output);
+    message2.writeTo(output);
+    message3.writeTo(output);
+    byte[] data = output.toByteArray();
+
+    Proto3MessageLite merged =
+        ExperimentalSerializationUtil.fromByteArray(data, Proto3MessageLite.class);
+    assertEquals(789, merged.getFieldMessage10().getFieldInt643());
+    assertEquals(456, merged.getFieldMessage10().getFieldInt325());
+  }
+
+  @Test
+  public void oneofFieldsShouldRoundtrip() throws IOException {
+    roundtrip("Field 53", newBuilder().setFieldDouble53(100).build());
+    roundtrip("Field 54", newBuilder().setFieldFloat54(100).build());
+    roundtrip("Field 55", newBuilder().setFieldInt6455(100).build());
+    roundtrip("Field 56", newBuilder().setFieldUint6456(100L).build());
+    roundtrip("Field 57", newBuilder().setFieldInt3257(100).build());
+    roundtrip("Field 58", newBuilder().setFieldFixed6458(100).build());
+    roundtrip("Field 59", newBuilder().setFieldFixed3259(100).build());
+    roundtrip("Field 60", newBuilder().setFieldBool60(true).build());
+    roundtrip("Field 61", newBuilder().setFieldString61(data().getString()).build());
+    roundtrip(
+        "Field 62", newBuilder().setFieldMessage62(newBuilder().setFieldDouble1(100)).build());
+    roundtrip("Field 63", newBuilder().setFieldBytes63(data().getBytes()).build());
+    roundtrip("Field 64", newBuilder().setFieldUint3264(100).build());
+    roundtrip("Field 65", newBuilder().setFieldSfixed3265(100).build());
+    roundtrip("Field 66", newBuilder().setFieldSfixed6466(100).build());
+    roundtrip("Field 67", newBuilder().setFieldSint3267(100).build());
+    roundtrip("Field 68", newBuilder().setFieldSint6468(100).build());
+  }
+
+  @Test
+  public void retainUnknownFields() {
+    // Unknown fields are retained in lite runtime.
+    Proto3MessageLite expectedMessage = messageFactory().newMessage();
+    Proto3EmptyLite empty =
+        ExperimentalSerializationUtil.fromByteArray(
+            expectedMessage.toByteArray(), Proto3EmptyLite.class);
+    assertEquals(expectedMessage.getSerializedSize(), empty.getSerializedSize());
+  }
+
+  @Test
+  public void mapsShouldRoundtrip() throws IOException {
+    roundtrip(
+        "Proto3MessageLiteWithMaps",
+        new Proto3MessageLiteFactory(2, 10, 2, 2).newMessageWithMaps(),
+        Protobuf.getInstance().schemaFor(Proto3MessageLiteWithMaps.class));
+  }
+
+  private static Proto3MessageLite.Builder newBuilder() {
+    return Proto3MessageLite.newBuilder();
+  }
+}

+ 151 - 0
java/core/src/test/java/com/google/protobuf/AbstractProto3SchemaTest.java

@@ -0,0 +1,151 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.protobuf.testing.Proto3Testing.Proto3Empty;
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3MessageWithMaps;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import org.junit.Test;
+
+/** Base class for tests using {@link Proto3Message}. */
+public abstract class AbstractProto3SchemaTest extends AbstractSchemaTest<Proto3Message> {
+  @Override
+  protected Proto3MessageFactory messageFactory() {
+    return new Proto3MessageFactory(10, 20, 2, 2);
+  }
+
+  @Override
+  protected List<ByteBuffer> serializedBytesWithInvalidUtf8() throws IOException {
+    List<ByteBuffer> invalidBytes = new ArrayList<>();
+    byte[] invalid = new byte[] {(byte) 0x80};
+    {
+      ByteBuffer buffer = ByteBuffer.allocate(100);
+      CodedOutputStream codedOutput = CodedOutputStream.newInstance(buffer);
+      codedOutput.writeByteArray(Proto3Message.FIELD_STRING_9_FIELD_NUMBER, invalid);
+      codedOutput.flush();
+      buffer.flip();
+      invalidBytes.add(buffer);
+    }
+    {
+      ByteBuffer buffer = ByteBuffer.allocate(100);
+      CodedOutputStream codedOutput = CodedOutputStream.newInstance(buffer);
+      codedOutput.writeByteArray(Proto3Message.FIELD_STRING_LIST_26_FIELD_NUMBER, invalid);
+      codedOutput.flush();
+      buffer.flip();
+      invalidBytes.add(buffer);
+    }
+    return invalidBytes;
+  }
+
+  @Test
+  public void mergeOptionalMessageFields() throws Exception {
+    Proto3Message message1 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(123).clearFieldInt325().build())
+            .build();
+    Proto3Message message2 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().clearFieldInt643().setFieldInt325(456).build())
+            .build();
+    Proto3Message message3 =
+        newBuilder()
+            .setFieldMessage10(newBuilder().setFieldInt643(789).clearFieldInt325().build())
+            .build();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    message1.writeTo(output);
+    message2.writeTo(output);
+    message3.writeTo(output);
+    byte[] data = output.toByteArray();
+
+    Proto3Message merged = ExperimentalSerializationUtil.fromByteArray(data, Proto3Message.class);
+    assertEquals(789, merged.getFieldMessage10().getFieldInt643());
+    assertEquals(456, merged.getFieldMessage10().getFieldInt325());
+  }
+
+  @Test
+  public void oneofFieldsShouldRoundtrip() throws IOException {
+    roundtrip("Field 53", newBuilder().setFieldDouble53(100).build());
+    roundtrip("Field 54", newBuilder().setFieldFloat54(100).build());
+    roundtrip("Field 55", newBuilder().setFieldInt6455(100).build());
+    roundtrip("Field 56", newBuilder().setFieldUint6456(100L).build());
+    roundtrip("Field 57", newBuilder().setFieldInt3257(100).build());
+    roundtrip("Field 58", newBuilder().setFieldFixed6458(100).build());
+    roundtrip("Field 59", newBuilder().setFieldFixed3259(100).build());
+    roundtrip("Field 60", newBuilder().setFieldBool60(true).build());
+    roundtrip("Field 61", newBuilder().setFieldString61(data().getString()).build());
+    roundtrip(
+        "Field 62", newBuilder().setFieldMessage62(newBuilder().setFieldDouble1(100)).build());
+    roundtrip("Field 63", newBuilder().setFieldBytes63(data().getBytes()).build());
+    roundtrip("Field 64", newBuilder().setFieldUint3264(100).build());
+    roundtrip("Field 65", newBuilder().setFieldSfixed3265(100).build());
+    roundtrip("Field 66", newBuilder().setFieldSfixed6466(100).build());
+    roundtrip("Field 67", newBuilder().setFieldSint3267(100).build());
+    roundtrip("Field 68", newBuilder().setFieldSint6468(100).build());
+  }
+
+  @Test
+  public void preserveUnknownFields() {
+    Proto3Message expectedMessage = messageFactory().newMessage();
+    Proto3Empty empty =
+        ExperimentalSerializationUtil.fromByteArray(
+            expectedMessage.toByteArray(), Proto3Empty.class);
+    assertEquals(expectedMessage.getSerializedSize(), empty.getSerializedSize());
+    assertEquals(expectedMessage.toByteString(), empty.toByteString());
+  }
+
+  @Test
+  public void preserveUnknownFieldsProto2() {
+    // Make sure we will be able to preserve valid proto2 wireformat, including those that are not
+    // supported in proto3, e.g. groups.
+    byte[] payload = new Proto2MessageFactory(10, 20, 2, 2).newMessage().toByteArray();
+    Proto3Empty empty = ExperimentalSerializationUtil.fromByteArray(payload, Proto3Empty.class);
+    assertEquals(payload.length, empty.getSerializedSize());
+  }
+
+  @Test
+  public void mapsShouldRoundtrip() throws IOException {
+    roundtrip(
+        "Proto3MessageWithMaps",
+        new Proto3MessageFactory(2, 10, 2, 2).newMessageWithMaps(),
+        Protobuf.getInstance().schemaFor(Proto3MessageWithMaps.class));
+  }
+
+  private static Proto3Message.Builder newBuilder() {
+    return Proto3Message.newBuilder();
+  }
+}

+ 157 - 0
java/core/src/test/java/com/google/protobuf/AbstractSchemaTest.java

@@ -0,0 +1,157 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+public abstract class AbstractSchemaTest<T extends MessageLite> {
+  private Schema<T> schema;
+
+  @Before
+  public void setup() {
+    schema = schema();
+    registerSchemas();
+  }
+
+  // Subclass should override this method if it needs to register more than one schemas.
+  protected void registerSchemas() {
+    // Register this schema with the runtime to support processing of nested messages.
+    Protobuf.getInstance().registerSchemaOverride(schema.newInstance().getClass(), schema);
+  }
+
+  protected abstract Schema<T> schema();
+
+  protected abstract ExperimentalMessageFactory<? extends T> messageFactory();
+
+  @SuppressWarnings("unused")
+  protected List<ByteBuffer> serializedBytesWithInvalidUtf8() throws IOException {
+    return Collections.emptyList();
+  }
+
+  @Test
+  public void randomMessageShouldRoundtrip() throws IOException {
+    roundtrip("", messageFactory().newMessage());
+  }
+
+  @Test
+  public void invalidUtf8StringParsing() throws IOException {
+    for (ByteBuffer invalidUtf8Bytes : serializedBytesWithInvalidUtf8()) {
+      Reader reader = BinaryReader.newInstance(invalidUtf8Bytes, /* bufferIsImmutable= */ true);
+
+      T newMsg = schema.newInstance();
+      try {
+        schema.mergeFrom(newMsg, reader, ExtensionRegistryLite.getEmptyRegistry());
+        fail("should throw invalid ");
+      } catch (InvalidProtocolBufferException expected) {
+      }
+    }
+  }
+
+  @Test
+  public void mergeFromByteArrayFastPathMayThrowIndexOutOfBoundsException() throws IOException {
+    if (!Android.isOnAndroidDevice()) {
+      // Skip this test if not on Android.
+      return;
+    }
+    byte[] data = messageFactory().newMessage().toByteArray();
+    int exceptionCount = 0;
+    for (int i = 0; i <= data.length; i++) {
+      byte[] truncatedData = Arrays.copyOf(data, i);
+      try {
+        T message = schema.newInstance();
+        // Test that this method throws the expected exceptions.
+        schema.mergeFrom(message, truncatedData, 0, i, new ArrayDecoders.Registers());
+      } catch (InvalidProtocolBufferException e) {
+        // Ignore expected exceptions.
+      } catch (IndexOutOfBoundsException e) {
+        exceptionCount += 1;
+      }
+    }
+    assertNotEquals(0, exceptionCount);
+  }
+
+  protected static final <M extends MessageLite> void roundtrip(
+      String failureMessage, M msg, Schema<M> schema) throws IOException {
+    byte[] serializedBytes = ExperimentalSerializationUtil.toByteArray(msg, schema);
+    assertEquals(failureMessage, msg.getSerializedSize(), serializedBytes.length);
+
+    // Now read it back in and verify it matches the original.
+    if (Android.isOnAndroidDevice()) {
+      // Test the fast path on Android.
+      M newMsg = schema.newInstance();
+      schema.mergeFrom(
+          newMsg, serializedBytes, 0, serializedBytes.length, new ArrayDecoders.Registers());
+      schema.makeImmutable(newMsg);
+      assertEquals(failureMessage, msg, newMsg);
+    }
+    M newMsg = schema.newInstance();
+    Reader reader = BinaryReader.newInstance(ByteBuffer.wrap(serializedBytes), true);
+    schema.mergeFrom(newMsg, reader, ExtensionRegistryLite.getEmptyRegistry());
+    schema.makeImmutable(newMsg);
+
+    assertEquals(failureMessage, msg, newMsg);
+  }
+
+  protected final void roundtrip(String failureMessage, T msg) throws IOException {
+    roundtrip(failureMessage, msg, schema);
+  }
+
+  protected final ExperimentalTestDataProvider data() {
+    return messageFactory().dataProvider();
+  }
+
+  protected List<T> newMessagesMissingRequiredFields() {
+    return Collections.emptyList();
+  }
+
+  @SuppressWarnings("unchecked")
+  @Test
+  public void testRequiredFields() throws Exception {
+    for (T msg : newMessagesMissingRequiredFields()) {
+      if (schema.isInitialized(msg)) {
+        assertEquals("", msg.toString());
+        msg = (T) msg.toBuilder().build();
+      }
+      assertFalse(schema.isInitialized(msg));
+    }
+  }
+}

+ 236 - 0
java/core/src/test/java/com/google/protobuf/ArrayDecodersTest.java

@@ -0,0 +1,236 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.ArrayDecoders.Registers;
+import java.io.IOException;
+import junit.framework.TestCase;
+
+public class ArrayDecodersTest extends TestCase {
+
+  private static final int TAG = WireFormat.makeTag(1, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+  private static final ByteString NEGATIVE_SIZE_0 = generateNegativeLength(0);
+  private static final ByteString NEGATIVE_SIZE_1 = generateNegativeLength(1);
+
+  private Registers registers;
+
+  @Override
+  public void setUp() {
+    registers = new Registers();
+    registers.int1 = TAG;
+  }
+
+  public void testException_decodeString() {
+    try {
+      ArrayDecoders.decodeString(NEGATIVE_SIZE_0.toByteArray(), 0, registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeStringRequireUtf8() {
+    try {
+      ArrayDecoders.decodeStringRequireUtf8(NEGATIVE_SIZE_0.toByteArray(), 0, registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeBytes() {
+    try {
+      ArrayDecoders.decodeBytes(NEGATIVE_SIZE_0.toByteArray(), 0, registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeStringList_first() {
+    try {
+      ArrayDecoders.decodeStringList(
+          TAG,
+          NEGATIVE_SIZE_0.toByteArray(),
+          0,
+          NEGATIVE_SIZE_0.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeStringList_second() {
+    try {
+      ArrayDecoders.decodeStringList(
+          TAG,
+          NEGATIVE_SIZE_1.toByteArray(),
+          0,
+          NEGATIVE_SIZE_1.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeStringListRequireUtf8_first() {
+    try {
+      ArrayDecoders.decodeStringListRequireUtf8(
+          TAG,
+          NEGATIVE_SIZE_0.toByteArray(),
+          0,
+          NEGATIVE_SIZE_0.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeStringListRequireUtf8_second() {
+    try {
+      ArrayDecoders.decodeStringListRequireUtf8(
+          TAG,
+          NEGATIVE_SIZE_1.toByteArray(),
+          0,
+          NEGATIVE_SIZE_1.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeBytesList_first() {
+    try {
+      ArrayDecoders.decodeBytesList(
+          TAG,
+          NEGATIVE_SIZE_0.toByteArray(),
+          0,
+          NEGATIVE_SIZE_0.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeBytesList_second() {
+    try {
+      ArrayDecoders.decodeBytesList(
+          TAG,
+          NEGATIVE_SIZE_1.toByteArray(),
+          0,
+          NEGATIVE_SIZE_1.size(),
+          new ProtobufArrayList<Object>(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeUnknownField() {
+    try {
+      ArrayDecoders.decodeUnknownField(
+          TAG,
+          NEGATIVE_SIZE_0.toByteArray(),
+          0,
+          NEGATIVE_SIZE_0.size(),
+          UnknownFieldSetLite.newInstance(),
+          registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  public void testException_decodeHugeField() {
+    byte[] badBytes =
+        new byte[] {
+          (byte) 0x80, (byte) 0xFF, (byte) 0xFF, (byte) 0xEF, 0x73, 0x74, 0x69, 0x6E, 0x67
+        };
+    try {
+      ArrayDecoders.decodeUnknownField(
+          TAG, badBytes, 0, badBytes.length, UnknownFieldSetLite.newInstance(), registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+
+    try {
+      ArrayDecoders.decodeBytes(badBytes, 0, registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+
+    byte[] badBytesList =
+        new byte[] {
+          0x01,
+          0x77,
+          0x0A,
+          (byte) 0x80,
+          (byte) 0xFF,
+          (byte) 0xFF,
+          (byte) 0xEF,
+          0x73,
+          0x74,
+          0x69,
+          0x6E,
+          0x67
+        };
+    try {
+      ArrayDecoders.decodeBytesList(
+          TAG, badBytesList, 0, badBytes.length, new ProtobufArrayList<>(), registers);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+  }
+
+  private static ByteString generateNegativeLength(int count) {
+    try {
+      ByteString.Output byteStringOutput = ByteString.newOutput();
+      CodedOutputStream codedOutput = CodedOutputStream.newInstance(byteStringOutput);
+
+      // Write out count - 1 valid 0 length fields; we only write out tags after the field since
+      // ArrayDecoders expects the first tag to already have been parsed.
+      for (int i = 0; i < count; i++) {
+        codedOutput.writeInt32NoTag(0);
+        codedOutput.writeInt32NoTag(TAG);
+      }
+
+      // Write out a negative length
+      codedOutput.writeInt32NoTag(-1);
+
+      codedOutput.flush();
+
+      return byteStringOutput.toByteString();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

+ 90 - 0
java/core/src/test/java/com/google/protobuf/BinaryProtocolTest.java

@@ -0,0 +1,90 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class BinaryProtocolTest {
+  @Before
+  public void setup() {
+    TestSchemas.registerGenericProto2Schemas();
+
+    Protobuf.getInstance()
+        .registerSchemaOverride(Proto3Message.class, TestSchemas.genericProto3Schema);
+  }
+
+  @Test
+  public void proto3Roundtrip() throws Exception {
+    Proto3Message expected = new Proto3MessageFactory(5, 10, 2, 2).newMessage();
+    byte[] expectedBytes = expected.toByteArray();
+
+    // Deserialize with BinaryReader and verify that the message matches the original.
+    Proto3Message result =
+        ExperimentalSerializationUtil.fromByteArray(expectedBytes, Proto3Message.class);
+    assertEquals(expected, result);
+
+    // Now write it back out using BinaryWriter and verify the output length.
+    byte[] actualBytes = ExperimentalSerializationUtil.toByteArray(result);
+    Assert.assertEquals(expectedBytes.length, actualBytes.length);
+
+    // Read back in the bytes and verify that it matches the original message.
+    Proto3Message actual = Proto3Message.parseFrom(actualBytes);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void proto2Roundtrip() throws Exception {
+    Proto2Message expected = new Proto2MessageFactory(5, 10, 2, 2).newMessage();
+    byte[] expectedBytes = expected.toByteArray();
+
+    // Deserialize with BinaryReader and verify that the message matches the original.
+    Proto2Message result =
+        ExperimentalSerializationUtil.fromByteArray(expectedBytes, Proto2Message.class);
+    assertEquals(expected, result);
+
+    // Now write it back out using BinaryWriter and verify the output length.
+    byte[] actualBytes = ExperimentalSerializationUtil.toByteArray(result);
+    Assert.assertEquals(expectedBytes.length, actualBytes.length);
+
+    // Read back in the bytes and verify that it matches the original message.
+    Proto2Message actual = Proto2Message.parseFrom(actualBytes);
+    assertEquals(expected, actual);
+  }
+}

+ 65 - 0
java/core/src/test/java/com/google/protobuf/CachedFieldSizeTest.java

@@ -0,0 +1,65 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import protobuf_unittest.UnittestProto.TestPackedTypes;
+import proto3_unittest.UnittestProto3;
+import protobuf_unittest.TestCachedFieldSizeMessage;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CachedFieldSizeTest {
+  // Regression test for b/74087933
+  @Test
+  public void testCachedFieldSize() throws Exception {
+    TestCachedFieldSizeMessage.Builder builder = TestCachedFieldSizeMessage.newBuilder();
+    builder.setProto2Child(TestUtil.getPackedSet());
+    builder.setProto3Child(
+        UnittestProto3.TestPackedTypes.parseFrom(TestUtil.getPackedSet().toByteArray()));
+    TestCachedFieldSizeMessage message = builder.build();
+
+    // Serialize once to cache all field sizes. This will use the experimental runtime because
+    // the proto has optimize_for = CODE_SIZE.
+    message.toByteArray();
+    // Serialize individual submessages. This will use the generated implementation. If the
+    // experimental runtime hasn't set the correct cached size, this will throw an exception.
+    byte[] data2 = message.getProto2Child().toByteArray();
+    byte[] data3 = message.getProto3Child().toByteArray();
+
+    // Make sure the serialized data is correct.
+    assertEquals(message.getProto2Child(), TestPackedTypes.parseFrom(data2));
+    assertEquals(message.getProto3Child(), UnittestProto3.TestPackedTypes.parseFrom(data3));
+  }
+}

+ 110 - 0
java/core/src/test/java/com/google/protobuf/CodedAdapterTest.java

@@ -0,0 +1,110 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import java.io.IOException;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class CodedAdapterTest {
+  @Before
+  public void setup() {
+    TestSchemas.registerGenericProto2Schemas();
+
+    Protobuf.getInstance()
+        .registerSchemaOverride(Proto3Message.class, TestSchemas.genericProto3Schema);
+  }
+
+  @Test
+  public void proto3Roundtrip() throws Exception {
+    Proto3Message expected = new Proto3MessageFactory(5, 10, 2, 2).newMessage();
+    byte[] expectedBytes = expected.toByteArray();
+
+    // Deserialize with BinaryReader and verify that the message matches the original.
+    Proto3Message result = fromByteArray(expectedBytes, Proto3Message.class);
+    assertEquals(expected, result);
+
+    // Now write it back out using BinaryWriter and verify the output length.
+    byte[] actualBytes = toByteArray(result, expectedBytes.length);
+
+    // Read back in the bytes and verify that it matches the original message.
+    Proto3Message actual = Proto3Message.parseFrom(actualBytes);
+    assertEquals(expected, actual);
+  }
+
+  @Test
+  public void proto2Roundtrip() throws Exception {
+    Proto2Message expected = new Proto2MessageFactory(5, 10, 2, 2).newMessage();
+    byte[] expectedBytes = expected.toByteArray();
+
+    // Deserialize with BinaryReader and verify that the message matches the original.
+    Proto2Message result = fromByteArray(expectedBytes, Proto2Message.class);
+    assertEquals(expected, result);
+
+    // Now write it back out using BinaryWriter and verify the output length.
+    byte[] actualBytes = toByteArray(result, expectedBytes.length);
+
+    // Read back in the bytes and verify that it matches the original message.
+    Proto2Message actual = Proto2Message.parseFrom(actualBytes);
+    assertEquals(expected, actual);
+  }
+
+  public static <T> byte[] toByteArray(T msg, int size) throws Exception {
+    Schema<T> schema = Protobuf.getInstance().schemaFor(msg);
+    byte[] out = new byte[size];
+    CodedOutputStreamWriter writer =
+        CodedOutputStreamWriter.forCodedOutput(CodedOutputStream.newInstance(out));
+    schema.writeTo(msg, writer);
+    assertEquals(out.length, writer.getTotalBytesWritten());
+    return out;
+  }
+
+  public static <T> T fromByteArray(byte[] data, Class<T> messageType) {
+    Schema<T> schema = Protobuf.getInstance().schemaFor(messageType);
+    try {
+      T msg = schema.newInstance();
+      schema.mergeFrom(
+          msg,
+          CodedInputStreamReader.forCodedInput(CodedInputStream.newInstance(data)),
+          ExtensionRegistryLite.EMPTY_REGISTRY_LITE);
+      return msg;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

+ 56 - 0
java/core/src/test/java/com/google/protobuf/CodedInputStreamTest.java

@@ -44,6 +44,7 @@ import java.io.InputStream;
 import java.nio.ByteBuffer;
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Arrays;
+import java.util.List;
 import junit.framework.TestCase;
 import junit.framework.TestCase;
 
 
 /**
 /**
@@ -1195,4 +1196,59 @@ public class CodedInputStreamTest extends TestCase {
       // Expected
       // Expected
     }
     }
   }
   }
+  
+  public void testMaliciousInputStream() throws Exception {
+    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+    CodedOutputStream codedOutputStream = CodedOutputStream.newInstance(outputStream);
+    codedOutputStream.writeByteArrayNoTag(new byte[] { 0x0, 0x1, 0x2, 0x3, 0x4, 0x5 });
+    codedOutputStream.flush();
+    final List<byte[]> maliciousCapture = new ArrayList<>();
+    InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()) {
+      @Override
+      public synchronized int read(byte[] b, int off, int len) {
+        maliciousCapture.add(b);
+        return super.read(b, off, len);
+      }
+    };
+    
+    // test ByteString
+    
+    CodedInputStream codedInputStream = CodedInputStream.newInstance(inputStream, 1);
+    ByteString byteString = codedInputStream.readBytes();
+    assertEquals(0x0, byteString.byteAt(0));
+    maliciousCapture.get(1)[0] = 0x9;
+    assertEquals(0x0, byteString.byteAt(0));
+    
+    // test ByteBuffer
+    
+    inputStream.reset();
+    maliciousCapture.clear();
+    codedInputStream = CodedInputStream.newInstance(inputStream, 1);
+    ByteBuffer byteBuffer = codedInputStream.readByteBuffer();
+    assertEquals(0x0, byteBuffer.get(0));
+    maliciousCapture.get(1)[0] = 0x9;
+    assertEquals(0x0, byteBuffer.get(0));
+    
+
+    // test byte[]
+    
+    inputStream.reset();
+    maliciousCapture.clear();
+    codedInputStream = CodedInputStream.newInstance(inputStream, 1);
+    byte[] byteArray = codedInputStream.readByteArray();
+    assertEquals(0x0, byteArray[0]);
+    maliciousCapture.get(1)[0] = 0x9;
+    assertEquals(0x9, byteArray[0]); // MODIFICATION! Should we fix?
+
+    // test rawBytes
+    
+    inputStream.reset();
+    maliciousCapture.clear();
+    codedInputStream = CodedInputStream.newInstance(inputStream, 1);
+    int length = codedInputStream.readRawVarint32();
+    byteArray = codedInputStream.readRawBytes(length);
+    assertEquals(0x0, byteArray[0]);
+    maliciousCapture.get(1)[0] = 0x9;
+    assertEquals(0x9, byteArray[0]); // MODIFICATION! Should we fix?
+  }
 }
 }

+ 21 - 0
java/core/src/test/java/com/google/protobuf/DescriptorsTest.java

@@ -30,6 +30,8 @@
 
 
 package com.google.protobuf;
 package com.google.protobuf;
 
 
+import protobuf_unittest.NestedExtension;
+import protobuf_unittest.NonNestedExtension;
 import com.google.protobuf.DescriptorProtos.DescriptorProto;
 import com.google.protobuf.DescriptorProtos.DescriptorProto;
 import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
 import com.google.protobuf.DescriptorProtos.EnumDescriptorProto;
 import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
 import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto;
@@ -779,4 +781,23 @@ public class DescriptorsTest extends TestCase {
     assertEquals("FIELDNAME5", d.getFields().get(4).getJsonName());
     assertEquals("FIELDNAME5", d.getFields().get(4).getJsonName());
     assertEquals("@type", d.getFields().get(5).getJsonName());
     assertEquals("@type", d.getFields().get(5).getJsonName());
   }
   }
+
+  public void testExtensionRenamesKeywords() {
+    assertTrue(NonNestedExtension.if_ instanceof GeneratedMessage.GeneratedExtension);
+    assertTrue(
+        NestedExtension.MyNestedExtension.default_
+            instanceof GeneratedMessage.GeneratedExtension);
+
+    NonNestedExtension.MessageToBeExtended msg =
+        NonNestedExtension.MessageToBeExtended.newBuilder()
+            .setExtension(NonNestedExtension.if_, "!fi")
+            .build();
+    assertEquals("!fi", msg.getExtension(NonNestedExtension.if_));
+
+    msg =
+        NonNestedExtension.MessageToBeExtended.newBuilder()
+            .setExtension(NestedExtension.MyNestedExtension.default_, 8)
+            .build();
+    assertEquals(8, msg.getExtension(NestedExtension.MyNestedExtension.default_).intValue());
+  }
 }
 }

+ 40 - 0
java/core/src/test/java/com/google/protobuf/ExperimentalMessageFactory.java

@@ -0,0 +1,40 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** Interface for a test factory for messages. */
+public interface ExperimentalMessageFactory<T extends MessageLite> {
+  /** Creates a new random message instance. */
+  T newMessage();
+
+  /** Gets the underlying data provider. */
+  ExperimentalTestDataProvider dataProvider();
+}

+ 113 - 0
java/core/src/test/java/com/google/protobuf/ExperimentalSerializationUtil.java

@@ -0,0 +1,113 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Queue;
+
+/** Utilities for serialization. */
+public class ExperimentalSerializationUtil {
+
+  /**
+   * Serializes the given message to a byte array using {@link com.google.protobuf.BinaryWriter}.
+   */
+  public static <T> byte[] toByteArray(T msg) throws IOException {
+    return toByteArray(msg, Protobuf.getInstance().schemaFor(msg));
+  }
+
+  /**
+   * Serializes the given message to a byte array using {@link com.google.protobuf.BinaryWriter}
+   * with a customized Schema.
+   */
+  public static <T> byte[] toByteArray(T msg, Schema<T> schema) throws IOException {
+    BinaryWriter writer = BinaryWriter.newHeapInstance(BufferAllocator.unpooled());
+    schema.writeTo(msg, writer);
+
+    byte[] out = new byte[writer.getTotalBytesWritten()];
+    int outPos = 0;
+    Queue<AllocatedBuffer> buffers = writer.complete();
+    while (true) {
+      AllocatedBuffer buffer = buffers.poll();
+      if (buffer == null) {
+        break;
+      }
+      int length = buffer.limit() - buffer.position();
+      System.arraycopy(
+          buffer.array(), buffer.arrayOffset() + buffer.position(), out, outPos, length);
+      outPos += length;
+    }
+    if (out.length != outPos) {
+      throw new IllegalArgumentException("Failed to serialize test message");
+    }
+    return out;
+  }
+
+  /** Deserializes a message from the given byte array. */
+  public static <T> T fromByteArray(byte[] data, Class<T> messageType) {
+    if (Android.isOnAndroidDevice()) {
+      return fromByteArrayFastPath(data, messageType);
+    } else {
+      return fromByteArray(data, messageType, ExtensionRegistryLite.getEmptyRegistry());
+    }
+  }
+
+  /**
+   * Deserializes a message from the given byte array using {@link com.google.protobuf.BinaryReader}
+   * with an extension registry and a customized Schema.
+   */
+  public static <T> T fromByteArray(
+      byte[] data, Class<T> messageType, ExtensionRegistryLite extensionRegistry) {
+    try {
+      Schema<T> schema = Protobuf.getInstance().schemaFor(messageType);
+      T msg = schema.newInstance();
+      schema.mergeFrom(
+          msg, BinaryReader.newInstance(ByteBuffer.wrap(data), true), extensionRegistry);
+      schema.makeImmutable(msg);
+      return msg;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  /** Deserializes a lite message from the given byte array using fast path. */
+  private static <T> T fromByteArrayFastPath(byte[] data, Class<T> messageType) {
+    try {
+      Schema<T> schema = Protobuf.getInstance().schemaFor(messageType);
+      T msg = schema.newInstance();
+      schema.mergeFrom(msg, data, 0, data.length, new ArrayDecoders.Registers());
+      schema.makeImmutable(msg);
+      return msg;
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
+  }
+}

+ 189 - 0
java/core/src/test/java/com/google/protobuf/ExperimentalTestDataProvider.java

@@ -0,0 +1,189 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import java.util.Random;
+
+/** Utility class that provides data primitives for filling out protobuf messages. */
+public final class ExperimentalTestDataProvider {
+  private static final Random RANDOM = new Random(100);
+
+  private final Varint32Provider varint32s = new Varint32Provider();
+  private final Varint64Provider varint64s = new Varint64Provider();
+  private final int stringLength;
+
+  public ExperimentalTestDataProvider(int stringLength) {
+    this.stringLength = stringLength;
+  }
+
+  public double getDouble() {
+    double value = 0.0;
+    while (Double.compare(0.0, value) == 0) {
+      value = RANDOM.nextDouble();
+    }
+    return value;
+  }
+
+  public float getFloat() {
+    float value = 0.0f;
+    while (Float.compare(0.0f, value) == 0) {
+      value = RANDOM.nextFloat();
+    }
+    return value;
+  }
+
+  public long getLong() {
+    return varint64s.getLong();
+  }
+
+  public int getInt() {
+    return varint32s.getInt();
+  }
+
+  public boolean getBool() {
+    return true;
+  }
+
+  public int getEnum() {
+    return Math.abs(getInt()) % 3;
+  }
+
+  public String getString() {
+    StringBuilder builder = new StringBuilder(stringLength);
+    for (int i = 0; i < stringLength; ++i) {
+      builder.append((char) (RANDOM.nextInt('z' - 'a') + 'a'));
+    }
+    return builder.toString();
+  }
+
+  public ByteString getBytes() {
+    return ByteString.copyFromUtf8(getString());
+  }
+
+  /**
+   * Iterator over integer values. Uses a simple distribution over 32-bit varints (generally
+   * favoring smaller values).
+   */
+  private static final class Varint32Provider {
+    private static final int[][] VALUES = {
+      new int[] {1, 50, 100, 127}, // 1 byte values
+      new int[] {128, 500, 10000, 16383}, // 2 bytes values
+      new int[] {16384, 50000, 1000000, 2097151}, // 3 bytes values
+      new int[] {2097152, 10000000, 200000000, 268435455}, // 4 bytes values
+      new int[] {268435456, 0x30000000, 0x7FFFFFFF, 0xFFFFFFFF} // 5 bytes values
+    };
+
+    /** Number of samples that should be taken from each value array. */
+    private static final int[] NUM_SAMPLES = {3, 2, 1, 1, 2};
+
+    /**
+     * The index into the {@link #VALUES} array that identifies the list of samples currently being
+     * iterated over.
+     */
+    private int listIndex;
+
+    /** The index of the next sample within a list. */
+    private int sampleIndex;
+
+    /** The number of successive samples that have been taken from the current list. */
+    private int samplesTaken;
+
+    public int getInt() {
+      if (samplesTaken++ > NUM_SAMPLES[listIndex]) {
+        // Done taking samples from this list. Go to the next one.
+        listIndex = (listIndex + 1) % VALUES.length;
+        sampleIndex = 0;
+        samplesTaken = 0;
+      }
+
+      int value = VALUES[listIndex][sampleIndex];
+
+      // All lists are exactly 4 long (i.e. power of 2), so we can optimize the mod operation
+      // with masking.
+      sampleIndex = (sampleIndex + 1) & 3;
+
+      return value;
+    }
+  }
+
+  /**
+   * Iterator over integer values. Uses a simple distribution over 64-bit varints (generally
+   * favoring smaller values).
+   */
+  private static final class Varint64Provider {
+    private static final long[][] VALUES = {
+      new long[] {1, 50, 100, 127},
+      new long[] {128, 500, 10000, 16383},
+      new long[] {16384, 50000, 1000000, 2097151},
+      new long[] {2097152, 10000000, 200000000, 268435455},
+      new long[] {268435456, 0x30000000, 0x7FFFFFFF, 34359738367L},
+      new long[] {34359738368L, 2000000000000L, 4000000000000L, 4398046511103L},
+      new long[] {4398046511104L, 200000000000000L, 500000000000000L, 562949953421311L},
+      new long[] {0x4000000000000L, 0x5000000000000L, 0x6000000000000L, 0x0FFFFFFFFFFFFFFL},
+      new long[] {0x100000000000000L, 0x3FFFFFFFFFFFFFFFL, 0x5FFFFFFFFFFFFFFL, 0x7FFFFFFFFFFFFFFFL},
+      new long[] {
+        0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL
+      }
+    };
+
+    /** Number of samples that should be taken from each value array. */
+    private static final int[] NUM_SAMPLES = {4, 2, 2, 1, 1, 1, 1, 2, 2, 4};
+
+    /**
+     * The index into the {@link #VALUES} array that identifies the list of samples currently being
+     * iterated over.
+     */
+    private int listIndex;
+
+    /** The index of the next sample within a list. */
+    private int sampleIndex;
+
+    /** The number of successive samples that have been taken from the current list. */
+    private int samplesTaken;
+
+    public long getLong() {
+      if (samplesTaken++ > NUM_SAMPLES[listIndex]) {
+        // Done taking samples from this list. Go to the next one.
+        listIndex = (listIndex + 1) % VALUES.length;
+        sampleIndex = 0;
+        samplesTaken = 0;
+      }
+
+      long value = VALUES[listIndex][sampleIndex];
+
+      // All lists are exactly 4 long (i.e. power of 2), so we can optimize the mod operation
+      // with masking.
+      sampleIndex = (sampleIndex + 1) & 3;
+
+      return value;
+    }
+  }
+}

+ 4 - 39
java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java

@@ -30,8 +30,6 @@
 
 
 package com.google.protobuf;
 package com.google.protobuf;
 
 
-import protobuf_unittest.NestedExtension;
-import protobuf_unittest.NestedExtensionLite;
 import protobuf_unittest.NonNestedExtension;
 import protobuf_unittest.NonNestedExtension;
 import protobuf_unittest.NonNestedExtensionLite;
 import protobuf_unittest.NonNestedExtensionLite;
 import java.lang.reflect.Method;
 import java.lang.reflect.Method;
@@ -71,8 +69,6 @@ public class ExtensionRegistryFactoryTest extends TestCase {
     void testAdd();
     void testAdd();
 
 
     void testAdd_immutable();
     void testAdd_immutable();
-
-    void testExtensionRenamesKeywords();
   }
   }
 
 
   /** Test implementations for the non-Lite usage of ExtensionRegistryFactory. */
   /** Test implementations for the non-Lite usage of ExtensionRegistryFactory. */
@@ -160,23 +156,6 @@ public class ExtensionRegistryFactoryTest extends TestCase {
       } catch (IllegalArgumentException expected) {
       } catch (IllegalArgumentException expected) {
       }
       }
     }
     }
-
-    @Override
-    public void testExtensionRenamesKeywords() {
-      assertTrue(NonNestedExtension.if_ instanceof GeneratedMessage.GeneratedExtension);
-      assertTrue(NestedExtension.MyNestedExtension.default_ instanceof GeneratedMessage.GeneratedExtension);
-
-      NonNestedExtension.MessageToBeExtended msg =
-              NonNestedExtension.MessageToBeExtended.newBuilder()
-                      .setExtension(NonNestedExtension.if_, "!fi")
-                      .build();
-      assertEquals("!fi", msg.getExtension(NonNestedExtension.if_));
-
-      msg = NonNestedExtension.MessageToBeExtended.newBuilder()
-              .setExtension(NestedExtension.MyNestedExtension.default_, 8)
-              .build();
-      assertEquals(8, msg.getExtension(NestedExtension.MyNestedExtension.default_).intValue());
-    }
   }
   }
 
 
   /** Test implementations for the Lite usage of ExtensionRegistryFactory. */
   /** Test implementations for the Lite usage of ExtensionRegistryFactory. */
@@ -223,23 +202,6 @@ public class ExtensionRegistryFactoryTest extends TestCase {
       } catch (UnsupportedOperationException expected) {
       } catch (UnsupportedOperationException expected) {
       }
       }
     }
     }
-
-    @Override
-    public void testExtensionRenamesKeywords() {
-      assertTrue(NonNestedExtensionLite.package_ instanceof GeneratedMessageLite.GeneratedExtension);
-      assertTrue(NestedExtensionLite.MyNestedExtensionLite.private_ instanceof GeneratedMessageLite.GeneratedExtension);
-
-      NonNestedExtensionLite.MessageLiteToBeExtended msg =
-              NonNestedExtensionLite.MessageLiteToBeExtended.newBuilder()
-              .setExtension(NonNestedExtensionLite.package_, true)
-              .build();
-      assertTrue(msg.getExtension(NonNestedExtensionLite.package_));
-
-      msg = NonNestedExtensionLite.MessageLiteToBeExtended.newBuilder()
-              .setExtension(NestedExtensionLite.MyNestedExtensionLite.private_, 2.4)
-              .build();
-      assertEquals(2.4, msg.getExtension(NestedExtensionLite.MyNestedExtensionLite.private_), 0.001);
-    }
   }
   }
 
 
   /** Defines a suite of tests which the JUnit3 runner retrieves by reflection. */
   /** Defines a suite of tests which the JUnit3 runner retrieves by reflection. */
@@ -311,7 +273,10 @@ public class ExtensionRegistryFactoryTest extends TestCase {
               resolveClass(loadedClass);
               resolveClass(loadedClass);
             }
             }
           }
           }
-        } catch (ClassNotFoundException e) {
+        } catch (ClassNotFoundException | SecurityException e) {
+          // Java 8+ would throw a SecurityException if we attempt to find a loaded class from
+          // java.lang.* package. We don't really care about those anyway, so just delegate to the
+          // parent class loader.
           loadedClass = super.loadClass(name, resolve);
           loadedClass = super.loadClass(name, resolve);
         }
         }
         return loadedClass;
         return loadedClass;

+ 894 - 0
java/core/src/test/java/com/google/protobuf/MapLiteTest.java

@@ -0,0 +1,894 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import map_lite_test.MapTestProto.BizarroTestMap;
+import map_lite_test.MapTestProto.TestMap;
+import map_lite_test.MapTestProto.TestMap.MessageValue;
+import map_lite_test.MapTestProto.TestMapOrBuilder;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import junit.framework.TestCase;
+
+/** Unit tests for map fields. */
+public final class MapLiteTest extends TestCase {
+
+  private void setMapValues(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 11)
+        .putInt32ToInt32Field(2, 22)
+        .putInt32ToInt32Field(3, 33)
+        .putInt32ToStringField(1, "11")
+        .putInt32ToStringField(2, "22")
+        .putInt32ToStringField(3, "33")
+        .putInt32ToBytesField(1, TestUtil.toBytes("11"))
+        .putInt32ToBytesField(2, TestUtil.toBytes("22"))
+        .putInt32ToBytesField(3, TestUtil.toBytes("33"))
+        .putInt32ToEnumField(1, TestMap.EnumValue.FOO)
+        .putInt32ToEnumField(2, TestMap.EnumValue.BAR)
+        .putInt32ToEnumField(3, TestMap.EnumValue.BAZ)
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build())
+        .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build())
+        .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build())
+        .putStringToInt32Field("1", 11)
+        .putStringToInt32Field("2", 22)
+        .putStringToInt32Field("3", 33);
+  }
+
+  public void testSetMapValues() {
+    TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder();
+    setMapValues(usingMutableMapBuilder);
+    TestMap usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesSet(usingMutableMap);
+
+    TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder();
+    setMapValues(usingAccessorsBuilder);
+    TestMap usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesSet(usingAccessors);
+    assertEquals(usingAccessors, usingMutableMap);
+  }
+
+  private void copyMapValues(TestMap source, TestMap.Builder destination) {
+    destination
+        .putAllInt32ToInt32Field(source.getInt32ToInt32Field())
+        .putAllInt32ToStringField(source.getInt32ToStringField())
+        .putAllInt32ToBytesField(source.getInt32ToBytesField())
+        .putAllInt32ToEnumField(source.getInt32ToEnumField())
+        .putAllInt32ToMessageField(source.getInt32ToMessageField())
+        .putAllStringToInt32Field(source.getStringToInt32Field());
+  }
+
+  private void assertMapValuesSet(TestMap message) {
+    assertEquals(3, message.getInt32ToInt32Field().size());
+    assertEquals(11, message.getInt32ToInt32Field().get(1).intValue());
+    assertEquals(22, message.getInt32ToInt32Field().get(2).intValue());
+    assertEquals(33, message.getInt32ToInt32Field().get(3).intValue());
+
+    assertEquals(3, message.getInt32ToStringField().size());
+    assertEquals("11", message.getInt32ToStringField().get(1));
+    assertEquals("22", message.getInt32ToStringField().get(2));
+    assertEquals("33", message.getInt32ToStringField().get(3));
+
+    assertEquals(3, message.getInt32ToBytesField().size());
+    assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1));
+    assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2));
+    assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
+
+    assertEquals(3, message.getInt32ToEnumField().size());
+    assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1));
+    assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2));
+    assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
+
+    assertEquals(3, message.getInt32ToMessageField().size());
+    assertEquals(11, message.getInt32ToMessageField().get(1).getValue());
+    assertEquals(22, message.getInt32ToMessageField().get(2).getValue());
+    assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
+
+    assertEquals(3, message.getStringToInt32Field().size());
+    assertEquals(11, message.getStringToInt32Field().get("1").intValue());
+    assertEquals(22, message.getStringToInt32Field().get("2").intValue());
+    assertEquals(33, message.getStringToInt32Field().get("3").intValue());
+  }
+
+  private void updateMapValues(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 111)
+        .removeInt32ToInt32Field(2)
+        .putInt32ToInt32Field(4, 44)
+        .putInt32ToStringField(1, "111")
+        .removeInt32ToStringField(2)
+        .putInt32ToStringField(4, "44")
+        .putInt32ToBytesField(1, TestUtil.toBytes("111"))
+        .removeInt32ToBytesField(2)
+        .putInt32ToBytesField(4, TestUtil.toBytes("44"))
+        .putInt32ToEnumField(1, TestMap.EnumValue.BAR)
+        .removeInt32ToEnumField(2)
+        .putInt32ToEnumField(4, TestMap.EnumValue.QUX)
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build())
+        .removeInt32ToMessageField(2)
+        .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build())
+        .putStringToInt32Field("1", 111)
+        .removeStringToInt32Field("2")
+        .putStringToInt32Field("4", 44);
+  }
+
+  public void testUpdateMapValues() {
+    TestMap.Builder mapBuilder = TestMap.newBuilder();
+    setMapValues(mapBuilder);
+    TestMap map = mapBuilder.build();
+    assertMapValuesSet(map);
+
+    mapBuilder = map.toBuilder();
+    updateMapValues(mapBuilder);
+    map = mapBuilder.build();
+    assertMapValuesUpdated(map);
+  }
+
+  private void assertMapValuesUpdated(TestMap message) {
+    assertEquals(3, message.getInt32ToInt32Field().size());
+    assertEquals(111, message.getInt32ToInt32Field().get(1).intValue());
+    assertEquals(33, message.getInt32ToInt32Field().get(3).intValue());
+    assertEquals(44, message.getInt32ToInt32Field().get(4).intValue());
+
+    assertEquals(3, message.getInt32ToStringField().size());
+    assertEquals("111", message.getInt32ToStringField().get(1));
+    assertEquals("33", message.getInt32ToStringField().get(3));
+    assertEquals("44", message.getInt32ToStringField().get(4));
+
+    assertEquals(3, message.getInt32ToBytesField().size());
+    assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1));
+    assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
+    assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4));
+
+    assertEquals(3, message.getInt32ToEnumField().size());
+    assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1));
+    assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
+    assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4));
+
+    assertEquals(3, message.getInt32ToMessageField().size());
+    assertEquals(111, message.getInt32ToMessageField().get(1).getValue());
+    assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
+    assertEquals(44, message.getInt32ToMessageField().get(4).getValue());
+
+    assertEquals(3, message.getStringToInt32Field().size());
+    assertEquals(111, message.getStringToInt32Field().get("1").intValue());
+    assertEquals(33, message.getStringToInt32Field().get("3").intValue());
+    assertEquals(44, message.getStringToInt32Field().get("4").intValue());
+  }
+
+  private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(0, testMapOrBuilder.getStringToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testSanityCopyOnWrite() throws InvalidProtocolBufferException {
+    // Since builders are implemented as a thin wrapper around a message
+    // instance, we attempt to verify that we can't cause the builder to modify
+    // a produced message.
+
+    TestMap.Builder builder = TestMap.newBuilder();
+    TestMap message = builder.build();
+    builder.putInt32ToInt32Field(1, 2);
+    assertTrue(message.getInt32ToInt32Field().isEmpty());
+    assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
+    message = builder.build();
+    builder.putInt32ToInt32Field(2, 3);
+    assertEquals(newMap(1, 2), message.getInt32ToInt32Field());
+    assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field());
+  }
+
+  public void testGetMapIsImmutable() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+
+    setMapValues(builder);
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+  }
+
+  private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) {
+    assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2);
+    assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2");
+    assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2"));
+    assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO);
+    assertImmutable(
+        testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance());
+    assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2);
+  }
+
+  private <K, V> void assertImmutable(Map<K, V> map, K key, V value) {
+    try {
+      map.put(key, value);
+      fail();
+    } catch (UnsupportedOperationException e) {
+      // expected
+    }
+    if (!map.isEmpty()) {
+      try {
+        map.entrySet().remove(map.entrySet().iterator().next());
+        fail();
+      } catch (UnsupportedOperationException e) {
+        // expected
+      }
+    }
+  }
+
+  public void testMapFieldClear() {
+    TestMap.Builder builder = TestMap.newBuilder().putInt32ToInt32Field(1, 2);
+    builder.clearInt32ToInt32Field();
+    assertEquals(0, builder.getInt32ToInt32FieldCount());
+  }
+
+  public void testMutableMapLifecycle() {
+    TestMap.Builder builder = TestMap.newBuilder().putInt32ToInt32Field(1, 2);
+    assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
+    assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
+    builder.putInt32ToInt32Field(2, 3);
+    assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field());
+
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.BAR);
+    assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField());
+    assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField());
+    builder.putInt32ToEnumField(2, TestMap.EnumValue.FOO);
+    assertEquals(
+        newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO), builder.getInt32ToEnumField());
+
+    builder.putInt32ToStringField(1, "1");
+    assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField());
+    assertEquals(newMap(1, "1"), builder.getInt32ToStringField());
+    builder.putInt32ToStringField(2, "2");
+    assertEquals(newMap(1, "1", 2, "2"), builder.getInt32ToStringField());
+
+    builder.putInt32ToMessageField(1, TestMap.MessageValue.getDefaultInstance());
+    assertEquals(
+        newMap(1, TestMap.MessageValue.getDefaultInstance()),
+        builder.build().getInt32ToMessageField());
+    assertEquals(
+        newMap(1, TestMap.MessageValue.getDefaultInstance()), builder.getInt32ToMessageField());
+    builder.putInt32ToMessageField(2, TestMap.MessageValue.getDefaultInstance());
+    assertEquals(
+        newMap(
+            1,
+            TestMap.MessageValue.getDefaultInstance(),
+            2,
+            TestMap.MessageValue.getDefaultInstance()),
+        builder.getInt32ToMessageField());
+  }
+
+  public void testGettersAndSetters() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+    TestMap message = builder.build();
+    assertMapValuesCleared(message);
+
+    builder = message.toBuilder();
+    setMapValues(builder);
+    message = builder.build();
+    assertMapValuesSet(message);
+
+    builder = message.toBuilder();
+    updateMapValues(builder);
+    message = builder.build();
+    assertMapValuesUpdated(message);
+
+    builder = message.toBuilder();
+    builder.clear();
+    assertMapValuesCleared(builder);
+    message = builder.build();
+    assertMapValuesCleared(message);
+  }
+
+  public void testPutAll() throws Exception {
+    TestMap.Builder sourceBuilder = TestMap.newBuilder();
+    setMapValues(sourceBuilder);
+    TestMap source = sourceBuilder.build();
+    assertMapValuesSet(source);
+
+    TestMap.Builder destination = TestMap.newBuilder();
+    copyMapValues(source, destination);
+    assertMapValuesSet(destination.build());
+  }
+
+  public void testPutAllForUnknownEnumValues() throws Exception {
+    TestMap.Builder sourceBuilder =
+        TestMap.newBuilder()
+            .putInt32ToEnumFieldValue(0, 0)
+            .putInt32ToEnumFieldValue(1, 1)
+            .putAllInt32ToEnumFieldValue(newMap(2, 1000)); // unknown value.
+    TestMap source = sourceBuilder.build();
+
+    TestMap.Builder destinationBuilder = TestMap.newBuilder();
+    destinationBuilder.putAllInt32ToEnumFieldValue(source.getInt32ToEnumFieldValue());
+    TestMap destination = destinationBuilder.build();
+
+    assertEquals(0, destination.getInt32ToEnumFieldValue().get(0).intValue());
+    assertEquals(1, destination.getInt32ToEnumFieldValue().get(1).intValue());
+    assertEquals(1000, destination.getInt32ToEnumFieldValue().get(2).intValue());
+    assertEquals(3, destination.getInt32ToEnumFieldCount());
+  }
+
+  public void testPutForUnknownEnumValues() throws Exception {
+    TestMap.Builder builder =
+        TestMap.newBuilder()
+            .putInt32ToEnumFieldValue(0, 0)
+            .putInt32ToEnumFieldValue(1, 1)
+            .putInt32ToEnumFieldValue(2, 1000); // unknown value.
+    TestMap message = builder.build();
+
+    assertEquals(0, message.getInt32ToEnumFieldValueOrThrow(0));
+    assertEquals(1, message.getInt32ToEnumFieldValueOrThrow(1));
+    assertEquals(1000, message.getInt32ToEnumFieldValueOrThrow(2));
+    assertEquals(3, message.getInt32ToEnumFieldCount());
+  }
+
+  public void testPutChecksNullKeysAndValues() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToMessageField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putStringToInt32Field(null, 1);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+  }
+
+  public void testSerializeAndParse() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    TestMap message = builder.build();
+    assertEquals(message.getSerializedSize(), message.toByteString().size());
+    message = TestMap.parser().parseFrom(message.toByteString());
+    assertMapValuesSet(message);
+
+    builder = message.toBuilder();
+    updateMapValues(builder);
+    message = builder.build();
+    assertEquals(message.getSerializedSize(), message.toByteString().size());
+    message = TestMap.parser().parseFrom(message.toByteString());
+    assertMapValuesUpdated(message);
+
+    builder = message.toBuilder();
+    builder.clear();
+    message = builder.build();
+    assertEquals(message.getSerializedSize(), message.toByteString().size());
+    message = TestMap.parser().parseFrom(message.toByteString());
+    assertMapValuesCleared(message);
+  }
+
+  private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream);
+    bizarroMap.writeTo(output);
+    output.flush();
+    return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray()));
+  }
+
+  public void testParseError() throws Exception {
+    ByteString bytes = TestUtil.toBytes("SOME BYTES");
+    String stringKey = "a string key";
+
+    TestMap map =
+        tryParseTestMap(BizarroTestMap.newBuilder().putInt32ToInt32Field(5, bytes).build());
+    assertEquals(0, map.getInt32ToInt32FieldOrDefault(5, -1));
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder().putInt32ToStringField(stringKey, 5).build());
+    assertEquals("", map.getInt32ToStringFieldOrDefault(0, null));
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder().putInt32ToBytesField(stringKey, 5).build());
+    assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY);
+
+    map =
+        tryParseTestMap(BizarroTestMap.newBuilder().putInt32ToEnumField(stringKey, bytes).build());
+    assertEquals(TestMap.EnumValue.FOO, map.getInt32ToEnumFieldOrDefault(0, null));
+
+    try {
+      tryParseTestMap(BizarroTestMap.newBuilder().putInt32ToMessageField(stringKey, bytes).build());
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertTrue(expected.getUnfinishedMessage() instanceof TestMap);
+      map = (TestMap) expected.getUnfinishedMessage();
+      assertTrue(map.getInt32ToMessageField().isEmpty());
+    }
+
+    map =
+        tryParseTestMap(
+            BizarroTestMap.newBuilder().putStringToInt32Field(stringKey, bytes).build());
+    assertEquals(0, map.getStringToInt32FieldOrDefault(stringKey, -1));
+  }
+
+  public void testMergeFrom() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    TestMap message = builder.build();
+
+    TestMap.Builder other = TestMap.newBuilder();
+    other.mergeFrom(message);
+    assertMapValuesSet(other.build());
+  }
+
+  public void testEqualsAndHashCode() throws Exception {
+    // Test that generated equals() and hashCode() will disregard the order
+    // of map entries when comparing/hashing map fields.
+
+    // We can't control the order of elements in a HashMap. The best we can do
+    // here is to add elements in different order.
+    TestMap.Builder b1 =
+        TestMap.newBuilder()
+            .putInt32ToInt32Field(1, 2)
+            .putInt32ToInt32Field(3, 4)
+            .putInt32ToInt32Field(5, 6);
+    TestMap m1 = b1.build();
+
+    TestMap.Builder b2 =
+        TestMap.newBuilder()
+            .putInt32ToInt32Field(5, 6)
+            .putInt32ToInt32Field(1, 2)
+            .putInt32ToInt32Field(3, 4);
+    TestMap m2 = b2.build();
+
+    assertEquals(m1, m2);
+    assertEquals(m1.hashCode(), m2.hashCode());
+
+    // Make sure we did compare map fields.
+    b2.putInt32ToInt32Field(1, 0);
+    m2 = b2.build();
+    assertFalse(m1.equals(m2));
+    // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed
+    // to be different.
+
+    // Regression test for b/18549190: if a map is a subset of the other map,
+    // equals() should return false.
+    b2.removeInt32ToInt32Field(1);
+    m2 = b2.build();
+    assertFalse(m1.equals(m2));
+    assertFalse(m2.equals(m1));
+  }
+
+  public void testUnknownEnumValues() throws Exception {
+    TestMap.Builder builder =
+        TestMap.newBuilder()
+            .putInt32ToEnumFieldValue(0, 0)
+            .putInt32ToEnumFieldValue(1, 1)
+            .putInt32ToEnumFieldValue(2, 1000); // unknown value.
+    TestMap message = builder.build();
+
+    assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(0));
+    assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1));
+    assertEquals(TestMap.EnumValue.UNRECOGNIZED, message.getInt32ToEnumField().get(2));
+
+    builder.putAllInt32ToEnumFieldValue(newMap(2, 1000)); // unknown value.
+    message = builder.build();
+    assertEquals(TestMap.EnumValue.UNRECOGNIZED, message.getInt32ToEnumField().get(2));
+
+    // Unknown enum values should be preserved after:
+    //   1. Serialization and parsing.
+    //   2. toBuild().
+    //   3. mergeFrom().
+    message = TestMap.parseFrom(message.toByteString());
+    assertEquals(1000, message.getInt32ToEnumFieldValue().get(2).intValue());
+    builder = message.toBuilder();
+    assertEquals(1000, builder.getInt32ToEnumFieldValue().get(2).intValue());
+    builder = TestMap.newBuilder().mergeFrom(message);
+    assertEquals(1000, builder.getInt32ToEnumFieldValue().get(2).intValue());
+
+    // hashCode()/equals() should take unknown enum values into account.
+    builder.putAllInt32ToEnumFieldValue(newMap(2, 1001));
+    TestMap message2 = builder.build();
+    assertFalse(message.hashCode() == message2.hashCode());
+    assertFalse(message.equals(message2));
+    // Unknown values will be converted to UNRECOGNIZED so the resulted enum map
+    // should be the same.
+    assertEquals(message2.getInt32ToEnumField(), message.getInt32ToEnumField());
+  }
+
+  public void testIterationOrder() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    TestMap message = builder.build();
+
+    assertEquals(
+        Arrays.asList("1", "2", "3"), new ArrayList<>(message.getStringToInt32Field().keySet()));
+  }
+
+  public void testGetMap() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    TestMap message = builder.build();
+    assertEquals(message.getStringToInt32Field(), message.getStringToInt32FieldMap());
+    assertEquals(message.getInt32ToBytesField(), message.getInt32ToBytesFieldMap());
+    assertEquals(message.getInt32ToEnumField(), message.getInt32ToEnumFieldMap());
+    assertEquals(message.getInt32ToEnumFieldValue(), message.getInt32ToEnumFieldValueMap());
+    assertEquals(message.getInt32ToMessageField(), message.getInt32ToMessageFieldMap());
+  }
+
+  public void testContains() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    assertMapContainsSetValues(builder);
+    assertMapContainsSetValues(builder.build());
+  }
+
+  private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) {
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3));
+    assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToStringField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1));
+
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("1"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("2"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("3"));
+    assertFalse(testMapOrBuilder.containsStringToInt32Field("-1"));
+  }
+
+  public void testCount() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+
+    setMapValues(builder);
+    assertMapCounts(3, builder);
+
+    TestMap message = builder.build();
+    assertMapCounts(3, message);
+
+    builder = message.toBuilder().putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+    assertEquals(4, builder.build().getInt32ToInt32FieldCount());
+
+    // already present - should be unchanged
+    builder.putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+  }
+
+  private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetOrDefault() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValues(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11));
+    assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11));
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11"));
+    assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null));
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null));
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null));
+
+    assertEquals(
+        TestMap.EnumValue.BAR.getNumber(),
+        testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(2, -1));
+    assertEquals(-1, testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(-1000, -1));
+
+    assertEquals(
+        MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null));
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11));
+    assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11));
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testGetOrThrow() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValues(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToStringFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(
+        TestMap.EnumValue.BAR.getNumber(), testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(2));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(
+        MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1"));
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow("-1");
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testPut() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    builder.putInt32ToInt32Field(1, 11);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+
+    builder.putInt32ToStringField(1, "a");
+    assertEquals("a", builder.getInt32ToStringFieldOrThrow(1));
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToBytesField(1, TestUtil.toBytes("11"));
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO);
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putStringToInt32Field("a", 1);
+    assertEquals(1, builder.getStringToInt32FieldOrThrow("a"));
+    try {
+      builder.putStringToInt32Field(null, -1);
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testRemove() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToInt32Field(1);
+      assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1));
+    }
+
+    assertEquals("11", builder.getInt32ToStringFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToStringField(1);
+      assertNull(builder.getInt32ToStringFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToBytesField(1);
+      assertNull(builder.getInt32ToBytesFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToEnumField(1);
+      assertNull(builder.getInt32ToEnumFieldOrDefault(1, null));
+    }
+
+    assertEquals(11, builder.getStringToInt32FieldOrThrow("1"));
+    for (int times = 0; times < 2; times++) {
+      builder.removeStringToInt32Field("1");
+      assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1));
+    }
+
+    try {
+      builder.removeStringToInt32Field(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  private static <K, V> Map<K, V> newMap(K key1, V value1) {
+    Map<K, V> map = new HashMap<>();
+    map.put(key1, value1);
+    return map;
+  }
+
+  private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) {
+    Map<K, V> map = new HashMap<>();
+    map.put(key1, value1);
+    map.put(key2, value2);
+    return map;
+  }
+
+  public void testMap_withNulls() {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putStringToInt32Field(null, 3);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllStringToInt32Field(newMap(null, 3, "hi", 4));
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putInt32ToMessageField(3, null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllInt32ToMessageField(
+          MapLiteTest.<Integer, MessageValue>newMap(4, null, 5, null));
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllInt32ToMessageField(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    assertArrayEquals(new byte[0], builder.build().toByteArray());
+  }
+}

+ 232 - 0
java/core/src/test/java/com/google/protobuf/PackedFieldTest.java

@@ -0,0 +1,232 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.PackedFieldTestProto.TestAllTypes;
+import com.google.protobuf.PackedFieldTestProto.TestAllTypes.NestedEnum;
+import com.google.protobuf.PackedFieldTestProto.TestUnpackedTypes;
+import junit.framework.TestCase;
+
+/** Tests primitive repeated fields in proto3 are packed in wire format. */
+public class PackedFieldTest extends TestCase {
+  static final ByteString expectedPackedRawBytes =
+      ByteString.copyFrom(
+          new byte[] {
+            (byte) 0xFA,
+            0x01,
+            0x01,
+            0x01, // repeated int32
+            (byte) 0x82,
+            0x02,
+            0x01,
+            0x01, // repeated int64
+            (byte) 0x8A,
+            0x02,
+            0x01,
+            0x01, // repeated uint32
+            (byte) 0x92,
+            0x02,
+            0x01,
+            0x01, // repeated uint64
+            (byte) 0x9A,
+            0x02,
+            0x01,
+            0x02, // repeated sint32
+            (byte) 0xA2,
+            0x02,
+            0x01,
+            0x02, // repeated sint64
+            (byte) 0xAA,
+            0x02,
+            0x04,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated fixed32
+            (byte) 0xB2,
+            0x02,
+            0x08,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated fixed64
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            (byte) 0xBA,
+            0x02,
+            0x04,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated sfixed32
+            (byte) 0xC2,
+            0x02,
+            0x08,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated sfixed64
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            (byte) 0xCA,
+            0x02,
+            0x04,
+            0x00,
+            0x00,
+            (byte) 0x80,
+            0x3f, // repeated float
+            (byte) 0xD2,
+            0x02,
+            0x08,
+            0x00,
+            0x00,
+            0x00,
+            0x00, // repeated double
+            0x00,
+            0x00,
+            (byte) 0xf0,
+            0x3f,
+            (byte) 0xDA,
+            0x02,
+            0x01,
+            0x01, // repeated bool
+            (byte) 0x9A,
+            0x03,
+            0x01,
+            0x01 // repeated nested enum
+          });
+
+  static final ByteString expectedUnpackedRawBytes =
+      ByteString.copyFrom(
+          new byte[] {
+            0x08,
+            0x01, // repeated int32
+            0x10,
+            0x01, // repeated int64
+            0x18,
+            0x01, // repeated uint32
+            0x20,
+            0x01, // repeated uint64
+            0x28,
+            0x02, // repeated sint32
+            0x30,
+            0x02, // repeated sint64
+            0x3D,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated fixed32
+            0x41,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated fixed64
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            0x4D,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated sfixed32
+            0x51,
+            0x01,
+            0x00,
+            0x00,
+            0x00, // repeated sfixed64
+            0x00,
+            0x00,
+            0x00,
+            0x00,
+            0x5D,
+            0x00,
+            0x00,
+            (byte) 0x80,
+            0x3f, // repeated float
+            0x61,
+            0x00,
+            0x00,
+            0x00,
+            0x00, // repeated double
+            0x00,
+            0x00,
+            (byte) 0xf0,
+            0x3f,
+            0x68,
+            0x01, // repeated bool
+            0x70,
+            0x01, // repeated nested enum
+          });
+
+  public void testPackedGeneratedMessage() throws Exception {
+    TestAllTypes message = TestAllTypes.parseFrom(expectedPackedRawBytes);
+    assertEquals(expectedPackedRawBytes, message.toByteString());
+  }
+
+  public void testPackedDynamicMessageSerialize() throws Exception {
+    DynamicMessage message =
+        DynamicMessage.parseFrom(TestAllTypes.getDescriptor(), expectedPackedRawBytes);
+    assertEquals(expectedPackedRawBytes, message.toByteString());
+  }
+
+  public void testUnpackedGeneratedMessage() throws Exception {
+    TestUnpackedTypes message = TestUnpackedTypes.parseFrom(expectedUnpackedRawBytes);
+    assertEquals(expectedUnpackedRawBytes, message.toByteString());
+  }
+
+  public void testUnPackedDynamicMessageSerialize() throws Exception {
+    DynamicMessage message =
+        DynamicMessage.parseFrom(TestUnpackedTypes.getDescriptor(), expectedUnpackedRawBytes);
+    assertEquals(expectedUnpackedRawBytes, message.toByteString());
+  }
+
+  // Make sure we haven't screwed up the code generation for packing fields by default.
+  public void testPackedSerialization() throws Exception {
+    TestAllTypes message =
+        TestAllTypes.newBuilder()
+            .addRepeatedInt32(1234)
+            .addRepeatedNestedEnum(NestedEnum.BAR)
+            .build();
+
+    CodedInputStream in = CodedInputStream.newInstance(message.toByteArray());
+
+    while (!in.isAtEnd()) {
+      int tag = in.readTag();
+      assertEquals(WireFormat.WIRETYPE_LENGTH_DELIMITED, WireFormat.getTagWireType(tag));
+      in.skipField(tag);
+    }
+  }
+}

+ 191 - 0
java/core/src/test/java/com/google/protobuf/ParserLiteTest.java

@@ -0,0 +1,191 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.UnittestLite.TestAllTypesLite;
+import com.google.protobuf.UnittestLite.TestPackedExtensionsLite;
+import com.google.protobuf.UnittestLite.TestParsingMergeLite;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import junit.framework.TestCase;
+
+public class ParserLiteTest extends TestCase {
+  private void assertRoundTripEquals(MessageLite message, ExtensionRegistryLite registry)
+      throws Exception {
+    final byte[] data = message.toByteArray();
+    final int offset = 20;
+    final int length = data.length;
+    final int padding = 30;
+    Parser<? extends MessageLite> parser = message.getParserForType();
+    assertEquals(message, parser.parseFrom(data, registry));
+    assertEquals(
+        message,
+        parser.parseFrom(generatePaddingArray(data, offset, padding), offset, length, registry));
+    assertEquals(message, parser.parseFrom(message.toByteString(), registry));
+    assertEquals(message, parser.parseFrom(new ByteArrayInputStream(data), registry));
+    assertEquals(message, parser.parseFrom(CodedInputStream.newInstance(data), registry));
+    assertEquals(
+        message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer(), registry));
+  }
+
+  @SuppressWarnings("unchecked")
+  private void assertRoundTripEquals(MessageLite message) throws Exception {
+    final byte[] data = message.toByteArray();
+    final int offset = 20;
+    final int length = data.length;
+    final int padding = 30;
+
+    Parser<MessageLite> parser = (Parser<MessageLite>) message.getParserForType();
+    assertEquals(message, parser.parseFrom(data));
+    assertEquals(
+        message, parser.parseFrom(generatePaddingArray(data, offset, padding), offset, length));
+    assertEquals(message, parser.parseFrom(message.toByteString()));
+    assertEquals(message, parser.parseFrom(new ByteArrayInputStream(data)));
+    assertEquals(message, parser.parseFrom(CodedInputStream.newInstance(data)));
+    assertEquals(message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer()));
+  }
+
+  private byte[] generatePaddingArray(byte[] data, int offset, int padding) {
+    byte[] result = new byte[offset + data.length + padding];
+    System.arraycopy(data, 0, result, offset, data.length);
+    return result;
+  }
+
+  public void testParseExtensionsLite() throws Exception {
+    assertRoundTripEquals(
+        TestUtilLite.getAllLiteExtensionsSet(), TestUtilLite.getExtensionRegistryLite());
+  }
+
+  public void testParsePacked() throws Exception {
+    assertRoundTripEquals(TestUtil.getPackedSet());
+    assertRoundTripEquals(TestUtil.getPackedExtensionsSet(), TestUtil.getExtensionRegistry());
+  }
+
+  public void testParsePackedLite() throws Exception {
+    assertRoundTripEquals(
+        TestUtilLite.getLitePackedExtensionsSet(), TestUtilLite.getExtensionRegistryLite());
+  }
+
+  public void testParseDelimitedToLite() throws Exception {
+    // Write MessageLite with packed extension fields.
+    TestPackedExtensionsLite packedMessage = TestUtilLite.getLitePackedExtensionsSet();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    packedMessage.writeDelimitedTo(output);
+    packedMessage.writeDelimitedTo(output);
+
+    InputStream input = new ByteArrayInputStream(output.toByteArray());
+    assertEquals(
+        packedMessage,
+        packedMessage
+            .getParserForType()
+            .parseDelimitedFrom(input, TestUtilLite.getExtensionRegistryLite()));
+    assertEquals(
+        packedMessage,
+        packedMessage
+            .getParserForType()
+            .parseDelimitedFrom(input, TestUtilLite.getExtensionRegistryLite()));
+  }
+
+  /** Helper method for {@link #testParsingMergeLite()}. */
+  private void assertMessageMerged(TestAllTypesLite allTypes) throws Exception {
+    assertEquals(3, allTypes.getOptionalInt32());
+    assertEquals(2, allTypes.getOptionalInt64());
+    assertEquals("hello", allTypes.getOptionalString());
+  }
+
+  public void testParsingMergeLite() throws Exception {
+    // Build messages.
+    TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder();
+    TestAllTypesLite msg1 = builder.setOptionalInt32(1).build();
+    builder.clear();
+    TestAllTypesLite msg2 = builder.setOptionalInt64(2).build();
+    builder.clear();
+    TestAllTypesLite msg3 = builder.setOptionalInt32(3).setOptionalString("hello").build();
+
+    // Build groups.
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG1 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg1).build();
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG2 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg2).build();
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group1 optionalG3 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group1.newBuilder().setField1(msg3).build();
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG1 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg1).build();
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG2 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg2).build();
+    TestParsingMergeLite.RepeatedFieldsGenerator.Group2 repeatedG3 =
+        TestParsingMergeLite.RepeatedFieldsGenerator.Group2.newBuilder().setField1(msg3).build();
+
+    // Assign and serialize RepeatedFieldsGenerator.
+    ByteString data =
+        TestParsingMergeLite.RepeatedFieldsGenerator.newBuilder()
+            .addField1(msg1)
+            .addField1(msg2)
+            .addField1(msg3)
+            .addField2(msg1)
+            .addField2(msg2)
+            .addField2(msg3)
+            .addField3(msg1)
+            .addField3(msg2)
+            .addField3(msg3)
+            .addGroup1(optionalG1)
+            .addGroup1(optionalG2)
+            .addGroup1(optionalG3)
+            .addGroup2(repeatedG1)
+            .addGroup2(repeatedG2)
+            .addGroup2(repeatedG3)
+            .addExt1(msg1)
+            .addExt1(msg2)
+            .addExt1(msg3)
+            .addExt2(msg1)
+            .addExt2(msg2)
+            .addExt2(msg3)
+            .build()
+            .toByteString();
+
+    // Parse TestParsingMergeLite.
+    ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
+    UnittestLite.registerAllExtensions(registry);
+    TestParsingMergeLite parsingMerge = TestParsingMergeLite.parser().parseFrom(data, registry);
+
+    // Required and optional fields should be merged.
+    assertMessageMerged(parsingMerge.getRequiredAllTypes());
+    assertMessageMerged(parsingMerge.getOptionalAllTypes());
+    assertMessageMerged(parsingMerge.getOptionalGroup().getOptionalGroupAllTypes());
+    assertMessageMerged(parsingMerge.getExtension(TestParsingMergeLite.optionalExt));
+
+    // Repeated fields should not be merged.
+    assertEquals(3, parsingMerge.getRepeatedAllTypesCount());
+    assertEquals(3, parsingMerge.getRepeatedGroupCount());
+    assertEquals(3, parsingMerge.getExtensionCount(TestParsingMergeLite.repeatedExt));
+  }
+}

+ 171 - 0
java/core/src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java

@@ -0,0 +1,171 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import com.google.protobuf.testing.Proto2Testing;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.TestEnum;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithExtensions;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Proto2ExtensionLookupSchemaTest {
+  private byte[] data;
+  private ExtensionRegistry extensionRegistry;
+
+  @Before
+  public void setup() {
+    TestSchemas.registerGenericProto2Schemas();
+
+    Protobuf.getInstance().schemaFor(Proto2MessageWithExtensions.class);
+    data = new Proto2MessageFactory(10, 20, 1, 1).newMessage().toByteArray();
+    extensionRegistry = ExtensionRegistry.newInstance();
+    Proto2Testing.registerAllExtensions(extensionRegistry);
+  }
+
+  @Test
+  public void testExtensions() throws Exception {
+    Proto2MessageWithExtensions base =
+        Proto2MessageWithExtensions.parseFrom(data, extensionRegistry);
+
+    Proto2MessageWithExtensions message =
+        ExperimentalSerializationUtil.fromByteArray(
+            data, Proto2MessageWithExtensions.class, extensionRegistry);
+    assertEquals(base, message);
+
+    Proto2MessageWithExtensions roundtripMessage =
+        ExperimentalSerializationUtil.fromByteArray(
+            ExperimentalSerializationUtil.toByteArray(message),
+            Proto2MessageWithExtensions.class,
+            extensionRegistry);
+    assertEquals(base, roundtripMessage);
+  }
+
+  @Test
+  public void testUnknownEnum() throws Exception {
+    // Use unknown fields to hold invalid enum values.
+    UnknownFieldSetLite unknowns = UnknownFieldSetLite.newInstance();
+    final int outOfRange = 1000;
+    assertNull(TestEnum.forNumber(outOfRange));
+    unknowns.storeField(
+        WireFormat.makeTag(Proto2Message.FIELD_ENUM_13_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.ONE_VALUE);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) outOfRange);
+    unknowns.storeField(
+        WireFormat.makeTag(
+            Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER, WireFormat.WIRETYPE_VARINT),
+        (long) TestEnum.TWO_VALUE);
+
+    {
+      // Construct a packed enum list.
+      int packedSize =
+          CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE)
+              + CodedOutputStream.computeUInt32SizeNoTag(outOfRange)
+              + CodedOutputStream.computeUInt32SizeNoTag(TestEnum.ONE_VALUE);
+      ByteString.CodedBuilder packedBuilder = ByteString.newCodedBuilder(packedSize);
+      CodedOutputStream packedOut = packedBuilder.getCodedOutput();
+      packedOut.writeEnumNoTag(TestEnum.ONE_VALUE);
+      packedOut.writeEnumNoTag(outOfRange);
+      packedOut.writeEnumNoTag(TestEnum.TWO_VALUE);
+      unknowns.storeField(
+          WireFormat.makeTag(
+              Proto2Message.FIELD_ENUM_LIST_PACKED_44_FIELD_NUMBER,
+              WireFormat.WIRETYPE_LENGTH_DELIMITED),
+          packedBuilder.build());
+    }
+    int size = unknowns.getSerializedSize();
+    byte[] output = new byte[size];
+    CodedOutputStream codedOutput = CodedOutputStream.newInstance(output);
+    unknowns.writeTo(codedOutput);
+    codedOutput.flush();
+
+    Proto2MessageWithExtensions parsed =
+        ExperimentalSerializationUtil.fromByteArray(
+            output, Proto2MessageWithExtensions.class, extensionRegistry);
+    assertFalse(
+        "out-of-range singular enum should not be in message",
+        parsed.hasExtension(Proto2Testing.fieldEnum13));
+    {
+      List<Long> singularEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_13_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, singularEnum.size());
+      assertEquals((Long) (long) outOfRange, singularEnum.get(0));
+    }
+    {
+      List<Long> repeatedEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_LIST_30_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, repeatedEnum.size());
+      assertEquals((Long) (long) outOfRange, repeatedEnum.get(0));
+    }
+    {
+      List<Long> packedRepeatedEnum =
+          parsed
+              .getUnknownFields()
+              .getField(Proto2Message.FIELD_ENUM_LIST_PACKED_44_FIELD_NUMBER)
+              .getVarintList();
+      assertEquals(1, packedRepeatedEnum.size());
+      assertEquals((Long) (long) outOfRange, packedRepeatedEnum.get(0));
+    }
+    assertEquals(
+        "out-of-range repeated enum should not be in message",
+        2,
+        parsed.getExtension(Proto2Testing.fieldEnumList30).size());
+    assertEquals(TestEnum.ONE, parsed.getExtension(Proto2Testing.fieldEnumList30, 0));
+    assertEquals(TestEnum.TWO, parsed.getExtension(Proto2Testing.fieldEnumList30, 1));
+    assertEquals(
+        "out-of-range packed repeated enum should not be in message",
+        2,
+        parsed.getExtension(Proto2Testing.fieldEnumListPacked44).size());
+    assertEquals(TestEnum.ONE, parsed.getExtension(Proto2Testing.fieldEnumListPacked44, 0));
+    assertEquals(TestEnum.TWO, parsed.getExtension(Proto2Testing.fieldEnumListPacked44, 1));
+  }
+}

+ 49 - 0
java/core/src/test/java/com/google/protobuf/Proto2LiteSchemaTest.java

@@ -0,0 +1,49 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLite;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Proto2LiteSchemaTest extends AbstractProto2LiteSchemaTest {
+
+  @Override
+  protected Schema<Proto2MessageLite> schema() {
+    return TestSchemasLite.genericProto2LiteSchema;
+  }
+
+  @Override
+  protected void registerSchemas() {
+    TestSchemasLite.registerGenericProto2LiteSchemas();
+  }
+}

+ 555 - 0
java/core/src/test/java/com/google/protobuf/Proto2MessageFactory.java

@@ -0,0 +1,555 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithMaps;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Creates instances of {@link Proto2Message} based on the tree configuration. */
+public final class Proto2MessageFactory implements ExperimentalMessageFactory<Proto2Message> {
+  private final int numRepeatedFields;
+  private final int branchingFactor;
+  private final Proto2MessageFactory nextLevel;
+  private final ExperimentalTestDataProvider data;
+
+  public Proto2MessageFactory(
+      int numRepeatedFields, int stringLength, int branchingFactor, int treeDepth) {
+    this(
+        new ExperimentalTestDataProvider(stringLength),
+        numRepeatedFields,
+        branchingFactor,
+        treeDepth);
+  }
+
+  private Proto2MessageFactory(
+      ExperimentalTestDataProvider data,
+      int numRepeatedFields,
+      int branchingFactor,
+      int treeDepth) {
+    this.numRepeatedFields = numRepeatedFields;
+    this.branchingFactor = branchingFactor;
+    this.data = data;
+    if (treeDepth > 0) {
+      nextLevel = new Proto2MessageFactory(data, numRepeatedFields, branchingFactor, treeDepth - 1);
+    } else {
+      nextLevel = null;
+    }
+  }
+
+  @Override
+  public ExperimentalTestDataProvider dataProvider() {
+    return data;
+  }
+
+  @Override
+  public Proto2Message newMessage() {
+    Proto2Message.Builder builder = Proto2Message.newBuilder();
+    builder.setFieldDouble1(data.getDouble());
+    builder.setFieldFloat2(data.getFloat());
+    builder.setFieldInt643(data.getLong());
+    builder.setFieldUint644(data.getLong());
+    builder.setFieldInt325(data.getInt());
+    builder.setFieldFixed646(data.getLong());
+    builder.setFieldFixed327(data.getInt());
+    builder.setFieldBool8(data.getBool());
+    builder.setFieldString9(data.getString());
+    // We don't populate the message field. Instead we apply the branching factor to the
+    // repeated message field below.
+    builder.setFieldBytes11(data.getBytes());
+    builder.setFieldUint3212(data.getInt());
+    builder.setFieldEnum13(Proto2Message.TestEnum.forNumber(data.getEnum()));
+    builder.setFieldSfixed3214(data.getInt());
+    builder.setFieldSfixed6415(data.getLong());
+    builder.setFieldSint3216(data.getInt());
+    builder.setFieldSint6417(data.getLong());
+
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      builder.addFieldDoubleList18(data.getDouble());
+      builder.addFieldFloatList19(data.getFloat());
+      builder.addFieldInt64List20(data.getLong());
+      builder.addFieldUint64List21(data.getLong());
+      builder.addFieldInt32List22(data.getInt());
+      builder.addFieldFixed64List23(data.getLong());
+      builder.addFieldFixed32List24(data.getInt());
+      builder.addFieldBoolList25(data.getBool());
+      builder.addFieldStringList26(data.getString());
+      // Repeated message field is controlled by the branching factor below.
+      builder.addFieldBytesList28(data.getBytes());
+      builder.addFieldUint32List29(data.getInt());
+      builder.addFieldEnumList30(Proto2Message.TestEnum.forNumber(data.getEnum()));
+      builder.addFieldSfixed32List31(data.getInt());
+      builder.addFieldSfixed64List32(data.getLong());
+      builder.addFieldSint32List33(data.getInt());
+      builder.addFieldSint64List34(data.getLong());
+
+      builder.addFieldDoubleListPacked35(data.getDouble());
+      builder.addFieldFloatListPacked36(data.getFloat());
+      builder.addFieldInt64ListPacked37(data.getLong());
+      builder.addFieldUint64ListPacked38(data.getLong());
+      builder.addFieldInt32ListPacked39(data.getInt());
+      builder.addFieldFixed64ListPacked40(data.getLong());
+      builder.addFieldFixed32ListPacked41(data.getInt());
+      builder.addFieldBoolListPacked42(data.getBool());
+      builder.addFieldUint32ListPacked43(data.getInt());
+      builder.addFieldEnumListPacked44(Proto2Message.TestEnum.forNumber(data.getEnum()));
+      builder.addFieldSfixed32ListPacked45(data.getInt());
+      builder.addFieldSfixed64ListPacked46(data.getLong());
+      builder.addFieldSint32ListPacked47(data.getInt());
+      builder.addFieldSint64ListPacked48(data.getLong());
+    }
+
+    builder.setFieldGroup49(Proto2Message.FieldGroup49.newBuilder().setFieldInt3250(data.getInt()));
+
+    for (int i = 0; i < branchingFactor; ++i) {
+      builder.addFieldGroupList51(
+          Proto2Message.FieldGroupList51.newBuilder().setFieldInt3252(data.getInt()));
+    }
+
+    // Set all required fields.
+    populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+
+    // Handle the branching factor.
+    if (nextLevel != null) {
+      for (int i = 0; i < branchingFactor; ++i) {
+        builder.addFieldMessageList27(nextLevel.newMessage());
+      }
+    }
+
+    return builder.build();
+  }
+
+  private interface MapValueProvider<T> {
+    public T getValue();
+  }
+
+  private final MapValueProvider<Integer> integerProvider =
+      new MapValueProvider<Integer>() {
+        @Override
+        public Integer getValue() {
+          return data.getInt();
+        }
+      };
+  private final MapValueProvider<Long> longProvider =
+      new MapValueProvider<Long>() {
+        @Override
+        public Long getValue() {
+          return data.getLong();
+        }
+      };
+  private final MapValueProvider<String> stringProvider =
+      new MapValueProvider<String>() {
+        @Override
+        public String getValue() {
+          return data.getString();
+        }
+      };
+  private final MapValueProvider<ByteString> bytesProvider =
+      new MapValueProvider<ByteString>() {
+        @Override
+        public ByteString getValue() {
+          return data.getBytes();
+        }
+      };
+  private final MapValueProvider<Boolean> booleanProvider =
+      new MapValueProvider<Boolean>() {
+        @Override
+        public Boolean getValue() {
+          return data.getBool();
+        }
+      };
+  private final MapValueProvider<Float> floatProvider =
+      new MapValueProvider<Float>() {
+        @Override
+        public Float getValue() {
+          return data.getFloat();
+        }
+      };
+  private final MapValueProvider<Double> doubleProvider =
+      new MapValueProvider<Double>() {
+        @Override
+        public Double getValue() {
+          return data.getDouble();
+        }
+      };
+  private final MapValueProvider<Proto2Message> messageProvider =
+      new MapValueProvider<Proto2Message>() {
+        @Override
+        public Proto2Message getValue() {
+          return newMessage();
+        }
+      };
+  private final MapValueProvider<Proto2Message.TestEnum> enumProvider =
+      new MapValueProvider<Proto2Message.TestEnum>() {
+        @Override
+        public Proto2Message.TestEnum getValue() {
+          return Proto2Message.TestEnum.forNumber(data.getEnum());
+        }
+      };
+
+  private <V> Map<Integer, V> populateIntegerMap(MapValueProvider<V> provider) {
+    Map<Integer, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getInt(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Long, V> populateLongMap(MapValueProvider<V> provider) {
+    Map<Long, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getLong(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<String, V> populateStringMap(MapValueProvider<V> provider) {
+    Map<String, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getString(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Boolean, V> populateBooleanMap(MapValueProvider<V> provider) {
+    Map<Boolean, V> map = new HashMap<>();
+    map.put(false, provider.getValue());
+    map.put(true, provider.getValue());
+    return map;
+  }
+
+  public Proto2MessageWithMaps newMessageWithMaps() {
+    Proto2MessageWithMaps.Builder builder = Proto2MessageWithMaps.newBuilder();
+
+    builder.putAllFieldMapBoolBool1(populateBooleanMap(booleanProvider));
+    builder.putAllFieldMapBoolBytes2(populateBooleanMap(bytesProvider));
+    builder.putAllFieldMapBoolDouble3(populateBooleanMap(doubleProvider));
+    builder.putAllFieldMapBoolEnum4(populateBooleanMap(enumProvider));
+    builder.putAllFieldMapBoolFixed325(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolFixed646(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolFloat7(populateBooleanMap(floatProvider));
+    builder.putAllFieldMapBoolInt328(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolInt649(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolMessage10(populateBooleanMap(messageProvider));
+    builder.putAllFieldMapBoolSfixed3211(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSfixed6412(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolSint3213(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSint6414(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolString15(populateBooleanMap(stringProvider));
+    builder.putAllFieldMapBoolUint3216(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolUint6417(populateBooleanMap(longProvider));
+    builder.putAllFieldMapFixed32Bool18(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapFixed32Bytes19(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapFixed32Double20(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapFixed32Enum21(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapFixed32Fixed3222(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Fixed6423(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Float24(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapFixed32Int3225(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Int6426(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Message27(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapFixed32Sfixed3228(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sfixed6429(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Sint3230(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sint6431(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32String32(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapFixed32Uint3233(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Uint6434(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed64Bool35(populateLongMap(booleanProvider));
+    builder.putAllFieldMapFixed64Bytes36(populateLongMap(bytesProvider));
+    builder.putAllFieldMapFixed64Double37(populateLongMap(doubleProvider));
+    builder.putAllFieldMapFixed64Enum38(populateLongMap(enumProvider));
+    builder.putAllFieldMapFixed64Fixed3239(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Fixed6440(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Float41(populateLongMap(floatProvider));
+    builder.putAllFieldMapFixed64Int3242(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Int6443(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Message44(populateLongMap(messageProvider));
+    builder.putAllFieldMapFixed64Sfixed3245(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sfixed6446(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Sint3247(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sint6448(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64String49(populateLongMap(stringProvider));
+    builder.putAllFieldMapFixed64Uint3250(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Uint6451(populateLongMap(longProvider));
+    builder.putAllFieldMapInt32Bool52(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapInt32Bytes53(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapInt32Double54(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapInt32Enum55(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapInt32Fixed3256(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Fixed6457(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Float58(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapInt32Int3259(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Int6460(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Message61(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapInt32Sfixed3262(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sfixed6463(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Sint3264(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sint6465(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32String66(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapInt32Uint3267(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Uint6468(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt64Bool69(populateLongMap(booleanProvider));
+    builder.putAllFieldMapInt64Bytes70(populateLongMap(bytesProvider));
+    builder.putAllFieldMapInt64Double71(populateLongMap(doubleProvider));
+    builder.putAllFieldMapInt64Enum72(populateLongMap(enumProvider));
+    builder.putAllFieldMapInt64Fixed3273(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Fixed6474(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Float75(populateLongMap(floatProvider));
+    builder.putAllFieldMapInt64Int3276(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Int6477(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Message78(populateLongMap(messageProvider));
+    builder.putAllFieldMapInt64Sfixed3279(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sfixed6480(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Sint3281(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sint6482(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64String83(populateLongMap(stringProvider));
+    builder.putAllFieldMapInt64Uint3284(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Uint6485(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed32Bool86(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSfixed32Bytes87(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSfixed32Double88(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSfixed32Enum89(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSfixed32Fixed3290(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Fixed6491(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Float92(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSfixed32Int3293(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Int6494(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Message95(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSfixed32Sfixed3296(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sfixed6497(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Sint3298(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sint6499(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32String100(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSfixed32Uint32101(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Uint64102(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed64Bool103(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSfixed64Bytes104(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSfixed64Double105(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSfixed64Enum106(populateLongMap(enumProvider));
+    builder.putAllFieldMapSfixed64Fixed32107(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Fixed64108(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Float109(populateLongMap(floatProvider));
+    builder.putAllFieldMapSfixed64Int32110(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Int64111(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Message112(populateLongMap(messageProvider));
+    builder.putAllFieldMapSfixed64Sfixed32113(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sfixed64114(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Sint32115(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sint64116(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64String117(populateLongMap(stringProvider));
+    builder.putAllFieldMapSfixed64Uint32118(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Uint64119(populateLongMap(longProvider));
+    builder.putAllFieldMapSint32Bool120(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSint32Bytes121(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSint32Double122(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSint32Enum123(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSint32Fixed32124(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Fixed64125(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Float126(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSint32Int32127(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Int64128(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Message129(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSint32Sfixed32130(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sfixed64131(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Sint32132(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sint64133(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32String134(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSint32Uint32135(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Uint64136(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint64Bool137(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSint64Bytes138(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSint64Double139(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSint64Enum140(populateLongMap(enumProvider));
+    builder.putAllFieldMapSint64Fixed32141(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Fixed64142(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Float143(populateLongMap(floatProvider));
+    builder.putAllFieldMapSint64Int32144(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Int64145(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Message146(populateLongMap(messageProvider));
+    builder.putAllFieldMapSint64Sfixed32147(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sfixed64148(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Sint32149(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sint64150(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64String151(populateLongMap(stringProvider));
+    builder.putAllFieldMapSint64Uint32152(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Uint64153(populateLongMap(longProvider));
+    builder.putAllFieldMapStringBool154(populateStringMap(booleanProvider));
+    builder.putAllFieldMapStringBytes155(populateStringMap(bytesProvider));
+    builder.putAllFieldMapStringDouble156(populateStringMap(doubleProvider));
+    builder.putAllFieldMapStringEnum157(populateStringMap(enumProvider));
+    builder.putAllFieldMapStringFixed32158(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringFixed64159(populateStringMap(longProvider));
+    builder.putAllFieldMapStringFloat160(populateStringMap(floatProvider));
+    builder.putAllFieldMapStringInt32161(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringInt64162(populateStringMap(longProvider));
+    builder.putAllFieldMapStringMessage163(populateStringMap(messageProvider));
+    builder.putAllFieldMapStringSfixed32164(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSfixed64165(populateStringMap(longProvider));
+    builder.putAllFieldMapStringSint32166(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSint64167(populateStringMap(longProvider));
+    builder.putAllFieldMapStringString168(populateStringMap(stringProvider));
+    builder.putAllFieldMapStringUint32169(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringUint64170(populateStringMap(longProvider));
+    builder.putAllFieldMapUint32Bool171(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapUint32Bytes172(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapUint32Double173(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapUint32Enum174(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapUint32Fixed32175(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Fixed64176(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Float177(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapUint32Int32178(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Int64179(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Message180(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapUint32Sfixed32181(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sfixed64182(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Sint32183(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sint64184(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32String185(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapUint32Uint32186(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Uint64187(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint64Bool188(populateLongMap(booleanProvider));
+    builder.putAllFieldMapUint64Bytes189(populateLongMap(bytesProvider));
+    builder.putAllFieldMapUint64Double190(populateLongMap(doubleProvider));
+    builder.putAllFieldMapUint64Enum191(populateLongMap(enumProvider));
+    builder.putAllFieldMapUint64Fixed32192(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Fixed64193(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Float194(populateLongMap(floatProvider));
+    builder.putAllFieldMapUint64Int32195(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Int64196(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Message197(populateLongMap(messageProvider));
+    builder.putAllFieldMapUint64Sfixed32198(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sfixed64199(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Sint32200(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sint64201(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64String202(populateLongMap(stringProvider));
+    builder.putAllFieldMapUint64Uint32203(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Uint64204(populateLongMap(longProvider));
+
+    return builder.build();
+  }
+
+  public List<Proto2Message> newMessagesMissingRequiredFields() {
+    List<Proto2Message> results = new ArrayList<>();
+    for (int i = 71; i <= 88; ++i) {
+      Proto2Message.Builder builder = Proto2Message.newBuilder();
+      populateRequiredFields(builder, i);
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested optional message field is missing required fields.
+      Proto2Message.Builder builder = Proto2Message.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.setFieldMessage10(Proto2Message.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested repeated message field is missing required fields.
+      Proto2Message.Builder builder = Proto2Message.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.addFieldMessageList27(Proto2Message.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested oneof message field is missing required fields.
+      Proto2Message.Builder builder = Proto2Message.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.setFieldMessage62(Proto2Message.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    return results;
+  }
+
+  // 0 is not a valid field number so we use it to mean no fields are excluded.
+  private static final int INCLUDE_ALL_REQUIRED_FIELDS = 0;
+
+  private void populateRequiredFields(Proto2Message.Builder builder, int excludedFieldNumber) {
+    if (excludedFieldNumber != 71) {
+      builder.setFieldRequiredDouble71(data.getDouble());
+    }
+    if (excludedFieldNumber != 72) {
+      builder.setFieldRequiredFloat72(data.getFloat());
+    }
+    if (excludedFieldNumber != 73) {
+      builder.setFieldRequiredInt6473(data.getLong());
+    }
+    if (excludedFieldNumber != 74) {
+      builder.setFieldRequiredUint6474(data.getLong());
+    }
+    if (excludedFieldNumber != 75) {
+      builder.setFieldRequiredInt3275(data.getInt());
+    }
+    if (excludedFieldNumber != 76) {
+      builder.setFieldRequiredFixed6476(data.getLong());
+    }
+    if (excludedFieldNumber != 77) {
+      builder.setFieldRequiredFixed3277(data.getInt());
+    }
+    if (excludedFieldNumber != 78) {
+      builder.setFieldRequiredBool78(data.getBool());
+    }
+    if (excludedFieldNumber != 79) {
+      builder.setFieldRequiredString79(data.getString());
+    }
+    if (excludedFieldNumber != 80) {
+      builder.setFieldRequiredMessage80(
+          Proto2Message.RequiredNestedMessage.newBuilder().setValue(data.getInt()));
+    }
+    if (excludedFieldNumber != 81) {
+      builder.setFieldRequiredBytes81(data.getBytes());
+    }
+    if (excludedFieldNumber != 82) {
+      builder.setFieldRequiredUint3282(data.getInt());
+    }
+    if (excludedFieldNumber != 83) {
+      builder.setFieldRequiredEnum83(Proto2Message.TestEnum.forNumber(data.getEnum()));
+    }
+    if (excludedFieldNumber != 84) {
+      builder.setFieldRequiredSfixed3284(data.getInt());
+    }
+    if (excludedFieldNumber != 85) {
+      builder.setFieldRequiredSfixed6485(data.getLong());
+    }
+    if (excludedFieldNumber != 86) {
+      builder.setFieldRequiredSint3286(data.getInt());
+    }
+    if (excludedFieldNumber != 87) {
+      builder.setFieldRequiredSint6487(data.getLong());
+    }
+    if (excludedFieldNumber != 88) {
+      builder.setFieldRequiredGroup88(
+          Proto2Message.FieldRequiredGroup88.newBuilder().setFieldInt3289(data.getInt()));
+    }
+  }
+}

+ 892 - 0
java/core/src/test/java/com/google/protobuf/Proto2MessageInfoFactory.java

@@ -0,0 +1,892 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.FieldInfo.forField;
+import static com.google.protobuf.FieldInfo.forFieldWithEnumVerifier;
+import static com.google.protobuf.FieldInfo.forMapField;
+import static com.google.protobuf.FieldInfo.forOneofMemberField;
+import static com.google.protobuf.FieldInfo.forProto2OptionalField;
+import static com.google.protobuf.FieldInfo.forProto2RequiredField;
+import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
+
+import com.google.protobuf.testing.Proto2Testing;
+import com.google.protobuf.testing.Proto2Testing.Proto2Empty;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.FieldGroup49;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.FieldGroup69;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.FieldGroupList51;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.FieldRequiredGroup88;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.RequiredNestedMessage;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message.TestEnum;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithExtensions;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithMaps;
+import java.lang.reflect.Field;
+
+/** A factory that generates a hard-coded message info for {@link Proto2Message}. */
+public final class Proto2MessageInfoFactory implements MessageInfoFactory {
+  private static final Proto2MessageInfoFactory INSTANCE = new Proto2MessageInfoFactory();
+
+  private Proto2MessageInfoFactory() {}
+
+  public static Proto2MessageInfoFactory getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isSupported(Class<?> clazz) {
+    return true;
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> clazz) {
+    if (Proto2Message.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto2Message();
+    } else if (FieldGroup49.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForFieldGroup49();
+    } else if (FieldGroupList51.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForFieldGroupList51();
+    } else if (FieldGroup69.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForFieldGroup69();
+    } else if (FieldRequiredGroup88.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForFieldRequiredGroup88();
+    } else if (RequiredNestedMessage.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForRequiredNestedMessage();
+    } else if (Proto2Empty.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto2Empty();
+    } else if (Proto2MessageWithExtensions.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto2MessageWithExtensions();
+    } else if (Proto2Testing.FieldGroup49.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForExtensionFieldGroup49();
+    } else if (Proto2Testing.FieldGroupList51.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForExtensionFieldGroupList51();
+    } else if (Proto2Testing.Proto2MessageWithMaps.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto2MessageWithMaps();
+    } else {
+      throw new IllegalArgumentException("Unsupported class: " + clazz.getName());
+    }
+  }
+
+  /**
+   * Creates a new hard-coded info for {@link Proto2Message}. Each time this is called, we manually
+   * go through the entire process of what a message would do if it self-registered its own info,
+   * including looking up each field by name. This is done for benchmarking purposes, so that we get
+   * a more accurate representation of the time it takes to perform this process.
+   */
+  private static StructuralMessageInfo newMessageInfoForProto2Message() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(50);
+    builder.withCheckInitialized(
+        new int[] {
+          10, 27, 62, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88,
+        });
+    lookupFieldsByName(builder);
+    return builder.build();
+  }
+
+  private static void lookupFieldsByName(StructuralMessageInfo.Builder builder) {
+    Field bitField0 = field("bitField0_");
+
+    builder.withDefaultInstance(Proto2Message.getDefaultInstance());
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldDouble1_"), 1, FieldType.DOUBLE, bitField0, 0x00000001, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldFloat2_"), 2, FieldType.FLOAT, bitField0, 0x00000002, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldInt643_"), 3, FieldType.INT64, bitField0, 0x00000004, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldUint644_"), 4, FieldType.UINT64, bitField0, 0x00000008, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldInt325_"), 5, FieldType.INT32, bitField0, 0x00000010, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldFixed646_"), 6, FieldType.FIXED64, bitField0, 0x00000020, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldFixed327_"), 7, FieldType.FIXED32, bitField0, 0x00000040, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldBool8_"), 8, FieldType.BOOL, bitField0, 0x00000080, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldString9_"), 9, FieldType.STRING, bitField0, 0x00000100, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldMessage10_"), 10, FieldType.MESSAGE, bitField0, 0x00000200, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldBytes11_"), 11, FieldType.BYTES, bitField0, 0x00000400, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldUint3212_"), 12, FieldType.UINT32, bitField0, 0x00000800, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldEnum13_"),
+            13,
+            FieldType.ENUM,
+            bitField0,
+            0x00001000,
+            false,
+            asVerifier(TestEnum.internalGetValueMap())));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldSfixed3214_"), 14, FieldType.SFIXED32, bitField0, 0x00002000, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldSfixed6415_"), 15, FieldType.SFIXED64, bitField0, 0x00004000, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldSint3216_"), 16, FieldType.SINT32, bitField0, 0x00008000, false, null));
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldSint6417_"), 17, FieldType.SINT64, bitField0, 0x00010000, false, null));
+    builder.withField(forField(field("fieldDoubleList18_"), 18, FieldType.DOUBLE_LIST, false));
+    builder.withField(forField(field("fieldFloatList19_"), 19, FieldType.FLOAT_LIST, false));
+    builder.withField(forField(field("fieldInt64List20_"), 20, FieldType.INT64_LIST, false));
+    builder.withField(forField(field("fieldUint64List21_"), 21, FieldType.UINT64_LIST, false));
+    builder.withField(forField(field("fieldInt32List22_"), 22, FieldType.INT32_LIST, false));
+    builder.withField(forField(field("fieldFixed64List23_"), 23, FieldType.FIXED64_LIST, false));
+    builder.withField(forField(field("fieldFixed32List24_"), 24, FieldType.FIXED32_LIST, false));
+    builder.withField(forField(field("fieldBoolList25_"), 25, FieldType.BOOL_LIST, false));
+    builder.withField(forField(field("fieldStringList26_"), 26, FieldType.STRING_LIST, false));
+    builder.withField(
+        forRepeatedMessageField(
+            field("fieldMessageList27_"), 27, FieldType.MESSAGE_LIST, Proto2Message.class));
+    builder.withField(forField(field("fieldBytesList28_"), 28, FieldType.BYTES_LIST, false));
+    builder.withField(forField(field("fieldUint32List29_"), 29, FieldType.UINT32_LIST, false));
+    builder.withField(
+        forFieldWithEnumVerifier(
+            field("fieldEnumList30_"),
+            30,
+            FieldType.ENUM_LIST,
+            asVerifier(TestEnum.internalGetValueMap())));
+    builder.withField(forField(field("fieldSfixed32List31_"), 31, FieldType.SFIXED32_LIST, false));
+    builder.withField(forField(field("fieldSfixed64List32_"), 32, FieldType.SFIXED64_LIST, false));
+    builder.withField(forField(field("fieldSint32List33_"), 33, FieldType.SINT32_LIST, false));
+    builder.withField(forField(field("fieldSint64List34_"), 34, FieldType.SINT64_LIST, false));
+    builder.withField(
+        forField(field("fieldDoubleListPacked35_"), 35, FieldType.DOUBLE_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldFloatListPacked36_"), 36, FieldType.FLOAT_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldInt64ListPacked37_"), 37, FieldType.INT64_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldUint64ListPacked38_"), 38, FieldType.UINT64_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldInt32ListPacked39_"), 39, FieldType.INT32_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldFixed64ListPacked40_"), 40, FieldType.FIXED64_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldFixed32ListPacked41_"), 41, FieldType.FIXED32_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldBoolListPacked42_"), 42, FieldType.BOOL_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldUint32ListPacked43_"), 43, FieldType.UINT32_LIST_PACKED, false));
+    builder.withField(
+        forFieldWithEnumVerifier(
+            field("fieldEnumListPacked44_"),
+            44,
+            FieldType.ENUM_LIST_PACKED,
+            asVerifier(TestEnum.internalGetValueMap())));
+    builder.withField(
+        forField(field("fieldSfixed32ListPacked45_"), 45, FieldType.SFIXED32_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldSfixed64ListPacked46_"), 46, FieldType.SFIXED64_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldSint32ListPacked47_"), 47, FieldType.SINT32_LIST_PACKED, false));
+    builder.withField(
+        forField(field("fieldSint64ListPacked48_"), 48, FieldType.SINT64_LIST_PACKED, false));
+
+    builder.withField(
+        forProto2OptionalField(
+            field("fieldGroup49_"), 49, FieldType.GROUP, bitField0, 0x00020000, false, null));
+    builder.withField(
+        forRepeatedMessageField(
+            field("fieldGroupList51_"),
+            51,
+            FieldType.GROUP_LIST,
+            Proto2Message.FieldGroupList51.class));
+
+    OneofInfo oneof = new OneofInfo(0, field("testOneofCase_"), field("testOneof_"));
+    builder.withField(forOneofMemberField(53, FieldType.DOUBLE, oneof, Double.class, false, null));
+    builder.withField(forOneofMemberField(54, FieldType.FLOAT, oneof, Float.class, false, null));
+    builder.withField(forOneofMemberField(55, FieldType.INT64, oneof, Long.class, false, null));
+    builder.withField(forOneofMemberField(56, FieldType.UINT64, oneof, Long.class, false, null));
+    builder.withField(forOneofMemberField(57, FieldType.INT32, oneof, Integer.class, false, null));
+    builder.withField(forOneofMemberField(58, FieldType.FIXED64, oneof, Long.class, false, null));
+    builder.withField(
+        forOneofMemberField(59, FieldType.FIXED32, oneof, Integer.class, false, null));
+    builder.withField(forOneofMemberField(60, FieldType.BOOL, oneof, Boolean.class, false, null));
+    builder.withField(forOneofMemberField(61, FieldType.STRING, oneof, String.class, false, null));
+    builder.withField(
+        forOneofMemberField(62, FieldType.MESSAGE, oneof, Proto2Message.class, false, null));
+    builder.withField(
+        forOneofMemberField(63, FieldType.BYTES, oneof, ByteString.class, false, null));
+    builder.withField(forOneofMemberField(64, FieldType.UINT32, oneof, Integer.class, false, null));
+    builder.withField(
+        forOneofMemberField(65, FieldType.SFIXED32, oneof, Integer.class, false, null));
+    builder.withField(forOneofMemberField(66, FieldType.SFIXED64, oneof, Long.class, false, null));
+    builder.withField(forOneofMemberField(67, FieldType.SINT32, oneof, Integer.class, false, null));
+    builder.withField(forOneofMemberField(68, FieldType.SINT64, oneof, Long.class, false, null));
+    builder.withField(
+        forOneofMemberField(
+            69, FieldType.GROUP, oneof, Proto2Message.FieldGroup69.class, false, null));
+
+    Field bitField1 = field("bitField1_");
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredDouble71_"),
+            71,
+            FieldType.DOUBLE,
+            bitField1,
+            0x00000008,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredFloat72_"),
+            72,
+            FieldType.FLOAT,
+            bitField1,
+            0x00000010,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredInt6473_"),
+            73,
+            FieldType.INT64,
+            bitField1,
+            0x00000020,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredUint6474_"),
+            74,
+            FieldType.UINT64,
+            bitField1,
+            0x00000040,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredInt3275_"),
+            75,
+            FieldType.INT32,
+            bitField1,
+            0x00000080,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredFixed6476_"),
+            76,
+            FieldType.FIXED64,
+            bitField1,
+            0x00000100,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredFixed3277_"),
+            77,
+            FieldType.FIXED32,
+            bitField1,
+            0x00000200,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredBool78_"), 78, FieldType.BOOL, bitField1, 0x00000400, false, null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredString79_"),
+            79,
+            FieldType.STRING,
+            bitField1,
+            0x00000800,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredMessage80_"),
+            80,
+            FieldType.MESSAGE,
+            bitField1,
+            0x00001000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredBytes81_"),
+            81,
+            FieldType.BYTES,
+            bitField1,
+            0x00002000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredUint3282_"),
+            82,
+            FieldType.UINT32,
+            bitField1,
+            0x00004000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredEnum83_"),
+            83,
+            FieldType.ENUM,
+            bitField1,
+            0x00008000,
+            false,
+            asVerifier(TestEnum.internalGetValueMap())));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredSfixed3284_"),
+            84,
+            FieldType.SFIXED32,
+            bitField1,
+            0x00010000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredSfixed6485_"),
+            85,
+            FieldType.SFIXED64,
+            bitField1,
+            0x00020000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredSint3286_"),
+            86,
+            FieldType.SINT32,
+            bitField1,
+            0x00040000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredSint6487_"),
+            87,
+            FieldType.SINT64,
+            bitField1,
+            0x00080000,
+            false,
+            null));
+    builder.withField(
+        forProto2RequiredField(
+            field("fieldRequiredGroup88_"),
+            88,
+            FieldType.GROUP,
+            bitField1,
+            0x00100000,
+            false,
+            null));
+  }
+
+  private static StructuralMessageInfo newMessageInfoForFieldGroup49() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(FieldGroup49.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(FieldGroup49.class, "fieldInt3250_"),
+            50,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForFieldGroupList51() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(FieldGroupList51.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(FieldGroupList51.class, "fieldInt3252_"),
+            52,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForFieldGroup69() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(FieldGroup69.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(FieldGroup69.class, "fieldInt3270_"),
+            70,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForRequiredNestedMessage() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(RequiredNestedMessage.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(RequiredNestedMessage.class, "value_"),
+            1,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForFieldRequiredGroup88() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(FieldRequiredGroup88.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(FieldRequiredGroup88.class, "fieldInt3289_"),
+            89,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForProto2Empty() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForProto2MessageWithExtensions() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(0);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForExtensionFieldGroup49() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(Proto2Testing.FieldGroup49.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(Proto2Testing.FieldGroup49.class, "fieldInt3250_"),
+            50,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForExtensionFieldGroupList51() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    Field bitField0 = field(Proto2Testing.FieldGroupList51.class, "bitField0_");
+    builder.withField(
+        forProto2OptionalField(
+            field(Proto2Testing.FieldGroupList51.class, "fieldInt3252_"),
+            52,
+            FieldType.INT32,
+            bitField0,
+            0x00000001,
+            false,
+            null));
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForProto2MessageWithMaps() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder();
+    builder.withCheckInitialized(
+        new int[] {
+          10, 27, 44, 61, 78, 95, 112, 129, 146, 163, 180, 197,
+        });
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_bool_1", 1));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_bytes_2", 2));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_double_3", 3));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_enum_4", 4));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_fixed32_5", 5));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_fixed64_6", 6));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_float_7", 7));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_int32_8", 8));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_int64_9", 9));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_message_10", 10));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_sfixed32_11", 11));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_sfixed64_12", 12));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_sint32_13", 13));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_sint64_14", 14));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_string_15", 15));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_uint32_16", 16));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_bool_uint64_17", 17));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_bool_18", 18));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_bytes_19", 19));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_double_20", 20));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_enum_21", 21));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_fixed32_22", 22));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_fixed64_23", 23));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_float_24", 24));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_int32_25", 25));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_int64_26", 26));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_message_27", 27));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_sfixed32_28", 28));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_sfixed64_29", 29));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_sint32_30", 30));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_sint64_31", 31));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_string_32", 32));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_uint32_33", 33));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed32_uint64_34", 34));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_bool_35", 35));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_bytes_36", 36));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_double_37", 37));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_enum_38", 38));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_fixed32_39", 39));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_fixed64_40", 40));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_float_41", 41));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_int32_42", 42));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_int64_43", 43));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_message_44", 44));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_sfixed32_45", 45));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_sfixed64_46", 46));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_sint32_47", 47));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_sint64_48", 48));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_string_49", 49));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_uint32_50", 50));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_fixed64_uint64_51", 51));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_bool_52", 52));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_bytes_53", 53));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_double_54", 54));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_enum_55", 55));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_fixed32_56", 56));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_fixed64_57", 57));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_float_58", 58));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_int32_59", 59));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_int64_60", 60));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_message_61", 61));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_sfixed32_62", 62));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_sfixed64_63", 63));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_sint32_64", 64));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_sint64_65", 65));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_string_66", 66));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_uint32_67", 67));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int32_uint64_68", 68));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_bool_69", 69));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_bytes_70", 70));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_double_71", 71));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_enum_72", 72));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_fixed32_73", 73));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_fixed64_74", 74));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_float_75", 75));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_int32_76", 76));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_int64_77", 77));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_message_78", 78));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_sfixed32_79", 79));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_sfixed64_80", 80));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_sint32_81", 81));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_sint64_82", 82));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_string_83", 83));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_uint32_84", 84));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_int64_uint64_85", 85));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_bool_86", 86));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_bytes_87", 87));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_double_88", 88));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_enum_89", 89));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_fixed32_90", 90));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_fixed64_91", 91));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_float_92", 92));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_int32_93", 93));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_int64_94", 94));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_message_95", 95));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_sfixed32_96", 96));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_sfixed64_97", 97));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_sint32_98", 98));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_sint64_99", 99));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_string_100", 100));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_uint32_101", 101));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed32_uint64_102", 102));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_bool_103", 103));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_bytes_104", 104));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_double_105", 105));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_enum_106", 106));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_fixed32_107", 107));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_fixed64_108", 108));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_float_109", 109));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_int32_110", 110));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_int64_111", 111));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_message_112", 112));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_sfixed32_113", 113));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_sfixed64_114", 114));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_sint32_115", 115));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_sint64_116", 116));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_string_117", 117));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_uint32_118", 118));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sfixed64_uint64_119", 119));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_bool_120", 120));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_bytes_121", 121));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_double_122", 122));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_enum_123", 123));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_fixed32_124", 124));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_fixed64_125", 125));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_float_126", 126));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_int32_127", 127));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_int64_128", 128));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_message_129", 129));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_sfixed32_130", 130));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_sfixed64_131", 131));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_sint32_132", 132));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_sint64_133", 133));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_string_134", 134));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_uint32_135", 135));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint32_uint64_136", 136));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_bool_137", 137));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_bytes_138", 138));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_double_139", 139));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_enum_140", 140));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_fixed32_141", 141));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_fixed64_142", 142));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_float_143", 143));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_int32_144", 144));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_int64_145", 145));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_message_146", 146));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_sfixed32_147", 147));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_sfixed64_148", 148));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_sint32_149", 149));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_sint64_150", 150));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_string_151", 151));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_uint32_152", 152));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_sint64_uint64_153", 153));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_bool_154", 154));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_bytes_155", 155));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_double_156", 156));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_enum_157", 157));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_fixed32_158", 158));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_fixed64_159", 159));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_float_160", 160));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_int32_161", 161));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_int64_162", 162));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_message_163", 163));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_sfixed32_164", 164));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_sfixed64_165", 165));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_sint32_166", 166));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_sint64_167", 167));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_string_168", 168));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_uint32_169", 169));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_string_uint64_170", 170));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_bool_171", 171));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_bytes_172", 172));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_double_173", 173));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_enum_174", 174));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_fixed32_175", 175));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_fixed64_176", 176));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_float_177", 177));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_int32_178", 178));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_int64_179", 179));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_message_180", 180));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_sfixed32_181", 181));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_sfixed64_182", 182));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_sint32_183", 183));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_sint64_184", 184));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_string_185", 185));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_uint32_186", 186));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint32_uint64_187", 187));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_bool_188", 188));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_bytes_189", 189));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_double_190", 190));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_enum_191", 191));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_fixed32_192", 192));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_fixed64_193", 193));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_float_194", 194));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_int32_195", 195));
+    builder.withField(mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_int64_196", 196));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_message_197", 197));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_sfixed32_198", 198));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_sfixed64_199", 199));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_sint32_200", 200));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_sint64_201", 201));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_string_202", 202));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_uint32_203", 203));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_uint64_204", 204));
+
+    return builder.build();
+  }
+
+  private static FieldInfo mapFieldInfo(Class<?> clazz, String fieldName, int fieldNumber) {
+    try {
+      return forMapField(
+          field(clazz, SchemaUtil.toCamelCase(fieldName, false) + "_"),
+          fieldNumber,
+          SchemaUtil.getMapDefaultEntry(clazz, fieldName),
+          fieldName.contains("_enum_") ? asVerifier(TestEnum.internalGetValueMap()) : null);
+    } catch (Throwable t) {
+      throw new RuntimeException(t);
+    }
+  }
+
+
+  private static Field field(String name) {
+    return field(Proto2Message.class, name);
+  }
+
+  private static Field field(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException | SecurityException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static Internal.EnumVerifier asVerifier(final Internal.EnumLiteMap<?> map) {
+    return new Internal.EnumVerifier() {
+      @Override
+      public boolean isInRange(int number) {
+        return map.findValueByNumber(number) != null;
+      }
+    };
+  }
+}

+ 558 - 0
java/core/src/test/java/com/google/protobuf/Proto2MessageLiteFactory.java

@@ -0,0 +1,558 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLiteWithMaps;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Creates instances of {@link Proto2MessageLite} based on the tree configuration. */
+public final class Proto2MessageLiteFactory
+    implements ExperimentalMessageFactory<Proto2MessageLite> {
+  private final int numRepeatedFields;
+  private final int branchingFactor;
+  private final Proto2MessageLiteFactory nextLevel;
+  private final ExperimentalTestDataProvider data;
+
+  public Proto2MessageLiteFactory(
+      int numRepeatedFields, int stringLength, int branchingFactor, int treeDepth) {
+    this(
+        new ExperimentalTestDataProvider(stringLength),
+        numRepeatedFields,
+        branchingFactor,
+        treeDepth);
+  }
+
+  private Proto2MessageLiteFactory(
+      ExperimentalTestDataProvider data,
+      int numRepeatedFields,
+      int branchingFactor,
+      int treeDepth) {
+    this.numRepeatedFields = numRepeatedFields;
+    this.branchingFactor = branchingFactor;
+    this.data = data;
+    if (treeDepth > 0) {
+      nextLevel =
+          new Proto2MessageLiteFactory(data, numRepeatedFields, branchingFactor, treeDepth - 1);
+    } else {
+      nextLevel = null;
+    }
+  }
+
+  @Override
+  public ExperimentalTestDataProvider dataProvider() {
+    return data;
+  }
+
+  @Override
+  public Proto2MessageLite newMessage() {
+    Proto2MessageLite.Builder builder = Proto2MessageLite.newBuilder();
+    builder.setFieldDouble1(data.getDouble());
+    builder.setFieldFloat2(data.getFloat());
+    builder.setFieldInt643(data.getLong());
+    builder.setFieldUint644(data.getLong());
+    builder.setFieldInt325(data.getInt());
+    builder.setFieldFixed646(data.getLong());
+    builder.setFieldFixed327(data.getInt());
+    builder.setFieldBool8(data.getBool());
+    builder.setFieldString9(data.getString());
+    // We don't populate the message field. Instead we apply the branching factor to the
+    // repeated message field below.
+    builder.setFieldBytes11(data.getBytes());
+    builder.setFieldUint3212(data.getInt());
+    builder.setFieldEnum13(Proto2MessageLite.TestEnum.forNumber(data.getEnum()));
+    builder.setFieldSfixed3214(data.getInt());
+    builder.setFieldSfixed6415(data.getLong());
+    builder.setFieldSint3216(data.getInt());
+    builder.setFieldSint6417(data.getLong());
+
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      builder.addFieldDoubleList18(data.getDouble());
+      builder.addFieldFloatList19(data.getFloat());
+      builder.addFieldInt64List20(data.getLong());
+      builder.addFieldUint64List21(data.getLong());
+      builder.addFieldInt32List22(data.getInt());
+      builder.addFieldFixed64List23(data.getLong());
+      builder.addFieldFixed32List24(data.getInt());
+      builder.addFieldBoolList25(data.getBool());
+      builder.addFieldStringList26(data.getString());
+      // Repeated message field is controlled by the branching factor below.
+      builder.addFieldBytesList28(data.getBytes());
+      builder.addFieldUint32List29(data.getInt());
+      builder.addFieldEnumList30(Proto2MessageLite.TestEnum.forNumber(data.getEnum()));
+      builder.addFieldSfixed32List31(data.getInt());
+      builder.addFieldSfixed64List32(data.getLong());
+      builder.addFieldSint32List33(data.getInt());
+      builder.addFieldSint64List34(data.getLong());
+
+      builder.addFieldDoubleListPacked35(data.getDouble());
+      builder.addFieldFloatListPacked36(data.getFloat());
+      builder.addFieldInt64ListPacked37(data.getLong());
+      builder.addFieldUint64ListPacked38(data.getLong());
+      builder.addFieldInt32ListPacked39(data.getInt());
+      builder.addFieldFixed64ListPacked40(data.getLong());
+      builder.addFieldFixed32ListPacked41(data.getInt());
+      builder.addFieldBoolListPacked42(data.getBool());
+      builder.addFieldUint32ListPacked43(data.getInt());
+      builder.addFieldEnumListPacked44(Proto2MessageLite.TestEnum.forNumber(data.getEnum()));
+      builder.addFieldSfixed32ListPacked45(data.getInt());
+      builder.addFieldSfixed64ListPacked46(data.getLong());
+      builder.addFieldSint32ListPacked47(data.getInt());
+      builder.addFieldSint64ListPacked48(data.getLong());
+    }
+
+    builder.setFieldGroup49(
+        Proto2MessageLite.FieldGroup49.newBuilder().setFieldInt3250(data.getInt()));
+
+    for (int i = 0; i < branchingFactor; ++i) {
+      builder.addFieldGroupList51(
+          Proto2MessageLite.FieldGroupList51.newBuilder().setFieldInt3252(data.getInt()));
+    }
+
+    // Set all required fields.
+    populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+
+    // Handle the branching factor.
+    if (nextLevel != null) {
+      for (int i = 0; i < branchingFactor; ++i) {
+        builder.addFieldMessageList27(nextLevel.newMessage());
+      }
+    }
+
+    return builder.build();
+  }
+
+  private interface MapValueProvider<T> {
+    public T getValue();
+  }
+
+  private final MapValueProvider<Integer> integerProvider =
+      new MapValueProvider<Integer>() {
+        @Override
+        public Integer getValue() {
+          return data.getInt();
+        }
+      };
+  private final MapValueProvider<Long> longProvider =
+      new MapValueProvider<Long>() {
+        @Override
+        public Long getValue() {
+          return data.getLong();
+        }
+      };
+  private final MapValueProvider<String> stringProvider =
+      new MapValueProvider<String>() {
+        @Override
+        public String getValue() {
+          return data.getString();
+        }
+      };
+  private final MapValueProvider<ByteString> bytesProvider =
+      new MapValueProvider<ByteString>() {
+        @Override
+        public ByteString getValue() {
+          return data.getBytes();
+        }
+      };
+  private final MapValueProvider<Boolean> booleanProvider =
+      new MapValueProvider<Boolean>() {
+        @Override
+        public Boolean getValue() {
+          return data.getBool();
+        }
+      };
+  private final MapValueProvider<Float> floatProvider =
+      new MapValueProvider<Float>() {
+        @Override
+        public Float getValue() {
+          return data.getFloat();
+        }
+      };
+  private final MapValueProvider<Double> doubleProvider =
+      new MapValueProvider<Double>() {
+        @Override
+        public Double getValue() {
+          return data.getDouble();
+        }
+      };
+  private final MapValueProvider<Proto2MessageLite> messageProvider =
+      new MapValueProvider<Proto2MessageLite>() {
+        @Override
+        public Proto2MessageLite getValue() {
+          return newMessage();
+        }
+      };
+  private final MapValueProvider<Proto2MessageLite.TestEnum> enumProvider =
+      new MapValueProvider<Proto2MessageLite.TestEnum>() {
+        @Override
+        public Proto2MessageLite.TestEnum getValue() {
+          return Proto2MessageLite.TestEnum.forNumber(data.getEnum());
+        }
+      };
+
+  private <V> Map<Integer, V> populateIntegerMap(MapValueProvider<V> provider) {
+    Map<Integer, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getInt(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Long, V> populateLongMap(MapValueProvider<V> provider) {
+    Map<Long, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getLong(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<String, V> populateStringMap(MapValueProvider<V> provider) {
+    Map<String, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getString(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Boolean, V> populateBooleanMap(MapValueProvider<V> provider) {
+    Map<Boolean, V> map = new HashMap<>();
+    map.put(false, provider.getValue());
+    map.put(true, provider.getValue());
+    return map;
+  }
+
+  public Proto2MessageLiteWithMaps newMessageWithMaps() {
+    Proto2MessageLiteWithMaps.Builder builder = Proto2MessageLiteWithMaps.newBuilder();
+
+    builder.putAllFieldMapBoolBool1(populateBooleanMap(booleanProvider));
+    builder.putAllFieldMapBoolBytes2(populateBooleanMap(bytesProvider));
+    builder.putAllFieldMapBoolDouble3(populateBooleanMap(doubleProvider));
+    builder.putAllFieldMapBoolEnum4(populateBooleanMap(enumProvider));
+    builder.putAllFieldMapBoolFixed325(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolFixed646(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolFloat7(populateBooleanMap(floatProvider));
+    builder.putAllFieldMapBoolInt328(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolInt649(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolMessage10(populateBooleanMap(messageProvider));
+    builder.putAllFieldMapBoolSfixed3211(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSfixed6412(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolSint3213(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSint6414(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolString15(populateBooleanMap(stringProvider));
+    builder.putAllFieldMapBoolUint3216(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolUint6417(populateBooleanMap(longProvider));
+    builder.putAllFieldMapFixed32Bool18(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapFixed32Bytes19(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapFixed32Double20(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapFixed32Enum21(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapFixed32Fixed3222(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Fixed6423(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Float24(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapFixed32Int3225(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Int6426(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Message27(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapFixed32Sfixed3228(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sfixed6429(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Sint3230(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sint6431(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32String32(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapFixed32Uint3233(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Uint6434(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed64Bool35(populateLongMap(booleanProvider));
+    builder.putAllFieldMapFixed64Bytes36(populateLongMap(bytesProvider));
+    builder.putAllFieldMapFixed64Double37(populateLongMap(doubleProvider));
+    builder.putAllFieldMapFixed64Enum38(populateLongMap(enumProvider));
+    builder.putAllFieldMapFixed64Fixed3239(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Fixed6440(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Float41(populateLongMap(floatProvider));
+    builder.putAllFieldMapFixed64Int3242(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Int6443(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Message44(populateLongMap(messageProvider));
+    builder.putAllFieldMapFixed64Sfixed3245(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sfixed6446(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Sint3247(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sint6448(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64String49(populateLongMap(stringProvider));
+    builder.putAllFieldMapFixed64Uint3250(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Uint6451(populateLongMap(longProvider));
+    builder.putAllFieldMapInt32Bool52(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapInt32Bytes53(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapInt32Double54(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapInt32Enum55(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapInt32Fixed3256(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Fixed6457(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Float58(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapInt32Int3259(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Int6460(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Message61(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapInt32Sfixed3262(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sfixed6463(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Sint3264(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sint6465(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32String66(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapInt32Uint3267(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Uint6468(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt64Bool69(populateLongMap(booleanProvider));
+    builder.putAllFieldMapInt64Bytes70(populateLongMap(bytesProvider));
+    builder.putAllFieldMapInt64Double71(populateLongMap(doubleProvider));
+    builder.putAllFieldMapInt64Enum72(populateLongMap(enumProvider));
+    builder.putAllFieldMapInt64Fixed3273(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Fixed6474(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Float75(populateLongMap(floatProvider));
+    builder.putAllFieldMapInt64Int3276(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Int6477(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Message78(populateLongMap(messageProvider));
+    builder.putAllFieldMapInt64Sfixed3279(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sfixed6480(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Sint3281(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sint6482(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64String83(populateLongMap(stringProvider));
+    builder.putAllFieldMapInt64Uint3284(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Uint6485(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed32Bool86(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSfixed32Bytes87(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSfixed32Double88(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSfixed32Enum89(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSfixed32Fixed3290(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Fixed6491(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Float92(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSfixed32Int3293(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Int6494(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Message95(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSfixed32Sfixed3296(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sfixed6497(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Sint3298(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sint6499(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32String100(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSfixed32Uint32101(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Uint64102(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed64Bool103(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSfixed64Bytes104(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSfixed64Double105(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSfixed64Enum106(populateLongMap(enumProvider));
+    builder.putAllFieldMapSfixed64Fixed32107(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Fixed64108(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Float109(populateLongMap(floatProvider));
+    builder.putAllFieldMapSfixed64Int32110(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Int64111(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Message112(populateLongMap(messageProvider));
+    builder.putAllFieldMapSfixed64Sfixed32113(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sfixed64114(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Sint32115(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sint64116(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64String117(populateLongMap(stringProvider));
+    builder.putAllFieldMapSfixed64Uint32118(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Uint64119(populateLongMap(longProvider));
+    builder.putAllFieldMapSint32Bool120(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSint32Bytes121(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSint32Double122(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSint32Enum123(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSint32Fixed32124(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Fixed64125(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Float126(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSint32Int32127(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Int64128(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Message129(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSint32Sfixed32130(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sfixed64131(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Sint32132(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sint64133(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32String134(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSint32Uint32135(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Uint64136(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint64Bool137(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSint64Bytes138(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSint64Double139(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSint64Enum140(populateLongMap(enumProvider));
+    builder.putAllFieldMapSint64Fixed32141(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Fixed64142(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Float143(populateLongMap(floatProvider));
+    builder.putAllFieldMapSint64Int32144(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Int64145(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Message146(populateLongMap(messageProvider));
+    builder.putAllFieldMapSint64Sfixed32147(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sfixed64148(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Sint32149(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sint64150(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64String151(populateLongMap(stringProvider));
+    builder.putAllFieldMapSint64Uint32152(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Uint64153(populateLongMap(longProvider));
+    builder.putAllFieldMapStringBool154(populateStringMap(booleanProvider));
+    builder.putAllFieldMapStringBytes155(populateStringMap(bytesProvider));
+    builder.putAllFieldMapStringDouble156(populateStringMap(doubleProvider));
+    builder.putAllFieldMapStringEnum157(populateStringMap(enumProvider));
+    builder.putAllFieldMapStringFixed32158(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringFixed64159(populateStringMap(longProvider));
+    builder.putAllFieldMapStringFloat160(populateStringMap(floatProvider));
+    builder.putAllFieldMapStringInt32161(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringInt64162(populateStringMap(longProvider));
+    builder.putAllFieldMapStringMessage163(populateStringMap(messageProvider));
+    builder.putAllFieldMapStringSfixed32164(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSfixed64165(populateStringMap(longProvider));
+    builder.putAllFieldMapStringSint32166(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSint64167(populateStringMap(longProvider));
+    builder.putAllFieldMapStringString168(populateStringMap(stringProvider));
+    builder.putAllFieldMapStringUint32169(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringUint64170(populateStringMap(longProvider));
+    builder.putAllFieldMapUint32Bool171(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapUint32Bytes172(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapUint32Double173(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapUint32Enum174(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapUint32Fixed32175(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Fixed64176(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Float177(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapUint32Int32178(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Int64179(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Message180(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapUint32Sfixed32181(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sfixed64182(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Sint32183(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sint64184(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32String185(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapUint32Uint32186(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Uint64187(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint64Bool188(populateLongMap(booleanProvider));
+    builder.putAllFieldMapUint64Bytes189(populateLongMap(bytesProvider));
+    builder.putAllFieldMapUint64Double190(populateLongMap(doubleProvider));
+    builder.putAllFieldMapUint64Enum191(populateLongMap(enumProvider));
+    builder.putAllFieldMapUint64Fixed32192(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Fixed64193(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Float194(populateLongMap(floatProvider));
+    builder.putAllFieldMapUint64Int32195(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Int64196(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Message197(populateLongMap(messageProvider));
+    builder.putAllFieldMapUint64Sfixed32198(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sfixed64199(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Sint32200(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sint64201(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64String202(populateLongMap(stringProvider));
+    builder.putAllFieldMapUint64Uint32203(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Uint64204(populateLongMap(longProvider));
+
+    return builder.build();
+  }
+
+  public List<Proto2MessageLite> newMessagesMissingRequiredFields() {
+    List<Proto2MessageLite> results = new ArrayList<>();
+    for (int i = 71; i <= 88; ++i) {
+      Proto2MessageLite.Builder builder = Proto2MessageLite.newBuilder();
+      populateRequiredFields(builder, i);
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested optional message field is missing required fields.
+      Proto2MessageLite.Builder builder = Proto2MessageLite.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.setFieldMessage10(Proto2MessageLite.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested repeated message field is missing required fields.
+      Proto2MessageLite.Builder builder = Proto2MessageLite.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.addFieldMessageList27(Proto2MessageLite.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    {
+      // A nested oneof message field is missing required fields.
+      Proto2MessageLite.Builder builder = Proto2MessageLite.newBuilder();
+      populateRequiredFields(builder, INCLUDE_ALL_REQUIRED_FIELDS);
+      builder.setFieldMessage62(Proto2MessageLite.getDefaultInstance());
+      results.add(builder.buildPartial());
+    }
+    return results;
+  }
+
+  // 0 is not a valid field number so we use it to mean no fields are excluded.
+  private static final int INCLUDE_ALL_REQUIRED_FIELDS = 0;
+
+  private void populateRequiredFields(Proto2MessageLite.Builder builder, int excludedFieldNumber) {
+    if (excludedFieldNumber != 71) {
+      builder.setFieldRequiredDouble71(data.getDouble());
+    }
+    if (excludedFieldNumber != 72) {
+      builder.setFieldRequiredFloat72(data.getFloat());
+    }
+    if (excludedFieldNumber != 73) {
+      builder.setFieldRequiredInt6473(data.getLong());
+    }
+    if (excludedFieldNumber != 74) {
+      builder.setFieldRequiredUint6474(data.getLong());
+    }
+    if (excludedFieldNumber != 75) {
+      builder.setFieldRequiredInt3275(data.getInt());
+    }
+    if (excludedFieldNumber != 76) {
+      builder.setFieldRequiredFixed6476(data.getLong());
+    }
+    if (excludedFieldNumber != 77) {
+      builder.setFieldRequiredFixed3277(data.getInt());
+    }
+    if (excludedFieldNumber != 78) {
+      builder.setFieldRequiredBool78(data.getBool());
+    }
+    if (excludedFieldNumber != 79) {
+      builder.setFieldRequiredString79(data.getString());
+    }
+    if (excludedFieldNumber != 80) {
+      builder.setFieldRequiredMessage80(
+          Proto2MessageLite.RequiredNestedMessage.newBuilder().setValue(data.getInt()));
+    }
+    if (excludedFieldNumber != 81) {
+      builder.setFieldRequiredBytes81(data.getBytes());
+    }
+    if (excludedFieldNumber != 82) {
+      builder.setFieldRequiredUint3282(data.getInt());
+    }
+    if (excludedFieldNumber != 83) {
+      builder.setFieldRequiredEnum83(Proto2MessageLite.TestEnum.forNumber(data.getEnum()));
+    }
+    if (excludedFieldNumber != 84) {
+      builder.setFieldRequiredSfixed3284(data.getInt());
+    }
+    if (excludedFieldNumber != 85) {
+      builder.setFieldRequiredSfixed6485(data.getLong());
+    }
+    if (excludedFieldNumber != 86) {
+      builder.setFieldRequiredSint3286(data.getInt());
+    }
+    if (excludedFieldNumber != 87) {
+      builder.setFieldRequiredSint6487(data.getLong());
+    }
+    if (excludedFieldNumber != 88) {
+      builder.setFieldRequiredGroup88(
+          Proto2MessageLite.FieldRequiredGroup88.newBuilder().setFieldInt3289(data.getInt()));
+    }
+  }
+}

+ 49 - 0
java/core/src/test/java/com/google/protobuf/Proto2SchemaTest.java

@@ -0,0 +1,49 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Proto2SchemaTest extends AbstractProto2SchemaTest {
+
+  @Override
+  protected Schema<Proto2Message> schema() {
+    return TestSchemas.genericProto2Schema;
+  }
+
+  @Override
+  protected void registerSchemas() {
+    TestSchemas.registerGenericProto2Schemas();
+  }
+}

+ 111 - 0
java/core/src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java

@@ -0,0 +1,111 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import protobuf_unittest.UnittestProto;
+import protobuf_unittest.UnittestProto.TestAllExtensions;
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import junit.framework.TestCase;
+
+/** Unit tests for proto2 that treats unknown enum values as unknown fields. */
+public class Proto2UnknownEnumValueTest extends TestCase {
+  FieldDescriptor singularField =
+      TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum");
+  FieldDescriptor repeatedField =
+      TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum");
+  byte[] payload = buildPayloadWithUnknownEnumValues();
+
+  private byte[] buildPayloadWithUnknownEnumValues() {
+    // Builds a payload with unknown enum values.
+    UnknownFieldSet.Builder builder = UnknownFieldSet.newBuilder();
+    builder.addField(
+        singularField.getNumber(),
+        UnknownFieldSet.Field.newBuilder()
+            .addVarint(TestAllTypes.NestedEnum.BAR.getNumber())
+            .addVarint(1901 /* unknown enum value */)
+            .build());
+    builder.addField(
+        repeatedField.getNumber(),
+        UnknownFieldSet.Field.newBuilder()
+            .addVarint(TestAllTypes.NestedEnum.FOO.getNumber())
+            .addVarint(1902 /* unknown enum value */)
+            .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber())
+            .addVarint(1903 /* unknown enum value */)
+            .build());
+    return builder.build().toByteArray();
+  }
+
+  public void testUnknownEnumValues() throws Exception {
+    TestAllTypes message = TestAllTypes.parseFrom(payload);
+
+    // Known enum values should be preserved.
+    assertEquals(TestAllTypes.NestedEnum.BAR, message.getOptionalNestedEnum());
+    assertEquals(2, message.getRepeatedNestedEnumList().size());
+    assertEquals(TestAllTypes.NestedEnum.FOO, message.getRepeatedNestedEnum(0));
+    assertEquals(TestAllTypes.NestedEnum.BAZ, message.getRepeatedNestedEnum(1));
+
+    // Unknown enum values should be found in UnknownFieldSet.
+    UnknownFieldSet unknown = message.getUnknownFields();
+    assertEquals(
+        1901, unknown.getField(singularField.getNumber()).getVarintList().get(0).longValue());
+    assertEquals(
+        1902, unknown.getField(repeatedField.getNumber()).getVarintList().get(0).longValue());
+    assertEquals(
+        1903, unknown.getField(repeatedField.getNumber()).getVarintList().get(1).longValue());
+  }
+
+  public void testExtensionUnknownEnumValues() throws Exception {
+    ExtensionRegistry registry = ExtensionRegistry.newInstance();
+    UnittestProto.registerAllExtensions(registry);
+    TestAllExtensions message = TestAllExtensions.parseFrom(payload, registry);
+
+    assertEquals(
+        TestAllTypes.NestedEnum.BAR,
+        message.getExtension(UnittestProto.optionalNestedEnumExtension));
+    assertEquals(2, message.getExtension(UnittestProto.repeatedNestedEnumExtension).size());
+    assertEquals(
+        TestAllTypes.NestedEnum.FOO,
+        message.getExtension(UnittestProto.repeatedNestedEnumExtension, 0));
+    assertEquals(
+        TestAllTypes.NestedEnum.BAZ,
+        message.getExtension(UnittestProto.repeatedNestedEnumExtension, 1));
+
+    // Unknown enum values should be found in UnknownFieldSet.
+    UnknownFieldSet unknown = message.getUnknownFields();
+    assertEquals(
+        1901, unknown.getField(singularField.getNumber()).getVarintList().get(0).longValue());
+    assertEquals(
+        1902, unknown.getField(repeatedField.getNumber()).getVarintList().get(0).longValue());
+    assertEquals(
+        1903, unknown.getField(repeatedField.getNumber()).getVarintList().get(1).longValue());
+  }
+}

+ 49 - 0
java/core/src/test/java/com/google/protobuf/Proto3LiteSchemaTest.java

@@ -0,0 +1,49 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLite;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Proto3LiteSchemaTest extends AbstractProto3LiteSchemaTest {
+
+  @Override
+  protected Schema<Proto3MessageLite> schema() {
+    return TestSchemasLite.genericProto3LiteSchema;
+  }
+
+  @Override
+  protected void registerSchemas() {
+    TestSchemasLite.registerGenericProto3LiteSchemas();
+  }
+}

+ 450 - 0
java/core/src/test/java/com/google/protobuf/Proto3MessageFactory.java

@@ -0,0 +1,450 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3MessageWithMaps;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Creates instances of {@link Proto3Message} based on the tree configuration. */
+public final class Proto3MessageFactory implements ExperimentalMessageFactory<Proto3Message> {
+  private final int numRepeatedFields;
+  private final int branchingFactor;
+  private final Proto3MessageFactory nextLevel;
+  private final ExperimentalTestDataProvider data;
+
+  public Proto3MessageFactory(
+      int numRepeatedFields, int stringLength, int branchingFactor, int treeDepth) {
+    this(
+        new ExperimentalTestDataProvider(stringLength),
+        numRepeatedFields,
+        branchingFactor,
+        treeDepth);
+  }
+
+  private Proto3MessageFactory(
+      ExperimentalTestDataProvider data,
+      int numRepeatedFields,
+      int branchingFactor,
+      int treeDepth) {
+    this.numRepeatedFields = numRepeatedFields;
+    this.branchingFactor = branchingFactor;
+    this.data = data;
+    if (treeDepth > 0) {
+      nextLevel = new Proto3MessageFactory(data, numRepeatedFields, branchingFactor, treeDepth - 1);
+    } else {
+      nextLevel = null;
+    }
+  }
+
+  @Override
+  public ExperimentalTestDataProvider dataProvider() {
+    return data;
+  }
+
+  @Override
+  public Proto3Message newMessage() {
+    Proto3Message.Builder builder = Proto3Message.newBuilder();
+    builder.setFieldDouble1(data.getDouble());
+    builder.setFieldFloat2(data.getFloat());
+    builder.setFieldInt643(data.getLong());
+    builder.setFieldUint644(data.getLong());
+    builder.setFieldInt325(data.getInt());
+    builder.setFieldFixed646(data.getLong());
+    builder.setFieldFixed327(data.getInt());
+    builder.setFieldBool8(data.getBool());
+    builder.setFieldString9(data.getString());
+    // We don't populate the message field. Instead we apply the branching factor to the
+    // repeated message field below.
+    builder.setFieldBytes11(data.getBytes());
+    builder.setFieldUint3212(data.getInt());
+    builder.setFieldEnum13Value(data.getEnum());
+    builder.setFieldSfixed3214(data.getInt());
+    builder.setFieldSfixed6415(data.getLong());
+    builder.setFieldSint3216(data.getInt());
+    builder.setFieldSint6417(data.getLong());
+
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      builder.addFieldDoubleList18(data.getDouble());
+      builder.addFieldFloatList19(data.getFloat());
+      builder.addFieldInt64List20(data.getLong());
+      builder.addFieldUint64List21(data.getLong());
+      builder.addFieldInt32List22(data.getInt());
+      builder.addFieldFixed64List23(data.getLong());
+      builder.addFieldFixed32List24(data.getInt());
+      builder.addFieldBoolList25(data.getBool());
+      builder.addFieldStringList26(data.getString());
+      // Repeated message field is controlled by the branching factor below.
+      builder.addFieldBytesList28(data.getBytes());
+      builder.addFieldUint32List29(data.getInt());
+      builder.addFieldEnumList30Value(data.getEnum());
+      builder.addFieldSfixed32List31(data.getInt());
+      builder.addFieldSfixed64List32(data.getLong());
+      builder.addFieldSint32List33(data.getInt());
+      builder.addFieldSint64List34(data.getLong());
+
+      builder.addFieldDoubleListPacked35(data.getDouble());
+      builder.addFieldFloatListPacked36(data.getFloat());
+      builder.addFieldInt64ListPacked37(data.getLong());
+      builder.addFieldUint64ListPacked38(data.getLong());
+      builder.addFieldInt32ListPacked39(data.getInt());
+      builder.addFieldFixed64ListPacked40(data.getLong());
+      builder.addFieldFixed32ListPacked41(data.getInt());
+      builder.addFieldBoolListPacked42(data.getBool());
+      builder.addFieldUint32ListPacked43(data.getInt());
+      builder.addFieldEnumListPacked44Value(data.getEnum());
+      builder.addFieldSfixed32ListPacked45(data.getInt());
+      builder.addFieldSfixed64ListPacked46(data.getLong());
+      builder.addFieldSint32ListPacked47(data.getInt());
+      builder.addFieldSint64ListPacked48(data.getLong());
+    }
+
+    // Handle the branching factor.
+    if (nextLevel != null) {
+      for (int i = 0; i < branchingFactor; ++i) {
+        builder.addFieldMessageList27(nextLevel.newMessage());
+      }
+    }
+
+    return builder.build();
+  }
+
+  private interface MapValueProvider<T> {
+    public T getValue();
+  }
+
+  private final MapValueProvider<Integer> integerProvider =
+      new MapValueProvider<Integer>() {
+        @Override
+        public Integer getValue() {
+          return data.getInt();
+        }
+      };
+  private final MapValueProvider<Long> longProvider =
+      new MapValueProvider<Long>() {
+        @Override
+        public Long getValue() {
+          return data.getLong();
+        }
+      };
+  private final MapValueProvider<String> stringProvider =
+      new MapValueProvider<String>() {
+        @Override
+        public String getValue() {
+          return data.getString();
+        }
+      };
+  private final MapValueProvider<ByteString> bytesProvider =
+      new MapValueProvider<ByteString>() {
+        @Override
+        public ByteString getValue() {
+          return data.getBytes();
+        }
+      };
+  private final MapValueProvider<Boolean> booleanProvider =
+      new MapValueProvider<Boolean>() {
+        @Override
+        public Boolean getValue() {
+          return data.getBool();
+        }
+      };
+  private final MapValueProvider<Float> floatProvider =
+      new MapValueProvider<Float>() {
+        @Override
+        public Float getValue() {
+          return data.getFloat();
+        }
+      };
+  private final MapValueProvider<Double> doubleProvider =
+      new MapValueProvider<Double>() {
+        @Override
+        public Double getValue() {
+          return data.getDouble();
+        }
+      };
+  private final MapValueProvider<Proto3Message> messageProvider =
+      new MapValueProvider<Proto3Message>() {
+        @Override
+        public Proto3Message getValue() {
+          return newMessage();
+        }
+      };
+  private final MapValueProvider<Proto3Message.TestEnum> enumProvider =
+      new MapValueProvider<Proto3Message.TestEnum>() {
+        @Override
+        public Proto3Message.TestEnum getValue() {
+          return Proto3Message.TestEnum.forNumber(data.getEnum());
+        }
+      };
+
+  private <V> Map<Integer, V> populateIntegerMap(MapValueProvider<V> provider) {
+    Map<Integer, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getInt(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Long, V> populateLongMap(MapValueProvider<V> provider) {
+    Map<Long, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getLong(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<String, V> populateStringMap(MapValueProvider<V> provider) {
+    Map<String, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getString(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Boolean, V> populateBooleanMap(MapValueProvider<V> provider) {
+    Map<Boolean, V> map = new HashMap<>();
+    map.put(false, provider.getValue());
+    map.put(true, provider.getValue());
+    return map;
+  }
+
+  public Proto3MessageWithMaps newMessageWithMaps() {
+    Proto3MessageWithMaps.Builder builder = Proto3MessageWithMaps.newBuilder();
+
+    builder.putAllFieldMapBoolBool1(populateBooleanMap(booleanProvider));
+    builder.putAllFieldMapBoolBytes2(populateBooleanMap(bytesProvider));
+    builder.putAllFieldMapBoolDouble3(populateBooleanMap(doubleProvider));
+    builder.putAllFieldMapBoolEnum4(populateBooleanMap(enumProvider));
+    builder.putAllFieldMapBoolFixed325(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolFixed646(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolFloat7(populateBooleanMap(floatProvider));
+    builder.putAllFieldMapBoolInt328(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolInt649(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolMessage10(populateBooleanMap(messageProvider));
+    builder.putAllFieldMapBoolSfixed3211(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSfixed6412(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolSint3213(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSint6414(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolString15(populateBooleanMap(stringProvider));
+    builder.putAllFieldMapBoolUint3216(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolUint6417(populateBooleanMap(longProvider));
+    builder.putAllFieldMapFixed32Bool18(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapFixed32Bytes19(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapFixed32Double20(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapFixed32Enum21(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapFixed32Fixed3222(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Fixed6423(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Float24(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapFixed32Int3225(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Int6426(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Message27(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapFixed32Sfixed3228(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sfixed6429(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Sint3230(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sint6431(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32String32(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapFixed32Uint3233(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Uint6434(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed64Bool35(populateLongMap(booleanProvider));
+    builder.putAllFieldMapFixed64Bytes36(populateLongMap(bytesProvider));
+    builder.putAllFieldMapFixed64Double37(populateLongMap(doubleProvider));
+    builder.putAllFieldMapFixed64Enum38(populateLongMap(enumProvider));
+    builder.putAllFieldMapFixed64Fixed3239(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Fixed6440(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Float41(populateLongMap(floatProvider));
+    builder.putAllFieldMapFixed64Int3242(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Int6443(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Message44(populateLongMap(messageProvider));
+    builder.putAllFieldMapFixed64Sfixed3245(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sfixed6446(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Sint3247(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sint6448(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64String49(populateLongMap(stringProvider));
+    builder.putAllFieldMapFixed64Uint3250(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Uint6451(populateLongMap(longProvider));
+    builder.putAllFieldMapInt32Bool52(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapInt32Bytes53(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapInt32Double54(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapInt32Enum55(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapInt32Fixed3256(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Fixed6457(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Float58(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapInt32Int3259(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Int6460(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Message61(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapInt32Sfixed3262(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sfixed6463(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Sint3264(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sint6465(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32String66(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapInt32Uint3267(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Uint6468(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt64Bool69(populateLongMap(booleanProvider));
+    builder.putAllFieldMapInt64Bytes70(populateLongMap(bytesProvider));
+    builder.putAllFieldMapInt64Double71(populateLongMap(doubleProvider));
+    builder.putAllFieldMapInt64Enum72(populateLongMap(enumProvider));
+    builder.putAllFieldMapInt64Fixed3273(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Fixed6474(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Float75(populateLongMap(floatProvider));
+    builder.putAllFieldMapInt64Int3276(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Int6477(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Message78(populateLongMap(messageProvider));
+    builder.putAllFieldMapInt64Sfixed3279(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sfixed6480(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Sint3281(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sint6482(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64String83(populateLongMap(stringProvider));
+    builder.putAllFieldMapInt64Uint3284(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Uint6485(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed32Bool86(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSfixed32Bytes87(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSfixed32Double88(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSfixed32Enum89(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSfixed32Fixed3290(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Fixed6491(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Float92(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSfixed32Int3293(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Int6494(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Message95(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSfixed32Sfixed3296(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sfixed6497(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Sint3298(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sint6499(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32String100(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSfixed32Uint32101(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Uint64102(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed64Bool103(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSfixed64Bytes104(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSfixed64Double105(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSfixed64Enum106(populateLongMap(enumProvider));
+    builder.putAllFieldMapSfixed64Fixed32107(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Fixed64108(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Float109(populateLongMap(floatProvider));
+    builder.putAllFieldMapSfixed64Int32110(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Int64111(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Message112(populateLongMap(messageProvider));
+    builder.putAllFieldMapSfixed64Sfixed32113(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sfixed64114(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Sint32115(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sint64116(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64String117(populateLongMap(stringProvider));
+    builder.putAllFieldMapSfixed64Uint32118(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Uint64119(populateLongMap(longProvider));
+    builder.putAllFieldMapSint32Bool120(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSint32Bytes121(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSint32Double122(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSint32Enum123(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSint32Fixed32124(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Fixed64125(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Float126(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSint32Int32127(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Int64128(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Message129(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSint32Sfixed32130(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sfixed64131(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Sint32132(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sint64133(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32String134(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSint32Uint32135(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Uint64136(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint64Bool137(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSint64Bytes138(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSint64Double139(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSint64Enum140(populateLongMap(enumProvider));
+    builder.putAllFieldMapSint64Fixed32141(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Fixed64142(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Float143(populateLongMap(floatProvider));
+    builder.putAllFieldMapSint64Int32144(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Int64145(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Message146(populateLongMap(messageProvider));
+    builder.putAllFieldMapSint64Sfixed32147(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sfixed64148(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Sint32149(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sint64150(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64String151(populateLongMap(stringProvider));
+    builder.putAllFieldMapSint64Uint32152(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Uint64153(populateLongMap(longProvider));
+    builder.putAllFieldMapStringBool154(populateStringMap(booleanProvider));
+    builder.putAllFieldMapStringBytes155(populateStringMap(bytesProvider));
+    builder.putAllFieldMapStringDouble156(populateStringMap(doubleProvider));
+    builder.putAllFieldMapStringEnum157(populateStringMap(enumProvider));
+    builder.putAllFieldMapStringFixed32158(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringFixed64159(populateStringMap(longProvider));
+    builder.putAllFieldMapStringFloat160(populateStringMap(floatProvider));
+    builder.putAllFieldMapStringInt32161(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringInt64162(populateStringMap(longProvider));
+    builder.putAllFieldMapStringMessage163(populateStringMap(messageProvider));
+    builder.putAllFieldMapStringSfixed32164(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSfixed64165(populateStringMap(longProvider));
+    builder.putAllFieldMapStringSint32166(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSint64167(populateStringMap(longProvider));
+    builder.putAllFieldMapStringString168(populateStringMap(stringProvider));
+    builder.putAllFieldMapStringUint32169(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringUint64170(populateStringMap(longProvider));
+    builder.putAllFieldMapUint32Bool171(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapUint32Bytes172(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapUint32Double173(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapUint32Enum174(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapUint32Fixed32175(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Fixed64176(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Float177(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapUint32Int32178(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Int64179(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Message180(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapUint32Sfixed32181(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sfixed64182(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Sint32183(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sint64184(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32String185(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapUint32Uint32186(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Uint64187(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint64Bool188(populateLongMap(booleanProvider));
+    builder.putAllFieldMapUint64Bytes189(populateLongMap(bytesProvider));
+    builder.putAllFieldMapUint64Double190(populateLongMap(doubleProvider));
+    builder.putAllFieldMapUint64Enum191(populateLongMap(enumProvider));
+    builder.putAllFieldMapUint64Fixed32192(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Fixed64193(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Float194(populateLongMap(floatProvider));
+    builder.putAllFieldMapUint64Int32195(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Int64196(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Message197(populateLongMap(messageProvider));
+    builder.putAllFieldMapUint64Sfixed32198(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sfixed64199(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Sint32200(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sint64201(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64String202(populateLongMap(stringProvider));
+    builder.putAllFieldMapUint64Uint32203(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Uint64204(populateLongMap(longProvider));
+
+    return builder.build();
+  }
+}

+ 506 - 0
java/core/src/test/java/com/google/protobuf/Proto3MessageInfoFactory.java

@@ -0,0 +1,506 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.FieldInfo.forField;
+import static com.google.protobuf.FieldInfo.forMapField;
+import static com.google.protobuf.FieldInfo.forOneofMemberField;
+import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
+
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithMaps;
+import com.google.protobuf.testing.Proto3Testing.Proto3Empty;
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3MessageWithMaps;
+import java.lang.reflect.Field;
+
+/** A factory that generates a hard-coded info for {@link Proto3Message}. */
+public final class Proto3MessageInfoFactory implements MessageInfoFactory {
+  private static final Proto3MessageInfoFactory INSTANCE = new Proto3MessageInfoFactory();
+
+  private Proto3MessageInfoFactory() {}
+
+  public static Proto3MessageInfoFactory getInstance() {
+    return INSTANCE;
+  }
+
+  @Override
+  public boolean isSupported(Class<?> clazz) {
+    return true;
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> clazz) {
+    if (Proto3Message.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3Message();
+    } else if (Proto3Empty.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3Empty();
+    } else if (Proto3MessageWithMaps.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3MessageWithMaps();
+    } else {
+      throw new IllegalArgumentException("Unsupported class: " + clazz.getName());
+    }
+  }
+
+  /**
+   * Creates a new hard-coded info for {@link Proto3Message}. Each time this is called, we manually
+   * go through the entire process of what a message would do if it self-registered its own info,
+   * including looking up each field by name. This is done for benchmarking purposes, so that we get
+   * a more accurate representation of the time it takes to perform this process.
+   */
+  private static StructuralMessageInfo newMessageInfoForProto3Message() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(48);
+    lookupFieldsByName(builder);
+    return builder.build();
+  }
+
+  private static void lookupFieldsByName(StructuralMessageInfo.Builder builder) {
+    builder.withDefaultInstance(Proto3Message.getDefaultInstance());
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    builder.withField(forField(field("fieldDouble1_"), 1, FieldType.DOUBLE, true));
+    builder.withField(forField(field("fieldFloat2_"), 2, FieldType.FLOAT, true));
+    builder.withField(forField(field("fieldInt643_"), 3, FieldType.INT64, true));
+    builder.withField(forField(field("fieldUint644_"), 4, FieldType.UINT64, true));
+    builder.withField(forField(field("fieldInt325_"), 5, FieldType.INT32, true));
+    builder.withField(forField(field("fieldFixed646_"), 6, FieldType.FIXED64, true));
+    builder.withField(forField(field("fieldFixed327_"), 7, FieldType.FIXED32, true));
+    builder.withField(forField(field("fieldBool8_"), 8, FieldType.BOOL, true));
+    builder.withField(forField(field("fieldString9_"), 9, FieldType.STRING, true));
+    builder.withField(forField(field("fieldMessage10_"), 10, FieldType.MESSAGE, true));
+    builder.withField(forField(field("fieldBytes11_"), 11, FieldType.BYTES, true));
+    builder.withField(forField(field("fieldUint3212_"), 12, FieldType.UINT32, true));
+    builder.withField(forField(field("fieldEnum13_"), 13, FieldType.ENUM, true));
+    builder.withField(forField(field("fieldSfixed3214_"), 14, FieldType.SFIXED32, true));
+    builder.withField(forField(field("fieldSfixed6415_"), 15, FieldType.SFIXED64, true));
+    builder.withField(forField(field("fieldSint3216_"), 16, FieldType.SINT32, true));
+    builder.withField(forField(field("fieldSint6417_"), 17, FieldType.SINT64, true));
+    builder.withField(forField(field("fieldDoubleList18_"), 18, FieldType.DOUBLE_LIST, true));
+    builder.withField(forField(field("fieldFloatList19_"), 19, FieldType.FLOAT_LIST, true));
+    builder.withField(forField(field("fieldInt64List20_"), 20, FieldType.INT64_LIST, true));
+    builder.withField(forField(field("fieldUint64List21_"), 21, FieldType.UINT64_LIST, true));
+    builder.withField(forField(field("fieldInt32List22_"), 22, FieldType.INT32_LIST, true));
+    builder.withField(forField(field("fieldFixed64List23_"), 23, FieldType.FIXED64_LIST, true));
+    builder.withField(forField(field("fieldFixed32List24_"), 24, FieldType.FIXED32_LIST, true));
+    builder.withField(forField(field("fieldBoolList25_"), 25, FieldType.BOOL_LIST, true));
+    builder.withField(forField(field("fieldStringList26_"), 26, FieldType.STRING_LIST, true));
+    builder.withField(
+        forRepeatedMessageField(
+            field("fieldMessageList27_"), 27, FieldType.MESSAGE_LIST, Proto3Message.class));
+    builder.withField(forField(field("fieldBytesList28_"), 28, FieldType.BYTES_LIST, true));
+    builder.withField(forField(field("fieldUint32List29_"), 29, FieldType.UINT32_LIST, true));
+    builder.withField(forField(field("fieldEnumList30_"), 30, FieldType.ENUM_LIST, true));
+    builder.withField(forField(field("fieldSfixed32List31_"), 31, FieldType.SFIXED32_LIST, true));
+    builder.withField(forField(field("fieldSfixed64List32_"), 32, FieldType.SFIXED64_LIST, true));
+    builder.withField(forField(field("fieldSint32List33_"), 33, FieldType.SINT32_LIST, true));
+    builder.withField(forField(field("fieldSint64List34_"), 34, FieldType.SINT64_LIST, true));
+    builder.withField(
+        forField(field("fieldDoubleListPacked35_"), 35, FieldType.DOUBLE_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldFloatListPacked36_"), 36, FieldType.FLOAT_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldInt64ListPacked37_"), 37, FieldType.INT64_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldUint64ListPacked38_"), 38, FieldType.UINT64_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldInt32ListPacked39_"), 39, FieldType.INT32_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldFixed64ListPacked40_"), 40, FieldType.FIXED64_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldFixed32ListPacked41_"), 41, FieldType.FIXED32_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldBoolListPacked42_"), 42, FieldType.BOOL_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldUint32ListPacked43_"), 43, FieldType.UINT32_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldEnumListPacked44_"), 44, FieldType.ENUM_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldSfixed32ListPacked45_"), 45, FieldType.SFIXED32_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldSfixed64ListPacked46_"), 46, FieldType.SFIXED64_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldSint32ListPacked47_"), 47, FieldType.SINT32_LIST_PACKED, true));
+    builder.withField(
+        forField(field("fieldSint64ListPacked48_"), 48, FieldType.SINT64_LIST_PACKED, true));
+
+    OneofInfo oneof = new OneofInfo(0, field("testOneofCase_"), field("testOneof_"));
+    builder.withField(forOneofMemberField(53, FieldType.DOUBLE, oneof, Double.class, true, null));
+    builder.withField(forOneofMemberField(54, FieldType.FLOAT, oneof, Float.class, true, null));
+    builder.withField(forOneofMemberField(55, FieldType.INT64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(56, FieldType.UINT64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(57, FieldType.INT32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(58, FieldType.FIXED64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(59, FieldType.FIXED32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(60, FieldType.BOOL, oneof, Boolean.class, true, null));
+    builder.withField(forOneofMemberField(61, FieldType.STRING, oneof, String.class, true, null));
+    builder.withField(
+        forOneofMemberField(62, FieldType.MESSAGE, oneof, Proto3Message.class, true, null));
+    builder.withField(
+        forOneofMemberField(63, FieldType.BYTES, oneof, ByteString.class, true, null));
+    builder.withField(forOneofMemberField(64, FieldType.UINT32, oneof, Integer.class, true, null));
+    builder.withField(
+        forOneofMemberField(65, FieldType.SFIXED32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(66, FieldType.SFIXED64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(67, FieldType.SINT32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(68, FieldType.SINT64, oneof, Long.class, true, null));
+  }
+
+  private StructuralMessageInfo newMessageInfoForProto3Empty() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    return builder.build();
+  }
+
+  private StructuralMessageInfo newMessageInfoForProto3MessageWithMaps() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder();
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_bool_1", 1));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_bytes_2", 2));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_double_3", 3));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_enum_4", 4));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_fixed32_5", 5));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_fixed64_6", 6));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_float_7", 7));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_int32_8", 8));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_int64_9", 9));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_message_10", 10));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_sfixed32_11", 11));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_sfixed64_12", 12));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_sint32_13", 13));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_sint64_14", 14));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_string_15", 15));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_uint32_16", 16));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_bool_uint64_17", 17));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_bool_18", 18));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_bytes_19", 19));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_double_20", 20));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_enum_21", 21));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_fixed32_22", 22));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_fixed64_23", 23));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_float_24", 24));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_int32_25", 25));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_int64_26", 26));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_message_27", 27));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_sfixed32_28", 28));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_sfixed64_29", 29));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_sint32_30", 30));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_sint64_31", 31));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_string_32", 32));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_uint32_33", 33));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed32_uint64_34", 34));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_bool_35", 35));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_bytes_36", 36));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_double_37", 37));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_enum_38", 38));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_fixed32_39", 39));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_fixed64_40", 40));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_float_41", 41));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_int32_42", 42));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_int64_43", 43));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_message_44", 44));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_sfixed32_45", 45));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_sfixed64_46", 46));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_sint32_47", 47));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_sint64_48", 48));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_string_49", 49));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_uint32_50", 50));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_fixed64_uint64_51", 51));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_bool_52", 52));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_bytes_53", 53));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_double_54", 54));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_enum_55", 55));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_fixed32_56", 56));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_fixed64_57", 57));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_float_58", 58));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_int32_59", 59));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_int64_60", 60));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_message_61", 61));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_sfixed32_62", 62));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_sfixed64_63", 63));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_sint32_64", 64));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_sint64_65", 65));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_string_66", 66));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_uint32_67", 67));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int32_uint64_68", 68));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_bool_69", 69));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_bytes_70", 70));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_double_71", 71));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_enum_72", 72));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_fixed32_73", 73));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_fixed64_74", 74));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_float_75", 75));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_int32_76", 76));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_int64_77", 77));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_message_78", 78));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_sfixed32_79", 79));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_sfixed64_80", 80));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_sint32_81", 81));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_sint64_82", 82));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_string_83", 83));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_uint32_84", 84));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_int64_uint64_85", 85));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_bool_86", 86));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_bytes_87", 87));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_double_88", 88));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_enum_89", 89));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_fixed32_90", 90));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_fixed64_91", 91));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_float_92", 92));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_int32_93", 93));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_int64_94", 94));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_message_95", 95));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_sfixed32_96", 96));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_sfixed64_97", 97));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_sint32_98", 98));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_sint64_99", 99));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_string_100", 100));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_uint32_101", 101));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed32_uint64_102", 102));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_bool_103", 103));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_bytes_104", 104));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_double_105", 105));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_enum_106", 106));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_fixed32_107", 107));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_fixed64_108", 108));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_float_109", 109));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_int32_110", 110));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_int64_111", 111));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_message_112", 112));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_sfixed32_113", 113));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_sfixed64_114", 114));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_sint32_115", 115));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_sint64_116", 116));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_string_117", 117));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_uint32_118", 118));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sfixed64_uint64_119", 119));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_bool_120", 120));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_bytes_121", 121));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_double_122", 122));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_enum_123", 123));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_fixed32_124", 124));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_fixed64_125", 125));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_float_126", 126));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_int32_127", 127));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_int64_128", 128));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_message_129", 129));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_sfixed32_130", 130));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_sfixed64_131", 131));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_sint32_132", 132));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_sint64_133", 133));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_string_134", 134));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_uint32_135", 135));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint32_uint64_136", 136));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_bool_137", 137));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_bytes_138", 138));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_double_139", 139));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_enum_140", 140));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_fixed32_141", 141));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_fixed64_142", 142));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_float_143", 143));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_int32_144", 144));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_int64_145", 145));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_message_146", 146));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_sfixed32_147", 147));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_sfixed64_148", 148));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_sint32_149", 149));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_sint64_150", 150));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_string_151", 151));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_uint32_152", 152));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_sint64_uint64_153", 153));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_bool_154", 154));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_bytes_155", 155));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_double_156", 156));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_enum_157", 157));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_fixed32_158", 158));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_fixed64_159", 159));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_float_160", 160));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_int32_161", 161));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_int64_162", 162));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_message_163", 163));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_sfixed32_164", 164));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_sfixed64_165", 165));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_sint32_166", 166));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_sint64_167", 167));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_string_168", 168));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_uint32_169", 169));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_string_uint64_170", 170));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_bool_171", 171));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_bytes_172", 172));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_double_173", 173));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_enum_174", 174));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_fixed32_175", 175));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_fixed64_176", 176));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_float_177", 177));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_int32_178", 178));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_int64_179", 179));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_message_180", 180));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_sfixed32_181", 181));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_sfixed64_182", 182));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_sint32_183", 183));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_sint64_184", 184));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_string_185", 185));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_uint32_186", 186));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint32_uint64_187", 187));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_bool_188", 188));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_bytes_189", 189));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_double_190", 190));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_enum_191", 191));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_fixed32_192", 192));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_fixed64_193", 193));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_float_194", 194));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_int32_195", 195));
+    builder.withField(mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_int64_196", 196));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_message_197", 197));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_sfixed32_198", 198));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_sfixed64_199", 199));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_sint32_200", 200));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_sint64_201", 201));
+    builder.withField(
+        mapFieldInfo(Proto3MessageWithMaps.class, "field_map_uint64_string_202", 202));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_uint32_203", 203));
+    builder.withField(
+        mapFieldInfo(Proto2MessageWithMaps.class, "field_map_uint64_uint64_204", 204));
+    return builder.build();
+  }
+
+  private static Field field(String name) {
+    return field(Proto3Message.class, name);
+  }
+
+  private static Field field(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException | SecurityException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static FieldInfo mapFieldInfo(Class<?> clazz, String fieldName, int fieldNumber) {
+    try {
+      return forMapField(
+          field(clazz, SchemaUtil.toCamelCase(fieldName, false) + "_"),
+          fieldNumber,
+          SchemaUtil.getMapDefaultEntry(clazz, fieldName),
+          null);
+    } catch (Throwable t) {
+      throw new RuntimeException(t);
+    }
+  }
+}

+ 452 - 0
java/core/src/test/java/com/google/protobuf/Proto3MessageLiteFactory.java

@@ -0,0 +1,452 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLiteWithMaps;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Creates instances of {@link Proto3MessageLite} based on the tree configuration. */
+public final class Proto3MessageLiteFactory
+    implements ExperimentalMessageFactory<Proto3MessageLite> {
+  private final int numRepeatedFields;
+  private final int branchingFactor;
+  private final Proto3MessageLiteFactory nextLevel;
+  private final ExperimentalTestDataProvider data;
+
+  public Proto3MessageLiteFactory(
+      int numRepeatedFields, int stringLength, int branchingFactor, int treeDepth) {
+    this(
+        new ExperimentalTestDataProvider(stringLength),
+        numRepeatedFields,
+        branchingFactor,
+        treeDepth);
+  }
+
+  private Proto3MessageLiteFactory(
+      ExperimentalTestDataProvider data,
+      int numRepeatedFields,
+      int branchingFactor,
+      int treeDepth) {
+    this.numRepeatedFields = numRepeatedFields;
+    this.branchingFactor = branchingFactor;
+    this.data = data;
+    if (treeDepth > 0) {
+      nextLevel =
+          new Proto3MessageLiteFactory(data, numRepeatedFields, branchingFactor, treeDepth - 1);
+    } else {
+      nextLevel = null;
+    }
+  }
+
+  @Override
+  public ExperimentalTestDataProvider dataProvider() {
+    return data;
+  }
+
+  @Override
+  public Proto3MessageLite newMessage() {
+    Proto3MessageLite.Builder builder = Proto3MessageLite.newBuilder();
+    builder.setFieldDouble1(data.getDouble());
+    builder.setFieldFloat2(data.getFloat());
+    builder.setFieldInt643(data.getLong());
+    builder.setFieldUint644(data.getLong());
+    builder.setFieldInt325(data.getInt());
+    builder.setFieldFixed646(data.getLong());
+    builder.setFieldFixed327(data.getInt());
+    builder.setFieldBool8(data.getBool());
+    builder.setFieldString9(data.getString());
+    // We don't populate the message field. Instead we apply the branching factor to the
+    // repeated message field below.
+    builder.setFieldBytes11(data.getBytes());
+    builder.setFieldUint3212(data.getInt());
+    builder.setFieldEnum13Value(data.getEnum());
+    builder.setFieldSfixed3214(data.getInt());
+    builder.setFieldSfixed6415(data.getLong());
+    builder.setFieldSint3216(data.getInt());
+    builder.setFieldSint6417(data.getLong());
+
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      builder.addFieldDoubleList18(data.getDouble());
+      builder.addFieldFloatList19(data.getFloat());
+      builder.addFieldInt64List20(data.getLong());
+      builder.addFieldUint64List21(data.getLong());
+      builder.addFieldInt32List22(data.getInt());
+      builder.addFieldFixed64List23(data.getLong());
+      builder.addFieldFixed32List24(data.getInt());
+      builder.addFieldBoolList25(data.getBool());
+      builder.addFieldStringList26(data.getString());
+      // Repeated message field is controlled by the branching factor below.
+      builder.addFieldBytesList28(data.getBytes());
+      builder.addFieldUint32List29(data.getInt());
+      builder.addFieldEnumList30Value(data.getEnum());
+      builder.addFieldSfixed32List31(data.getInt());
+      builder.addFieldSfixed64List32(data.getLong());
+      builder.addFieldSint32List33(data.getInt());
+      builder.addFieldSint64List34(data.getLong());
+
+      builder.addFieldDoubleListPacked35(data.getDouble());
+      builder.addFieldFloatListPacked36(data.getFloat());
+      builder.addFieldInt64ListPacked37(data.getLong());
+      builder.addFieldUint64ListPacked38(data.getLong());
+      builder.addFieldInt32ListPacked39(data.getInt());
+      builder.addFieldFixed64ListPacked40(data.getLong());
+      builder.addFieldFixed32ListPacked41(data.getInt());
+      builder.addFieldBoolListPacked42(data.getBool());
+      builder.addFieldUint32ListPacked43(data.getInt());
+      builder.addFieldEnumListPacked44Value(data.getEnum());
+      builder.addFieldSfixed32ListPacked45(data.getInt());
+      builder.addFieldSfixed64ListPacked46(data.getLong());
+      builder.addFieldSint32ListPacked47(data.getInt());
+      builder.addFieldSint64ListPacked48(data.getLong());
+    }
+
+    // Handle the branching factor.
+    if (nextLevel != null) {
+      for (int i = 0; i < branchingFactor; ++i) {
+        builder.addFieldMessageList27(nextLevel.newMessage());
+      }
+    }
+
+    return builder.build();
+  }
+
+  private interface MapValueProvider<T> {
+    public T getValue();
+  }
+
+  private final MapValueProvider<Integer> integerProvider =
+      new MapValueProvider<Integer>() {
+        @Override
+        public Integer getValue() {
+          return data.getInt();
+        }
+      };
+  private final MapValueProvider<Long> longProvider =
+      new MapValueProvider<Long>() {
+        @Override
+        public Long getValue() {
+          return data.getLong();
+        }
+      };
+  private final MapValueProvider<String> stringProvider =
+      new MapValueProvider<String>() {
+        @Override
+        public String getValue() {
+          return data.getString();
+        }
+      };
+  private final MapValueProvider<ByteString> bytesProvider =
+      new MapValueProvider<ByteString>() {
+        @Override
+        public ByteString getValue() {
+          return data.getBytes();
+        }
+      };
+  private final MapValueProvider<Boolean> booleanProvider =
+      new MapValueProvider<Boolean>() {
+        @Override
+        public Boolean getValue() {
+          return data.getBool();
+        }
+      };
+  private final MapValueProvider<Float> floatProvider =
+      new MapValueProvider<Float>() {
+        @Override
+        public Float getValue() {
+          return data.getFloat();
+        }
+      };
+  private final MapValueProvider<Double> doubleProvider =
+      new MapValueProvider<Double>() {
+        @Override
+        public Double getValue() {
+          return data.getDouble();
+        }
+      };
+  private final MapValueProvider<Proto3MessageLite> messageProvider =
+      new MapValueProvider<Proto3MessageLite>() {
+        @Override
+        public Proto3MessageLite getValue() {
+          return newMessage();
+        }
+      };
+  private final MapValueProvider<Proto3MessageLite.TestEnum> enumProvider =
+      new MapValueProvider<Proto3MessageLite.TestEnum>() {
+        @Override
+        public Proto3MessageLite.TestEnum getValue() {
+          return Proto3MessageLite.TestEnum.forNumber(data.getEnum());
+        }
+      };
+
+  private <V> Map<Integer, V> populateIntegerMap(MapValueProvider<V> provider) {
+    Map<Integer, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getInt(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Long, V> populateLongMap(MapValueProvider<V> provider) {
+    Map<Long, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getLong(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<String, V> populateStringMap(MapValueProvider<V> provider) {
+    Map<String, V> map = new HashMap<>();
+    for (int i = 0; i < numRepeatedFields; ++i) {
+      map.put(data.getString(), provider.getValue());
+    }
+    return map;
+  }
+
+  private <V> Map<Boolean, V> populateBooleanMap(MapValueProvider<V> provider) {
+    Map<Boolean, V> map = new HashMap<>();
+    map.put(false, provider.getValue());
+    map.put(true, provider.getValue());
+    return map;
+  }
+
+  public Proto3MessageLiteWithMaps newMessageWithMaps() {
+    Proto3MessageLiteWithMaps.Builder builder = Proto3MessageLiteWithMaps.newBuilder();
+
+    builder.putAllFieldMapBoolBool1(populateBooleanMap(booleanProvider));
+    builder.putAllFieldMapBoolBytes2(populateBooleanMap(bytesProvider));
+    builder.putAllFieldMapBoolDouble3(populateBooleanMap(doubleProvider));
+    builder.putAllFieldMapBoolEnum4(populateBooleanMap(enumProvider));
+    builder.putAllFieldMapBoolFixed325(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolFixed646(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolFloat7(populateBooleanMap(floatProvider));
+    builder.putAllFieldMapBoolInt328(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolInt649(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolMessage10(populateBooleanMap(messageProvider));
+    builder.putAllFieldMapBoolSfixed3211(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSfixed6412(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolSint3213(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolSint6414(populateBooleanMap(longProvider));
+    builder.putAllFieldMapBoolString15(populateBooleanMap(stringProvider));
+    builder.putAllFieldMapBoolUint3216(populateBooleanMap(integerProvider));
+    builder.putAllFieldMapBoolUint6417(populateBooleanMap(longProvider));
+    builder.putAllFieldMapFixed32Bool18(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapFixed32Bytes19(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapFixed32Double20(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapFixed32Enum21(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapFixed32Fixed3222(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Fixed6423(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Float24(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapFixed32Int3225(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Int6426(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Message27(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapFixed32Sfixed3228(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sfixed6429(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32Sint3230(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Sint6431(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed32String32(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapFixed32Uint3233(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapFixed32Uint6434(populateIntegerMap(longProvider));
+    builder.putAllFieldMapFixed64Bool35(populateLongMap(booleanProvider));
+    builder.putAllFieldMapFixed64Bytes36(populateLongMap(bytesProvider));
+    builder.putAllFieldMapFixed64Double37(populateLongMap(doubleProvider));
+    builder.putAllFieldMapFixed64Enum38(populateLongMap(enumProvider));
+    builder.putAllFieldMapFixed64Fixed3239(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Fixed6440(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Float41(populateLongMap(floatProvider));
+    builder.putAllFieldMapFixed64Int3242(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Int6443(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Message44(populateLongMap(messageProvider));
+    builder.putAllFieldMapFixed64Sfixed3245(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sfixed6446(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64Sint3247(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Sint6448(populateLongMap(longProvider));
+    builder.putAllFieldMapFixed64String49(populateLongMap(stringProvider));
+    builder.putAllFieldMapFixed64Uint3250(populateLongMap(integerProvider));
+    builder.putAllFieldMapFixed64Uint6451(populateLongMap(longProvider));
+    builder.putAllFieldMapInt32Bool52(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapInt32Bytes53(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapInt32Double54(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapInt32Enum55(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapInt32Fixed3256(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Fixed6457(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Float58(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapInt32Int3259(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Int6460(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Message61(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapInt32Sfixed3262(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sfixed6463(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32Sint3264(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Sint6465(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt32String66(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapInt32Uint3267(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapInt32Uint6468(populateIntegerMap(longProvider));
+    builder.putAllFieldMapInt64Bool69(populateLongMap(booleanProvider));
+    builder.putAllFieldMapInt64Bytes70(populateLongMap(bytesProvider));
+    builder.putAllFieldMapInt64Double71(populateLongMap(doubleProvider));
+    builder.putAllFieldMapInt64Enum72(populateLongMap(enumProvider));
+    builder.putAllFieldMapInt64Fixed3273(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Fixed6474(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Float75(populateLongMap(floatProvider));
+    builder.putAllFieldMapInt64Int3276(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Int6477(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Message78(populateLongMap(messageProvider));
+    builder.putAllFieldMapInt64Sfixed3279(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sfixed6480(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64Sint3281(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Sint6482(populateLongMap(longProvider));
+    builder.putAllFieldMapInt64String83(populateLongMap(stringProvider));
+    builder.putAllFieldMapInt64Uint3284(populateLongMap(integerProvider));
+    builder.putAllFieldMapInt64Uint6485(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed32Bool86(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSfixed32Bytes87(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSfixed32Double88(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSfixed32Enum89(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSfixed32Fixed3290(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Fixed6491(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Float92(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSfixed32Int3293(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Int6494(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Message95(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSfixed32Sfixed3296(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sfixed6497(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32Sint3298(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Sint6499(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed32String100(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSfixed32Uint32101(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSfixed32Uint64102(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSfixed64Bool103(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSfixed64Bytes104(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSfixed64Double105(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSfixed64Enum106(populateLongMap(enumProvider));
+    builder.putAllFieldMapSfixed64Fixed32107(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Fixed64108(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Float109(populateLongMap(floatProvider));
+    builder.putAllFieldMapSfixed64Int32110(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Int64111(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Message112(populateLongMap(messageProvider));
+    builder.putAllFieldMapSfixed64Sfixed32113(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sfixed64114(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64Sint32115(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Sint64116(populateLongMap(longProvider));
+    builder.putAllFieldMapSfixed64String117(populateLongMap(stringProvider));
+    builder.putAllFieldMapSfixed64Uint32118(populateLongMap(integerProvider));
+    builder.putAllFieldMapSfixed64Uint64119(populateLongMap(longProvider));
+    builder.putAllFieldMapSint32Bool120(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapSint32Bytes121(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapSint32Double122(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapSint32Enum123(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapSint32Fixed32124(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Fixed64125(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Float126(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapSint32Int32127(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Int64128(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Message129(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapSint32Sfixed32130(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sfixed64131(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32Sint32132(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Sint64133(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint32String134(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapSint32Uint32135(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapSint32Uint64136(populateIntegerMap(longProvider));
+    builder.putAllFieldMapSint64Bool137(populateLongMap(booleanProvider));
+    builder.putAllFieldMapSint64Bytes138(populateLongMap(bytesProvider));
+    builder.putAllFieldMapSint64Double139(populateLongMap(doubleProvider));
+    builder.putAllFieldMapSint64Enum140(populateLongMap(enumProvider));
+    builder.putAllFieldMapSint64Fixed32141(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Fixed64142(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Float143(populateLongMap(floatProvider));
+    builder.putAllFieldMapSint64Int32144(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Int64145(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Message146(populateLongMap(messageProvider));
+    builder.putAllFieldMapSint64Sfixed32147(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sfixed64148(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64Sint32149(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Sint64150(populateLongMap(longProvider));
+    builder.putAllFieldMapSint64String151(populateLongMap(stringProvider));
+    builder.putAllFieldMapSint64Uint32152(populateLongMap(integerProvider));
+    builder.putAllFieldMapSint64Uint64153(populateLongMap(longProvider));
+    builder.putAllFieldMapStringBool154(populateStringMap(booleanProvider));
+    builder.putAllFieldMapStringBytes155(populateStringMap(bytesProvider));
+    builder.putAllFieldMapStringDouble156(populateStringMap(doubleProvider));
+    builder.putAllFieldMapStringEnum157(populateStringMap(enumProvider));
+    builder.putAllFieldMapStringFixed32158(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringFixed64159(populateStringMap(longProvider));
+    builder.putAllFieldMapStringFloat160(populateStringMap(floatProvider));
+    builder.putAllFieldMapStringInt32161(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringInt64162(populateStringMap(longProvider));
+    builder.putAllFieldMapStringMessage163(populateStringMap(messageProvider));
+    builder.putAllFieldMapStringSfixed32164(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSfixed64165(populateStringMap(longProvider));
+    builder.putAllFieldMapStringSint32166(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringSint64167(populateStringMap(longProvider));
+    builder.putAllFieldMapStringString168(populateStringMap(stringProvider));
+    builder.putAllFieldMapStringUint32169(populateStringMap(integerProvider));
+    builder.putAllFieldMapStringUint64170(populateStringMap(longProvider));
+    builder.putAllFieldMapUint32Bool171(populateIntegerMap(booleanProvider));
+    builder.putAllFieldMapUint32Bytes172(populateIntegerMap(bytesProvider));
+    builder.putAllFieldMapUint32Double173(populateIntegerMap(doubleProvider));
+    builder.putAllFieldMapUint32Enum174(populateIntegerMap(enumProvider));
+    builder.putAllFieldMapUint32Fixed32175(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Fixed64176(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Float177(populateIntegerMap(floatProvider));
+    builder.putAllFieldMapUint32Int32178(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Int64179(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Message180(populateIntegerMap(messageProvider));
+    builder.putAllFieldMapUint32Sfixed32181(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sfixed64182(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32Sint32183(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Sint64184(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint32String185(populateIntegerMap(stringProvider));
+    builder.putAllFieldMapUint32Uint32186(populateIntegerMap(integerProvider));
+    builder.putAllFieldMapUint32Uint64187(populateIntegerMap(longProvider));
+    builder.putAllFieldMapUint64Bool188(populateLongMap(booleanProvider));
+    builder.putAllFieldMapUint64Bytes189(populateLongMap(bytesProvider));
+    builder.putAllFieldMapUint64Double190(populateLongMap(doubleProvider));
+    builder.putAllFieldMapUint64Enum191(populateLongMap(enumProvider));
+    builder.putAllFieldMapUint64Fixed32192(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Fixed64193(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Float194(populateLongMap(floatProvider));
+    builder.putAllFieldMapUint64Int32195(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Int64196(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Message197(populateLongMap(messageProvider));
+    builder.putAllFieldMapUint64Sfixed32198(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sfixed64199(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64Sint32200(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Sint64201(populateLongMap(longProvider));
+    builder.putAllFieldMapUint64String202(populateLongMap(stringProvider));
+    builder.putAllFieldMapUint64Uint32203(populateLongMap(integerProvider));
+    builder.putAllFieldMapUint64Uint64204(populateLongMap(longProvider));
+
+    return builder.build();
+  }
+}

+ 816 - 0
java/core/src/test/java/com/google/protobuf/Proto3MessageLiteInfoFactory.java

@@ -0,0 +1,816 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import static com.google.protobuf.FieldInfo.forField;
+import static com.google.protobuf.FieldInfo.forMapField;
+import static com.google.protobuf.FieldInfo.forOneofMemberField;
+import static com.google.protobuf.FieldInfo.forRepeatedMessageField;
+
+import com.google.protobuf.testing.Proto3TestingLite.Proto3EmptyLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLiteWithMaps;
+import java.lang.reflect.Field;
+
+/** A factory that generates a hard-coded info for {@link Proto3MessageLite}. */
+public final class Proto3MessageLiteInfoFactory implements MessageInfoFactory {
+  private static final Proto3MessageLiteInfoFactory instanceForRawMessageInfo =
+      new Proto3MessageLiteInfoFactory(true);
+  private static final Proto3MessageLiteInfoFactory instanceForStructuralMessageInfo =
+      new Proto3MessageLiteInfoFactory(false);
+
+  public static Proto3MessageLiteInfoFactory getInstanceForRawMessageInfo() {
+    return instanceForRawMessageInfo;
+  }
+
+  public static Proto3MessageLiteInfoFactory getInstanceForStructuralMessageInfo() {
+    return instanceForStructuralMessageInfo;
+  }
+
+  private final boolean produceRawMessageInfo;
+
+  private Proto3MessageLiteInfoFactory(boolean produceRawMessageInfo) {
+    this.produceRawMessageInfo = produceRawMessageInfo;
+  }
+
+  @Override
+  public boolean isSupported(Class<?> clazz) {
+    return true;
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> clazz) {
+    return produceRawMessageInfo ? rawMessageInfoFor(clazz) : structuralMessageInfoFor(clazz);
+  }
+
+  private MessageInfo rawMessageInfoFor(Class<?> clazz) {
+    if (Proto3MessageLite.class.isAssignableFrom(clazz)) {
+      return newRawMessageInfoForProto3MessageLite();
+    } else {
+      throw new IllegalArgumentException("Unsupported class: " + clazz.getName());
+    }
+  }
+
+  private MessageInfo newRawMessageInfoForProto3MessageLite() {
+    java.lang.Object[] objects =
+        new java.lang.Object[] {
+          "testOneof_",
+          "testOneofCase_",
+          "fieldDouble1_",
+          "fieldFloat2_",
+          "fieldInt643_",
+          "fieldUint644_",
+          "fieldInt325_",
+          "fieldFixed646_",
+          "fieldFixed327_",
+          "fieldBool8_",
+          "fieldString9_",
+          "fieldMessage10_",
+          "fieldBytes11_",
+          "fieldUint3212_",
+          "fieldEnum13_",
+          "fieldSfixed3214_",
+          "fieldSfixed6415_",
+          "fieldSint3216_",
+          "fieldSint6417_",
+          "fieldDoubleList18_",
+          "fieldFloatList19_",
+          "fieldInt64List20_",
+          "fieldUint64List21_",
+          "fieldInt32List22_",
+          "fieldFixed64List23_",
+          "fieldFixed32List24_",
+          "fieldBoolList25_",
+          "fieldStringList26_",
+          "fieldMessageList27_",
+          Proto3MessageLite.class,
+          "fieldBytesList28_",
+          "fieldUint32List29_",
+          "fieldEnumList30_",
+          "fieldSfixed32List31_",
+          "fieldSfixed64List32_",
+          "fieldSint32List33_",
+          "fieldSint64List34_",
+          "fieldDoubleListPacked35_",
+          "fieldFloatListPacked36_",
+          "fieldInt64ListPacked37_",
+          "fieldUint64ListPacked38_",
+          "fieldInt32ListPacked39_",
+          "fieldFixed64ListPacked40_",
+          "fieldFixed32ListPacked41_",
+          "fieldBoolListPacked42_",
+          "fieldUint32ListPacked43_",
+          "fieldEnumListPacked44_",
+          "fieldSfixed32ListPacked45_",
+          "fieldSfixed64ListPacked46_",
+          "fieldSint32ListPacked47_",
+          "fieldSint64ListPacked48_",
+          Proto3MessageLite.class,
+        };
+    // To update this after a proto change, run protoc on proto3_message_lite.proto and copy over
+    // the content of the generated buildMessageInfo() method here.
+    java.lang.String info =
+        "\u0000@\u0001\u0000\u0001D@\u0000\u001f\u0000\u0001\u0000\u0002\u0001\u0003\u0002"
+            + "\u0004\u0003\u0005\u0004\u0006\u0005\u0007\u0006\b\u0007\t\u0208\n\t\u000b\n\f\u000b"
+            + "\r\f\u000e\r\u000f\u000e\u0010\u000f\u0011\u0010\u0012\u0012\u0013\u0013\u0014\u0014"
+            + "\u0015\u0015\u0016\u0016\u0017\u0017\u0018\u0018\u0019\u0019\u001a\u021a\u001b\u001b"
+            + "\u001c\u001c\u001d\u001d\u001e\u001e\u001f\u001f  !!\"\"##$$%%&&\'\'(())**++,,--"
+            + "..//0053\u000064\u000075\u000086\u000097\u0000:8\u0000;9\u0000<:\u0000=\u023b\u0000"
+            + "><\u0000?=\u0000@>\u0000A@\u0000BA\u0000CB\u0000DC\u0000";
+    return new RawMessageInfo(Proto3MessageLite.getDefaultInstance(), info, objects);
+  }
+
+  private MessageInfo structuralMessageInfoFor(Class<?> clazz) {
+    if (Proto3MessageLite.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3MessageLite();
+    } else if (Proto3EmptyLite.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3EmptyLite();
+    } else if (Proto3MessageLiteWithMaps.class.isAssignableFrom(clazz)) {
+      return newMessageInfoForProto3MessageLiteWithMaps();
+    } else {
+      throw new IllegalArgumentException("Unsupported class: " + clazz.getName());
+    }
+  }
+
+  /**
+   * Creates a new hard-coded info for {@link Proto3MessageLite}. Each time this is called, we
+   * manually go through the entire process of what a message would do if it self-registered its own
+   * info, including looking up each field by name. This is done for benchmarking purposes, so that
+   * we get a more accurate representation of the time it takes to perform this process.
+   */
+  private static StructuralMessageInfo newMessageInfoForProto3MessageLite() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(48);
+    lookupFieldsByName(builder);
+    return builder.build();
+  }
+
+  private static void lookupFieldsByName(StructuralMessageInfo.Builder builder) {
+    builder.withDefaultInstance(Proto3MessageLite.getDefaultInstance());
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldDouble1_"), 1, FieldType.DOUBLE, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldFloat2_"), 2, FieldType.FLOAT, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldInt643_"), 3, FieldType.INT64, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldUint644_"), 4, FieldType.UINT64, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldInt325_"), 5, FieldType.INT32, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldFixed646_"), 6, FieldType.FIXED64, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldFixed327_"), 7, FieldType.FIXED32, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldBool8_"), 8, FieldType.BOOL, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldString9_"), 9, FieldType.STRING, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldMessage10_"), 10, FieldType.MESSAGE, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldBytes11_"), 11, FieldType.BYTES, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldUint3212_"), 12, FieldType.UINT32, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldEnum13_"), 13, FieldType.ENUM, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldSfixed3214_"), 14, FieldType.SFIXED32, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldSfixed6415_"), 15, FieldType.SFIXED64, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldSint3216_"), 16, FieldType.SINT32, true));
+    builder.withField(
+        forField(field(Proto3MessageLite.class, "fieldSint6417_"), 17, FieldType.SINT64, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldDoubleList18_"), 18, FieldType.DOUBLE_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFloatList19_"), 19, FieldType.FLOAT_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldInt64List20_"), 20, FieldType.INT64_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldUint64List21_"), 21, FieldType.UINT64_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldInt32List22_"), 22, FieldType.INT32_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFixed64List23_"),
+            23,
+            FieldType.FIXED64_LIST,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFixed32List24_"),
+            24,
+            FieldType.FIXED32_LIST,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldBoolList25_"), 25, FieldType.BOOL_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldStringList26_"), 26, FieldType.STRING_LIST, true));
+    builder.withField(
+        forRepeatedMessageField(
+            field(Proto3MessageLite.class, "fieldMessageList27_"),
+            27,
+            FieldType.MESSAGE_LIST,
+            Proto3MessageLite.class));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldBytesList28_"), 28, FieldType.BYTES_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldUint32List29_"), 29, FieldType.UINT32_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldEnumList30_"), 30, FieldType.ENUM_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSfixed32List31_"),
+            31,
+            FieldType.SFIXED32_LIST,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSfixed64List32_"),
+            32,
+            FieldType.SFIXED64_LIST,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSint32List33_"), 33, FieldType.SINT32_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSint64List34_"), 34, FieldType.SINT64_LIST, true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldDoubleListPacked35_"),
+            35,
+            FieldType.DOUBLE_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFloatListPacked36_"),
+            36,
+            FieldType.FLOAT_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldInt64ListPacked37_"),
+            37,
+            FieldType.INT64_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldUint64ListPacked38_"),
+            38,
+            FieldType.UINT64_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldInt32ListPacked39_"),
+            39,
+            FieldType.INT32_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFixed64ListPacked40_"),
+            40,
+            FieldType.FIXED64_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldFixed32ListPacked41_"),
+            41,
+            FieldType.FIXED32_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldBoolListPacked42_"),
+            42,
+            FieldType.BOOL_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldUint32ListPacked43_"),
+            43,
+            FieldType.UINT32_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldEnumListPacked44_"),
+            44,
+            FieldType.ENUM_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSfixed32ListPacked45_"),
+            45,
+            FieldType.SFIXED32_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSfixed64ListPacked46_"),
+            46,
+            FieldType.SFIXED64_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSint32ListPacked47_"),
+            47,
+            FieldType.SINT32_LIST_PACKED,
+            true));
+    builder.withField(
+        forField(
+            field(Proto3MessageLite.class, "fieldSint64ListPacked48_"),
+            48,
+            FieldType.SINT64_LIST_PACKED,
+            true));
+
+    OneofInfo oneof =
+        new OneofInfo(
+            0,
+            field(Proto3MessageLite.class, "testOneofCase_"),
+            field(Proto3MessageLite.class, "testOneof_"));
+    builder.withField(forOneofMemberField(53, FieldType.DOUBLE, oneof, Double.class, true, null));
+    builder.withField(forOneofMemberField(54, FieldType.FLOAT, oneof, Float.class, true, null));
+    builder.withField(forOneofMemberField(55, FieldType.INT64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(56, FieldType.UINT64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(57, FieldType.INT32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(58, FieldType.FIXED64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(59, FieldType.FIXED32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(60, FieldType.BOOL, oneof, Boolean.class, true, null));
+    builder.withField(forOneofMemberField(61, FieldType.STRING, oneof, String.class, true, null));
+    builder.withField(
+        forOneofMemberField(62, FieldType.MESSAGE, oneof, Proto3MessageLite.class, true, null));
+    builder.withField(
+        forOneofMemberField(63, FieldType.BYTES, oneof, ByteString.class, true, null));
+    builder.withField(forOneofMemberField(64, FieldType.UINT32, oneof, Integer.class, true, null));
+    builder.withField(
+        forOneofMemberField(65, FieldType.SFIXED32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(66, FieldType.SFIXED64, oneof, Long.class, true, null));
+    builder.withField(forOneofMemberField(67, FieldType.SINT32, oneof, Integer.class, true, null));
+    builder.withField(forOneofMemberField(68, FieldType.SINT64, oneof, Long.class, true, null));
+  }
+
+  private StructuralMessageInfo newMessageInfoForProto3EmptyLite() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder(1);
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    return builder.build();
+  }
+
+  private static StructuralMessageInfo newMessageInfoForProto3MessageLiteWithMaps() {
+    StructuralMessageInfo.Builder builder = StructuralMessageInfo.newBuilder();
+    builder.withSyntax(ProtoSyntax.PROTO2);
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_bool_1", 1));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_bytes_2", 2));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_double_3", 3));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_enum_4", 4));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_fixed32_5", 5));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_fixed64_6", 6));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_float_7", 7));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_int32_8", 8));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_int64_9", 9));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_message_10", 10));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_sfixed32_11", 11));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_sfixed64_12", 12));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_sint32_13", 13));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_sint64_14", 14));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_string_15", 15));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_uint32_16", 16));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_bool_uint64_17", 17));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_bool_18", 18));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_bytes_19", 19));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_double_20", 20));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_enum_21", 21));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_fixed32_22", 22));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_fixed64_23", 23));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_float_24", 24));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_int32_25", 25));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_int64_26", 26));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_message_27", 27));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_sfixed32_28", 28));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_sfixed64_29", 29));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_sint32_30", 30));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_sint64_31", 31));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_string_32", 32));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_uint32_33", 33));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed32_uint64_34", 34));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_bool_35", 35));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_bytes_36", 36));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_double_37", 37));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_enum_38", 38));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_fixed32_39", 39));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_fixed64_40", 40));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_float_41", 41));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_int32_42", 42));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_int64_43", 43));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_message_44", 44));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_sfixed32_45", 45));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_sfixed64_46", 46));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_sint32_47", 47));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_sint64_48", 48));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_string_49", 49));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_uint32_50", 50));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_fixed64_uint64_51", 51));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_bool_52", 52));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_bytes_53", 53));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_double_54", 54));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_enum_55", 55));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_fixed32_56", 56));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_fixed64_57", 57));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_float_58", 58));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_int32_59", 59));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_int64_60", 60));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_message_61", 61));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_sfixed32_62", 62));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_sfixed64_63", 63));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_sint32_64", 64));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_sint64_65", 65));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_string_66", 66));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_uint32_67", 67));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int32_uint64_68", 68));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_bool_69", 69));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_bytes_70", 70));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_double_71", 71));
+    builder.withField(mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_enum_72", 72));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_fixed32_73", 73));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_fixed64_74", 74));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_float_75", 75));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_int32_76", 76));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_int64_77", 77));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_message_78", 78));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_sfixed32_79", 79));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_sfixed64_80", 80));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_sint32_81", 81));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_sint64_82", 82));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_string_83", 83));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_uint32_84", 84));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_int64_uint64_85", 85));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_bool_86", 86));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_bytes_87", 87));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_double_88", 88));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_enum_89", 89));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_fixed32_90", 90));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_fixed64_91", 91));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_float_92", 92));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_int32_93", 93));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_int64_94", 94));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_message_95", 95));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_sfixed32_96", 96));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_sfixed64_97", 97));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_sint32_98", 98));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_sint64_99", 99));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_string_100", 100));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_uint32_101", 101));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed32_uint64_102", 102));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_bool_103", 103));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_bytes_104", 104));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_double_105", 105));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_enum_106", 106));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_fixed32_107", 107));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_fixed64_108", 108));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_float_109", 109));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_int32_110", 110));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_int64_111", 111));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_message_112", 112));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_sfixed32_113", 113));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_sfixed64_114", 114));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_sint32_115", 115));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_sint64_116", 116));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_string_117", 117));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_uint32_118", 118));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sfixed64_uint64_119", 119));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_bool_120", 120));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_bytes_121", 121));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_double_122", 122));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_enum_123", 123));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_fixed32_124", 124));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_fixed64_125", 125));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_float_126", 126));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_int32_127", 127));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_int64_128", 128));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_message_129", 129));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_sfixed32_130", 130));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_sfixed64_131", 131));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_sint32_132", 132));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_sint64_133", 133));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_string_134", 134));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_uint32_135", 135));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint32_uint64_136", 136));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_bool_137", 137));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_bytes_138", 138));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_double_139", 139));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_enum_140", 140));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_fixed32_141", 141));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_fixed64_142", 142));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_float_143", 143));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_int32_144", 144));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_int64_145", 145));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_message_146", 146));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_sfixed32_147", 147));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_sfixed64_148", 148));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_sint32_149", 149));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_sint64_150", 150));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_string_151", 151));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_uint32_152", 152));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_sint64_uint64_153", 153));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_bool_154", 154));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_bytes_155", 155));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_double_156", 156));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_enum_157", 157));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_fixed32_158", 158));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_fixed64_159", 159));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_float_160", 160));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_int32_161", 161));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_int64_162", 162));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_message_163", 163));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_sfixed32_164", 164));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_sfixed64_165", 165));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_sint32_166", 166));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_sint64_167", 167));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_string_168", 168));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_uint32_169", 169));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_string_uint64_170", 170));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_bool_171", 171));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_bytes_172", 172));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_double_173", 173));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_enum_174", 174));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_fixed32_175", 175));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_fixed64_176", 176));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_float_177", 177));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_int32_178", 178));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_int64_179", 179));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_message_180", 180));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_sfixed32_181", 181));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_sfixed64_182", 182));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_sint32_183", 183));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_sint64_184", 184));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_string_185", 185));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_uint32_186", 186));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint32_uint64_187", 187));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_bool_188", 188));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_bytes_189", 189));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_double_190", 190));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_enum_191", 191));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_fixed32_192", 192));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_fixed64_193", 193));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_float_194", 194));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_int32_195", 195));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_int64_196", 196));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_message_197", 197));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_sfixed32_198", 198));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_sfixed64_199", 199));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_sint32_200", 200));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_sint64_201", 201));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_string_202", 202));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_uint32_203", 203));
+    builder.withField(
+        mapFieldInfo(Proto3MessageLiteWithMaps.class, "field_map_uint64_uint64_204", 204));
+
+    return builder.build();
+  }
+
+  private static Field field(Class<?> clazz, String name) {
+    try {
+      return clazz.getDeclaredField(name);
+    } catch (NoSuchFieldException | SecurityException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  private static FieldInfo mapFieldInfo(Class<?> clazz, String fieldName, int fieldNumber) {
+    try {
+      return forMapField(
+          field(clazz, SchemaUtil.toCamelCase(fieldName, false) + "_"),
+          fieldNumber,
+          SchemaUtil.getMapDefaultEntry(clazz, fieldName),
+          null);
+    } catch (Throwable t) {
+      throw new RuntimeException(t);
+    }
+  }
+}

+ 48 - 0
java/core/src/test/java/com/google/protobuf/Proto3SchemaTest.java

@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class Proto3SchemaTest extends AbstractProto3SchemaTest {
+  @Override
+  protected void registerSchemas() {
+    TestSchemas.registerGenericProto3Schemas();
+  }
+
+  @Override
+  protected Schema<Proto3Message> schema() {
+    return TestSchemas.genericProto3Schema;
+  }
+}

+ 94 - 0
java/core/src/test/java/com/google/protobuf/TestSchemas.java

@@ -0,0 +1,94 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2Testing;
+import com.google.protobuf.testing.Proto2Testing.Proto2Empty;
+import com.google.protobuf.testing.Proto2Testing.Proto2Message;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithExtensions;
+import com.google.protobuf.testing.Proto2Testing.Proto2MessageWithMaps;
+import com.google.protobuf.testing.Proto3Testing.Proto3Empty;
+import com.google.protobuf.testing.Proto3Testing.Proto3Message;
+import com.google.protobuf.testing.Proto3Testing.Proto3MessageWithMaps;
+
+/** Schemas to support testing. */
+public class TestSchemas {
+  public static final Schema<Proto2Message> genericProto2Schema =
+      new ManifestSchemaFactory().createSchema(Proto2Message.class);
+  public static final Schema<Proto3Message> genericProto3Schema =
+      new ManifestSchemaFactory().createSchema(Proto3Message.class);
+
+  public static void registerGenericProto2Schemas() {
+    registerProto2Schemas();
+  }
+
+  public static void registerGenericProto3Schemas() {
+    registerProto3Schemas();
+  }
+
+  private static void registerProto2Schemas() {
+    Protobuf protobuf = Protobuf.getInstance();
+    ManifestSchemaFactory factory = new ManifestSchemaFactory();
+    protobuf.registerSchemaOverride(Proto2Message.class, factory.createSchema(Proto2Message.class));
+    protobuf.registerSchemaOverride(
+        Proto2Message.FieldGroup49.class, factory.createSchema(Proto2Message.FieldGroup49.class));
+    protobuf.registerSchemaOverride(
+        Proto2Message.FieldGroupList51.class,
+        factory.createSchema(Proto2Message.FieldGroupList51.class));
+    protobuf.registerSchemaOverride(
+        Proto2Message.FieldGroup69.class, factory.createSchema(Proto2Message.FieldGroup69.class));
+    protobuf.registerSchemaOverride(
+        Proto2Message.RequiredNestedMessage.class,
+        factory.createSchema(Proto2Message.RequiredNestedMessage.class));
+    protobuf.registerSchemaOverride(
+        Proto2Message.FieldRequiredGroup88.class,
+        factory.createSchema(Proto2Message.FieldRequiredGroup88.class));
+    protobuf.registerSchemaOverride(Proto2Empty.class, factory.createSchema(Proto2Empty.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageWithExtensions.class, factory.createSchema(Proto2MessageWithExtensions.class));
+    protobuf.registerSchemaOverride(
+        Proto2Testing.FieldGroup49.class, factory.createSchema(Proto2Testing.FieldGroup49.class));
+    protobuf.registerSchemaOverride(
+        Proto2Testing.FieldGroupList51.class,
+        factory.createSchema(Proto2Testing.FieldGroupList51.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageWithMaps.class, factory.createSchema(Proto2MessageWithMaps.class));
+  }
+
+  private static void registerProto3Schemas() {
+    Protobuf protobuf = Protobuf.getInstance();
+    ManifestSchemaFactory factory = new ManifestSchemaFactory();
+    protobuf.registerSchemaOverride(Proto3Message.class, factory.createSchema(Proto3Message.class));
+    protobuf.registerSchemaOverride(Proto3Empty.class, factory.createSchema(Proto3Empty.class));
+    protobuf.registerSchemaOverride(
+        Proto3MessageWithMaps.class, factory.createSchema(Proto3MessageWithMaps.class));
+  }
+}

+ 103 - 0
java/core/src/test/java/com/google/protobuf/TestSchemasLite.java

@@ -0,0 +1,103 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import com.google.protobuf.testing.Proto2TestingLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2EmptyLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLite;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLiteWithExtensions;
+import com.google.protobuf.testing.Proto2TestingLite.Proto2MessageLiteWithMaps;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3EmptyLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLite;
+import com.google.protobuf.testing.Proto3TestingLite.Proto3MessageLiteWithMaps;
+
+/** Schemas to support testing. */
+public final class TestSchemasLite {
+
+  public static final Schema<Proto2MessageLite> genericProto2LiteSchema =
+      new ManifestSchemaFactory().createSchema(Proto2MessageLite.class);
+  public static final Schema<Proto3MessageLite> genericProto3LiteSchema =
+      new ManifestSchemaFactory().createSchema(Proto3MessageLite.class);
+
+  public static void registerGenericProto2LiteSchemas() {
+    registerProto2LiteSchemas();
+  }
+
+  public static void registerGenericProto3LiteSchemas() {
+    registerProto3LiteSchemas();
+  }
+
+  private static void registerProto2LiteSchemas() {
+    Protobuf protobuf = Protobuf.getInstance();
+    ManifestSchemaFactory factory = new ManifestSchemaFactory();
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.class, factory.createSchema(Proto2MessageLite.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.FieldGroup49.class,
+        factory.createSchema(Proto2MessageLite.FieldGroup49.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.FieldGroupList51.class,
+        factory.createSchema(Proto2MessageLite.FieldGroupList51.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.FieldGroup69.class,
+        factory.createSchema(Proto2MessageLite.FieldGroup69.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.RequiredNestedMessage.class,
+        factory.createSchema(Proto2MessageLite.RequiredNestedMessage.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLite.FieldRequiredGroup88.class,
+        factory.createSchema(Proto2MessageLite.FieldRequiredGroup88.class));
+    protobuf.registerSchemaOverride(
+        Proto2EmptyLite.class, factory.createSchema(Proto2EmptyLite.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLiteWithExtensions.class,
+        factory.createSchema(Proto2MessageLiteWithExtensions.class));
+    protobuf.registerSchemaOverride(
+        Proto2TestingLite.FieldGroup49.class,
+        factory.createSchema(Proto2TestingLite.FieldGroup49.class));
+    protobuf.registerSchemaOverride(
+        Proto2TestingLite.FieldGroupList51.class,
+        factory.createSchema(Proto2TestingLite.FieldGroupList51.class));
+    protobuf.registerSchemaOverride(
+        Proto2MessageLiteWithMaps.class, factory.createSchema(Proto2MessageLiteWithMaps.class));
+  }
+
+  private static void registerProto3LiteSchemas() {
+    Protobuf protobuf = Protobuf.getInstance();
+    ManifestSchemaFactory factory = new ManifestSchemaFactory();
+    protobuf.registerSchemaOverride(
+        Proto3MessageLite.class, factory.createSchema(Proto3MessageLite.class));
+    protobuf.registerSchemaOverride(
+        Proto3EmptyLite.class, factory.createSchema(Proto3EmptyLite.class));
+    protobuf.registerSchemaOverride(
+        Proto3MessageLiteWithMaps.class, factory.createSchema(Proto3MessageLiteWithMaps.class));
+  }
+}

+ 21 - 0
java/core/src/test/java/com/google/protobuf/TextFormatTest.java

@@ -132,6 +132,9 @@ public class TextFormatTest extends TestCase {
           + "  i: 456\n"
           + "  i: 456\n"
           + "}\n";
           + "}\n";
 
 
+  private final TextFormat.Parser parserAllowingUnknownFields =
+      TextFormat.Parser.newBuilder().setAllowUnknownFields(true).build();
+
   private final TextFormat.Parser parserAllowingUnknownExtensions =
   private final TextFormat.Parser parserAllowingUnknownExtensions =
       TextFormat.Parser.newBuilder().setAllowUnknownExtensions(true).build();
       TextFormat.Parser.newBuilder().setAllowUnknownExtensions(true).build();
 
 
@@ -502,6 +505,7 @@ public class TextFormatTest extends TestCase {
     assertEquals(2, builder.getOptionalInt64());
     assertEquals(2, builder.getOptionalInt64());
   }
   }
 
 
+
   private void assertParseError(String error, String text) {
   private void assertParseError(String error, String text) {
     // Test merge().
     // Test merge().
     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
@@ -521,6 +525,22 @@ public class TextFormatTest extends TestCase {
     }
     }
   }
   }
 
 
+  private void assertParseErrorWithUnknownFields(String error, String text) {
+    TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+    try {
+      parserAllowingUnknownFields.merge(text, TestUtil.getFullExtensionRegistry(), builder);
+      fail("Expected parse exception.");
+    } catch (TextFormat.ParseException e) {
+      assertEquals(error, e.getMessage());
+    }
+  }
+
+  private TestAllTypes assertParseSuccessWithUnknownFields(String text)
+      throws TextFormat.ParseException {
+    TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+    parserAllowingUnknownFields.merge(text, TestUtil.getFullExtensionRegistry(), builder);
+    return builder.build();
+  }
 
 
   private void assertParseErrorWithUnknownExtensions(String error, String text) {
   private void assertParseErrorWithUnknownExtensions(String error, String text) {
     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
     TestAllTypes.Builder builder = TestAllTypes.newBuilder();
@@ -1223,6 +1243,7 @@ public class TextFormatTest extends TestCase {
     // Set to allow unknown fields
     // Set to allow unknown fields
     TextFormat.Parser parser =
     TextFormat.Parser parser =
         TextFormat.Parser.newBuilder()
         TextFormat.Parser.newBuilder()
+            .setAllowUnknownFields(true)
             .setParseInfoTreeBuilder(treeBuilder)
             .setParseInfoTreeBuilder(treeBuilder)
             .build();
             .build();
 
 

+ 0 - 601
java/core/src/test/java/com/google/protobuf/UnknownFieldSetLiteTest.java

@@ -1,601 +0,0 @@
-// Protocol Buffers - Google's data interchange format
-// Copyright 2008 Google Inc.  All rights reserved.
-// https://developers.google.com/protocol-buffers/
-//
-// Redistribution and use in source and binary forms, with or without
-// modification, are permitted provided that the following conditions are
-// met:
-//
-//     * Redistributions of source code must retain the above copyright
-// notice, this list of conditions and the following disclaimer.
-//     * Redistributions in binary form must reproduce the above
-// copyright notice, this list of conditions and the following disclaimer
-// in the documentation and/or other materials provided with the
-// distribution.
-//     * Neither the name of Google Inc. nor the names of its
-// contributors may be used to endorse or promote products derived from
-// this software without specific prior written permission.
-//
-// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-package com.google.protobuf;
-
-import static junit.framework.TestCase.assertEquals;
-
-import com.google.protobuf.UnittestLite.TestAllExtensionsLite;
-import com.google.protobuf.UnittestLite.TestAllTypesLite;
-import protobuf_unittest.UnittestProto;
-import protobuf_unittest.UnittestProto.ForeignEnum;
-import protobuf_unittest.UnittestProto.TestAllExtensions;
-import protobuf_unittest.UnittestProto.TestAllTypes;
-import protobuf_unittest.UnittestProto.TestEmptyMessage;
-import protobuf_unittest.UnittestProto.TestPackedExtensions;
-import protobuf_unittest.UnittestProto.TestPackedTypes;
-import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash;
-import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Bar;
-import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-import junit.framework.TestCase;
-
-/**
- * Tests for {@link UnknownFieldSetLite}.
- *
- * @author dweis@google.com (Daniel Weis)
- */
-public class UnknownFieldSetLiteTest extends TestCase {
-  @Override
-  public void setUp() throws Exception {
-    allFields = TestUtil.getAllSet();
-    allFieldsData = allFields.toByteString();
-    emptyMessage = TestEmptyMessage.parseFrom(allFieldsData);
-    unknownFields = emptyMessage.getUnknownFields();
-  }
-
-  TestAllTypes allFields;
-  ByteString allFieldsData;
-
-  // Constructs a protocol buffer which contains fields with all the same
-  // numbers as allFieldsData except that each field is some other wire
-  // type.
-  private ByteString getBizarroData() throws Exception {
-    UnknownFieldSet.Builder bizarroFields = UnknownFieldSet.newBuilder();
-
-    UnknownFieldSet.Field varintField = UnknownFieldSet.Field.newBuilder().addVarint(1).build();
-    UnknownFieldSet.Field fixed32Field = UnknownFieldSet.Field.newBuilder().addFixed32(1).build();
-
-    for (Map.Entry<Integer, UnknownFieldSet.Field> entry : unknownFields.asMap().entrySet()) {
-      if (entry.getValue().getVarintList().isEmpty()) {
-        // Original field is not a varint, so use a varint.
-        bizarroFields.addField(entry.getKey(), varintField);
-      } else {
-        // Original field *is* a varint, so use something else.
-        bizarroFields.addField(entry.getKey(), fixed32Field);
-      }
-    }
-
-    return bizarroFields.build().toByteString();
-  }
-
-  // An empty message that has been parsed from allFieldsData.  So, it has
-  // unknown fields of every type.
-  TestEmptyMessage emptyMessage;
-  UnknownFieldSet unknownFields;
-
-  public void testDefaultInstance() {
-    UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance();
-
-    assertEquals(0, unknownFields.getSerializedSize());
-    assertEquals(ByteString.EMPTY, toByteString(unknownFields));
-  }
-
-  public void testEmptyInstance() {
-    UnknownFieldSetLite instance = UnknownFieldSetLite.newInstance();
-
-    assertEquals(0, instance.getSerializedSize());
-    assertEquals(ByteString.EMPTY, toByteString(instance));
-    assertEquals(UnknownFieldSetLite.getDefaultInstance(), instance);
-  }
-
-  public void testMergeFieldFrom() throws IOException {
-    Foo foo = Foo.newBuilder().setValue(2).build();
-
-    CodedInputStream input = CodedInputStream.newInstance(foo.toByteArray());
-
-    UnknownFieldSetLite instance = UnknownFieldSetLite.newInstance();
-    instance.mergeFieldFrom(input.readTag(), input);
-
-    assertEquals(foo.toByteString(), toByteString(instance));
-  }
-
-  public void testSerializedSize() throws IOException {
-    Foo foo = Foo.newBuilder().setValue(2).build();
-
-    CodedInputStream input = CodedInputStream.newInstance(foo.toByteArray());
-
-    UnknownFieldSetLite instance = UnknownFieldSetLite.newInstance();
-    instance.mergeFieldFrom(input.readTag(), input);
-
-    assertEquals(foo.toByteString().size(), instance.getSerializedSize());
-  }
-
-  public void testHashCodeAfterDeserialization() throws IOException {
-    Foo foo = Foo.newBuilder().setValue(2).build();
-
-    Foo fooDeserialized = Foo.parseFrom(foo.toByteArray());
-
-    assertEquals(fooDeserialized, foo);
-    assertEquals(foo.hashCode(), fooDeserialized.hashCode());
-  }
-
-  public void testNewInstanceHashCode() {
-    UnknownFieldSetLite emptyFieldSet = UnknownFieldSetLite.getDefaultInstance();
-    UnknownFieldSetLite paddedFieldSet = UnknownFieldSetLite.newInstance();
-
-    assertEquals(emptyFieldSet, paddedFieldSet);
-    assertEquals(emptyFieldSet.hashCode(), paddedFieldSet.hashCode());
-  }
-
-  public void testMergeVarintField() throws IOException {
-    UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance();
-    unknownFields.mergeVarintField(10, 2);
-
-    CodedInputStream input =
-        CodedInputStream.newInstance(toByteString(unknownFields).toByteArray());
-
-    int tag = input.readTag();
-    assertEquals(10, WireFormat.getTagFieldNumber(tag));
-    assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag));
-    assertEquals(2, input.readUInt64());
-    assertTrue(input.isAtEnd());
-  }
-
-  public void testMergeVarintField_negative() throws IOException {
-    UnknownFieldSetLite builder = UnknownFieldSetLite.newInstance();
-    builder.mergeVarintField(10, -6);
-
-    CodedInputStream input = CodedInputStream.newInstance(toByteString(builder).toByteArray());
-
-    int tag = input.readTag();
-    assertEquals(10, WireFormat.getTagFieldNumber(tag));
-    assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag));
-    assertEquals(-6, input.readUInt64());
-    assertTrue(input.isAtEnd());
-  }
-
-  public void testEqualsAndHashCode() {
-    UnknownFieldSetLite unknownFields1 = UnknownFieldSetLite.newInstance();
-    unknownFields1.mergeVarintField(10, 2);
-
-    UnknownFieldSetLite unknownFields2 = UnknownFieldSetLite.newInstance();
-    unknownFields2.mergeVarintField(10, 2);
-
-    assertEquals(unknownFields1, unknownFields2);
-    assertEquals(unknownFields1.hashCode(), unknownFields2.hashCode());
-    assertFalse(unknownFields1.equals(UnknownFieldSetLite.getDefaultInstance()));
-    assertFalse(unknownFields1.hashCode() == UnknownFieldSetLite.getDefaultInstance().hashCode());
-  }
-
-  public void testMutableCopyOf() throws IOException {
-    UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance();
-    unknownFields.mergeVarintField(10, 2);
-    unknownFields = UnknownFieldSetLite.mutableCopyOf(unknownFields, unknownFields);
-    unknownFields.checkMutable();
-
-    CodedInputStream input =
-        CodedInputStream.newInstance(toByteString(unknownFields).toByteArray());
-
-    int tag = input.readTag();
-    assertEquals(10, WireFormat.getTagFieldNumber(tag));
-    assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag));
-    assertEquals(2, input.readUInt64());
-    assertFalse(input.isAtEnd());
-    input.readTag();
-    assertEquals(10, WireFormat.getTagFieldNumber(tag));
-    assertEquals(WireFormat.WIRETYPE_VARINT, WireFormat.getTagWireType(tag));
-    assertEquals(2, input.readUInt64());
-    assertTrue(input.isAtEnd());
-  }
-
-  public void testMutableCopyOf_empty() {
-    UnknownFieldSetLite unknownFields =
-        UnknownFieldSetLite.mutableCopyOf(
-            UnknownFieldSetLite.getDefaultInstance(), UnknownFieldSetLite.getDefaultInstance());
-    unknownFields.checkMutable();
-
-    assertEquals(0, unknownFields.getSerializedSize());
-    assertEquals(ByteString.EMPTY, toByteString(unknownFields));
-  }
-
-  public void testRoundTrips() throws InvalidProtocolBufferException {
-    Foo foo =
-        Foo.newBuilder()
-            .setValue(1)
-            .setExtension(Bar.fooExt, Bar.newBuilder().setName("name").build())
-            .setExtension(LiteEqualsAndHash.varint, 22)
-            .setExtension(LiteEqualsAndHash.fixed32, 44)
-            .setExtension(LiteEqualsAndHash.fixed64, 66L)
-            .setExtension(
-                LiteEqualsAndHash.myGroup,
-                LiteEqualsAndHash.MyGroup.newBuilder().setGroupValue("value").build())
-            .build();
-
-    Foo copy = Foo.parseFrom(foo.toByteArray());
-
-    assertEquals(foo.getSerializedSize(), copy.getSerializedSize());
-    assertFalse(foo.equals(copy));
-
-    Foo secondCopy = Foo.parseFrom(foo.toByteArray());
-    assertEquals(copy, secondCopy);
-
-    ExtensionRegistryLite extensionRegistry = ExtensionRegistryLite.newInstance();
-    LiteEqualsAndHash.registerAllExtensions(extensionRegistry);
-    Foo copyOfCopy = Foo.parseFrom(copy.toByteArray(), extensionRegistry);
-
-    assertEquals(foo, copyOfCopy);
-  }
-
-  public void testMalformedBytes() throws Exception {
-    try {
-      Foo.parseFrom("this is a malformed protocol buffer".getBytes(Internal.UTF_8));
-      fail();
-    } catch (InvalidProtocolBufferException e) {
-      // Expected.
-    }
-  }
-
-  public void testMissingStartGroupTag() throws IOException {
-    ByteString.Output byteStringOutput = ByteString.newOutput();
-    CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput);
-    output.writeGroupNoTag(Foo.newBuilder().setValue(11).build());
-    output.writeTag(100, WireFormat.WIRETYPE_END_GROUP);
-    output.flush();
-
-    try {
-      Foo.parseFrom(byteStringOutput.toByteString());
-      fail();
-    } catch (InvalidProtocolBufferException e) {
-      // Expected.
-    }
-  }
-
-  public void testMissingEndGroupTag() throws IOException {
-    ByteString.Output byteStringOutput = ByteString.newOutput();
-    CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput);
-    output.writeTag(100, WireFormat.WIRETYPE_START_GROUP);
-    output.writeGroupNoTag(Foo.newBuilder().setValue(11).build());
-    output.flush();
-
-    try {
-      Foo.parseFrom(byteStringOutput.toByteString());
-      fail();
-    } catch (InvalidProtocolBufferException e) {
-      // Expected.
-    }
-  }
-
-  public void testMismatchingGroupTags() throws IOException {
-    ByteString.Output byteStringOutput = ByteString.newOutput();
-    CodedOutputStream output = CodedOutputStream.newInstance(byteStringOutput);
-    output.writeTag(100, WireFormat.WIRETYPE_START_GROUP);
-    output.writeGroupNoTag(Foo.newBuilder().setValue(11).build());
-    output.writeTag(101, WireFormat.WIRETYPE_END_GROUP);
-    output.flush();
-
-    try {
-      Foo.parseFrom(byteStringOutput.toByteString());
-      fail();
-    } catch (InvalidProtocolBufferException e) {
-      // Expected.
-    }
-  }
-
-  public void testTruncatedInput() {
-    Foo foo =
-        Foo.newBuilder()
-            .setValue(1)
-            .setExtension(Bar.fooExt, Bar.newBuilder().setName("name").build())
-            .setExtension(LiteEqualsAndHash.varint, 22)
-            .setExtension(
-                LiteEqualsAndHash.myGroup,
-                LiteEqualsAndHash.MyGroup.newBuilder().setGroupValue("value").build())
-            .build();
-
-    try {
-      Foo.parseFrom(foo.toByteString().substring(0, foo.toByteString().size() - 10));
-      fail();
-    } catch (InvalidProtocolBufferException e) {
-      // Expected.
-    }
-  }
-
-  public void testMakeImmutable() throws Exception {
-    UnknownFieldSetLite unknownFields = UnknownFieldSetLite.newInstance();
-    unknownFields.makeImmutable();
-
-    try {
-      unknownFields.mergeVarintField(1, 1);
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    try {
-      unknownFields.mergeLengthDelimitedField(2, ByteString.copyFromUtf8("hello"));
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    try {
-      unknownFields.mergeFieldFrom(1, CodedInputStream.newInstance(new byte[0]));
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-  }
-
-  public void testEndToEnd() throws Exception {
-    TestAllTypesLite testAllTypes = TestAllTypesLite.getDefaultInstance();
-    try {
-      testAllTypes.unknownFields.checkMutable();
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    testAllTypes = TestAllTypesLite.parseFrom(new byte[0]);
-    try {
-      testAllTypes.unknownFields.checkMutable();
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    testAllTypes = TestAllTypesLite.newBuilder().build();
-    try {
-      testAllTypes.unknownFields.checkMutable();
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    testAllTypes = TestAllTypesLite.newBuilder().setDefaultBool(true).build();
-    try {
-      testAllTypes.unknownFields.checkMutable();
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-
-    TestAllExtensionsLite testAllExtensions =
-        TestAllExtensionsLite.newBuilder()
-            .mergeFrom(
-                TestAllExtensionsLite.newBuilder()
-                    .setExtension(UnittestLite.optionalInt32ExtensionLite, 2)
-                    .build()
-                    .toByteArray())
-            .build();
-    try {
-      testAllExtensions.unknownFields.checkMutable();
-      fail();
-    } catch (UnsupportedOperationException expected) {
-    }
-  }
-
-  private ByteString toByteString(UnknownFieldSetLite unknownFields) {
-    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
-    CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream);
-    try {
-      unknownFields.writeTo(output);
-      output.flush();
-    } catch (IOException e) {
-      throw new RuntimeException(e);
-    }
-    return ByteString.copyFrom(byteArrayOutputStream.toByteArray());
-  }
-
-  public void testSerializeLite() throws Exception {
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData);
-    assertEquals(allFieldsData.size(), emptyMessageLite.getSerializedSize());
-    ByteString data = emptyMessageLite.toByteString();
-    TestAllTypes message = TestAllTypes.parseFrom(data);
-    TestUtil.assertAllFieldsSet(message);
-    assertEquals(allFieldsData, data);
-  }
-
-  public void testAllExtensionsLite() throws Exception {
-    TestAllExtensions allExtensions = TestUtil.getAllExtensionsSet();
-    ByteString allExtensionsData = allExtensions.toByteString();
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parser().parseFrom(allExtensionsData);
-    ByteString data = emptyMessageLite.toByteString();
-    TestAllExtensions message = TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry());
-    TestUtil.assertAllExtensionsSet(message);
-    assertEquals(allExtensionsData, data);
-  }
-
-  public void testAllPackedFieldsLite() throws Exception {
-    TestPackedTypes allPackedFields = TestUtil.getPackedSet();
-    ByteString allPackedData = allPackedFields.toByteString();
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(allPackedData);
-    ByteString data = emptyMessageLite.toByteString();
-    TestPackedTypes message = TestPackedTypes.parseFrom(data, TestUtil.getExtensionRegistry());
-    TestUtil.assertPackedFieldsSet(message);
-    assertEquals(allPackedData, data);
-  }
-
-  public void testAllPackedExtensionsLite() throws Exception {
-    TestPackedExtensions allPackedExtensions = TestUtil.getPackedExtensionsSet();
-    ByteString allPackedExtensionsData = allPackedExtensions.toByteString();
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(allPackedExtensionsData);
-    ByteString data = emptyMessageLite.toByteString();
-    TestPackedExtensions message =
-        TestPackedExtensions.parseFrom(data, TestUtil.getExtensionRegistry());
-    TestUtil.assertPackedExtensionsSet(message);
-    assertEquals(allPackedExtensionsData, data);
-  }
-
-  public void testCopyFromLite() throws Exception {
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData);
-    UnittestLite.TestEmptyMessageLite emptyMessageLite2 =
-        UnittestLite.TestEmptyMessageLite.newBuilder().mergeFrom(emptyMessageLite).build();
-    assertEquals(emptyMessageLite.toByteString(), emptyMessageLite2.toByteString());
-  }
-
-  public void testMergeFromLite() throws Exception {
-    TestAllTypes message1 =
-        TestAllTypes.newBuilder()
-            .setOptionalInt32(1)
-            .setOptionalString("foo")
-            .addRepeatedString("bar")
-            .setOptionalNestedEnum(TestAllTypes.NestedEnum.BAZ)
-            .build();
-
-    TestAllTypes message2 =
-        TestAllTypes.newBuilder()
-            .setOptionalInt64(2)
-            .setOptionalString("baz")
-            .addRepeatedString("qux")
-            .setOptionalForeignEnum(ForeignEnum.FOREIGN_BAZ)
-            .build();
-
-    ByteString data1 = message1.toByteString();
-    UnittestLite.TestEmptyMessageLite emptyMessageLite1 =
-        UnittestLite.TestEmptyMessageLite.parseFrom(data1);
-    ByteString data2 = message2.toByteString();
-    UnittestLite.TestEmptyMessageLite emptyMessageLite2 =
-        UnittestLite.TestEmptyMessageLite.parseFrom(data2);
-
-    message1 = TestAllTypes.newBuilder(message1).mergeFrom(message2).build();
-    emptyMessageLite1 =
-        UnittestLite.TestEmptyMessageLite.newBuilder(emptyMessageLite1)
-            .mergeFrom(emptyMessageLite2)
-            .build();
-
-    data1 = emptyMessageLite1.toByteString();
-    message2 = TestAllTypes.parseFrom(data1);
-
-    assertEquals(message1, message2);
-  }
-
-  public void testWrongTypeTreatedAsUnknownLite() throws Exception {
-    // Test that fields of the wrong wire type are treated like unknown fields
-    // when parsing.
-
-    ByteString bizarroData = getBizarroData();
-    TestAllTypes allTypesMessage = TestAllTypes.parseFrom(bizarroData);
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData);
-    ByteString data = emptyMessageLite.toByteString();
-    TestAllTypes allTypesMessage2 = TestAllTypes.parseFrom(data);
-
-    assertEquals(allTypesMessage.toString(), allTypesMessage2.toString());
-  }
-
-  public void testUnknownExtensionsLite() throws Exception {
-    // Make sure fields are properly parsed to the UnknownFieldSet even when
-    // they are declared as extension numbers.
-
-    UnittestLite.TestEmptyMessageWithExtensionsLite message =
-        UnittestLite.TestEmptyMessageWithExtensionsLite.parseFrom(allFieldsData);
-
-    assertEquals(allFieldsData, message.toByteString());
-  }
-
-  public void testWrongExtensionTypeTreatedAsUnknownLite() throws Exception {
-    // Test that fields of the wrong wire type are treated like unknown fields
-    // when parsing extensions.
-
-    ByteString bizarroData = getBizarroData();
-    TestAllExtensions allExtensionsMessage = TestAllExtensions.parseFrom(bizarroData);
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(bizarroData);
-
-    // All fields should have been interpreted as unknown, so the byte strings
-    // should be the same.
-    assertEquals(emptyMessageLite.toByteString(), allExtensionsMessage.toByteString());
-  }
-
-  public void testParseUnknownEnumValueLite() throws Exception {
-    Descriptors.FieldDescriptor singularField =
-        TestAllTypes.getDescriptor().findFieldByName("optional_nested_enum");
-    Descriptors.FieldDescriptor repeatedField =
-        TestAllTypes.getDescriptor().findFieldByName("repeated_nested_enum");
-    assertNotNull(singularField);
-    assertNotNull(repeatedField);
-
-    ByteString data =
-        UnknownFieldSet.newBuilder()
-            .addField(
-                singularField.getNumber(),
-                UnknownFieldSet.Field.newBuilder()
-                    .addVarint(TestAllTypes.NestedEnum.BAR.getNumber())
-                    .addVarint(5) // not valid
-                    .build())
-            .addField(
-                repeatedField.getNumber(),
-                UnknownFieldSet.Field.newBuilder()
-                    .addVarint(TestAllTypes.NestedEnum.FOO.getNumber())
-                    .addVarint(4) // not valid
-                    .addVarint(TestAllTypes.NestedEnum.BAZ.getNumber())
-                    .addVarint(6) // not valid
-                    .build())
-            .build()
-            .toByteString();
-
-    UnittestLite.TestEmptyMessageLite emptyMessageLite =
-        UnittestLite.TestEmptyMessageLite.parseFrom(data);
-    data = emptyMessageLite.toByteString();
-
-    {
-      TestAllTypes message = TestAllTypes.parseFrom(data);
-      assertEquals(TestAllTypes.NestedEnum.BAR, message.getOptionalNestedEnum());
-      assertEquals(
-          Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ),
-          message.getRepeatedNestedEnumList());
-      assertEquals(
-          Arrays.asList(5L),
-          message.getUnknownFields().getField(singularField.getNumber()).getVarintList());
-      assertEquals(
-          Arrays.asList(4L, 6L),
-          message.getUnknownFields().getField(repeatedField.getNumber()).getVarintList());
-    }
-
-    {
-      TestAllExtensions message =
-          TestAllExtensions.parseFrom(data, TestUtil.getExtensionRegistry());
-      assertEquals(
-          TestAllTypes.NestedEnum.BAR,
-          message.getExtension(UnittestProto.optionalNestedEnumExtension));
-      assertEquals(
-          Arrays.asList(TestAllTypes.NestedEnum.FOO, TestAllTypes.NestedEnum.BAZ),
-          message.getExtension(UnittestProto.repeatedNestedEnumExtension));
-      assertEquals(
-          Arrays.asList(5L),
-          message.getUnknownFields().getField(singularField.getNumber()).getVarintList());
-      assertEquals(
-          Arrays.asList(4L, 6L),
-          message.getUnknownFields().getField(repeatedField.getNumber()).getVarintList());
-    }
-  }
-
-  public void testClearLite() throws Exception {
-    UnittestLite.TestEmptyMessageLite emptyMessageLite1 =
-        UnittestLite.TestEmptyMessageLite.parseFrom(allFieldsData);
-    UnittestLite.TestEmptyMessageLite emptyMessageLite2 =
-        UnittestLite.TestEmptyMessageLite.newBuilder().mergeFrom(emptyMessageLite1).clear().build();
-    assertEquals(0, emptyMessageLite2.getSerializedSize());
-    ByteString data = emptyMessageLite2.toByteString();
-    assertEquals(0, data.size());
-  }
-}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно