瀏覽代碼

Adding experimental runtime

nmittler 9 年之前
父節點
當前提交
0fdfe635e8
共有 41 個文件被更改,包括 12403 次插入3 次删除
  1. 38 0
      Makefile.am
  2. 183 0
      java/core/src/main/java/com/google/protobuf/AbstractProto2LiteSchema.java
  3. 400 0
      java/core/src/main/java/com/google/protobuf/AbstractProto2Schema.java
  4. 127 0
      java/core/src/main/java/com/google/protobuf/AbstractProto2StandardSchema.java
  5. 178 0
      java/core/src/main/java/com/google/protobuf/AbstractProto3LiteSchema.java
  6. 347 0
      java/core/src/main/java/com/google/protobuf/AbstractProto3Schema.java
  7. 124 0
      java/core/src/main/java/com/google/protobuf/AbstractProto3StandardSchema.java
  8. 263 0
      java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java
  9. 117 0
      java/core/src/main/java/com/google/protobuf/BinaryProtocolUtil.java
  10. 1516 0
      java/core/src/main/java/com/google/protobuf/BinaryReader.java
  11. 2804 0
      java/core/src/main/java/com/google/protobuf/BinaryWriter.java
  12. 71 0
      java/core/src/main/java/com/google/protobuf/BufferAllocator.java
  13. 18 3
      java/core/src/main/java/com/google/protobuf/ByteString.java
  14. 264 0
      java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java
  15. 175 0
      java/core/src/main/java/com/google/protobuf/FieldInfo.java
  16. 535 0
      java/core/src/main/java/com/google/protobuf/FieldType.java
  17. 754 0
      java/core/src/main/java/com/google/protobuf/Int2ObjectHashMap.java
  18. 87 0
      java/core/src/main/java/com/google/protobuf/JavaType.java
  19. 158 0
      java/core/src/main/java/com/google/protobuf/MessageInfo.java
  20. 38 0
      java/core/src/main/java/com/google/protobuf/MessageInfoFactory.java
  21. 281 0
      java/core/src/main/java/com/google/protobuf/Proto2LiteLookupSchema.java
  22. 291 0
      java/core/src/main/java/com/google/protobuf/Proto2LiteTableSchema.java
  23. 273 0
      java/core/src/main/java/com/google/protobuf/Proto2LookupSchema.java
  24. 261 0
      java/core/src/main/java/com/google/protobuf/Proto2Manifest.java
  25. 112 0
      java/core/src/main/java/com/google/protobuf/Proto2SchemaFactory.java
  26. 270 0
      java/core/src/main/java/com/google/protobuf/Proto2TableSchema.java
  27. 253 0
      java/core/src/main/java/com/google/protobuf/Proto3LiteLookupSchema.java
  28. 263 0
      java/core/src/main/java/com/google/protobuf/Proto3LiteTableSchema.java
  29. 245 0
      java/core/src/main/java/com/google/protobuf/Proto3LookupSchema.java
  30. 233 0
      java/core/src/main/java/com/google/protobuf/Proto3Manifest.java
  31. 115 0
      java/core/src/main/java/com/google/protobuf/Proto3SchemaFactory.java
  32. 242 0
      java/core/src/main/java/com/google/protobuf/Proto3TableSchema.java
  33. 38 0
      java/core/src/main/java/com/google/protobuf/ProtoSyntax.java
  34. 148 0
      java/core/src/main/java/com/google/protobuf/Protobuf.java
  35. 96 0
      java/core/src/main/java/com/google/protobuf/ProtobufLists.java
  36. 312 0
      java/core/src/main/java/com/google/protobuf/Reader.java
  37. 5 0
      java/core/src/main/java/com/google/protobuf/RopeByteString.java
  38. 55 0
      java/core/src/main/java/com/google/protobuf/Schema.java
  39. 42 0
      java/core/src/main/java/com/google/protobuf/SchemaFactory.java
  40. 516 0
      java/core/src/main/java/com/google/protobuf/SchemaUtil.java
  41. 155 0
      java/core/src/main/java/com/google/protobuf/Writer.java

+ 38 - 0
Makefile.am

@@ -187,6 +187,44 @@ csharp_EXTRA_DIST=                                                           \
   csharp/src/packages/repositories.config
   csharp/src/packages/repositories.config
 
 
 java_EXTRA_DIST=                                                                   \
 java_EXTRA_DIST=                                                                   \
+  java/core/src/main/java/com/google/protobuf/AllocatedBuffer.java \
+  java/core/src/main/java/com/google/protobuf/BinaryProtocolUtil.java \
+  java/core/src/main/java/com/google/protobuf/BinaryReader.java \
+  java/core/src/main/java/com/google/protobuf/BinaryWriter.java \
+  java/core/src/main/java/com/google/protobuf/BufferAllocator.java \
+  java/core/src/main/java/com/google/protobuf/FieldInfo.java \
+  java/core/src/main/java/com/google/protobuf/Int2ObjectHashMap.java \
+  java/core/src/main/java/com/google/protobuf/MessageInfo.java \
+  java/core/src/main/java/com/google/protobuf/MessageInfoFactory.java \
+  java/core/src/main/java/com/google/protobuf/Protobuf.java \
+  java/core/src/main/java/com/google/protobuf/ProtobufLists.java \
+  java/core/src/main/java/com/google/protobuf/Reader.java \
+  java/core/src/main/java/com/google/protobuf/Schema.java \
+  java/core/src/main/java/com/google/protobuf/SchemaFactory.java \
+  java/core/src/main/java/com/google/protobuf/SchemaUtil.java \
+  java/core/src/main/java/com/google/protobuf/Writer.java \
+  java/core/src/main/java/com/google/protobuf/DescriptorMessageInfoFactory.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto2LiteSchema.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto2Schema.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto2StandardSchema.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto3LiteSchema.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto3Schema.java \
+  java/core/src/main/java/com/google/protobuf/AbstractProto3StandardSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto2LiteLookupSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto2LiteTableSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto2LookupSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto2Manifest.java \
+  java/core/src/main/java/com/google/protobuf/Proto2SchemaFactory.java \
+  java/core/src/main/java/com/google/protobuf/Proto2TableSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto3LiteLookupSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto3LiteTableSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto3LookupSchema.java \
+  java/core/src/main/java/com/google/protobuf/Proto3Manifest.java \
+  java/core/src/main/java/com/google/protobuf/Proto3SchemaFactory.java \
+  java/core/src/main/java/com/google/protobuf/Proto3TableSchema.java \
+  java/core/src/main/java/com/google/protobuf/ProtoSyntax.java \
+  java/core/src/main/java/com/google/protobuf/FieldType.java \
+  java/core/src/main/java/com/google/protobuf/JavaType.java \
   java/README.md                                                                   \
   java/README.md                                                                   \
   java/core/generate-sources-build.xml                                             \
   java/core/generate-sources-build.xml                                             \
   java/core/generate-test-sources-build.xml                                        \
   java/core/generate-test-sources-build.xml                                        \

+ 183 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto2LiteSchema.java

@@ -0,0 +1,183 @@
+// 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.Proto2Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+/** Base class for all proto2-lite-based schemas. */
+abstract class AbstractProto2LiteSchema<T> extends AbstractProto2Schema<T> {
+  final Class<?> messageClass;
+
+  public AbstractProto2LiteSchema(Class<T> messageClass, Proto2Manifest manifest) {
+    super(manifest);
+    this.messageClass = messageClass;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final T newInstance() {
+    T msg = (T) UnsafeUtil.allocateInstance(messageClass);
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+        case 1: //FLOAT:
+        case 2: //INT64:
+        case 3: //UINT64:
+        case 4: //INT32:
+        case 5: //FIXED64:
+        case 6: //FIXED32:
+        case 7: //BOOL:
+        case 9: //MESSAGE:
+        case 11: //UINT32:
+        case 12: //ENUM:
+        case 13: //SFIXED32:
+        case 14: //SFIXED64:
+        case 15: //SINT32:
+        case 16: //SINT64:
+          // Do nothing, just use default values.
+          break;
+        case 8: //STRING:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), "");
+          break;
+        case 10: //BYTES:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ByteString.EMPTY);
+          break;
+        case 17: //DOUBLE_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), DoubleArrayList.emptyList());
+          break;
+        case 18: //FLOAT_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), FloatArrayList.emptyList());
+          break;
+        case 19: //INT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 20: //UINT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 21: //INT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 22: //FIXED64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 23: //FIXED32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 24: //BOOL_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), BooleanArrayList.emptyList());
+          break;
+        case 25: //STRING_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LazyStringArrayList.EMPTY);
+          break;
+        case 26: //MESSAGE_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ProtobufArrayList.emptyList());
+          break;
+        case 27: //BYTES_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ProtobufArrayList.emptyList());
+          break;
+        case 28: //UINT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 29: //ENUM_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 30: //SFIXED32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 31: //SFIXED64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 32: //SINT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 33: //SINT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 34: //DOUBLE_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), DoubleArrayList.emptyList());
+          break;
+        case 35: //FLOAT_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), FloatArrayList.emptyList());
+          break;
+        case 36: //INT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 37: //UINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 38: //INT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 39: //FIXED64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 40: //FIXED32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 41: //BOOL_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), BooleanArrayList.emptyList());
+          break;
+        case 42: //UINT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 43: //ENUM_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 44: //SFIXED32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 45: //SFIXED64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 46: //SINT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 47: //SINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case -4: //GROUP:
+          break;
+        case -3: //GROUP_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ProtobufArrayList.emptyList());
+          break;
+        default:
+          break;
+      }
+    }
+
+    // Initialize the base class fields.
+    SchemaUtil.initLiteBaseClassFields(msg);
+    return msg;
+  }
+}

+ 400 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto2Schema.java

@@ -0,0 +1,400 @@
+// 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.Proto2Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.util.List;
+
+/** Abstract base class for all proto3-based schemas. */
+abstract class AbstractProto2Schema<T> implements Schema<T> {
+  protected final Proto2Manifest manifest;
+
+  public AbstractProto2Schema(Proto2Manifest manifest) {
+    this.manifest = manifest;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final void writeTo(T message, Writer writer) {
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeDouble(
+                manifest.numberAt(pos), UnsafeUtil.getDouble(message, offset(typeAndOffset)));
+          }
+          break;
+        case 1: //FLOAT:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeFloat(
+                manifest.numberAt(pos), UnsafeUtil.getFloat(message, offset(typeAndOffset)));
+          }
+          break;
+        case 2: //INT64:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeInt64(
+                manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)));
+          }
+          break;
+        case 3: //UINT64:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeUInt64(
+                manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)));
+          }
+          break;
+        case 4: //INT32:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeInt32(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 5: //FIXED64:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeFixed64(
+                manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)));
+          }
+          break;
+        case 6: //FIXED32:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeFixed32(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 7: //BOOL:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeBool(
+                manifest.numberAt(pos), UnsafeUtil.getBoolean(message, offset(typeAndOffset)));
+          }
+          break;
+        case 8: //STRING:
+          if (manifest.isFieldPresent(message, pos)) {
+            writeString(
+                manifest.numberAt(pos),
+                UnsafeUtil.getObject(message, offset(typeAndOffset)),
+                writer);
+          }
+          break;
+        case 9: //MESSAGE:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeMessage(
+                manifest.numberAt(pos), UnsafeUtil.getObject(message, offset(typeAndOffset)));
+          }
+          break;
+        case 10: //BYTES:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeBytes(
+                manifest.numberAt(pos),
+                (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)));
+          }
+          break;
+        case 11: //UINT32:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeUInt32(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 12: //ENUM:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeEnum(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 13: //SFIXED32:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeSFixed32(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 14: //SFIXED64:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeSFixed64(
+                manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)));
+          }
+          break;
+        case 15: //SINT32:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeSInt32(
+                manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)));
+          }
+          break;
+        case 16: //SINT64:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeSInt64(
+                manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)));
+          }
+          break;
+        case 17: //DOUBLE_LIST:
+          SchemaUtil.writeDoubleList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 18: //FLOAT_LIST:
+          SchemaUtil.writeFloatList(
+              manifest.numberAt(pos),
+              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 19: //INT64_LIST:
+          SchemaUtil.writeInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 20: //UINT64_LIST:
+          SchemaUtil.writeUInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 21: //INT32_LIST:
+          SchemaUtil.writeInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 22: //FIXED64_LIST:
+          SchemaUtil.writeFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 23: //FIXED32_LIST:
+          SchemaUtil.writeFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 24: //BOOL_LIST:
+          SchemaUtil.writeBoolList(
+              manifest.numberAt(pos),
+              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 25: //STRING_LIST:
+          SchemaUtil.writeStringList(
+              manifest.numberAt(pos),
+              (List<String>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 26: //MESSAGE_LIST:
+          SchemaUtil.writeMessageList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 27: //BYTES_LIST:
+          SchemaUtil.writeBytesList(
+              manifest.numberAt(pos),
+              (List<ByteString>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 28: //UINT32_LIST:
+          SchemaUtil.writeUInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 29: //ENUM_LIST:
+          SchemaUtil.writeEnumList(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 30: //SFIXED32_LIST:
+          SchemaUtil.writeSFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 31: //SFIXED64_LIST:
+          SchemaUtil.writeSFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 32: //SINT32_LIST:
+          SchemaUtil.writeSInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 33: //SINT64_LIST:
+          SchemaUtil.writeSInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 34: //DOUBLE_LIST_PACKED:
+          SchemaUtil.writeDoubleList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 35: //FLOAT_LIST_PACKED:
+          SchemaUtil.writeFloatList(
+              manifest.numberAt(pos),
+              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 36: //INT64_LIST_PACKED:
+          SchemaUtil.writeInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 37: //UINT64_LIST_PACKED:
+          SchemaUtil.writeUInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 38: //INT32_LIST_PACKED:
+          SchemaUtil.writeInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 39: //FIXED64_LIST_PACKED:
+          SchemaUtil.writeFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 40: //FIXED32_LIST_PACKED:
+          SchemaUtil.writeFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 41: //BOOL_LIST_PACKED:
+          SchemaUtil.writeBoolList(
+              manifest.numberAt(pos),
+              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 42: //UINT32_LIST_PACKED:
+          SchemaUtil.writeUInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 43: //ENUM_LIST_PACKED:
+          SchemaUtil.writeEnumList(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 44: //SFIXED32_LIST_PACKED:
+          SchemaUtil.writeSFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 45: //SFIXED64_LIST_PACKED:
+          SchemaUtil.writeSFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 46: //SINT32_LIST_PACKED:
+          SchemaUtil.writeSInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 47: //SINT64_LIST_PACKED:
+          SchemaUtil.writeSInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case -4: //GROUP:
+          if (manifest.isFieldPresent(message, pos)) {
+            writer.writeGroup(
+                manifest.numberAt(pos), UnsafeUtil.getObject(message, offset(typeAndOffset)));
+          }
+          break;
+        case -3: //GROUP_LIST:
+          SchemaUtil.writeGroupList(
+              manifest.numberAt(pos),
+              (List<?>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        default:
+          // Assume it's an empty entry - just go to the next entry.
+          break;
+      }
+    }
+  }
+
+  private void writeString(int fieldNumber, Object value, Writer writer) {
+    if (value instanceof String) {
+      writer.writeString(fieldNumber, (String) value);
+    } else {
+      writer.writeBytes(fieldNumber, (ByteString) value);
+    }
+  }
+}

+ 127 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto2StandardSchema.java

@@ -0,0 +1,127 @@
+// 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.Proto2Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.util.Collections;
+
+/** Abtract base class for standard (i.e. non-lite) proto2 schemas. */
+abstract class AbstractProto2StandardSchema<T> extends AbstractProto2Schema<T> {
+  final Class<?> messageClass;
+
+  public AbstractProto2StandardSchema(Class<T> messageClass, Proto2Manifest manifest) {
+    super(manifest);
+    this.messageClass = messageClass;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final T newInstance() {
+    T msg = (T) UnsafeUtil.allocateInstance(messageClass);
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+        case 1: //FLOAT:
+        case 2: //INT64:
+        case 3: //UINT64:
+        case 4: //INT32:
+        case 5: //FIXED64:
+        case 6: //FIXED32:
+        case 7: //BOOL:
+        case 9: //MESSAGE:
+        case 11: //UINT32:
+        case 12: //ENUM:
+        case 13: //SFIXED32:
+        case 14: //SFIXED64:
+        case 15: //SINT32:
+        case 16: //SINT64:
+          // Do nothing, just use default values.
+          break;
+        case 8: //STRING:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), "");
+          break;
+        case 10: //BYTES:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ByteString.EMPTY);
+          break;
+        case 17: //DOUBLE_LIST:
+        case 18: //FLOAT_LIST:
+        case 19: //INT64_LIST:
+        case 20: //UINT64_LIST:
+        case 21: //INT32_LIST:
+        case 22: //FIXED64_LIST:
+        case 23: //FIXED32_LIST:
+        case 24: //BOOL_LIST:
+        case 26: //MESSAGE_LIST:
+        case 27: //BYTES_LIST:
+        case 28: //UINT32_LIST:
+        case 29: //ENUM_LIST:
+        case 30: //SFIXED32_LIST:
+        case 31: //SFIXED64_LIST:
+        case 32: //SINT32_LIST:
+        case 33: //SINT64_LIST:
+        case 34: //DOUBLE_LIST_PACKED:
+        case 35: //FLOAT_LIST_PACKED:
+        case 36: //INT64_LIST_PACKED:
+        case 37: //UINT64_LIST_PACKED:
+        case 38: //INT32_LIST_PACKED:
+        case 39: //FIXED64_LIST_PACKED:
+        case 40: //FIXED32_LIST_PACKED:
+        case 41: //BOOL_LIST_PACKED:
+        case 42: //UINT32_LIST_PACKED:
+        case 43: //ENUM_LIST_PACKED:
+        case 44: //SFIXED32_LIST_PACKED:
+        case 45: //SFIXED64_LIST_PACKED:
+        case 46: //SINT32_LIST_PACKED:
+        case 47: //SINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), Collections.emptyList());
+          break;
+        case 25: //STRING_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LazyStringArrayList.EMPTY);
+          break;
+        case -4: //GROUP
+          break;
+        case -3: //GROUP_LIST
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), Collections.emptyList());
+          break;
+        default:
+          break;
+      }
+    }
+
+    // Initialize the base class fields.
+    SchemaUtil.initBaseClassFields(msg);
+    return msg;
+  }
+}

+ 178 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto3LiteSchema.java

@@ -0,0 +1,178 @@
+// 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.Proto3Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+/** Base class for all proto3-lite-based schemas. */
+abstract class AbstractProto3LiteSchema<T> extends AbstractProto3Schema<T> {
+  final Class<?> messageClass;
+
+  public AbstractProto3LiteSchema(Class<T> messageClass, Proto3Manifest manifest) {
+    super(manifest);
+    this.messageClass = messageClass;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final T newInstance() {
+    T msg = (T) UnsafeUtil.allocateInstance(messageClass);
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+        case 1: //FLOAT:
+        case 2: //INT64:
+        case 3: //UINT64:
+        case 4: //INT32:
+        case 5: //FIXED64:
+        case 6: //FIXED32:
+        case 7: //BOOL:
+        case 9: //MESSAGE:
+        case 11: //UINT32:
+        case 12: //ENUM:
+        case 13: //SFIXED32:
+        case 14: //SFIXED64:
+        case 15: //SINT32:
+        case 16: //SINT64:
+          // Do nothing, just use default values.
+          break;
+        case 8: //STRING:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), "");
+          break;
+        case 10: //BYTES:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ByteString.EMPTY);
+          break;
+        case 17: //DOUBLE_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), DoubleArrayList.emptyList());
+          break;
+        case 18: //FLOAT_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), FloatArrayList.emptyList());
+          break;
+        case 19: //INT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 20: //UINT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 21: //INT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 22: //FIXED64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 23: //FIXED32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 24: //BOOL_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), BooleanArrayList.emptyList());
+          break;
+        case 25: //STRING_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LazyStringArrayList.EMPTY);
+          break;
+        case 26: //MESSAGE_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ProtobufArrayList.emptyList());
+          break;
+        case 27: //BYTES_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ProtobufArrayList.emptyList());
+          break;
+        case 28: //UINT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 29: //ENUM_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 30: //SFIXED32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 31: //SFIXED64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 32: //SINT32_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 33: //SINT64_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 34: //DOUBLE_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), DoubleArrayList.emptyList());
+          break;
+        case 35: //FLOAT_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), FloatArrayList.emptyList());
+          break;
+        case 36: //INT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 37: //UINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 38: //INT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 39: //FIXED64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 40: //FIXED32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 41: //BOOL_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), BooleanArrayList.emptyList());
+          break;
+        case 42: //UINT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 43: //ENUM_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 44: //SFIXED32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 45: //SFIXED64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        case 46: //SINT32_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), IntArrayList.emptyList());
+          break;
+        case 47: //SINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LongArrayList.emptyList());
+          break;
+        default:
+          break;
+      }
+    }
+
+    // Initialize the base class fields.
+    SchemaUtil.initLiteBaseClassFields(msg);
+    return msg;
+  }
+}

+ 347 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto3Schema.java

@@ -0,0 +1,347 @@
+// 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.Proto3Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.util.List;
+
+/** Abstract base class for all proto3-based schemas. */
+abstract class AbstractProto3Schema<T> implements Schema<T> {
+  protected final Proto3Manifest manifest;
+
+  public AbstractProto3Schema(Proto3Manifest manifest) {
+    this.manifest = manifest;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final void writeTo(T message, Writer writer) {
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+          SchemaUtil.writeDouble(
+              manifest.numberAt(pos), UnsafeUtil.getDouble(message, offset(typeAndOffset)), writer);
+          break;
+        case 1: //FLOAT:
+          SchemaUtil.writeFloat(
+              manifest.numberAt(pos), UnsafeUtil.getFloat(message, offset(typeAndOffset)), writer);
+          break;
+        case 2: //INT64:
+          SchemaUtil.writeInt64(
+              manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)), writer);
+          break;
+        case 3: //UINT64:
+          SchemaUtil.writeUInt64(
+              manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)), writer);
+          break;
+        case 4: //INT32:
+          SchemaUtil.writeInt32(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 5: //FIXED64:
+          SchemaUtil.writeFixed64(
+              manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)), writer);
+          break;
+        case 6: //FIXED32:
+          SchemaUtil.writeFixed32(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 7: //BOOL:
+          SchemaUtil.writeBool(
+              manifest.numberAt(pos),
+              UnsafeUtil.getBoolean(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 8: //STRING:
+          SchemaUtil.writeString(
+              manifest.numberAt(pos), UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
+          break;
+        case 9: //MESSAGE:
+          SchemaUtil.writeMessage(
+              manifest.numberAt(pos), UnsafeUtil.getObject(message, offset(typeAndOffset)), writer);
+          break;
+        case 10: //BYTES:
+          SchemaUtil.writeBytes(
+              manifest.numberAt(pos),
+              (ByteString) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 11: //UINT32:
+          SchemaUtil.writeUInt32(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 12: //ENUM:
+          SchemaUtil.writeEnum(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 13: //SFIXED32:
+          SchemaUtil.writeSFixed32(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 14: //SFIXED64:
+          SchemaUtil.writeSFixed64(
+              manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)), writer);
+          break;
+        case 15: //SINT32:
+          SchemaUtil.writeSInt32(
+              manifest.numberAt(pos), UnsafeUtil.getInt(message, offset(typeAndOffset)), writer);
+          break;
+        case 16: //SINT64:
+          SchemaUtil.writeSInt64(
+              manifest.numberAt(pos), UnsafeUtil.getLong(message, offset(typeAndOffset)), writer);
+          break;
+        case 17: //DOUBLE_LIST:
+          SchemaUtil.writeDoubleList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 18: //FLOAT_LIST:
+          SchemaUtil.writeFloatList(
+              manifest.numberAt(pos),
+              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 19: //INT64_LIST:
+          SchemaUtil.writeInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 20: //UINT64_LIST:
+          SchemaUtil.writeUInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 21: //INT32_LIST:
+          SchemaUtil.writeInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 22: //FIXED64_LIST:
+          SchemaUtil.writeFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 23: //FIXED32_LIST:
+          SchemaUtil.writeFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 24: //BOOL_LIST:
+          SchemaUtil.writeBoolList(
+              manifest.numberAt(pos),
+              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 25: //STRING_LIST:
+          SchemaUtil.writeStringList(
+              manifest.numberAt(pos),
+              (List<String>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 26: //MESSAGE_LIST:
+          SchemaUtil.writeMessageList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 27: //BYTES_LIST:
+          SchemaUtil.writeBytesList(
+              manifest.numberAt(pos),
+              (List<ByteString>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer);
+          break;
+        case 28: //UINT32_LIST:
+          SchemaUtil.writeUInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 29: //ENUM_LIST:
+          SchemaUtil.writeEnumList(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 30: //SFIXED32_LIST:
+          SchemaUtil.writeSFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 31: //SFIXED64_LIST:
+          SchemaUtil.writeSFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 32: //SINT32_LIST:
+          SchemaUtil.writeSInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 33: //SINT64_LIST:
+          SchemaUtil.writeSInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              false);
+          break;
+        case 34: //DOUBLE_LIST_PACKED:
+          SchemaUtil.writeDoubleList(
+              manifest.numberAt(pos),
+              (List<Double>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 35: //FLOAT_LIST_PACKED:
+          SchemaUtil.writeFloatList(
+              manifest.numberAt(pos),
+              (List<Float>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 36: //INT64_LIST_PACKED:
+          SchemaUtil.writeInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 37: //UINT64_LIST_PACKED:
+          SchemaUtil.writeUInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 38: //INT32_LIST_PACKED:
+          SchemaUtil.writeInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 39: //FIXED64_LIST_PACKED:
+          SchemaUtil.writeFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 40: //FIXED32_LIST_PACKED:
+          SchemaUtil.writeFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 41: //BOOL_LIST_PACKED:
+          SchemaUtil.writeBoolList(
+              manifest.numberAt(pos),
+              (List<Boolean>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 42: //UINT32_LIST_PACKED:
+          SchemaUtil.writeUInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 43: //ENUM_LIST_PACKED:
+          SchemaUtil.writeEnumList(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 44: //SFIXED32_LIST_PACKED:
+          SchemaUtil.writeSFixed32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 45: //SFIXED64_LIST_PACKED:
+          SchemaUtil.writeSFixed64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 46: //SINT32_LIST_PACKED:
+          SchemaUtil.writeSInt32List(
+              manifest.numberAt(pos),
+              (List<Integer>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        case 47: //SINT64_LIST_PACKED:
+          SchemaUtil.writeSInt64List(
+              manifest.numberAt(pos),
+              (List<Long>) UnsafeUtil.getObject(message, offset(typeAndOffset)),
+              writer,
+              true);
+          break;
+        default:
+          // Assume it's an empty entry - just go to the next entry.
+          break;
+      }
+    }
+  }
+}

+ 124 - 0
java/core/src/main/java/com/google/protobuf/AbstractProto3StandardSchema.java

@@ -0,0 +1,124 @@
+// 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.Proto3Manifest.FIELD_LENGTH;
+import static com.google.protobuf.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.util.Collections;
+
+/**
+ * Abtract base class for standard (i.e. non-lite) proto3 schemas.
+ */
+abstract class AbstractProto3StandardSchema<T> extends AbstractProto3Schema<T> {
+  final Class<?> messageClass;
+
+  public AbstractProto3StandardSchema(Class<T> messageClass, Proto3Manifest manifest) {
+    super(manifest);
+    this.messageClass = messageClass;
+  }
+
+  @SuppressWarnings("unchecked")
+  @Override
+  public final T newInstance() {
+    T msg = (T) UnsafeUtil.allocateInstance(messageClass);
+    for (long pos = manifest.address; pos < manifest.limit; pos += FIELD_LENGTH) {
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+      switch (type(typeAndOffset)) {
+        case 0: //DOUBLE:
+        case 1: //FLOAT:
+        case 2: //INT64:
+        case 3: //UINT64:
+        case 4: //INT32:
+        case 5: //FIXED64:
+        case 6: //FIXED32:
+        case 7: //BOOL:
+        case 9: //MESSAGE:
+        case 11: //UINT32:
+        case 12: //ENUM:
+        case 13: //SFIXED32:
+        case 14: //SFIXED64:
+        case 15: //SINT32:
+        case 16: //SINT64:
+          // Do nothing, just use default values.
+          break;
+        case 8: //STRING:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), "");
+          break;
+        case 10: //BYTES:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), ByteString.EMPTY);
+          break;
+        case 17: //DOUBLE_LIST:
+        case 18: //FLOAT_LIST:
+        case 19: //INT64_LIST:
+        case 20: //UINT64_LIST:
+        case 21: //INT32_LIST:
+        case 22: //FIXED64_LIST:
+        case 23: //FIXED32_LIST:
+        case 24: //BOOL_LIST:
+        case 26: //MESSAGE_LIST:
+        case 27: //BYTES_LIST:
+        case 28: //UINT32_LIST:
+        case 29: //ENUM_LIST:
+        case 30: //SFIXED32_LIST:
+        case 31: //SFIXED64_LIST:
+        case 32: //SINT32_LIST:
+        case 33: //SINT64_LIST:
+        case 34: //DOUBLE_LIST_PACKED:
+        case 35: //FLOAT_LIST_PACKED:
+        case 36: //INT64_LIST_PACKED:
+        case 37: //UINT64_LIST_PACKED:
+        case 38: //INT32_LIST_PACKED:
+        case 39: //FIXED64_LIST_PACKED:
+        case 40: //FIXED32_LIST_PACKED:
+        case 41: //BOOL_LIST_PACKED:
+        case 42: //UINT32_LIST_PACKED:
+        case 43: //ENUM_LIST_PACKED:
+        case 44: //SFIXED32_LIST_PACKED:
+        case 45: //SFIXED64_LIST_PACKED:
+        case 46: //SINT32_LIST_PACKED:
+        case 47: //SINT64_LIST_PACKED:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), Collections.emptyList());
+          break;
+        case 25: //STRING_LIST:
+          UnsafeUtil.putObject(msg, offset(typeAndOffset), LazyStringArrayList.EMPTY);
+          break;
+        default:
+          break;
+      }
+    }
+
+    // Initialize the base class fields.
+    SchemaUtil.initBaseClassFields(msg);
+    return msg;
+  }
+}

+ 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
+public 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, lenght=%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;
+      }
+    };
+  }
+}

+ 117 - 0
java/core/src/main/java/com/google/protobuf/BinaryProtocolUtil.java

@@ -0,0 +1,117 @@
+// 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;
+
+/** Common methods and constants for the binary protobuf protocol. */
+@ExperimentalApi
+final class BinaryProtocolUtil {
+  private BinaryProtocolUtil() {}
+
+  private static final int TAG_TYPE_BITS = 3;
+  private static final int TAG_TYPE_MASK = (1 << TAG_TYPE_BITS) - 1;
+
+  static final byte WIRETYPE_VARINT = 0;
+  static final byte WIRETYPE_FIXED64 = 1;
+  static final byte WIRETYPE_LENGTH_DELIMITED = 2;
+  static final byte WIRETYPE_START_GROUP = 3;
+  static final byte WIRETYPE_END_GROUP = 4;
+  static final byte WIRETYPE_FIXED32 = 5;
+  static final byte MAX_VARINT32_SIZE = 5;
+  static final byte MAX_VARINT64_SIZE = 10;
+  static final byte FIXED32_SIZE = 4;
+  static final byte FIXED64_SIZE = 8;
+
+  static int tagFor(int fieldNumber, byte wireType) {
+    return (fieldNumber << TAG_TYPE_BITS) | wireType;
+  }
+
+  static int getFieldNumber(final int tag) {
+    return tag >>> TAG_TYPE_BITS;
+  }
+
+  static int getWireType(final int tag) {
+    return tag & TAG_TYPE_MASK;
+  }
+
+  /**
+   * Decode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be
+   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
+   * to be varint encoded, thus always taking 10 bytes on the wire.)
+   *
+   * @param n An unsigned 32-bit integer, stored in a signed int because Java has no explicit
+   *     unsigned support.
+   * @return A signed 32-bit integer.
+   */
+  static int decodeZigZag32(final int n) {
+    return (n >>> 1) ^ -(n & 1);
+  }
+
+  /**
+   * Decode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
+   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
+   * to be varint encoded, thus always taking 10 bytes on the wire.)
+   *
+   * @param n An unsigned 64-bit integer, stored in a signed int because Java has no explicit
+   *     unsigned support.
+   * @return A signed 64-bit integer.
+   */
+  static long decodeZigZag64(final long n) {
+    return (n >>> 1) ^ -(n & 1);
+  }
+
+  /**
+   * Encode a ZigZag-encoded 32-bit value. ZigZag encodes signed integers into values that can be
+   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
+   * to be varint encoded, thus always taking 10 bytes on the wire.)
+   *
+   * @param n A signed 32-bit integer.
+   * @return An unsigned 32-bit integer, stored in a signed int because Java has no explicit
+   *     unsigned support.
+   */
+  static int encodeZigZag32(final int n) {
+    // Note:  the right-shift must be arithmetic
+    return (n << 1) ^ (n >> 31);
+  }
+
+  /**
+   * Encode a ZigZag-encoded 64-bit value. ZigZag encodes signed integers into values that can be
+   * efficiently encoded with varint. (Otherwise, negative values must be sign-extended to 64 bits
+   * to be varint encoded, thus always taking 10 bytes on the wire.)
+   *
+   * @param n A signed 64-bit integer.
+   * @return An unsigned 64-bit integer, stored in a signed int because Java has no explicit
+   *     unsigned support.
+   */
+  static long encodeZigZag64(final long n) {
+    // Note:  the right-shift must be arithmetic
+    return (n << 1) ^ (n >> 63);
+  }
+}

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

@@ -0,0 +1,1516 @@
+// 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.BinaryProtocolUtil.FIXED32_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.FIXED64_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_END_GROUP;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_FIXED32;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_FIXED64;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_LENGTH_DELIMITED;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_START_GROUP;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_VARINT;
+import static com.google.protobuf.BinaryProtocolUtil.decodeZigZag32;
+import static com.google.protobuf.BinaryProtocolUtil.decodeZigZag64;
+import static com.google.protobuf.BinaryProtocolUtil.getWireType;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * A {@link Reader} that reads from a buffer containing a message serialized with the binary
+ * protocol.
+ */
+@ExperimentalApi
+public 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();
+
+  /**
+   * 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 BinaryProtocolUtil.getFieldNumber(tag);
+    }
+
+    @Override
+    public boolean skipField() throws IOException {
+      if (isAtEnd() || tag == endGroupTag) {
+        return false;
+      }
+
+      switch (getWireType(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;
+        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 {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      final int size = readVarint32();
+      if (size == 0) {
+        return "";
+      }
+
+      requireBytes(size);
+      // TODO(nathanmittler): need to make this an option.
+      if (!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) throws IOException {
+      requireWireType(WIRETYPE_LENGTH_DELIMITED);
+      return readMessage(Protobuf.getInstance().schemaFor(clazz));
+    }
+
+    private <T> T readMessage(Schema<T> schema) 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);
+
+        if (pos != newLimit) {
+          throw InvalidProtocolBufferException.parseFailure();
+        }
+        return message;
+      } finally {
+        // Restore the limit.
+        limit = prevLimit;
+      }
+    }
+
+    @Override
+    public <T> T readGroup(Class<T> clazz) throws IOException {
+      requireWireType(WIRETYPE_START_GROUP);
+      return readGroup(Protobuf.getInstance().schemaFor(clazz));
+    }
+
+    private <T> T readGroup(Schema<T> schema) throws IOException {
+      int prevEndGroupTag = endGroupTag;
+      endGroupTag =
+          BinaryProtocolUtil.tagFor(BinaryProtocolUtil.getFieldNumber(tag), WIRETYPE_END_GROUP);
+
+      try {
+        // Allocate and read the message.
+        T message = schema.newInstance();
+        schema.mergeFrom(message, this);
+
+        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 decodeZigZag32(readVarint32());
+    }
+
+    @Override
+    public long readSInt64() throws IOException {
+      requireWireType(WIRETYPE_VARINT);
+      return decodeZigZag64(readVarint64());
+    }
+
+    @Override
+    public void readDoubleList(List<Double> target) throws IOException {
+      if (target instanceof DoubleArrayList) {
+        DoubleArrayList plist = (DoubleArrayList) target;
+        switch (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readVarint64());
+            }
+            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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint64());
+            }
+            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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(readVarint64());
+            }
+            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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint64());
+            }
+            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 (getWireType(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(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 (getWireType(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(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addBoolean(readVarint32() != 0);
+            }
+            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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(readVarint32() != 0);
+            }
+            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 {
+      if (getWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+
+      while (true) {
+        target.add(readString());
+
+        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) throws IOException {
+      if (getWireType(tag) != WIRETYPE_LENGTH_DELIMITED) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+
+      final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+      final int listTag = tag;
+      while (true) {
+        target.add(readMessage(schema));
+
+        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) throws IOException {
+      if (getWireType(tag) != WIRETYPE_START_GROUP) {
+        throw InvalidProtocolBufferException.invalidWireType();
+      }
+
+      final Schema<T> schema = Protobuf.getInstance().schemaFor(targetType);
+      final int listTag = tag;
+      while (true) {
+        target.add(readGroup(schema));
+
+        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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addInt(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              plist.addLong(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 (getWireType(tag)) {
+          case WIRETYPE_LENGTH_DELIMITED:
+            final int bytes = readVarint32();
+            final int fieldEndPos = pos + bytes;
+            while (pos < fieldEndPos) {
+              target.add(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();
+        }
+      }
+    }
+
+    /** 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 requireBytes(int size) throws IOException {
+      if (size < 0 || size > (limit - pos)) {
+        throw InvalidProtocolBufferException.truncatedMessage();
+      }
+    }
+
+    private void requireWireType(int requiredWireType) throws IOException {
+      if (getWireType(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();
+      }
+    }
+  }
+}

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

@@ -0,0 +1,2804 @@
+// 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.BinaryProtocolUtil.FIXED32_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.FIXED64_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.MAX_VARINT32_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.MAX_VARINT64_SIZE;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_END_GROUP;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_FIXED32;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_FIXED64;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_LENGTH_DELIMITED;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_START_GROUP;
+import static com.google.protobuf.BinaryProtocolUtil.WIRETYPE_VARINT;
+import static com.google.protobuf.BinaryProtocolUtil.encodeZigZag32;
+import static com.google.protobuf.BinaryProtocolUtil.encodeZigZag64;
+import static com.google.protobuf.BinaryProtocolUtil.tagFor;
+import static com.google.protobuf.Internal.checkNotNull;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayDeque;
+import java.util.List;
+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
+public 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;
+  }
+
+  /**
+   * 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) {
+    writeFixed32(fieldNumber, value);
+  }
+
+  @Override
+  public final void writeInt64(int fieldNumber, long value) {
+    writeUInt64(fieldNumber, value);
+  }
+
+  @Override
+  public final void writeSFixed64(int fieldNumber, long value) {
+    writeFixed64(fieldNumber, value);
+  }
+
+  @Override
+  public final void writeFloat(int fieldNumber, float value) {
+    writeFixed32(fieldNumber, Float.floatToRawIntBits(value));
+  }
+
+  @Override
+  public final void writeDouble(int fieldNumber, double value) {
+    writeFixed64(fieldNumber, Double.doubleToRawLongBits(value));
+  }
+
+  @Override
+  public final void writeEnum(int fieldNumber, int value) {
+    writeInt32(fieldNumber, value);
+  }
+
+  @Override
+  public final void writeInt32List(int fieldNumber, List<Integer> list, boolean packed) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    writeUInt64List(fieldNumber, list, packed);
+  }
+
+  @Override
+  public final void writeUInt64List(int fieldNumber, List<Long> list, boolean packed) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    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) {
+    writeInt32List(fieldNumber, list, packed);
+  }
+
+  @Override
+  public final void writeBoolList(int fieldNumber, List<Boolean> list, boolean packed) {
+    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) {
+    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) {
+    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) {
+    for (int i = list.size() - 1; i >= 0; i--) {
+      writeString(fieldNumber, list.get(i));
+    }
+  }
+
+  @Override
+  public final void writeBytesList(int fieldNumber, List<ByteString> list) {
+    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) {
+    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) {
+    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) {
+    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) {
+    writeFixed32List(fieldNumber, list, packed);
+  }
+
+  @Override
+  public final void writeSFixed64List(int fieldNumber, List<Long> list, boolean packed) {
+    writeFixed64List(fieldNumber, list, packed);
+  }
+
+  @Override
+  public final void writeSInt32List(int fieldNumber, List<Integer> list, boolean packed) {
+    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) {
+    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) {
+    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) {
+    if (list instanceof LongArrayList) {
+      writeSInt64List_Internal(fieldNumber, (LongArrayList) list, packed);
+    } else {
+      writeSInt64List_Internal(fieldNumber, list, packed);
+    }
+  }
+
+  private final void writeSInt64List_Internal(int fieldNumber, List<Long> list, boolean packed) {
+    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) {
+    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) {
+    for (int i = list.size() - 1; i >= 0; i--) {
+      writeMessage(fieldNumber, list.get(i));
+    }
+  }
+
+  @Override
+  public final void writeGroupList(int fieldNumber, List<?> list) {
+    for (int i = list.size() - 1; i >= 0; i--) {
+      writeGroup(fieldNumber, list.get(i));
+    }
+  }
+
+  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, byte 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) {
+      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) {
+      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 writeGroup(int fieldNumber, Object value) {
+      writeTag(fieldNumber, WIRETYPE_END_GROUP);
+      Protobuf.getInstance().writeTo(value, this);
+      writeTag(fieldNumber, WIRETYPE_START_GROUP);
+    }
+
+    @Override
+    void writeInt32(int value) {
+      if (value >= 0) {
+        writeVarint32(value);
+      } else {
+        writeVarint64(value);
+      }
+    }
+
+    @Override
+    void writeSInt32(int value) {
+      writeVarint32(encodeZigZag32(value));
+    }
+
+    @Override
+    void writeSInt64(long value) {
+      writeVarint64(encodeZigZag64(value));
+    }
+
+    @Override
+    void writeBool(boolean value) {
+      write((byte) (value ? 1 : 0));
+    }
+
+    @Override
+    void writeTag(int fieldNumber, byte wireType) {
+      writeVarint32(tagFor(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://wikis.oracle.com/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 - UnsafeUtil.getArrayBaseOffset());
+    }
+
+    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();
+      long byteArrayOffset = UnsafeUtil.getArrayBaseOffset();
+      this.limit = byteArrayOffset + arrayOffset + allocatedBuffer.limit();
+      this.offset = byteArrayOffset + 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) {
+      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 writeGroup(int fieldNumber, Object value) {
+      writeTag(fieldNumber, WIRETYPE_END_GROUP);
+      Protobuf.getInstance().writeTo(value, this);
+      writeTag(fieldNumber, WIRETYPE_START_GROUP);
+    }
+
+    @Override
+    void writeInt32(int value) {
+      if (value >= 0) {
+        writeVarint32(value);
+      } else {
+        writeVarint64(value);
+      }
+    }
+
+    @Override
+    void writeSInt32(int value) {
+      writeVarint32(encodeZigZag32(value));
+    }
+
+    @Override
+    void writeSInt64(long value) {
+      writeVarint64(encodeZigZag64(value));
+    }
+
+    @Override
+    void writeBool(boolean value) {
+      write((byte) (value ? 1 : 0));
+    }
+
+    @Override
+    void writeTag(int fieldNumber, byte wireType) {
+      writeVarint32(tagFor(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://wikis.oracle.com/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) {
+      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 writeGroup(int fieldNumber, Object value) {
+      writeTag(fieldNumber, WIRETYPE_END_GROUP);
+      Protobuf.getInstance().writeTo(value, this);
+      writeTag(fieldNumber, WIRETYPE_START_GROUP);
+    }
+
+    @Override
+    void writeInt32(int value) {
+      if (value >= 0) {
+        writeVarint32(value);
+      } else {
+        writeVarint64(value);
+      }
+    }
+
+    @Override
+    void writeSInt32(int value) {
+      writeVarint32(encodeZigZag32(value));
+    }
+
+    @Override
+    void writeSInt64(long value) {
+      writeVarint64(encodeZigZag64(value));
+    }
+
+    @Override
+    void writeBool(boolean value) {
+      write((byte) (value ? 1 : 0));
+    }
+
+    @Override
+    void writeTag(int fieldNumber, byte wireType) {
+      writeVarint32(tagFor(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://wikis.oracle.com/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) {
+      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 writeGroup(int fieldNumber, Object value) {
+      writeTag(fieldNumber, WIRETYPE_END_GROUP);
+      Protobuf.getInstance().writeTo(value, this);
+      writeTag(fieldNumber, WIRETYPE_START_GROUP);
+    }
+
+    @Override
+    void writeInt32(int value) {
+      if (value >= 0) {
+        writeVarint32(value);
+      } else {
+        writeVarint64(value);
+      }
+    }
+
+    @Override
+    void writeSInt32(int value) {
+      writeVarint32(encodeZigZag32(value));
+    }
+
+    @Override
+    void writeSInt64(long value) {
+      writeVarint64(encodeZigZag64(value));
+    }
+
+    @Override
+    void writeBool(boolean value) {
+      write((byte) (value ? 1 : 0));
+    }
+
+    @Override
+    void writeTag(int fieldNumber, byte wireType) {
+      writeVarint32(tagFor(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://wikis.oracle.com/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;
+      long sourceOffset = UnsafeUtil.addressOffset(value);
+      long sourcePos = sourceOffset + value.position();
+      UnsafeUtil.copyMemory(sourcePos, 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();
+        return;
+      }
+
+      pos -= length;
+      long sourceOffset = UnsafeUtil.addressOffset(value);
+      long sourcePos = sourceOffset + value.position();
+      UnsafeUtil.copyMemory(sourcePos, pos + 1, length);
+    }
+
+    @Override
+    void requireSpace(int size) {
+      if (spaceLeft() < size) {
+        nextBuffer(size);
+      }
+    }
+  }
+}

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

@@ -0,0 +1,71 @@
+// 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
+public 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);
+}
+

+ 18 - 3
java/core/src/main/java/com/google/protobuf/ByteString.java

@@ -28,6 +28,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 
+
 package com.google.protobuf;
 package com.google.protobuf;
 
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayInputStream;
@@ -52,9 +53,9 @@ import java.util.NoSuchElementException;
 
 
 /**
 /**
  * Immutable sequence of bytes.  Substring is supported by sharing the reference
  * Immutable sequence of bytes.  Substring is supported by sharing the reference
- * to the immutable underlying bytes.  Concatenation is likewise supported
- * without copying (long strings) by building a tree of pieces in
- * {@link RopeByteString}.
+ * to the immutable underlying bytes, as with {@link String}.  Concatenation is
+ * likewise supported without copying (long strings) by building a tree of
+ * pieces in {@link RopeByteString}.
  * <p>
  * <p>
  * Like {@link String}, the contents of a {@link ByteString} can never be
  * Like {@link String}, the contents of a {@link ByteString} can never be
  * observed to change, not even in the presence of a data race or incorrect
  * observed to change, not even in the presence of a data race or incorrect
@@ -691,6 +692,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
    * Constructs a read-only {@code java.nio.ByteBuffer} whose content
@@ -833,6 +844,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
      * Check equality of the substring of given length of this object starting at

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

@@ -0,0 +1,264 @@
+// 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.forPresenceCheckedField;
+
+import com.google.protobuf.Descriptors.Descriptor;
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.List;
+
+/** A factory for message info based on protobuf descriptors for a {@link GeneratedMessage}. */
+@ExperimentalApi
+public final class DescriptorMessageInfoFactory implements MessageInfoFactory {
+  private static final String GET_DEFAULT_INSTANCE_METHOD_NAME = "getDefaultInstance";
+  private static final DescriptorMessageInfoFactory instance = new DescriptorMessageInfoFactory();
+
+  // Disallow construction - it's a singleton.
+  private DescriptorMessageInfoFactory() {
+  }
+
+  public static DescriptorMessageInfoFactory getInstance() {
+    return instance;
+  }
+
+  @Override
+  public MessageInfo messageInfoFor(Class<?> messageType) {
+    if (!GeneratedMessage.class.isAssignableFrom(messageType)) {
+      throw new IllegalArgumentException("Only generated protobuf messages are supported");
+    }
+
+    return convert(messageType, descriptorForType(messageType));
+  }
+
+  private static Descriptor descriptorForType(Class<?> messageType) {
+    try {
+      Method method = messageType.getDeclaredMethod(GET_DEFAULT_INSTANCE_METHOD_NAME);
+      GeneratedMessage message = (GeneratedMessage) method.invoke(null);
+      return message.getDescriptorForType();
+    } catch (Exception e) {
+      throw new IllegalArgumentException(
+          "Unable to get default instance for message class " + messageType.getName(), e);
+    }
+  }
+
+  private static MessageInfo convert(Class<?> messageType, Descriptor desc) {
+    switch (desc.getFile().getSyntax()) {
+      case PROTO2:
+        return convertProto2(messageType, desc);
+      case PROTO3:
+        return convertProto3(messageType, desc);
+      default:
+        throw new IllegalArgumentException("Unsupported syntax: " + desc.getFile().getSyntax());
+    }
+  }
+
+  private static MessageInfo convertProto2(Class<?> messageType, Descriptor desc) {
+    List<FieldDescriptor> fieldDescriptors = desc.getFields();
+    MessageInfo.Builder builder = MessageInfo.newBuilder(fieldDescriptors.size());
+    builder.withSyntax(ProtoSyntax.PROTO2);
+
+    int bitFieldIndex = 0;
+    int presenceMask = 1;
+    Field bitField = bitField(messageType, bitFieldIndex++);
+
+    // 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) {
+      FieldDescriptor fd = fieldDescriptors.get(i);
+      Field field = field(messageType, fd);
+      int number = fd.getNumber();
+      FieldType type = getFieldType(fd);
+
+      if (fd.isRepeated()) {
+        // Repeated fields are not presence-checked.
+        builder.add(forField(field, number, type));
+        continue;
+      }
+
+      // It's a presence-checked field.
+      builder.add(forPresenceCheckedField(field, number, type, bitField, presenceMask));
+
+      // Update the presence mask/bitfield
+      presenceMask <<= 1;
+      if (presenceMask == 0) {
+        // We've assigned all of the bits in the current bitField. Advance to the next one.
+        bitField = bitField(messageType, bitFieldIndex++);
+      }
+    }
+
+    return builder.build();
+  }
+
+  private static MessageInfo convertProto3(Class<?> messageType, Descriptor desc) {
+    List<FieldDescriptor> fieldDescriptors = desc.getFields();
+    MessageInfo.Builder builder = MessageInfo.newBuilder(fieldDescriptors.size());
+    builder.withSyntax(ProtoSyntax.PROTO3);
+    for (int i = 0; i < fieldDescriptors.size(); ++i) {
+      FieldDescriptor fd = fieldDescriptors.get(i);
+      builder.add(forField(field(messageType, fd), fd.getNumber(), getFieldType(fd)));
+    }
+
+    return builder.build();
+  }
+
+  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:
+        // TODO(nathanmittler): Add support for maps.
+        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 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());
+    }
+  }
+
+  // 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 getFieldName(FieldDescriptor fd) {
+    String snakeCase =
+        fd.getType() == FieldDescriptor.Type.GROUP ? fd.getMessageType().getName() : fd.getName();
+    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 (capNext) {
+        sb.append(Character.toUpperCase(next));
+        capNext = false;
+      } else if (ctr == 0) {
+        sb.append(Character.toLowerCase(next));
+      } else {
+        sb.append(next);
+      }
+    }
+    sb.append('_');
+    return sb.toString();
+  }
+}

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

@@ -0,0 +1,175 @@
+// 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.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/** Information for a single field in a protobuf message class. */
+@ExperimentalApi
+public final class FieldInfo implements Comparable<FieldInfo> {
+  private final Field field;
+  private final FieldType type;
+  private final int fieldNumber;
+  private final Field presenceField;
+  private final int presenceMask;
+
+  /** Constructs a new descriptor for a field. */
+  public static FieldInfo forField(Field field, int fieldNumber, FieldType fieldType) {
+    return new FieldInfo(field, fieldNumber, fieldType, null, 0);
+  }
+
+  /** Constructor for a field that uses a presence bit field (i.e. proto2 only). */
+  public static FieldInfo forPresenceCheckedField(
+      Field field, int fieldNumber, FieldType fieldType, Field presenceField, int presenceMask) {
+    checkNotNull(presenceField, "presenceField");
+    return new FieldInfo(field, fieldNumber, fieldType, presenceField, presenceMask);
+  }
+
+  private FieldInfo(
+      Field field, int fieldNumber, FieldType type, Field presenceField, int presenceMask) {
+    if (fieldNumber <= 0) {
+      throw new IllegalArgumentException("fieldNumber must be positive: " + fieldNumber);
+    }
+    if (presenceField != null && !isExactlyOneBitSet(presenceMask)) {
+      throw new IllegalArgumentException(
+          "presenceMask must have exactly one bit set: " + presenceMask);
+    }
+    this.field = checkNotNull(field, "field");
+    this.type = checkNotNull(type, "type");
+    this.fieldNumber = fieldNumber;
+    this.presenceField = presenceField;
+    this.presenceMask = presenceMask;
+  }
+
+  /** 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 field number for the field. */
+  public int getFieldNumber() {
+    return fieldNumber;
+  }
+
+  @Override
+  public int compareTo(FieldInfo o) {
+    return fieldNumber - o.fieldNumber;
+  }
+
+  /**
+   * For list fields, returns the generic argument that represents the type stored in the list. For
+   * non-list fields, returns {@code null}.
+   */
+  public Class<?> getListElementType() {
+    if (!type.isList()) {
+      return null;
+    }
+
+    Type genericType = field.getGenericType();
+    if (!(genericType instanceof ParameterizedType)) {
+      throw new IllegalStateException(
+          "Cannot determine parameterized type for list field " + fieldNumber);
+    }
+
+    Type type = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
+    return (Class<?>) type;
+  }
+
+  /** Gets the presence bit field. Only valid for unary fields. For lists, returns {@code null}. */
+  public Field getPresenceField() {
+    return presenceField;
+  }
+
+  /**
+   * 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;
+  }
+
+  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 Builder() {}
+
+    public Builder withField(Field field) {
+      this.field = field;
+      return this;
+    }
+
+    public Builder withType(FieldType type) {
+      this.type = type;
+      return this;
+    }
+
+    public Builder withFieldNumber(int fieldNumber) {
+      this.fieldNumber = fieldNumber;
+      return this;
+    }
+
+    public Builder withPresenceField(Field presenceField) {
+      this.presenceField = checkNotNull(presenceField, "presenceField");
+      return this;
+    }
+
+    public Builder withPresenceMask(int presenceMask) {
+      this.presenceMask = presenceMask;
+      return this;
+    }
+
+    public FieldInfo build() {
+      return new FieldInfo(field, fieldNumber, type, presenceField, presenceMask);
+    }
+  }
+
+  private static boolean isExactlyOneBitSet(int value) {
+    return value != 0 && (value & (value - 1)) == 0;
+  }
+}

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

@@ -0,0 +1,535 @@
+// 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, JavaType.VOID),
+  FLOAT(1, Collection.SCALAR, JavaType.FLOAT, JavaType.VOID),
+  INT64(2, Collection.SCALAR, JavaType.LONG, JavaType.VOID),
+  UINT64(3, Collection.SCALAR, JavaType.LONG, JavaType.VOID),
+  INT32(4, Collection.SCALAR, JavaType.INT, JavaType.VOID),
+  FIXED64(5, Collection.SCALAR, JavaType.LONG, JavaType.VOID),
+  FIXED32(6, Collection.SCALAR, JavaType.INT, JavaType.VOID),
+  BOOL(7, Collection.SCALAR, JavaType.BOOLEAN, JavaType.VOID),
+  STRING(8, Collection.SCALAR, JavaType.STRING, JavaType.VOID),
+  MESSAGE(9, Collection.SCALAR, JavaType.MESSAGE, JavaType.VOID),
+  BYTES(10, Collection.SCALAR, JavaType.BYTE_STRING, JavaType.VOID),
+  UINT32(11, Collection.SCALAR, JavaType.INT, JavaType.VOID),
+  ENUM(12, Collection.SCALAR, JavaType.ENUM, JavaType.VOID),
+  SFIXED32(13, Collection.SCALAR, JavaType.INT, JavaType.VOID),
+  SFIXED64(14, Collection.SCALAR, JavaType.LONG, JavaType.VOID),
+  SINT32(15, Collection.SCALAR, JavaType.INT, JavaType.VOID),
+  SINT64(16, Collection.SCALAR, JavaType.LONG, JavaType.VOID),
+  DOUBLE_LIST(17, Collection.VECTOR, JavaType.DOUBLE, JavaType.VOID),
+  FLOAT_LIST(18, Collection.VECTOR, JavaType.FLOAT, JavaType.VOID),
+  INT64_LIST(19, Collection.VECTOR, JavaType.LONG, JavaType.VOID),
+  UINT64_LIST(20, Collection.VECTOR, JavaType.LONG, JavaType.VOID),
+  INT32_LIST(21, Collection.VECTOR, JavaType.INT, JavaType.VOID),
+  FIXED64_LIST(22, Collection.VECTOR, JavaType.LONG, JavaType.VOID),
+  FIXED32_LIST(23, Collection.VECTOR, JavaType.INT, JavaType.VOID),
+  BOOL_LIST(24, Collection.VECTOR, JavaType.BOOLEAN, JavaType.VOID),
+  STRING_LIST(25, Collection.VECTOR, JavaType.STRING, JavaType.VOID),
+  MESSAGE_LIST(26, Collection.VECTOR, JavaType.MESSAGE, JavaType.VOID),
+  BYTES_LIST(27, Collection.VECTOR, JavaType.BYTE_STRING, JavaType.VOID),
+  UINT32_LIST(28, Collection.VECTOR, JavaType.INT, JavaType.VOID),
+  ENUM_LIST(29, Collection.VECTOR, JavaType.ENUM, JavaType.VOID),
+  SFIXED32_LIST(30, Collection.VECTOR, JavaType.INT, JavaType.VOID),
+  SFIXED64_LIST(31, Collection.VECTOR, JavaType.LONG, JavaType.VOID),
+  SINT32_LIST(32, Collection.VECTOR, JavaType.INT, JavaType.VOID),
+  SINT64_LIST(33, Collection.VECTOR, JavaType.LONG, JavaType.VOID),
+  DOUBLE_LIST_PACKED(34, Collection.PACKED_VECTOR, JavaType.DOUBLE, JavaType.VOID),
+  FLOAT_LIST_PACKED(35, Collection.PACKED_VECTOR, JavaType.FLOAT, JavaType.VOID),
+  INT64_LIST_PACKED(36, Collection.PACKED_VECTOR, JavaType.LONG, JavaType.VOID),
+  UINT64_LIST_PACKED(37, Collection.PACKED_VECTOR, JavaType.LONG, JavaType.VOID),
+  INT32_LIST_PACKED(38, Collection.PACKED_VECTOR, JavaType.INT, JavaType.VOID),
+  FIXED64_LIST_PACKED(39, Collection.PACKED_VECTOR, JavaType.LONG, JavaType.VOID),
+  FIXED32_LIST_PACKED(40, Collection.PACKED_VECTOR, JavaType.INT, JavaType.VOID),
+  BOOL_LIST_PACKED(41, Collection.PACKED_VECTOR, JavaType.BOOLEAN, JavaType.VOID),
+  UINT32_LIST_PACKED(42, Collection.PACKED_VECTOR, JavaType.INT, JavaType.VOID),
+  ENUM_LIST_PACKED(43, Collection.PACKED_VECTOR, JavaType.ENUM, JavaType.VOID),
+  SFIXED32_LIST_PACKED(44, Collection.PACKED_VECTOR, JavaType.INT, JavaType.VOID),
+  SFIXED64_LIST_PACKED(45, Collection.PACKED_VECTOR, JavaType.LONG, JavaType.VOID),
+  SINT32_LIST_PACKED(46, Collection.PACKED_VECTOR, JavaType.INT, JavaType.VOID),
+  SINT64_LIST_PACKED(47, Collection.PACKED_VECTOR, JavaType.LONG, JavaType.VOID),
+  INT32_TO_INT32_MAP(48, Collection.MAP, JavaType.INT, JavaType.INT),
+  INT32_TO_INT64_MAP(49, Collection.MAP, JavaType.INT, JavaType.LONG),
+  INT32_TO_UINT32_MAP(50, Collection.MAP, JavaType.INT, JavaType.INT),
+  INT32_TO_UINT64_MAP(51, Collection.MAP, JavaType.INT, JavaType.LONG),
+  INT32_TO_SINT32_MAP(52, Collection.MAP, JavaType.INT, JavaType.INT),
+  INT32_TO_SINT64_MAP(53, Collection.MAP, JavaType.INT, JavaType.LONG),
+  INT32_TO_FIXED32_MAP(54, Collection.MAP, JavaType.INT, JavaType.INT),
+  INT32_TO_FIXED64_MAP(55, Collection.MAP, JavaType.INT, JavaType.LONG),
+  INT32_TO_SFIXED32_MAP(56, Collection.MAP, JavaType.INT, JavaType.INT),
+  INT32_TO_SFIXED64_MAP(57, Collection.MAP, JavaType.INT, JavaType.LONG),
+  INT32_TO_BOOL_MAP(58, Collection.MAP, JavaType.INT, JavaType.BOOLEAN),
+  INT32_TO_STRING_MAP(59, Collection.MAP, JavaType.INT, JavaType.STRING),
+  INT32_TO_ENUM_MAP(60, Collection.MAP, JavaType.INT, JavaType.ENUM),
+  INT32_TO_MESSAGE_MAP(61, Collection.MAP, JavaType.INT, JavaType.MESSAGE),
+  INT32_TO_BYTES_MAP(62, Collection.MAP, JavaType.INT, JavaType.BYTE_STRING),
+  INT32_TO_DOUBLE_MAP(63, Collection.MAP, JavaType.INT, JavaType.DOUBLE),
+  INT32_TO_FLOAT_MAP(64, Collection.MAP, JavaType.INT, JavaType.FLOAT),
+  INT64_TO_INT32_MAP(65, Collection.MAP, JavaType.LONG, JavaType.INT),
+  INT64_TO_INT64_MAP(66, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  INT64_TO_UINT32_MAP(67, Collection.MAP, JavaType.LONG, JavaType.INT),
+  INT64_TO_UINT64_MAP(68, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  INT64_TO_SINT32_MAP(69, Collection.MAP, JavaType.LONG, JavaType.INT),
+  INT64_TO_SINT64_MAP(70, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  INT64_TO_FIXED32_MAP(71, Collection.MAP, JavaType.LONG, JavaType.INT),
+  INT64_TO_FIXED64_MAP(72, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  INT64_TO_SFIXED32_MAP(73, Collection.MAP, JavaType.LONG, JavaType.INT),
+  INT64_TO_SFIXED64_MAP(74, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  INT64_TO_BOOL_MAP(75, Collection.MAP, JavaType.LONG, JavaType.BOOLEAN),
+  INT64_TO_STRING_MAP(76, Collection.MAP, JavaType.LONG, JavaType.STRING),
+  INT64_TO_ENUM_MAP(77, Collection.MAP, JavaType.LONG, JavaType.ENUM),
+  INT64_TO_MESSAGE_MAP(78, Collection.MAP, JavaType.LONG, JavaType.MESSAGE),
+  INT64_TO_BYTES_MAP(79, Collection.MAP, JavaType.LONG, JavaType.BYTE_STRING),
+  INT64_TO_DOUBLE_MAP(80, Collection.MAP, JavaType.LONG, JavaType.DOUBLE),
+  INT64_TO_FLOAT_MAP(81, Collection.MAP, JavaType.LONG, JavaType.FLOAT),
+  UINT32_TO_INT32_MAP(82, Collection.MAP, JavaType.INT, JavaType.INT),
+  UINT32_TO_INT64_MAP(83, Collection.MAP, JavaType.INT, JavaType.LONG),
+  UINT32_TO_UINT32_MAP(84, Collection.MAP, JavaType.INT, JavaType.INT),
+  UINT32_TO_UINT64_MAP(85, Collection.MAP, JavaType.INT, JavaType.LONG),
+  UINT32_TO_SINT32_MAP(86, Collection.MAP, JavaType.INT, JavaType.INT),
+  UINT32_TO_SINT64_MAP(87, Collection.MAP, JavaType.INT, JavaType.LONG),
+  UINT32_TO_FIXED32_MAP(88, Collection.MAP, JavaType.INT, JavaType.INT),
+  UINT32_TO_FIXED64_MAP(89, Collection.MAP, JavaType.INT, JavaType.LONG),
+  UINT32_TO_SFIXED32_MAP(90, Collection.MAP, JavaType.INT, JavaType.INT),
+  UINT32_TO_SFIXED64_MAP(91, Collection.MAP, JavaType.INT, JavaType.LONG),
+  UINT32_TO_BOOL_MAP(92, Collection.MAP, JavaType.INT, JavaType.BOOLEAN),
+  UINT32_TO_STRING_MAP(93, Collection.MAP, JavaType.INT, JavaType.STRING),
+  UINT32_TO_ENUM_MAP(94, Collection.MAP, JavaType.INT, JavaType.ENUM),
+  UINT32_TO_MESSAGE_MAP(95, Collection.MAP, JavaType.INT, JavaType.MESSAGE),
+  UINT32_TO_BYTES_MAP(96, Collection.MAP, JavaType.INT, JavaType.BYTE_STRING),
+  UINT32_TO_DOUBLE_MAP(97, Collection.MAP, JavaType.INT, JavaType.DOUBLE),
+  UINT32_TO_FLOAT_MAP(98, Collection.MAP, JavaType.INT, JavaType.FLOAT),
+  UINT64_TO_INT32_MAP(99, Collection.MAP, JavaType.LONG, JavaType.INT),
+  UINT64_TO_INT64_MAP(100, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  UINT64_TO_UINT32_MAP(101, Collection.MAP, JavaType.LONG, JavaType.INT),
+  UINT64_TO_UINT64_MAP(102, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  UINT64_TO_SINT32_MAP(103, Collection.MAP, JavaType.LONG, JavaType.INT),
+  UINT64_TO_SINT64_MAP(104, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  UINT64_TO_FIXED32_MAP(105, Collection.MAP, JavaType.LONG, JavaType.INT),
+  UINT64_TO_FIXED64_MAP(106, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  UINT64_TO_SFIXED32_MAP(107, Collection.MAP, JavaType.LONG, JavaType.INT),
+  UINT64_TO_SFIXED64_MAP(108, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  UINT64_TO_BOOL_MAP(109, Collection.MAP, JavaType.LONG, JavaType.BOOLEAN),
+  UINT64_TO_STRING_MAP(110, Collection.MAP, JavaType.LONG, JavaType.STRING),
+  UINT64_TO_ENUM_MAP(111, Collection.MAP, JavaType.LONG, JavaType.ENUM),
+  UINT64_TO_MESSAGE_MAP(112, Collection.MAP, JavaType.LONG, JavaType.MESSAGE),
+  UINT64_TO_BYTES_MAP(113, Collection.MAP, JavaType.LONG, JavaType.BYTE_STRING),
+  UINT64_TO_DOUBLE_MAP(114, Collection.MAP, JavaType.LONG, JavaType.DOUBLE),
+  UINT64_TO_FLOAT_MAP(115, Collection.MAP, JavaType.LONG, JavaType.FLOAT),
+  SINT32_TO_INT32_MAP(116, Collection.MAP, JavaType.INT, JavaType.INT),
+  SINT32_TO_INT64_MAP(117, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SINT32_TO_UINT32_MAP(118, Collection.MAP, JavaType.INT, JavaType.INT),
+  SINT32_TO_UINT64_MAP(119, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SINT32_TO_SINT32_MAP(120, Collection.MAP, JavaType.INT, JavaType.INT),
+  SINT32_TO_SINT64_MAP(121, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SINT32_TO_FIXED32_MAP(122, Collection.MAP, JavaType.INT, JavaType.INT),
+  SINT32_TO_FIXED64_MAP(123, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SINT32_TO_SFIXED32_MAP(124, Collection.MAP, JavaType.INT, JavaType.INT),
+  SINT32_TO_SFIXED64_MAP(125, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SINT32_TO_BOOL_MAP(126, Collection.MAP, JavaType.INT, JavaType.BOOLEAN),
+  SINT32_TO_STRING_MAP(127, Collection.MAP, JavaType.INT, JavaType.STRING),
+  SINT32_TO_ENUM_MAP(128, Collection.MAP, JavaType.INT, JavaType.ENUM),
+  SINT32_TO_MESSAGE_MAP(129, Collection.MAP, JavaType.INT, JavaType.MESSAGE),
+  SINT32_TO_BYTES_MAP(130, Collection.MAP, JavaType.INT, JavaType.BYTE_STRING),
+  SINT32_TO_DOUBLE_MAP(131, Collection.MAP, JavaType.INT, JavaType.DOUBLE),
+  SINT32_TO_FLOAT_MAP(132, Collection.MAP, JavaType.INT, JavaType.FLOAT),
+  SINT64_TO_INT32_MAP(133, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SINT64_TO_INT64_MAP(134, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SINT64_TO_UINT32_MAP(135, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SINT64_TO_UINT64_MAP(136, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SINT64_TO_SINT32_MAP(137, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SINT64_TO_SINT64_MAP(138, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SINT64_TO_FIXED32_MAP(139, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SINT64_TO_FIXED64_MAP(140, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SINT64_TO_SFIXED32_MAP(141, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SINT64_TO_SFIXED64_MAP(142, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SINT64_TO_BOOL_MAP(143, Collection.MAP, JavaType.LONG, JavaType.BOOLEAN),
+  SINT64_TO_STRING_MAP(144, Collection.MAP, JavaType.LONG, JavaType.STRING),
+  SINT64_TO_ENUM_MAP(145, Collection.MAP, JavaType.LONG, JavaType.ENUM),
+  SINT64_TO_MESSAGE_MAP(146, Collection.MAP, JavaType.LONG, JavaType.MESSAGE),
+  SINT64_TO_BYTES_MAP(147, Collection.MAP, JavaType.LONG, JavaType.BYTE_STRING),
+  SINT64_TO_DOUBLE_MAP(148, Collection.MAP, JavaType.LONG, JavaType.DOUBLE),
+  SINT64_TO_FLOAT_MAP(149, Collection.MAP, JavaType.LONG, JavaType.FLOAT),
+  FIXED32_TO_INT32_MAP(150, Collection.MAP, JavaType.INT, JavaType.INT),
+  FIXED32_TO_INT64_MAP(151, Collection.MAP, JavaType.INT, JavaType.LONG),
+  FIXED32_TO_UINT32_MAP(152, Collection.MAP, JavaType.INT, JavaType.INT),
+  FIXED32_TO_UINT64_MAP(153, Collection.MAP, JavaType.INT, JavaType.LONG),
+  FIXED32_TO_SINT32_MAP(154, Collection.MAP, JavaType.INT, JavaType.INT),
+  FIXED32_TO_SINT64_MAP(155, Collection.MAP, JavaType.INT, JavaType.LONG),
+  FIXED32_TO_FIXED32_MAP(156, Collection.MAP, JavaType.INT, JavaType.INT),
+  FIXED32_TO_FIXED64_MAP(157, Collection.MAP, JavaType.INT, JavaType.LONG),
+  FIXED32_TO_SFIXED32_MAP(158, Collection.MAP, JavaType.INT, JavaType.INT),
+  FIXED32_TO_SFIXED64_MAP(159, Collection.MAP, JavaType.INT, JavaType.LONG),
+  FIXED32_TO_BOOL_MAP(160, Collection.MAP, JavaType.INT, JavaType.BOOLEAN),
+  FIXED32_TO_STRING_MAP(161, Collection.MAP, JavaType.INT, JavaType.STRING),
+  FIXED32_TO_ENUM_MAP(162, Collection.MAP, JavaType.INT, JavaType.ENUM),
+  FIXED32_TO_MESSAGE_MAP(163, Collection.MAP, JavaType.INT, JavaType.MESSAGE),
+  FIXED32_TO_BYTES_MAP(164, Collection.MAP, JavaType.INT, JavaType.BYTE_STRING),
+  FIXED32_TO_DOUBLE_MAP(165, Collection.MAP, JavaType.INT, JavaType.DOUBLE),
+  FIXED32_TO_FLOAT_MAP(166, Collection.MAP, JavaType.INT, JavaType.FLOAT),
+  FIXED64_TO_INT32_MAP(167, Collection.MAP, JavaType.LONG, JavaType.INT),
+  FIXED64_TO_INT64_MAP(168, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  FIXED64_TO_UINT32_MAP(169, Collection.MAP, JavaType.LONG, JavaType.INT),
+  FIXED64_TO_UINT64_MAP(170, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  FIXED64_TO_SINT32_MAP(171, Collection.MAP, JavaType.LONG, JavaType.INT),
+  FIXED64_TO_SINT64_MAP(172, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  FIXED64_TO_FIXED32_MAP(173, Collection.MAP, JavaType.LONG, JavaType.INT),
+  FIXED64_TO_FIXED64_MAP(174, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  FIXED64_TO_SFIXED32_MAP(175, Collection.MAP, JavaType.LONG, JavaType.INT),
+  FIXED64_TO_SFIXED64_MAP(176, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  FIXED64_TO_BOOL_MAP(177, Collection.MAP, JavaType.LONG, JavaType.BOOLEAN),
+  FIXED64_TO_STRING_MAP(178, Collection.MAP, JavaType.LONG, JavaType.STRING),
+  FIXED64_TO_ENUM_MAP(179, Collection.MAP, JavaType.LONG, JavaType.ENUM),
+  FIXED64_TO_MESSAGE_MAP(180, Collection.MAP, JavaType.LONG, JavaType.MESSAGE),
+  FIXED64_TO_BYTES_MAP(181, Collection.MAP, JavaType.LONG, JavaType.BYTE_STRING),
+  FIXED64_TO_DOUBLE_MAP(182, Collection.MAP, JavaType.LONG, JavaType.DOUBLE),
+  FIXED64_TO_FLOAT_MAP(183, Collection.MAP, JavaType.LONG, JavaType.FLOAT),
+  SFIXED32_TO_INT32_MAP(184, Collection.MAP, JavaType.INT, JavaType.INT),
+  SFIXED32_TO_INT64_MAP(185, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SFIXED32_TO_UINT32_MAP(186, Collection.MAP, JavaType.INT, JavaType.INT),
+  SFIXED32_TO_UINT64_MAP(187, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SFIXED32_TO_SINT32_MAP(188, Collection.MAP, JavaType.INT, JavaType.INT),
+  SFIXED32_TO_SINT64_MAP(189, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SFIXED32_TO_FIXED32_MAP(190, Collection.MAP, JavaType.INT, JavaType.INT),
+  SFIXED32_TO_FIXED64_MAP(191, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SFIXED32_TO_SFIXED32_MAP(192, Collection.MAP, JavaType.INT, JavaType.INT),
+  SFIXED32_TO_SFIXED64_MAP(193, Collection.MAP, JavaType.INT, JavaType.LONG),
+  SFIXED32_TO_BOOL_MAP(194, Collection.MAP, JavaType.INT, JavaType.BOOLEAN),
+  SFIXED32_TO_STRING_MAP(195, Collection.MAP, JavaType.INT, JavaType.STRING),
+  SFIXED32_TO_ENUM_MAP(196, Collection.MAP, JavaType.INT, JavaType.ENUM),
+  SFIXED32_TO_MESSAGE_MAP(197, Collection.MAP, JavaType.INT, JavaType.MESSAGE),
+  SFIXED32_TO_BYTES_MAP(198, Collection.MAP, JavaType.INT, JavaType.BYTE_STRING),
+  SFIXED32_TO_DOUBLE_MAP(199, Collection.MAP, JavaType.INT, JavaType.DOUBLE),
+  SFIXED32_TO_FLOAT_MAP(200, Collection.MAP, JavaType.INT, JavaType.FLOAT),
+  SFIXED64_TO_INT32_MAP(201, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SFIXED64_TO_INT64_MAP(202, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SFIXED64_TO_UINT32_MAP(203, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SFIXED64_TO_UINT64_MAP(204, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SFIXED64_TO_SINT32_MAP(205, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SFIXED64_TO_SINT64_MAP(206, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SFIXED64_TO_FIXED32_MAP(207, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SFIXED64_TO_FIXED64_MAP(208, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SFIXED64_TO_SFIXED32_MAP(209, Collection.MAP, JavaType.LONG, JavaType.INT),
+  SFIXED64_TO_SFIXED64_MAP(210, Collection.MAP, JavaType.LONG, JavaType.LONG),
+  SFIXED64_TO_BOOL_MAP(211, Collection.MAP, JavaType.LONG, JavaType.BOOLEAN),
+  SFIXED64_TO_STRING_MAP(212, Collection.MAP, JavaType.LONG, JavaType.STRING),
+  SFIXED64_TO_ENUM_MAP(213, Collection.MAP, JavaType.LONG, JavaType.ENUM),
+  SFIXED64_TO_MESSAGE_MAP(214, Collection.MAP, JavaType.LONG, JavaType.MESSAGE),
+  SFIXED64_TO_BYTES_MAP(215, Collection.MAP, JavaType.LONG, JavaType.BYTE_STRING),
+  SFIXED64_TO_DOUBLE_MAP(216, Collection.MAP, JavaType.LONG, JavaType.DOUBLE),
+  SFIXED64_TO_FLOAT_MAP(217, Collection.MAP, JavaType.LONG, JavaType.FLOAT),
+  BOOL_TO_INT32_MAP(218, Collection.MAP, JavaType.BOOLEAN, JavaType.INT),
+  BOOL_TO_INT64_MAP(219, Collection.MAP, JavaType.BOOLEAN, JavaType.LONG),
+  BOOL_TO_UINT32_MAP(220, Collection.MAP, JavaType.BOOLEAN, JavaType.INT),
+  BOOL_TO_UINT64_MAP(221, Collection.MAP, JavaType.BOOLEAN, JavaType.LONG),
+  BOOL_TO_SINT32_MAP(222, Collection.MAP, JavaType.BOOLEAN, JavaType.INT),
+  BOOL_TO_SINT64_MAP(223, Collection.MAP, JavaType.BOOLEAN, JavaType.LONG),
+  BOOL_TO_FIXED32_MAP(224, Collection.MAP, JavaType.BOOLEAN, JavaType.INT),
+  BOOL_TO_FIXED64_MAP(225, Collection.MAP, JavaType.BOOLEAN, JavaType.LONG),
+  BOOL_TO_SFIXED32_MAP(226, Collection.MAP, JavaType.BOOLEAN, JavaType.INT),
+  BOOL_TO_SFIXED64_MAP(227, Collection.MAP, JavaType.BOOLEAN, JavaType.LONG),
+  BOOL_TO_BOOL_MAP(228, Collection.MAP, JavaType.BOOLEAN, JavaType.BOOLEAN),
+  BOOL_TO_STRING_MAP(229, Collection.MAP, JavaType.BOOLEAN, JavaType.STRING),
+  BOOL_TO_ENUM_MAP(230, Collection.MAP, JavaType.BOOLEAN, JavaType.ENUM),
+  BOOL_TO_MESSAGE_MAP(231, Collection.MAP, JavaType.BOOLEAN, JavaType.MESSAGE),
+  BOOL_TO_BYTES_MAP(232, Collection.MAP, JavaType.BOOLEAN, JavaType.BYTE_STRING),
+  BOOL_TO_DOUBLE_MAP(233, Collection.MAP, JavaType.BOOLEAN, JavaType.DOUBLE),
+  BOOL_TO_FLOAT_MAP(234, Collection.MAP, JavaType.BOOLEAN, JavaType.FLOAT),
+  STRING_TO_INT32_MAP(235, Collection.MAP, JavaType.STRING, JavaType.INT),
+  STRING_TO_INT64_MAP(236, Collection.MAP, JavaType.STRING, JavaType.LONG),
+  STRING_TO_UINT32_MAP(237, Collection.MAP, JavaType.STRING, JavaType.INT),
+  STRING_TO_UINT64_MAP(238, Collection.MAP, JavaType.STRING, JavaType.LONG),
+  STRING_TO_SINT32_MAP(239, Collection.MAP, JavaType.STRING, JavaType.INT),
+  STRING_TO_SINT64_MAP(240, Collection.MAP, JavaType.STRING, JavaType.LONG),
+  STRING_TO_FIXED32_MAP(241, Collection.MAP, JavaType.STRING, JavaType.INT),
+  STRING_TO_FIXED64_MAP(242, Collection.MAP, JavaType.STRING, JavaType.LONG),
+  STRING_TO_SFIXED32_MAP(243, Collection.MAP, JavaType.STRING, JavaType.INT),
+  STRING_TO_SFIXED64_MAP(244, Collection.MAP, JavaType.STRING, JavaType.LONG),
+  STRING_TO_BOOL_MAP(245, Collection.MAP, JavaType.STRING, JavaType.BOOLEAN),
+  STRING_TO_STRING_MAP(246, Collection.MAP, JavaType.STRING, JavaType.STRING),
+  STRING_TO_ENUM_MAP(247, Collection.MAP, JavaType.STRING, JavaType.ENUM),
+  STRING_TO_MESSAGE_MAP(248, Collection.MAP, JavaType.STRING, JavaType.MESSAGE),
+  STRING_TO_BYTES_MAP(249, Collection.MAP, JavaType.STRING, JavaType.BYTE_STRING),
+  STRING_TO_DOUBLE_MAP(250, Collection.MAP, JavaType.STRING, JavaType.DOUBLE),
+  STRING_TO_FLOAT_MAP(251, Collection.MAP, JavaType.STRING, JavaType.FLOAT),
+  GROUP(252, Collection.SCALAR, JavaType.MESSAGE, JavaType.VOID),
+  GROUP_LIST(253, Collection.VECTOR, JavaType.MESSAGE, JavaType.VOID),
+  GROUP_LIST_PACKED(254, Collection.PACKED_VECTOR, JavaType.MESSAGE, JavaType.VOID);
+
+  private final JavaType javaType1;
+  private final JavaType javaType2;
+  private final int id;
+  private final Collection collection;
+  private final Class<?> elementType1;
+  private final Class<?> elementType2;
+
+  FieldType(int id, Collection collection, JavaType javaType1, JavaType javaType2) {
+    this.id = id;
+    this.collection = collection;
+    this.javaType1 = javaType1;
+    this.javaType2 = javaType2;
+
+    switch (collection) {
+      case MAP:
+        elementType1 = javaType1.getBoxedType();
+        elementType2 = javaType2.getBoxedType();
+        break;
+      case VECTOR:
+        elementType1 = javaType1.getBoxedType();
+        elementType2 = null;
+        break;
+      case SCALAR:
+      default:
+        elementType1 = null;
+        elementType2 = null;
+        break;
+    }
+  }
+
+  /**
+   * 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 javaType1;
+  }
+
+  /**
+   * 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 represents a list of values.
+   */
+  public boolean isList() {
+    return collection.isList();
+  }
+
+  /**
+   * 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 javaType1.getType().isAssignableFrom(field.getType());
+    }
+  }
+
+  private boolean isValidForList(Field field) {
+    Class<?> clazz = field.getType();
+    if (!javaType1.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 elementType1.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(byte 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;
+    }
+  }
+}

+ 754 - 0
java/core/src/main/java/com/google/protobuf/Int2ObjectHashMap.java

@@ -0,0 +1,754 @@
+// 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.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A hash map that uses primitive integers as keys with open addressing. To minimize the memory
+ * footprint, this class uses open addressing rather than chaining. Collisions are resolved using
+ * linear probing. Deletions implement compaction, so cost of remove can approach O(N) for full
+ * maps, which makes a small loadFactor recommended.
+ *
+ * @param <V> The value type stored in the map.
+ */
+@ExperimentalApi
+public final class Int2ObjectHashMap<V> implements Map<Integer, V> {
+  /**
+   * A primitive entry in the map, provided by the iterator from {@link #entries()}
+   *
+   * @param <V> the value type stored in the map.
+   */
+  public interface PrimitiveEntry<V> {
+    /** Gets the key for this entry. */
+    int key();
+
+    /** Gets the value for this entry. */
+    V value();
+
+    /** Sets the value for this entry. */
+    void setValue(V value);
+  }
+
+  /** Default initial capacity. Used if not specified in the constructor */
+  public static final int DEFAULT_CAPACITY = 8;
+
+  /** Default load factor. Used if not specified in the constructor */
+  public static final float DEFAULT_LOAD_FACTOR = 0.5f;
+
+  /**
+   * Placeholder for null values, so we can use the actual null to mean available. (Better than
+   * using a placeholder for available: less references for GC processing.)
+   */
+  private static final Object NULL_VALUE = new Object();
+
+  /** The maximum number of elements allowed without allocating more space. */
+  private int maxSize;
+
+  /** The load factor for the map. Used to calculate {@link #maxSize}. */
+  private final float loadFactor;
+
+  private int[] keys;
+  private V[] values;
+  private int size;
+  private int mask;
+
+  private final Set<Integer> keySet = new KeySet();
+  private final Set<Entry<Integer, V>> entrySet = new EntrySet();
+  private final Iterable<PrimitiveEntry<V>> entries =
+      new Iterable<PrimitiveEntry<V>>() {
+        @Override
+        public Iterator<PrimitiveEntry<V>> iterator() {
+          return new PrimitiveIterator();
+        }
+      };
+
+  public Int2ObjectHashMap() {
+    this(DEFAULT_CAPACITY, DEFAULT_LOAD_FACTOR);
+  }
+
+  public Int2ObjectHashMap(int initialCapacity) {
+    this(initialCapacity, DEFAULT_LOAD_FACTOR);
+  }
+
+  public Int2ObjectHashMap(int initialCapacity, float loadFactor) {
+    if (initialCapacity < 1) {
+      throw new IllegalArgumentException("initialCapacity must be >= 1");
+    }
+    if (loadFactor <= 0.0f || loadFactor > 1.0f) {
+      // Cannot exceed 1 because we can never store more than capacity elements;
+      // using a bigger loadFactor would trigger rehashing before the desired load is reached.
+      throw new IllegalArgumentException("loadFactor must be > 0 and <= 1");
+    }
+
+    this.loadFactor = loadFactor;
+
+    // Adjust the initial capacity if necessary.
+    int capacity = findNextPositivePowerOfTwo(initialCapacity);
+    mask = capacity - 1;
+
+    // Allocate the arrays.
+    keys = new int[capacity];
+    @SuppressWarnings({"unchecked", "SuspiciousArrayCast"})
+    V[] temp = (V[]) new Object[capacity];
+    values = temp;
+
+    // Initialize the maximum size value.
+    maxSize = calcMaxSize(capacity);
+  }
+
+  private static <T> T toExternal(T value) {
+    return value == NULL_VALUE ? null : value;
+  }
+
+  @SuppressWarnings("unchecked")
+  private static <T> T toInternal(T value) {
+    return value == null ? (T) NULL_VALUE : value;
+  }
+
+  public V get(int key) {
+    int index = indexOf(key);
+    return index == -1 ? null : toExternal(values[index]);
+  }
+
+  public V put(int key, V value) {
+    int startIndex = hashIndex(key);
+    int index = startIndex;
+
+    for (; ; ) {
+      if (values[index] == null) {
+        // Found empty slot, use it.
+        keys[index] = key;
+        values[index] = toInternal(value);
+        growSize();
+        return null;
+      }
+      if (keys[index] == key) {
+        // Found existing entry with this key, just replace the value.
+        V previousValue = values[index];
+        values[index] = toInternal(value);
+        return toExternal(previousValue);
+      }
+
+      // Conflict, keep probing ...
+      if ((index = probeNext(index)) == startIndex) {
+        // Can only happen if the map was full at MAX_ARRAY_SIZE and couldn't grow.
+        throw new IllegalStateException("Unable to insert");
+      }
+    }
+  }
+
+  @Override
+  public void putAll(Map<? extends Integer, ? extends V> sourceMap) {
+    if (sourceMap instanceof Int2ObjectHashMap) {
+      // Optimization - iterate through the arrays.
+      @SuppressWarnings("unchecked")
+      Int2ObjectHashMap<V> source = (Int2ObjectHashMap<V>) sourceMap;
+      for (int i = 0; i < source.values.length; ++i) {
+        V sourceValue = source.values[i];
+        if (sourceValue != null) {
+          put(source.keys[i], sourceValue);
+        }
+      }
+      return;
+    }
+
+    // Otherwise, just add each entry.
+    for (Entry<? extends Integer, ? extends V> entry : sourceMap.entrySet()) {
+      put(entry.getKey(), entry.getValue());
+    }
+  }
+
+  public V remove(int key) {
+    int index = indexOf(key);
+    if (index == -1) {
+      return null;
+    }
+
+    V prev = values[index];
+    removeAt(index);
+    return toExternal(prev);
+  }
+
+  @Override
+  public int size() {
+    return size;
+  }
+
+  @Override
+  public boolean isEmpty() {
+    return size == 0;
+  }
+
+  @Override
+  public void clear() {
+    Arrays.fill(keys, 0);
+    Arrays.fill(values, null);
+    size = 0;
+  }
+
+  public boolean containsKey(int key) {
+    return indexOf(key) >= 0;
+  }
+
+  @Override
+  public boolean containsValue(Object value) {
+    @SuppressWarnings("unchecked")
+    V v1 = toInternal((V) value);
+    for (V v2 : values) {
+      // The map supports null values; this will be matched as NULL_VALUE.equals(NULL_VALUE).
+      if (v2 != null && v2.equals(v1)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  public Iterable<PrimitiveEntry<V>> entries() {
+    return entries;
+  }
+
+  @Override
+  public Collection<V> values() {
+    return new AbstractCollection<V>() {
+      @Override
+      public Iterator<V> iterator() {
+        return new Iterator<V>() {
+          final PrimitiveIterator iter = new PrimitiveIterator();
+
+          @Override
+          public boolean hasNext() {
+            return iter.hasNext();
+          }
+
+          @Override
+          public V next() {
+            return iter.next().value();
+          }
+
+          @Override
+          public void remove() {
+            throw new UnsupportedOperationException();
+          }
+        };
+      }
+
+      @Override
+      public int size() {
+        return size;
+      }
+    };
+  }
+
+  @Override
+  public int hashCode() {
+    // Hashcode is based on all non-zero, valid keys. We have to scan the whole keys
+    // array, which may have different lengths for two maps of same size(), so the
+    // capacity cannot be used as input for hashing but the size can.
+    int hash = size;
+    for (int key : keys) {
+      // 0 can be a valid key or unused slot, but won't impact the hashcode in either case.
+      // This way we can use a cheap loop without conditionals, or hard-to-unroll operations,
+      // or the devastatingly bad memory locality of visiting value objects.
+      // Also, it's important to use a hash function that does not depend on the ordering
+      // of terms, only their values; since the map is an unordered collection and
+      // entries can end up in different positions in different maps that have the same
+      // elements, but with different history of puts/removes, due to conflicts.
+      hash ^= hashCode(key);
+    }
+    return hash;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+    if (!(obj instanceof Int2ObjectHashMap)) {
+      return false;
+    }
+    @SuppressWarnings("rawtypes")
+    Int2ObjectHashMap other = (Int2ObjectHashMap) obj;
+    if (size != other.size()) {
+      return false;
+    }
+    for (int i = 0; i < values.length; ++i) {
+      V value = values[i];
+      if (value != null) {
+        int key = keys[i];
+        Object otherValue = other.get(key);
+        if (value == NULL_VALUE) {
+          if (otherValue != null) {
+            return false;
+          }
+        } else if (!value.equals(otherValue)) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  @Override
+  public boolean containsKey(Object key) {
+    return containsKey(objectToKey(key));
+  }
+
+  @Override
+  public V get(Object key) {
+    return get(objectToKey(key));
+  }
+
+  @Override
+  public V put(Integer key, V value) {
+    return put(objectToKey(key), value);
+  }
+
+  @Override
+  public V remove(Object key) {
+    return remove(objectToKey(key));
+  }
+
+  @Override
+  public Set<Integer> keySet() {
+    return keySet;
+  }
+
+  @Override
+  public Set<Entry<Integer, V>> entrySet() {
+    return entrySet;
+  }
+
+  private int objectToKey(Object key) {
+    return ((Integer) key).intValue();
+  }
+
+  /**
+   * Locates the index for the given key. This method probes using double hashing.
+   *
+   * @param key the key for an entry in the map.
+   * @return the index where the key was found, or {@code -1} if no entry is found for that key.
+   */
+  private int indexOf(int key) {
+    int startIndex = hashIndex(key);
+    int index = startIndex;
+
+    for (; ; ) {
+      if (values[index] == null) {
+        // It's available, so no chance that this value exists anywhere in the map.
+        return -1;
+      }
+      if (key == keys[index]) {
+        return index;
+      }
+
+      // Conflict, keep probing ...
+      if ((index = probeNext(index)) == startIndex) {
+        return -1;
+      }
+    }
+  }
+
+  /** Returns the hashed index for the given key. */
+  private int hashIndex(int key) {
+    // The array lengths are always a power of two, so we can use a bitmask to stay inside the
+    // array bounds.
+    return hashCode(key) & mask;
+  }
+
+  /** Returns the hash code for the key. */
+  private static int hashCode(int key) {
+    return key;
+  }
+
+  /** Get the next sequential index after {@code index} and wraps if necessary. */
+  private int probeNext(int index) {
+    // The array lengths are always a power of two, so we can use a bitmask to stay inside the
+    // array bounds.
+    return (index + 1) & mask;
+  }
+
+  /** Grows the map size after an insertion. If necessary, performs a rehash of the map. */
+  private void growSize() {
+    size++;
+
+    if (size > maxSize) {
+      if (keys.length == Integer.MAX_VALUE) {
+        throw new IllegalStateException("Max capacity reached at size=" + size);
+      }
+
+      // Double the capacity.
+      rehash(keys.length << 1);
+    }
+  }
+
+  /**
+   * Removes entry at the given index position. Also performs opportunistic, incremental rehashing
+   * if necessary to not break conflict chains.
+   *
+   * @param index the index position of the element to remove.
+   * @return {@code true} if the next item was moved back. {@code false} otherwise.
+   */
+  private boolean removeAt(final int index) {
+    --size;
+    // Clearing the key is not strictly necessary (for GC like in a regular collection),
+    // but recommended for security. The memory location is still fresh in the cache anyway.
+    keys[index] = 0;
+    values[index] = null;
+
+    // In the interval from index to the next available entry, the arrays may have entries
+    // that are displaced from their base position due to prior conflicts. Iterate these
+    // entries and move them back if possible, optimizing future lookups.
+    // Knuth Section 6.4 Algorithm R, also used by the JDK's IdentityHashMap.
+
+    boolean movedBack = false;
+    int nextFree = index;
+    for (int i = probeNext(index); values[i] != null; i = probeNext(i)) {
+      int bucket = hashIndex(keys[i]);
+      if ((i < bucket && (bucket <= nextFree || nextFree <= i))
+          || (bucket <= nextFree && nextFree <= i)) {
+        // Move the displaced entry "back" to the first available position.
+        keys[nextFree] = keys[i];
+        values[nextFree] = values[i];
+        movedBack = true;
+        // Put the first entry after the displaced entry
+        keys[i] = 0;
+        values[i] = null;
+        nextFree = i;
+      }
+    }
+    return movedBack;
+  }
+
+  /** Calculates the maximum size allowed before rehashing. */
+  private int calcMaxSize(int capacity) {
+    // Clip the upper bound so that there will always be at least one available slot.
+    int upperBound = capacity - 1;
+    return Math.min(upperBound, (int) (capacity * loadFactor));
+  }
+
+  /**
+   * Rehashes the map for the given capacity.
+   *
+   * @param newCapacity the new capacity for the map.
+   */
+  private void rehash(int newCapacity) {
+    int[] oldKeys = keys;
+    V[] oldVals = values;
+
+    keys = new int[newCapacity];
+    @SuppressWarnings({"unchecked", "SuspiciousArrayCast"})
+    V[] temp = (V[]) new Object[newCapacity];
+    values = temp;
+
+    maxSize = calcMaxSize(newCapacity);
+    mask = newCapacity - 1;
+
+    // Insert to the new arrays.
+    for (int i = 0; i < oldVals.length; ++i) {
+      V oldVal = oldVals[i];
+      if (oldVal != null) {
+        // Inlined put(), but much simpler: we don't need to worry about
+        // duplicated keys, growing/rehashing, or failing to insert.
+        int oldKey = oldKeys[i];
+        int index = hashIndex(oldKey);
+
+        for (; ; ) {
+          if (values[index] == null) {
+            keys[index] = oldKey;
+            values[index] = oldVal;
+            break;
+          }
+
+          // Conflict, keep probing. Can wrap around, but never reaches startIndex again.
+          index = probeNext(index);
+        }
+      }
+    }
+  }
+
+  @Override
+  public String toString() {
+    if (isEmpty()) {
+      return "{}";
+    }
+    StringBuilder sb = new StringBuilder(4 * size);
+    sb.append('{');
+    boolean first = true;
+    for (int i = 0; i < values.length; ++i) {
+      V value = values[i];
+      if (value != null) {
+        if (!first) {
+          sb.append(", ");
+        }
+        sb.append(keyToString(keys[i]))
+            .append('=')
+            .append(value == this ? "(this Map)" : toExternal(value));
+        first = false;
+      }
+    }
+    return sb.append('}').toString();
+  }
+
+  /**
+   * Helper method called by {@link #toString()} in order to convert a single map key into a string.
+   * This is protected to allow subclasses to override the appearance of a given key.
+   */
+  protected String keyToString(int key) {
+    return Integer.toString(key);
+  }
+
+  /** Set implementation for iterating over the entries of the map. */
+  private final class EntrySet extends AbstractSet<Entry<Integer, V>> {
+    @Override
+    public Iterator<Entry<Integer, V>> iterator() {
+      return new MapIterator();
+    }
+
+    @Override
+    public int size() {
+      return Int2ObjectHashMap.this.size();
+    }
+  }
+
+  /** Set implementation for iterating over the keys. */
+  private final class KeySet extends AbstractSet<Integer> {
+    @Override
+    public int size() {
+      return Int2ObjectHashMap.this.size();
+    }
+
+    @Override
+    public boolean contains(Object o) {
+      return Int2ObjectHashMap.this.containsKey(o);
+    }
+
+    @Override
+    public boolean remove(Object o) {
+      return Int2ObjectHashMap.this.remove(o) != null;
+    }
+
+    @Override
+    public boolean retainAll(Collection<?> retainedKeys) {
+      boolean changed = false;
+      for (Iterator<PrimitiveEntry<V>> iter = entries().iterator(); iter.hasNext(); ) {
+        PrimitiveEntry<V> entry = iter.next();
+        if (!retainedKeys.contains(entry.key())) {
+          changed = true;
+          iter.remove();
+        }
+      }
+      return changed;
+    }
+
+    @Override
+    public void clear() {
+      Int2ObjectHashMap.this.clear();
+    }
+
+    @Override
+    public Iterator<Integer> iterator() {
+      return new Iterator<Integer>() {
+        private final Iterator<Entry<Integer, V>> iter = entrySet.iterator();
+
+        @Override
+        public boolean hasNext() {
+          return iter.hasNext();
+        }
+
+        @Override
+        public Integer next() {
+          return iter.next().getKey();
+        }
+
+        @Override
+        public void remove() {
+          iter.remove();
+        }
+      };
+    }
+  }
+
+  /**
+   * Iterator over primitive entries. Entry key/values are overwritten by each call to {@link
+   * #next()}.
+   */
+  private final class PrimitiveIterator implements Iterator<PrimitiveEntry<V>>, PrimitiveEntry<V> {
+    private int prevIndex = -1;
+    private int nextIndex = -1;
+    private int entryIndex = -1;
+
+    private void scanNext() {
+      for (; ; ) {
+        if (++nextIndex == values.length || values[nextIndex] != null) {
+          break;
+        }
+      }
+    }
+
+    @Override
+    public boolean hasNext() {
+      if (nextIndex == -1) {
+        scanNext();
+      }
+      return nextIndex < keys.length;
+    }
+
+    @Override
+    public PrimitiveEntry<V> next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+
+      prevIndex = nextIndex;
+      scanNext();
+
+      // Always return the same Entry object, just change its index each time.
+      entryIndex = prevIndex;
+      return this;
+    }
+
+    @Override
+    public void remove() {
+      if (prevIndex < 0) {
+        throw new IllegalStateException("next must be called before each remove.");
+      }
+      if (removeAt(prevIndex)) {
+        // removeAt may move elements "back" in the array if they have been displaced because
+        // their spot in the array was occupied when they were inserted. If this occurs then the
+        // nextIndex is now invalid and should instead point to the prevIndex which now holds an
+        // element which was "moved back".
+        nextIndex = prevIndex;
+      }
+      prevIndex = -1;
+    }
+
+    // Entry implementation. Since this implementation uses a single Entry, we coalesce that
+    // into the Iterator object (potentially making loop optimization much easier).
+
+    @Override
+    public int key() {
+      return keys[entryIndex];
+    }
+
+    @Override
+    public V value() {
+      return toExternal(values[entryIndex]);
+    }
+
+    @Override
+    public void setValue(V value) {
+      values[entryIndex] = toInternal(value);
+    }
+  }
+
+  /** Iterator used by the {@link Map} interface. */
+  private final class MapIterator implements Iterator<Entry<Integer, V>> {
+    private final PrimitiveIterator iter = new PrimitiveIterator();
+
+    @Override
+    public boolean hasNext() {
+      return iter.hasNext();
+    }
+
+    @Override
+    public Entry<Integer, V> next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+
+      iter.next();
+
+      return new MapEntry(iter.entryIndex);
+    }
+
+    @Override
+    public void remove() {
+      iter.remove();
+    }
+  }
+
+  /** A single entry in the map. */
+  final class MapEntry implements Entry<Integer, V> {
+    private final int entryIndex;
+
+    MapEntry(int entryIndex) {
+      this.entryIndex = entryIndex;
+    }
+
+    @Override
+    public Integer getKey() {
+      verifyExists();
+      return keys[entryIndex];
+    }
+
+    @Override
+    public V getValue() {
+      verifyExists();
+      return toExternal(values[entryIndex]);
+    }
+
+    @Override
+    public V setValue(V value) {
+      verifyExists();
+      V prevValue = toExternal(values[entryIndex]);
+      values[entryIndex] = toInternal(value);
+      return prevValue;
+    }
+
+    private void verifyExists() {
+      if (values[entryIndex] == null) {
+        throw new IllegalStateException("The map entry has been removed");
+      }
+    }
+  }
+
+  /**
+   * Fast method of finding the next power of 2 greater than or equal to the supplied value.
+   *
+   * <p>If the value is {@code <= 0} then 1 will be returned. This method is not suitable for {@link
+   * Integer#MIN_VALUE} or numbers greater than 2^30.
+   *
+   * @param value from which to search for next power of 2
+   * @return The next power of 2 or the value itself if it is a power of 2
+   */
+  private static int findNextPositivePowerOfTwo(final int value) {
+    assert value > Integer.MIN_VALUE && value < 0x40000000;
+    return 1 << (32 - Integer.numberOfLeadingZeros(value - 1));
+  }
+}

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

@@ -0,0 +1,87 @@
+// 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);
+  }
+}

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

@@ -0,0 +1,158 @@
+// 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
+public final class MessageInfo {
+  private final ProtoSyntax syntax;
+  private final boolean messageSetWireFormat;
+  private final List<FieldInfo> fields;
+
+  /**
+   * Constructor.
+   *
+   * @param fields the set of fields for the message.
+   */
+  private MessageInfo(ProtoSyntax syntax, boolean messageSetWireFormat, List<FieldInfo> fields) {
+    this.syntax = syntax;
+    this.messageSetWireFormat = messageSetWireFormat;
+    this.fields = fields;
+  }
+
+  /** Gets the syntax for the message (e.g. PROTO2, PROTO3). */
+  public ProtoSyntax getSyntax() {
+    return syntax;
+  }
+
+  /** Indicates whether or not the message should be represented with message set wire format. */
+  public boolean isMessageSetWireFormat() {
+    return messageSetWireFormat;
+  }
+
+  /**
+   * Gets the information for all fields within this message, sorted in ascending order by their
+   * field number.
+   */
+  public List<FieldInfo> getFields() {
+    return fields;
+  }
+
+  /** Creates a new map of field number to message class for message fields. */
+  public Int2ObjectHashMap<Class<?>> messageFieldClassMap() {
+    Int2ObjectHashMap<Class<?>> classMap = new Int2ObjectHashMap<Class<?>>();
+    for (int i = 0; i < fields.size(); ++i) {
+      FieldInfo fd = fields.get(i);
+      int fieldNumber = fd.getFieldNumber();
+
+      // Configure messages
+      switch (fd.getType()) {
+        case MESSAGE:
+          classMap.put(fieldNumber, fd.getField().getType());
+          break;
+        case MESSAGE_LIST:
+          classMap.put(fieldNumber, fd.getListElementType());
+          break;
+        case GROUP:
+          classMap.put(fieldNumber, fd.getField().getType());
+          break;
+        case GROUP_LIST:
+          classMap.put(fieldNumber, fd.getListElementType());
+          break;
+        default:
+          break;
+      }
+    }
+    return classMap;
+  }
+
+  /** 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 ArrayList<FieldInfo> fields;
+    private ProtoSyntax syntax;
+    private boolean wasBuilt;
+    private boolean messageSetWireFormat;
+
+    public Builder() {
+      fields = new ArrayList<FieldInfo>();
+    }
+
+    public Builder(int numFields) {
+      fields = new ArrayList<FieldInfo>(numFields);
+    }
+
+    public void withSyntax(ProtoSyntax syntax) {
+      this.syntax = checkNotNull(syntax, "syntax");
+    }
+
+    public void withMessageSetWireFormat(boolean messageSetWireFormat) {
+      this.messageSetWireFormat = messageSetWireFormat;
+    }
+
+    public void add(FieldInfo field) {
+      if (wasBuilt) {
+        throw new IllegalStateException("Builder can only build once");
+      }
+      fields.add(field);
+    }
+
+    public MessageInfo 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 MessageInfo(syntax, messageSetWireFormat, Collections.unmodifiableList(fields));
+    }
+  }
+}

+ 38 - 0
java/core/src/main/java/com/google/protobuf/MessageInfoFactory.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 creates {@link MessageInfo} instances for message types. */
+@ExperimentalApi
+public interface MessageInfoFactory {
+  /** Returns a information of the message class. */
+  MessageInfo messageInfoFor(Class<?> clazz);
+}

+ 281 - 0
java/core/src/main/java/com/google/protobuf/Proto2LiteLookupSchema.java

@@ -0,0 +1,281 @@
+// 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.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, lookup-based schema that can be used with any proto3-lite message class. The message
+ * class must extend {@link GeneratedMessage}.
+ */
+final class Proto2LiteLookupSchema<T> extends AbstractProto2LiteSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto2LiteLookupSchema(Class<T> messageClass, MessageInfo msgInfo) {
+    super(messageClass, Proto2Manifest.newLookupManifest(msgInfo));
+    this.messageFieldClassMap = msgInfo.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.lookupPositionForFieldNumber(fieldNumber);
+      if (pos >= 0L) {
+        final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+        try {
+          switch (type(typeAndOffset)) {
+            case 0: //DOUBLE:
+              UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 1: //FLOAT:
+              UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 2: //INT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 3: //UINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 4: //INT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 5: //FIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 6: //FIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 7: //BOOL:
+              UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 8: //STRING:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 9: //MESSAGE:
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 10: //BYTES:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 11: //UINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 12: //ENUM:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 13: //SFIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 14: //SFIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 15: //SINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 16: //SINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 17: //DOUBLE_LIST:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 18: //FLOAT_LIST:
+              reader.readFloatList(
+                  SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 19: //INT64_LIST:
+              reader.readInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 20: //UINT64_LIST:
+              reader.readUInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 21: //INT32_LIST:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 22: //FIXED64_LIST:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 23: //FIXED32_LIST:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 24: //BOOL_LIST:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 25: //STRING_LIST:
+              reader.readStringList(
+                  SchemaUtil.<String>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 26: //MESSAGE_LIST:
+              SchemaUtil.readProtobufMessageList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              continue;
+            case 27: //BYTES_LIST:
+              reader.readBytesList(
+                  SchemaUtil.<ByteString>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 28: //UINT32_LIST:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 29: //ENUM_LIST:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 30: //SFIXED32_LIST:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 31: //SFIXED64_LIST:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 32: //SINT32_LIST:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 33: //SINT64_LIST:
+              reader.readSInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 34: //DOUBLE_LIST_PACKED:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 35: //FLOAT_LIST_PACKED:
+              reader.readFloatList(
+                  SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 36: //INT64_LIST_PACKED:
+              reader.readInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 37: //UINT64_LIST_PACKED:
+              reader.readUInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 38: //INT32_LIST_PACKED:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 39: //FIXED64_LIST_PACKED:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 40: //FIXED32_LIST_PACKED:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 41: //BOOL_LIST_PACKED:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 42: //UINT32_LIST_PACKED:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 43: //ENUM_LIST_PACKED:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 44: //SFIXED32_LIST_PACKED:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 45: //SFIXED64_LIST_PACKED:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 46: //SINT32_LIST_PACKED:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 47: //SINT64_LIST_PACKED:
+              reader.readSInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case -4: //GROUP (actually should be 252, but byte is [-128, 127])
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readGroup(messageFieldClassMap.get(fieldNumber)));
+              manifest.setFieldPresent(message, pos);
+              break;
+            case -3: //GROUP_LIST (actually should be 253, but byte is [-128, 127])
+              SchemaUtil.readGroupList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              break;
+            default:
+              // Unknown field type - break out of loop and skip the field.
+              break;
+          }
+        } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+          // Treat it as an unknown field - same as the default case.
+        }
+      }
+
+      // Unknown field.
+      if (!reader.skipField()) {
+        // Done reading.
+        return;
+      }
+    }
+  }
+}

+ 291 - 0
java/core/src/main/java/com/google/protobuf/Proto2LiteTableSchema.java

@@ -0,0 +1,291 @@
+// 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.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, table-based schema that can be used with any proto3 lite message class. The message
+ * class must extend {@link GeneratedMessage}.
+ */
+final class Proto2LiteTableSchema<T> extends AbstractProto2LiteSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto2LiteTableSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto2Manifest.newTableManfiest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.tablePositionForFieldNumber(fieldNumber);
+      if (pos < 0) {
+        // Unknown field.
+        if (reader.skipField()) {
+          continue;
+        }
+        // Done reading.
+        return;
+      }
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      try {
+        switch (type(typeAndOffset)) {
+          case 0: //DOUBLE:
+            UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 1: //FLOAT:
+            UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 2: //INT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 3: //UINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 4: //INT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 5: //FIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 6: //FIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 7: //BOOL:
+            UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 8: //STRING:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 9: //MESSAGE:
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 10: //BYTES:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 11: //UINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 12: //ENUM:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 13: //SFIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 14: //SFIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 15: //SINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 16: //SINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 17: //DOUBLE_LIST:
+            reader.readDoubleList(
+                SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 18: //FLOAT_LIST:
+            reader.readFloatList(
+                SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 19: //INT64_LIST:
+            reader.readInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 20: //UINT64_LIST:
+            reader.readUInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 21: //INT32_LIST:
+            reader.readInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 22: //FIXED64_LIST:
+            reader.readFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 23: //FIXED32_LIST:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 24: //BOOL_LIST:
+            reader.readBoolList(
+                SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 25: //STRING_LIST:
+            reader.readStringList(
+                SchemaUtil.<String>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 26: //MESSAGE_LIST:
+            SchemaUtil.readProtobufMessageList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          case 27: //BYTES_LIST:
+            reader.readBytesList(
+                SchemaUtil.<ByteString>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 28: //UINT32_LIST:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 29: //ENUM_LIST:
+            reader.readEnumList(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 30: //SFIXED32_LIST:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 31: //SFIXED64_LIST:
+            reader.readSFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 32: //SINT32_LIST:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 33: //SINT64_LIST:
+            reader.readSInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 34: //DOUBLE_LIST_PACKED:
+            reader.readDoubleList(
+                SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 35: //FLOAT_LIST_PACKED:
+            reader.readFloatList(
+                SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 36: //INT64_LIST_PACKED:
+            reader.readInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 37: //UINT64_LIST_PACKED:
+            reader.readUInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 38: //INT32_LIST_PACKED:
+            reader.readInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 39: //FIXED64_LIST_PACKED:
+            reader.readFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 40: //FIXED32_LIST_PACKED:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 41: //BOOL_LIST_PACKED:
+            reader.readBoolList(
+                SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 42: //UINT32_LIST_PACKED:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 43: //ENUM_LIST_PACKED:
+            reader.readEnumList(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 44: //SFIXED32_LIST_PACKED:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 45: //SFIXED64_LIST_PACKED:
+            reader.readSFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 46: //SINT32_LIST_PACKED:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 47: //SINT64_LIST_PACKED:
+            reader.readSInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case -4: //GROUP (actually should be 252, but byte is [-128, 127])
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readGroup(messageFieldClassMap.get(fieldNumber)));
+            manifest.setFieldPresent(message, pos);
+            break;
+          case -3: //GROUP_LIST (actually should be 253, but byte is [-128, 127])
+            SchemaUtil.readGroupList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          default:
+            // Assume we've landed on an empty entry. Treat it as an unknown field - just skip it.
+            if (!reader.skipField()) {
+              // Done reading.
+              return;
+            }
+            break;
+        }
+        // TODO(nathanmittler): Do we need to make lists immutable?
+      } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+        // Treat fields with an invalid wire type as unknown fields (i.e. same as the default case).
+        if (!reader.skipField()) {
+          return;
+        }
+      }
+    }
+  }
+}

+ 273 - 0
java/core/src/main/java/com/google/protobuf/Proto2LookupSchema.java

@@ -0,0 +1,273 @@
+// 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.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, lookup-based schema that can be used with any standard (i.e. non-lite) proto3 message
+ * class. The message class must extend {@link GeneratedMessage}.
+ */
+final class Proto2LookupSchema<T> extends AbstractProto2StandardSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto2LookupSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto2Manifest.newLookupManifest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.lookupPositionForFieldNumber(fieldNumber);
+      if (pos >= 0L) {
+        final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+        try {
+          switch (type(typeAndOffset)) {
+            case 0: //DOUBLE:
+              UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 1: //FLOAT:
+              UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 2: //INT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 3: //UINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 4: //INT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 5: //FIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 6: //FIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 7: //BOOL:
+              UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 8: //STRING:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 9: //MESSAGE:
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 10: //BYTES:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 11: //UINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 12: //ENUM:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 13: //SFIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 14: //SFIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 15: //SINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 16: //SINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case 17: //DOUBLE_LIST:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 18: //FLOAT_LIST:
+              reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 19: //INT64_LIST:
+              reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 20: //UINT64_LIST:
+              reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 21: //INT32_LIST:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 22: //FIXED64_LIST:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 23: //FIXED32_LIST:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 24: //BOOL_LIST:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 25: //STRING_LIST:
+              reader.readStringList(
+                  SchemaUtil.<String>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 26: //MESSAGE_LIST:
+              SchemaUtil.readMessageList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              continue;
+            case 27: //BYTES_LIST:
+              reader.readBytesList(
+                  SchemaUtil.<ByteString>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 28: //UINT32_LIST:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 29: //ENUM_LIST:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 30: //SFIXED32_LIST:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 31: //SFIXED64_LIST:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 32: //SINT32_LIST:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 33: //SINT64_LIST:
+              reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 34: //DOUBLE_LIST_PACKED:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 35: //FLOAT_LIST_PACKED:
+              reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 36: //INT64_LIST_PACKED:
+              reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 37: //UINT64_LIST_PACKED:
+              reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 38: //INT32_LIST_PACKED:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 39: //FIXED64_LIST_PACKED:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 40: //FIXED32_LIST_PACKED:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 41: //BOOL_LIST_PACKED:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 42: //UINT32_LIST_PACKED:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 43: //ENUM_LIST_PACKED:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 44: //SFIXED32_LIST_PACKED:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 45: //SFIXED64_LIST_PACKED:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 46: //SINT32_LIST_PACKED:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 47: //SINT64_LIST_PACKED:
+              reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case -4: //GROUP (actually should be 252, but byte is [-128, 127])
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readGroup(messageFieldClassMap.get(fieldNumber)));
+              manifest.setFieldPresent(message, pos);
+              continue;
+            case -3: //GROUP_LIST (actually should be 253, but byte is [-128, 127])
+              SchemaUtil.readGroupList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              continue;
+            default:
+              // Unknown field type - break out of loop and skip the field.
+              break;
+          }
+        } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+          // Treat it as an unknown field - same as the default case.
+        }
+      }
+
+      // Unknown field.
+      if (!reader.skipField()) {
+        // Done reading.
+        return;
+      }
+    }
+  }
+}

+ 261 - 0
java/core/src/main/java/com/google/protobuf/Proto2Manifest.java

@@ -0,0 +1,261 @@
+// 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;
+import java.util.List;
+
+/** Container for the field metadata of a single proto2 schema. */
+final class Proto2Manifest {
+  static final int INT_LENGTH = 4;
+  static final int LONG_LENGTH = INT_LENGTH * 2;
+  static final int LONGS_PER_FIELD = 2;
+  /**
+   * Note that field length is always a power of two so that we can use bit shifting (rather than
+   * division) to find the location of a field when parsing.
+   */
+  static final int FIELD_LENGTH = LONGS_PER_FIELD * LONG_LENGTH;
+
+  static final int FIELD_SHIFT = 4 /* 2^4 = 16 */;
+  static final int OFFSET_BITS = 20;
+  static final int OFFSET_MASK = 0XFFFFF;
+  static final long EMPTY_LONG = 0xFFFFFFFFFFFFFFFFL;
+
+  /**
+   * 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 -  37] unused
+   * [38 -  43] field type
+   * [44 -  63] field offset
+   * [64 -  69] unused
+   * [70 -  75] field presence mask shift
+   * [76 -  95] presence field offset
+   * [96 - 127] unused
+   * </pre>
+   */
+  final ByteBuffer buffer;
+
+  final long address;
+  final long limit;
+  final int numFields;
+
+  final int minFieldNumber;
+  final int maxFieldNumber;
+
+  private Proto2Manifest(
+      ByteBuffer buffer,
+      long address,
+      long limit,
+      int numFields,
+      int minFieldNumber,
+      int maxFieldNumber) {
+    this.buffer = buffer;
+    this.address = address;
+    this.limit = limit;
+    this.numFields = numFields;
+    this.minFieldNumber = minFieldNumber;
+    this.maxFieldNumber = maxFieldNumber;
+  }
+
+  boolean isFieldInRange(int fieldNumber) {
+    return fieldNumber >= minFieldNumber && fieldNumber <= maxFieldNumber;
+  }
+
+  long tablePositionForFieldNumber(int fieldNumber) {
+    if (fieldNumber < minFieldNumber || fieldNumber > maxFieldNumber) {
+      return -1;
+    }
+
+    return indexToAddress(fieldNumber - minFieldNumber);
+  }
+
+  <T> boolean isFieldPresent(T message, long pos) {
+    int maskShiftAndOffset = UnsafeUtil.getInt(pos + LONG_LENGTH);
+    long offset = maskShiftAndOffset & OFFSET_MASK;
+    int mask = 1 << (maskShiftAndOffset >>> OFFSET_BITS);
+    return (UnsafeUtil.getInt(message, offset) & mask) != 0;
+  }
+
+  <T> void setFieldPresent(T message, long pos) {
+    int maskShiftAndOffset = UnsafeUtil.getInt(pos + LONG_LENGTH);
+    long offset = maskShiftAndOffset & OFFSET_MASK;
+    int mask = 1 << (maskShiftAndOffset >>> OFFSET_BITS);
+    UnsafeUtil.putInt(message, offset, UnsafeUtil.getInt(message, offset) | mask);
+  }
+
+  long lookupPositionForFieldNumber(int fieldNumber) {
+    int min = 0;
+    int max = numFields - 1;
+    while (min <= max) {
+      // Find the midpoint address.
+      int mid = (max + min) >>> 1;
+      long midAddress = indexToAddress(mid);
+      int midFieldNumber = numberAt(midAddress);
+      if (fieldNumber == midFieldNumber) {
+        // Found the field.
+        return midAddress;
+      }
+      if (fieldNumber < midFieldNumber) {
+        // Search the lower half.
+        max = mid - 1;
+      } else {
+        // Search the upper half.
+        min = mid + 1;
+      }
+    }
+    return -1;
+  }
+
+  int numberAt(long pos) {
+    return UnsafeUtil.getInt(pos);
+  }
+
+  int typeAndOffsetAt(long pos) {
+    return UnsafeUtil.getInt(pos + INT_LENGTH);
+  }
+
+  private long indexToAddress(int index) {
+    return address + (index << FIELD_SHIFT);
+  }
+
+  static byte type(int value) {
+    return (byte) (value >>> OFFSET_BITS);
+  }
+
+  static long offset(int value) {
+    return value & OFFSET_MASK;
+  }
+
+  static Proto2Manifest newTableManfiest(MessageInfo descriptor) {
+    List<FieldInfo> fds = descriptor.getFields();
+    if (fds.isEmpty()) {
+      throw new IllegalArgumentException("Table-based schema requires at least one field");
+    }
+
+    // Set up the buffer for direct indexing by field number.
+    final int minFieldNumber = fds.get(0).getFieldNumber();
+    final int maxFieldNumber = fds.get(fds.size() - 1).getFieldNumber();
+    final int numEntries = (maxFieldNumber - minFieldNumber) + 1;
+
+    int bufferLength = numEntries * FIELD_LENGTH;
+    ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
+    long tempAddress = UnsafeUtil.addressOffset(buffer);
+    if ((tempAddress & 7L) != 0) {
+      // Make sure that the memory address is 8-byte aligned.
+      tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
+    }
+    final long address = tempAddress;
+    final long limit = address + bufferLength;
+
+    // Fill in the manifest data from the descriptors.
+    int fieldIndex = 0;
+    FieldInfo fd = fds.get(fieldIndex++);
+    for (int bufferIndex = 0; bufferIndex < bufferLength; bufferIndex += FIELD_LENGTH) {
+      final int fieldNumber = fd.getFieldNumber();
+      if (bufferIndex < ((fieldNumber - minFieldNumber) << FIELD_SHIFT)) {
+        // Mark this entry as "empty".
+        long skipLimit = address + bufferIndex + FIELD_LENGTH;
+        for (long skipPos = address + bufferIndex; skipPos < skipLimit; skipPos += LONG_LENGTH) {
+          UnsafeUtil.putLong(skipPos, EMPTY_LONG);
+        }
+        continue;
+      }
+
+      // We found the entry for the next field. Store the entry in the manifest for
+      // this field and increment the field index.
+      FieldType type = fd.getType();
+      long pos = address + bufferIndex;
+      UnsafeUtil.putInt(pos, fieldNumber);
+      UnsafeUtil.putInt(
+          pos + INT_LENGTH,
+          (type.id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
+      if (!type.isList()) {
+        int presenceOffset = (int) UnsafeUtil.objectFieldOffset(fd.getPresenceField());
+        int maskShift = Integer.numberOfTrailingZeros(fd.getPresenceMask());
+        UnsafeUtil.putInt(pos + LONG_LENGTH, maskShift << OFFSET_BITS | presenceOffset);
+      }
+
+      // Advance to the next field, unless we're at the end.
+      if (fieldIndex < fds.size()) {
+        fd = fds.get(fieldIndex++);
+      }
+    }
+
+    return new Proto2Manifest(buffer, address, limit, fds.size(), minFieldNumber, maxFieldNumber);
+  }
+
+  static Proto2Manifest newLookupManifest(MessageInfo descriptor) {
+    List<FieldInfo> fds = descriptor.getFields();
+
+    final int numFields = fds.size();
+    int bufferLength = numFields * FIELD_LENGTH;
+    final ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
+    long tempAddress = UnsafeUtil.addressOffset(buffer);
+    if ((tempAddress & 7L) != 0) {
+      // Make sure that the memory address is 8-byte aligned.
+      tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
+    }
+    final long address = tempAddress;
+    final long limit = address + bufferLength;
+
+    // Allocate and populate the data buffer.
+    long pos = address;
+    for (int i = 0; i < fds.size(); ++i, pos += FIELD_LENGTH) {
+      FieldInfo fd = fds.get(i);
+      UnsafeUtil.putInt(pos, fd.getFieldNumber());
+      UnsafeUtil.putInt(
+          pos + INT_LENGTH,
+          (fd.getType().id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
+      if (!fd.getType().isList()) {
+        int presenceOffset = (int) UnsafeUtil.objectFieldOffset(fd.getPresenceField());
+        int maskShift = Integer.numberOfTrailingZeros(fd.getPresenceMask());
+        UnsafeUtil.putInt(pos + LONG_LENGTH, maskShift << OFFSET_BITS | presenceOffset);
+      }
+    }
+
+    if (numFields > 0) {
+      return new Proto2Manifest(
+          buffer,
+          address,
+          limit,
+          numFields,
+          fds.get(0).getFieldNumber(),
+          fds.get(numFields - 1).getFieldNumber());
+    }
+    return new Proto2Manifest(buffer, address, limit, numFields, -1, -1);
+  }
+}

+ 112 - 0
java/core/src/main/java/com/google/protobuf/Proto2SchemaFactory.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 static com.google.protobuf.Internal.checkNotNull;
+
+/** Manufactures instances of {@link Proto3TableSchema}. */
+@ExperimentalApi
+public final class Proto2SchemaFactory implements SchemaFactory {
+  /**
+   * The mode with which to generate schemas.
+   *
+   * <p>For testing purposes only.
+   */
+  public enum Mode {
+    /** Always use a table-based indexing of fields. */
+    TABLE,
+
+    /** Always used lookup-based (i.e. binary search) indexing of fields. */
+    LOOKUP,
+
+    /**
+     * Default. Determine the appropriate field indexing mode based on how sparse the field numbers
+     * are for the message.
+     */
+    DYNAMIC
+  }
+
+  private final MessageInfoFactory messageDescriptorFactory;
+  private final Mode mode;
+
+  public Proto2SchemaFactory() {
+    this(DescriptorMessageInfoFactory.getInstance());
+  }
+
+  public Proto2SchemaFactory(MessageInfoFactory messageDescriptorFactory) {
+    this(messageDescriptorFactory, Mode.DYNAMIC);
+  }
+
+  /** For testing purposes only. Allows specification of {@link Mode}. */
+  public Proto2SchemaFactory(MessageInfoFactory messageDescriptorFactory, Mode mode) {
+    if (!isSupported()) {
+      throw new IllegalStateException("Schema factory is unsupported on this platform");
+    }
+    this.messageDescriptorFactory =
+        checkNotNull(messageDescriptorFactory, "messageDescriptorFactory");
+    this.mode = checkNotNull(mode, "mode");
+  }
+
+  public static boolean isSupported() {
+    return UnsafeUtil.hasUnsafeArrayOperations() && UnsafeUtil.hasUnsafeByteBufferOperations();
+  }
+
+  @Override
+  public <T> Schema<T> createSchema(Class<T> messageType) {
+    SchemaUtil.requireGeneratedMessage(messageType);
+
+    MessageInfo descriptor = messageDescriptorFactory.messageInfoFor(messageType);
+    switch (mode) {
+      case TABLE:
+        return newTableSchema(messageType, descriptor);
+      case LOOKUP:
+        return newLookupSchema(messageType, descriptor);
+      default:
+        return SchemaUtil.shouldUseTableSwitch(descriptor.getFields())
+            ? newTableSchema(messageType, descriptor)
+            : newLookupSchema(messageType, descriptor);
+    }
+  }
+
+  private <T> Schema<T> newTableSchema(Class<T> messageType, MessageInfo descriptor) {
+    if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      return new Proto2LiteTableSchema<T>(messageType, descriptor);
+    }
+    return new Proto2TableSchema<T>(messageType, descriptor);
+  }
+
+  private <T> Schema<T> newLookupSchema(Class<T> messageType, MessageInfo descriptor) {
+    if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      return new Proto2LiteLookupSchema<T>(messageType, descriptor);
+    }
+    return new Proto2LookupSchema<T>(messageType, descriptor);
+  }
+}

+ 270 - 0
java/core/src/main/java/com/google/protobuf/Proto2TableSchema.java

@@ -0,0 +1,270 @@
+// 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.Proto2Manifest.offset;
+import static com.google.protobuf.Proto2Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, table-based schema that can be used with any proto2 message class. The message class
+ * must extend {@link GeneratedMessage}.
+ */
+final class Proto2TableSchema<T> extends AbstractProto2StandardSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto2TableSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto2Manifest.newTableManfiest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.tablePositionForFieldNumber(fieldNumber);
+      if (pos < 0) {
+        // Unknown field.
+        if (reader.skipField()) {
+          continue;
+        }
+        // Done reading.
+        return;
+      }
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      try {
+        switch (type(typeAndOffset)) {
+          case 0: //DOUBLE:
+            UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 1: //FLOAT:
+            UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 2: //INT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 3: //UINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 4: //INT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 5: //FIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 6: //FIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 7: //BOOL:
+            UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 8: //STRING:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 9: //MESSAGE:
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 10: //BYTES:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 11: //UINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 12: //ENUM:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 13: //SFIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 14: //SFIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 15: //SINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 16: //SINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+            manifest.setFieldPresent(message, pos);
+            break;
+          case 17: //DOUBLE_LIST:
+            reader.readDoubleList(SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 18: //FLOAT_LIST:
+            reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 19: //INT64_LIST:
+            reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 20: //UINT64_LIST:
+            reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 21: //INT32_LIST:
+            reader.readInt32List(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 22: //FIXED64_LIST:
+            reader.readFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 23: //FIXED32_LIST:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 24: //BOOL_LIST:
+            reader.readBoolList(SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 25: //STRING_LIST:
+            reader.readStringList(SchemaUtil.<String>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 26: //MESSAGE_LIST:
+            SchemaUtil.readMessageList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          case 27: //BYTES_LIST:
+            reader.readBytesList(
+                SchemaUtil.<ByteString>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 28: //UINT32_LIST:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 29: //ENUM_LIST:
+            reader.readEnumList(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 30: //SFIXED32_LIST:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 31: //SFIXED64_LIST:
+            reader.readSFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 32: //SINT32_LIST:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 33: //SINT64_LIST:
+            reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 34: //DOUBLE_LIST_PACKED:
+            reader.readDoubleList(SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 35: //FLOAT_LIST_PACKED:
+            reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 36: //INT64_LIST_PACKED:
+            reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 37: //UINT64_LIST_PACKED:
+            reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 38: //INT32_LIST_PACKED:
+            reader.readInt32List(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 39: //FIXED64_LIST_PACKED:
+            reader.readFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 40: //FIXED32_LIST_PACKED:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 41: //BOOL_LIST_PACKED:
+            reader.readBoolList(SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 42: //UINT32_LIST_PACKED:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 43: //ENUM_LIST_PACKED:
+            reader.readEnumList(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 44: //SFIXED32_LIST_PACKED:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 45: //SFIXED64_LIST_PACKED:
+            reader.readSFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 46: //SINT32_LIST_PACKED:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 47: //SINT64_LIST_PACKED:
+            reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case -4: //GROUP (actually should be 252, but byte is [-128, 127])
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readGroup(messageFieldClassMap.get(fieldNumber)));
+            manifest.setFieldPresent(message, pos);
+            break;
+          case -3: //GROUP_LIST (actually should be 253, but byte is [-128, 127])
+            SchemaUtil.readGroupList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          default:
+            // Assume we've landed on an empty entry. Treat it as an unknown field - just skip it.
+            if (!reader.skipField()) {
+              // Done reading.
+              return;
+            }
+            break;
+        }
+        // TODO(nathanmittler): Do we need to make lists immutable?
+      } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+        // Treat fields with an invalid wire type as unknown fields (i.e. same as the default case).
+        if (!reader.skipField()) {
+          return;
+        }
+      }
+    }
+  }
+}

+ 253 - 0
java/core/src/main/java/com/google/protobuf/Proto3LiteLookupSchema.java

@@ -0,0 +1,253 @@
+// 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.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, lookup-based schema that can be used with any proto3-lite message class. The message
+ * class must extend {@link GeneratedMessage}.
+ */
+final class Proto3LiteLookupSchema<T> extends AbstractProto3LiteSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto3LiteLookupSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto3Manifest.newLookupManifest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.lookupPositionForFieldNumber(fieldNumber);
+      if (pos >= 0L) {
+        final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+        try {
+          switch (type(typeAndOffset)) {
+            case 0: //DOUBLE:
+              UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+              continue;
+            case 1: //FLOAT:
+              UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+              continue;
+            case 2: //INT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+              continue;
+            case 3: //UINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+              continue;
+            case 4: //INT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+              continue;
+            case 5: //FIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+              continue;
+            case 6: //FIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+              continue;
+            case 7: //BOOL:
+              UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+              continue;
+            case 8: //STRING:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+              continue;
+            case 9: //MESSAGE:
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+              continue;
+            case 10: //BYTES:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+              continue;
+            case 11: //UINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+              continue;
+            case 12: //ENUM:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+              continue;
+            case 13: //SFIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+              continue;
+            case 14: //SFIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+              continue;
+            case 15: //SINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+              continue;
+            case 16: //SINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+              continue;
+            case 17: //DOUBLE_LIST:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 18: //FLOAT_LIST:
+              reader.readFloatList(
+                  SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 19: //INT64_LIST:
+              reader.readInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 20: //UINT64_LIST:
+              reader.readUInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 21: //INT32_LIST:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 22: //FIXED64_LIST:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 23: //FIXED32_LIST:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 24: //BOOL_LIST:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 25: //STRING_LIST:
+              reader.readStringList(
+                  SchemaUtil.<String>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 26: //MESSAGE_LIST:
+              SchemaUtil.readProtobufMessageList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              continue;
+            case 27: //BYTES_LIST:
+              reader.readBytesList(
+                  SchemaUtil.<ByteString>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 28: //UINT32_LIST:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 29: //ENUM_LIST:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 30: //SFIXED32_LIST:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 31: //SFIXED64_LIST:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 32: //SINT32_LIST:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 33: //SINT64_LIST:
+              reader.readSInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 34: //DOUBLE_LIST_PACKED:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 35: //FLOAT_LIST_PACKED:
+              reader.readFloatList(
+                  SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 36: //INT64_LIST_PACKED:
+              reader.readInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 37: //UINT64_LIST_PACKED:
+              reader.readUInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 38: //INT32_LIST_PACKED:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 39: //FIXED64_LIST_PACKED:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 40: //FIXED32_LIST_PACKED:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 41: //BOOL_LIST_PACKED:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 42: //UINT32_LIST_PACKED:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 43: //ENUM_LIST_PACKED:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 44: //SFIXED32_LIST_PACKED:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 45: //SFIXED64_LIST_PACKED:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 46: //SINT32_LIST_PACKED:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            case 47: //SINT64_LIST_PACKED:
+              reader.readSInt64List(
+                  SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+              continue;
+            default:
+              // Unknown field type - break out of loop and skip the field.
+              break;
+          }
+        } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+          // Treat it as an unknown field - same as the default case.
+        }
+      }
+
+      // Unknown field.
+      if (!reader.skipField()) {
+        // Done reading.
+        return;
+      }
+    }
+  }
+}

+ 263 - 0
java/core/src/main/java/com/google/protobuf/Proto3LiteTableSchema.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.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, table-based schema that can be used with any proto3 lite message class. The message
+ * class must extend {@link GeneratedMessage}.
+ */
+final class Proto3LiteTableSchema<T> extends AbstractProto3LiteSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto3LiteTableSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto3Manifest.newTableManfiest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.tablePositionForFieldNumber(fieldNumber);
+      if (pos < 0) {
+        // Unknown field.
+        if (reader.skipField()) {
+          continue;
+        }
+        // Done reading.
+        return;
+      }
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      try {
+        switch (type(typeAndOffset)) {
+          case 0: //DOUBLE:
+            UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+            break;
+          case 1: //FLOAT:
+            UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+            break;
+          case 2: //INT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+            break;
+          case 3: //UINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+            break;
+          case 4: //INT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+            break;
+          case 5: //FIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+            break;
+          case 6: //FIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+            break;
+          case 7: //BOOL:
+            UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+            break;
+          case 8: //STRING:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+            break;
+          case 9: //MESSAGE:
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+            break;
+          case 10: //BYTES:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+            break;
+          case 11: //UINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+            break;
+          case 12: //ENUM:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+            break;
+          case 13: //SFIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+            break;
+          case 14: //SFIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+            break;
+          case 15: //SINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+            break;
+          case 16: //SINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+            break;
+          case 17: //DOUBLE_LIST:
+            reader.readDoubleList(
+                SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 18: //FLOAT_LIST:
+            reader.readFloatList(
+                SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 19: //INT64_LIST:
+            reader.readInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 20: //UINT64_LIST:
+            reader.readUInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 21: //INT32_LIST:
+            reader.readInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 22: //FIXED64_LIST:
+            reader.readFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 23: //FIXED32_LIST:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 24: //BOOL_LIST:
+            reader.readBoolList(
+                SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 25: //STRING_LIST:
+            reader.readStringList(
+                SchemaUtil.<String>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 26: //MESSAGE_LIST:
+            SchemaUtil.readProtobufMessageList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          case 27: //BYTES_LIST:
+            reader.readBytesList(
+                SchemaUtil.<ByteString>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 28: //UINT32_LIST:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 29: //ENUM_LIST:
+            reader.readEnumList(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 30: //SFIXED32_LIST:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 31: //SFIXED64_LIST:
+            reader.readSFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 32: //SINT32_LIST:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 33: //SINT64_LIST:
+            reader.readSInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 34: //DOUBLE_LIST_PACKED:
+            reader.readDoubleList(
+                SchemaUtil.<Double>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 35: //FLOAT_LIST_PACKED:
+            reader.readFloatList(
+                SchemaUtil.<Float>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 36: //INT64_LIST_PACKED:
+            reader.readInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 37: //UINT64_LIST_PACKED:
+            reader.readUInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 38: //INT32_LIST_PACKED:
+            reader.readInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 39: //FIXED64_LIST_PACKED:
+            reader.readFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 40: //FIXED32_LIST_PACKED:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 41: //BOOL_LIST_PACKED:
+            reader.readBoolList(
+                SchemaUtil.<Boolean>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 42: //UINT32_LIST_PACKED:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 43: //ENUM_LIST_PACKED:
+            reader.readEnumList(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 44: //SFIXED32_LIST_PACKED:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 45: //SFIXED64_LIST_PACKED:
+            reader.readSFixed64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 46: //SINT32_LIST_PACKED:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          case 47: //SINT64_LIST_PACKED:
+            reader.readSInt64List(
+                SchemaUtil.<Long>mutableProtobufListAt(message, offset(typeAndOffset)));
+            break;
+          default:
+            // Assume we've landed on an empty entry. Treat it as an unknown field - just skip it.
+            if (!reader.skipField()) {
+              // Done reading.
+              return;
+            }
+            break;
+        }
+        // TODO(nathanmittler): Do we need to make lists immutable?
+      } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+        // Treat fields with an invalid wire type as unknown fields (i.e. same as the default case).
+        if (!reader.skipField()) {
+          return;
+        }
+      }
+    }
+  }
+}

+ 245 - 0
java/core/src/main/java/com/google/protobuf/Proto3LookupSchema.java

@@ -0,0 +1,245 @@
+// 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.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, lookup-based schema that can be used with any standard (i.e. non-lite) proto3 message
+ * class. The message class must extend {@link GeneratedMessage}.
+ */
+final class Proto3LookupSchema<T> extends AbstractProto3StandardSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto3LookupSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto3Manifest.newTableManfiest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.lookupPositionForFieldNumber(fieldNumber);
+      if (pos >= 0L) {
+        final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+        try {
+          switch (type(typeAndOffset)) {
+            case 0: //DOUBLE:
+              UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+              continue;
+            case 1: //FLOAT:
+              UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+              continue;
+            case 2: //INT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+              continue;
+            case 3: //UINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+              continue;
+            case 4: //INT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+              continue;
+            case 5: //FIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+              continue;
+            case 6: //FIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+              continue;
+            case 7: //BOOL:
+              UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+              continue;
+            case 8: //STRING:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+              continue;
+            case 9: //MESSAGE:
+              UnsafeUtil.putObject(
+                  message,
+                  offset(typeAndOffset),
+                  reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+              continue;
+            case 10: //BYTES:
+              UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+              continue;
+            case 11: //UINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+              continue;
+            case 12: //ENUM:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+              continue;
+            case 13: //SFIXED32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+              continue;
+            case 14: //SFIXED64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+              continue;
+            case 15: //SINT32:
+              UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+              continue;
+            case 16: //SINT64:
+              UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+              continue;
+            case 17: //DOUBLE_LIST:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 18: //FLOAT_LIST:
+              reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 19: //INT64_LIST:
+              reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 20: //UINT64_LIST:
+              reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 21: //INT32_LIST:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 22: //FIXED64_LIST:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 23: //FIXED32_LIST:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 24: //BOOL_LIST:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 25: //STRING_LIST:
+              reader.readStringList(
+                  SchemaUtil.<String>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 26: //MESSAGE_LIST:
+              SchemaUtil.readMessageList(
+                  message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+              continue;
+            case 27: //BYTES_LIST:
+              reader.readBytesList(
+                  SchemaUtil.<ByteString>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 28: //UINT32_LIST:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 29: //ENUM_LIST:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 30: //SFIXED32_LIST:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 31: //SFIXED64_LIST:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 32: //SINT32_LIST:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 33: //SINT64_LIST:
+              reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 34: //DOUBLE_LIST_PACKED:
+              reader.readDoubleList(
+                  SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 35: //FLOAT_LIST_PACKED:
+              reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 36: //INT64_LIST_PACKED:
+              reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 37: //UINT64_LIST_PACKED:
+              reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 38: //INT32_LIST_PACKED:
+              reader.readInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 39: //FIXED64_LIST_PACKED:
+              reader.readFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 40: //FIXED32_LIST_PACKED:
+              reader.readFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 41: //BOOL_LIST_PACKED:
+              reader.readBoolList(
+                  SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 42: //UINT32_LIST_PACKED:
+              reader.readUInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 43: //ENUM_LIST_PACKED:
+              reader.readEnumList(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 44: //SFIXED32_LIST_PACKED:
+              reader.readSFixed32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 45: //SFIXED64_LIST_PACKED:
+              reader.readSFixed64List(
+                  SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 46: //SINT32_LIST_PACKED:
+              reader.readSInt32List(
+                  SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            case 47: //SINT64_LIST_PACKED:
+              reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+              continue;
+            default:
+              // Unknown field type - break out of loop and skip the field.
+              break;
+          }
+        } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+          // Treat it as an unknown field - same as the default case.
+        }
+      }
+
+      // Unknown field.
+      if (!reader.skipField()) {
+        // Done reading.
+        return;
+      }
+    }
+  }
+}

+ 233 - 0
java/core/src/main/java/com/google/protobuf/Proto3Manifest.java

@@ -0,0 +1,233 @@
+// 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;
+import java.util.List;
+
+/** Container for the field metadata of a single proto3 schema. */
+final class Proto3Manifest {
+  static final int INT_LENGTH = 4;
+  static final int LONG_LENGTH = INT_LENGTH * 2;
+  static final int LONGS_PER_FIELD = 2;
+  /**
+   * Note that field length is always a power of two so that we can use bit shifting (rather than
+   * division) to find the location of a field when parsing.
+   */
+  static final int FIELD_LENGTH = LONGS_PER_FIELD * LONG_LENGTH;
+
+  static final int FIELD_SHIFT = 4 /* 2^4 = 16 */;
+  static final int OFFSET_BITS = 20;
+  static final int OFFSET_MASK = 0XFFFFF;
+  static final long EMPTY_LONG = 0xFFFFFFFFFFFFFFFFL;
+
+  /**
+   * 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 -  37] unused
+   * [38 -  43] field type
+   * [44 -  63] field offset
+   * [64 - 127] unused
+   * </pre>
+   */
+  final ByteBuffer buffer;
+
+  final long address;
+  final long limit;
+  final int numFields;
+
+  final int minFieldNumber;
+  final int maxFieldNumber;
+
+  private Proto3Manifest(
+      ByteBuffer buffer,
+      long address,
+      long limit,
+      int numFields,
+      int minFieldNumber,
+      int maxFieldNumber) {
+    this.buffer = buffer;
+    this.address = address;
+    this.limit = limit;
+    this.numFields = numFields;
+    this.minFieldNumber = minFieldNumber;
+    this.maxFieldNumber = maxFieldNumber;
+  }
+
+  boolean isFieldInRange(int fieldNumber) {
+    return fieldNumber >= minFieldNumber && fieldNumber <= maxFieldNumber;
+  }
+
+  long tablePositionForFieldNumber(int fieldNumber) {
+    if (fieldNumber < minFieldNumber || fieldNumber > maxFieldNumber) {
+      return -1;
+    }
+
+    return indexToAddress(fieldNumber - minFieldNumber);
+  }
+
+  long lookupPositionForFieldNumber(int fieldNumber) {
+    int min = 0;
+    int max = numFields - 1;
+    while (min <= max) {
+      // Find the midpoint address.
+      int mid = (max + min) >>> 1;
+      long midAddress = indexToAddress(mid);
+      int midFieldNumber = numberAt(midAddress);
+      if (fieldNumber == midFieldNumber) {
+        // Found the field.
+        return midAddress;
+      }
+      if (fieldNumber < midFieldNumber) {
+        // Search the lower half.
+        max = mid - 1;
+      } else {
+        // Search the upper half.
+        min = mid + 1;
+      }
+    }
+    return -1;
+  }
+
+  int numberAt(long pos) {
+    return UnsafeUtil.getInt(pos);
+  }
+
+  int typeAndOffsetAt(long pos) {
+    return UnsafeUtil.getInt(pos + INT_LENGTH);
+  }
+
+  private long indexToAddress(int index) {
+    return address + (index << FIELD_SHIFT);
+  }
+
+  static byte type(int value) {
+    return (byte) (value >>> OFFSET_BITS);
+  }
+
+  static long offset(int value) {
+    return value & OFFSET_MASK;
+  }
+
+  static Proto3Manifest newTableManfiest(MessageInfo descriptor) {
+    List<FieldInfo> fds = descriptor.getFields();
+    if (fds.isEmpty()) {
+      throw new IllegalArgumentException("Table-based schema requires at least one field");
+    }
+
+    // Set up the buffer for direct indexing by field number.
+    final int minFieldNumber = fds.get(0).getFieldNumber();
+    final int maxFieldNumber = fds.get(fds.size() - 1).getFieldNumber();
+    final int numEntries = (maxFieldNumber - minFieldNumber) + 1;
+
+    int bufferLength = numEntries * FIELD_LENGTH;
+    ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
+    long tempAddress = UnsafeUtil.addressOffset(buffer);
+    if ((tempAddress & 7L) != 0) {
+      // Make sure that the memory address is 8-byte aligned.
+      tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
+    }
+    final long address = tempAddress;
+    final long limit = address + bufferLength;
+
+    // Fill in the manifest data from the descriptors.
+    int fieldIndex = 0;
+    FieldInfo fd = fds.get(fieldIndex++);
+    for (int bufferIndex = 0; bufferIndex < bufferLength; bufferIndex += FIELD_LENGTH) {
+      final int fieldNumber = fd.getFieldNumber();
+      if (bufferIndex < ((fieldNumber - minFieldNumber) << FIELD_SHIFT)) {
+        // Mark this entry as "empty".
+        long skipLimit = address + bufferIndex + FIELD_LENGTH;
+        for (long skipPos = address + bufferIndex; skipPos < skipLimit; skipPos += LONG_LENGTH) {
+          UnsafeUtil.putLong(skipPos, EMPTY_LONG);
+        }
+        continue;
+      }
+
+      // We found the entry for the next field. Store the entry in the manifest for
+      // this field and increment the field index.
+      FieldType type = fd.getType();
+      UnsafeUtil.putInt(address + bufferIndex, fieldNumber);
+      UnsafeUtil.putInt(
+          address + bufferIndex + INT_LENGTH,
+          (type.id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
+
+      // Advance to the next field, unless we're at the end.
+      if (fieldIndex < fds.size()) {
+        fd = fds.get(fieldIndex++);
+      }
+    }
+
+    return new Proto3Manifest(buffer, address, limit, fds.size(), minFieldNumber, maxFieldNumber);
+  }
+
+  static Proto3Manifest newLookupManifest(MessageInfo descriptor) {
+    List<FieldInfo> fds = descriptor.getFields();
+
+    final int numFields = fds.size();
+    int bufferLength = numFields * FIELD_LENGTH;
+    final ByteBuffer buffer = ByteBuffer.allocateDirect(bufferLength + LONG_LENGTH);
+    long tempAddress = UnsafeUtil.addressOffset(buffer);
+    if ((tempAddress & 7L) != 0) {
+      // Make sure that the memory address is 8-byte aligned.
+      tempAddress = (tempAddress & ~7L) + LONG_LENGTH;
+    }
+    final long address = tempAddress;
+    final long limit = address + bufferLength;
+
+    // Allocate and populate the data buffer.
+    long pos = address;
+    for (int i = 0; i < fds.size(); ++i, pos += FIELD_LENGTH) {
+      FieldInfo fd = fds.get(i);
+      UnsafeUtil.putInt(pos, fd.getFieldNumber());
+      UnsafeUtil.putInt(
+          pos + INT_LENGTH,
+          (fd.getType().id() << OFFSET_BITS) | (int) UnsafeUtil.objectFieldOffset(fd.getField()));
+    }
+
+    if (numFields > 0) {
+      return new Proto3Manifest(
+          buffer,
+          address,
+          limit,
+          numFields,
+          fds.get(0).getFieldNumber(),
+          fds.get(numFields - 1).getFieldNumber());
+    }
+    return new Proto3Manifest(buffer, address, limit, numFields, -1, -1);
+  }
+}

+ 115 - 0
java/core/src/main/java/com/google/protobuf/Proto3SchemaFactory.java

@@ -0,0 +1,115 @@
+// 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;
+
+/**
+ * Manufactures schemas for proto3 messages. Message classes must extend {@link
+ * com.google.protobuf.GeneratedMessage} or {@link com.google.protobuf.GeneratedMessageLite}.
+ */
+@ExperimentalApi
+public final class Proto3SchemaFactory implements SchemaFactory {
+  /**
+   * The mode with which to generate schemas.
+   *
+   * <p>For testing purposes only.
+   */
+  public enum Mode {
+    /** Always use a table-based indexing of fields. */
+    TABLE,
+
+    /** Always used lookup-based (i.e. binary search) indexing of fields. */
+    LOOKUP,
+
+    /**
+     * Default. Determine the appropriate field indexing mode based on how sparse the field numbers
+     * are for the message.
+     */
+    DYNAMIC
+  }
+
+  private final MessageInfoFactory messageDescriptorFactory;
+  private final Mode mode;
+
+  public Proto3SchemaFactory() {
+    this(DescriptorMessageInfoFactory.getInstance());
+  }
+
+  public Proto3SchemaFactory(MessageInfoFactory messageDescriptorFactory) {
+    this(messageDescriptorFactory, Mode.DYNAMIC);
+  }
+
+  /** For testing purposes only. Allows specification of {@link Mode}. */
+  public Proto3SchemaFactory(MessageInfoFactory messageDescriptorFactory, Mode mode) {
+    if (!isSupported()) {
+      throw new IllegalStateException("Schema factory is unsupported on this platform");
+    }
+    this.messageDescriptorFactory =
+        checkNotNull(messageDescriptorFactory, "messageDescriptorFactory");
+    this.mode = checkNotNull(mode, "mode");
+  }
+
+  public static boolean isSupported() {
+    return UnsafeUtil.hasUnsafeArrayOperations() && UnsafeUtil.hasUnsafeByteBufferOperations();
+  }
+
+  @Override
+  public <T> Schema<T> createSchema(Class<T> messageType) {
+    SchemaUtil.requireGeneratedMessage(messageType);
+
+    MessageInfo descriptor = messageDescriptorFactory.messageInfoFor(messageType);
+    switch (mode) {
+      case TABLE:
+        return newTableSchema(messageType, descriptor);
+      case LOOKUP:
+        return newLookupSchema(messageType, descriptor);
+      default:
+        return SchemaUtil.shouldUseTableSwitch(descriptor.getFields())
+            ? newTableSchema(messageType, descriptor)
+            : newLookupSchema(messageType, descriptor);
+    }
+  }
+
+  private <T> Schema<T> newTableSchema(Class<T> messageType, MessageInfo descriptor) {
+    if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      return new Proto3LiteTableSchema<T>(messageType, descriptor);
+    }
+    return new Proto3TableSchema<T>(messageType, descriptor);
+  }
+
+  private <T> Schema<T> newLookupSchema(Class<T> messageType, MessageInfo descriptor) {
+    if (GeneratedMessageLite.class.isAssignableFrom(messageType)) {
+      return new Proto3LiteLookupSchema<T>(messageType, descriptor);
+    }
+    return new Proto3LookupSchema<T>(messageType, descriptor);
+  }
+}

+ 242 - 0
java/core/src/main/java/com/google/protobuf/Proto3TableSchema.java

@@ -0,0 +1,242 @@
+// 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.Proto3Manifest.offset;
+import static com.google.protobuf.Proto3Manifest.type;
+
+import java.io.IOException;
+
+/**
+ * A generic, table-based schema that can be used with any proto3 message class. The message class
+ * must extend {@link GeneratedMessage}.
+ */
+final class Proto3TableSchema<T> extends AbstractProto3StandardSchema<T> {
+  private final Int2ObjectHashMap<Class<?>> messageFieldClassMap;
+
+  Proto3TableSchema(Class<T> messageClass, MessageInfo descriptor) {
+    super(messageClass, Proto3Manifest.newTableManfiest(descriptor));
+    this.messageFieldClassMap = descriptor.messageFieldClassMap();
+  }
+
+  @Override
+  public void mergeFrom(T message, Reader reader) throws IOException {
+    while (true) {
+      final int fieldNumber = reader.getFieldNumber();
+      final long pos = manifest.tablePositionForFieldNumber(fieldNumber);
+      if (pos < 0) {
+        // Unknown field.
+        if (reader.skipField()) {
+          continue;
+        }
+        // Done reading.
+        return;
+      }
+      final int typeAndOffset = manifest.typeAndOffsetAt(pos);
+
+      // Benchmarks have shown that switching on a byte is faster than an enum.
+      try {
+        switch (type(typeAndOffset)) {
+          case 0: //DOUBLE:
+            UnsafeUtil.putDouble(message, offset(typeAndOffset), reader.readDouble());
+            break;
+          case 1: //FLOAT:
+            UnsafeUtil.putFloat(message, offset(typeAndOffset), reader.readFloat());
+            break;
+          case 2: //INT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readInt64());
+            break;
+          case 3: //UINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readUInt64());
+            break;
+          case 4: //INT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readInt32());
+            break;
+          case 5: //FIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readFixed64());
+            break;
+          case 6: //FIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readFixed32());
+            break;
+          case 7: //BOOL:
+            UnsafeUtil.putBoolean(message, offset(typeAndOffset), reader.readBool());
+            break;
+          case 8: //STRING:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readString());
+            break;
+          case 9: //MESSAGE:
+            UnsafeUtil.putObject(
+                message,
+                offset(typeAndOffset),
+                reader.readMessage(messageFieldClassMap.get(fieldNumber)));
+            break;
+          case 10: //BYTES:
+            UnsafeUtil.putObject(message, offset(typeAndOffset), reader.readBytes());
+            break;
+          case 11: //UINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readUInt32());
+            break;
+          case 12: //ENUM:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readEnum());
+            break;
+          case 13: //SFIXED32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSFixed32());
+            break;
+          case 14: //SFIXED64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSFixed64());
+            break;
+          case 15: //SINT32:
+            UnsafeUtil.putInt(message, offset(typeAndOffset), reader.readSInt32());
+            break;
+          case 16: //SINT64:
+            UnsafeUtil.putLong(message, offset(typeAndOffset), reader.readSInt64());
+            break;
+          case 17: //DOUBLE_LIST:
+            reader.readDoubleList(SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 18: //FLOAT_LIST:
+            reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 19: //INT64_LIST:
+            reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 20: //UINT64_LIST:
+            reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 21: //INT32_LIST:
+            reader.readInt32List(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 22: //FIXED64_LIST:
+            reader.readFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 23: //FIXED32_LIST:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 24: //BOOL_LIST:
+            reader.readBoolList(SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 25: //STRING_LIST:
+            reader.readStringList(SchemaUtil.<String>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 26: //MESSAGE_LIST:
+            SchemaUtil.readMessageList(
+                message, offset(typeAndOffset), reader, messageFieldClassMap.get(fieldNumber));
+            break;
+          case 27: //BYTES_LIST:
+            reader.readBytesList(
+                SchemaUtil.<ByteString>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 28: //UINT32_LIST:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 29: //ENUM_LIST:
+            reader.readEnumList(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 30: //SFIXED32_LIST:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 31: //SFIXED64_LIST:
+            reader.readSFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 32: //SINT32_LIST:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 33: //SINT64_LIST:
+            reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 34: //DOUBLE_LIST_PACKED:
+            reader.readDoubleList(SchemaUtil.<Double>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 35: //FLOAT_LIST_PACKED:
+            reader.readFloatList(SchemaUtil.<Float>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 36: //INT64_LIST_PACKED:
+            reader.readInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 37: //UINT64_LIST_PACKED:
+            reader.readUInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 38: //INT32_LIST_PACKED:
+            reader.readInt32List(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 39: //FIXED64_LIST_PACKED:
+            reader.readFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 40: //FIXED32_LIST_PACKED:
+            reader.readFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 41: //BOOL_LIST_PACKED:
+            reader.readBoolList(SchemaUtil.<Boolean>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 42: //UINT32_LIST_PACKED:
+            reader.readUInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 43: //ENUM_LIST_PACKED:
+            reader.readEnumList(SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 44: //SFIXED32_LIST_PACKED:
+            reader.readSFixed32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 45: //SFIXED64_LIST_PACKED:
+            reader.readSFixed64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 46: //SINT32_LIST_PACKED:
+            reader.readSInt32List(
+                SchemaUtil.<Integer>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          case 47: //SINT64_LIST_PACKED:
+            reader.readSInt64List(SchemaUtil.<Long>mutableListAt(message, offset(typeAndOffset)));
+            break;
+          default:
+            // Assume we've landed on an empty entry. Treat it as an unknown field - just skip it.
+            if (!reader.skipField()) {
+              // Done reading.
+              return;
+            }
+            break;
+        }
+        // TODO(nathanmittler): Do we need to make lists immutable?
+      } catch (InvalidProtocolBufferException.InvalidWireTypeException e) {
+        // Treat fields with an invalid wire type as unknown fields (i.e. same as the default case).
+        if (!reader.skipField()) {
+          return;
+        }
+      }
+    }
+  }
+}

+ 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;
+}

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

@@ -0,0 +1,148 @@
+// 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
+public 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) {
+    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 {
+    schemaFor(message).mergeFrom(message, reader);
+  }
+
+  /** 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() {
+    // TODO(nathanmittler): Detect the proper factory for the platform.
+    SchemaFactory factory = null;
+    for (String className :
+        new String[] {
+          "com.google.frameworks.protobuf.experimental.android.schema.AndroidProto3SchemaFactory",
+          // TODO(nathanmittler): Remove this once the code has been completely ported over.
+          "com.google.frameworks.protobuf.experimental.schema.generic.Proto3SchemaFactory",
+          "com.google.protobuf.Proto3SchemaFactory"
+        }) {
+      factory = newSchemaFactory(className);
+      if (factory != null) {
+        break;
+      }
+    }
+    if (factory == null) {
+      throw new IllegalStateException("Unable to locate a default SchemaFactory. Check classpath.");
+    }
+    schemaFactory = factory;
+  }
+
+  private static SchemaFactory newSchemaFactory(String className) {
+    try {
+      return (SchemaFactory) Class.forName(className).getConstructor().newInstance();
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+}

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

@@ -0,0 +1,96 @@
+// 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
+public 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();
+  }
+}

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

@@ -0,0 +1,312 @@
+// 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;
+
+/** A reader of fields from a serialized protobuf message. */
+// TODO(nathanmittler): Refactor to allow the reader to allocate properly sized lists.
+@ExperimentalApi
+public interface Reader {
+  /** Value used to indicate that the end of input has been reached. */
+  int READ_DONE = Integer.MAX_VALUE;
+
+  /**
+   * Gets the field number for the current field being read.
+   *
+   * @return the current field number or {@link #READ_DONE} if the end of input has been reached.
+   */
+  int getFieldNumber() throws IOException;
+
+  /**
+   * Skips the current field and advances the reader to the next field.
+   *
+   * @return {@code true} if there are more fields or {@link #READ_DONE} 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.
+   */
+  String readString() 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) 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) 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 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, Class<T> targetType) 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) 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;
+}

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

@@ -406,6 +406,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) {

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

@@ -0,0 +1,55 @@
+// 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;
+
+/**
+ * A runtime schema for a single protobuf message. A schema provides operations on message
+ * instances such as serialization/deserialization.
+ */
+@ExperimentalApi
+public interface Schema<T> {
+  /**
+   * Writes the given message to the target {@link Writer}.
+   */
+  void writeTo(T message, Writer writer);
+
+  /**
+   * Reads fields from the given {@link Reader} and merges them into the message.
+   */
+  void mergeFrom(T message, Reader reader) throws IOException;
+
+  /**
+   * Creates a new instance of the message class.
+   */
+  T newInstance();
+}

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

@@ -0,0 +1,42 @@
+// 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
+public interface SchemaFactory {
+  /**
+   * Creates a schema instance for the given protobuf message type.
+   */
+  <T> Schema<T> createSchema(Class<T> messageType);
+}

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

@@ -0,0 +1,516 @@
+// 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.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Helper methods used by schemas. */
+@ExperimentalApi
+public final class SchemaUtil {
+  private static final Class<?> UNMODIFIABLE_LIST_CLASS =
+      Collections.unmodifiableList(new ArrayList<Integer>()).getClass();
+  private static final Class<?> ABSTRACT_MESSAGE_CLASS = getAbstractMessageClass();
+  private static final Class<?> GENERATED_MESSAGE_CLASS = getGeneratedMessageClass();
+  private static final Class<?> UNKNOWN_FIELD_SET_CLASS = getUnknownFieldSetClass();
+  private static final long UNKNOWN_FIELD_OFFSET = unknownFieldOffset();
+  private static final long LITE_UNKNOWN_FIELD_OFFSET = unknownFieldLiteOffset();
+  private static final Object DEFAULT_UNKNOWN_FIELD_SET = defaultUnknownFieldSet();
+  private static final long MEMOIZED_SIZE_FIELD_OFFSET = getMemoizedSizeFieldOffset();
+  private static final long LITE_MEMOIZED_SIZE_FIELD_OFFSET = getLiteMemoizedSizeFieldOffset();
+
+  private SchemaUtil() {}
+
+  /**
+   * Initializes all of the base class fields for the given {@link GeneratedMessageLite} to their
+   * default values.
+   */
+  public static void initLiteBaseClassFields(Object msg) {
+    UnsafeUtil.putObject(msg, LITE_UNKNOWN_FIELD_OFFSET, UnknownFieldSetLite.getDefaultInstance());
+    UnsafeUtil.putInt(msg, LITE_MEMOIZED_SIZE_FIELD_OFFSET, -1);
+  }
+
+  /**
+   * Initializes all of the base class fields for the given {@link
+   * com.google.protobuf.GeneratedMessage} to their default values.
+   */
+  public static void initBaseClassFields(Object msg) {
+    UnsafeUtil.putObject(msg, UNKNOWN_FIELD_OFFSET, DEFAULT_UNKNOWN_FIELD_SET);
+    UnsafeUtil.putInt(msg, MEMOIZED_SIZE_FIELD_OFFSET, -1);
+  }
+
+  /**
+   * Requires that the given message extend {@link com.google.protobuf.GeneratedMessage} or {@link
+   * GeneratedMessageLite}.
+   */
+  public static void requireGeneratedMessage(Class<?> messageType) {
+    if (!GeneratedMessageLite.class.isAssignableFrom(messageType)
+        && !GENERATED_MESSAGE_CLASS.isAssignableFrom(messageType)) {
+      throw new IllegalArgumentException(
+          "Message classes must extend GeneratedMessage or GeneratedMessageLite");
+    }
+  }
+
+  /** Initializes the message's memoized size to the default value. */
+  public static void initMemoizedSize(Object msg) {
+    final long fieldOffset =
+        (msg instanceof GeneratedMessageLite)
+            ? LITE_MEMOIZED_SIZE_FIELD_OFFSET
+            : MEMOIZED_SIZE_FIELD_OFFSET;
+
+    if (fieldOffset < 0) {
+      throw new IllegalArgumentException(
+          "Unable to identify memoizedSize field offset for message of type: "
+              + msg.getClass().getName());
+    }
+
+    UnsafeUtil.putInt(msg, fieldOffset, -1);
+  }
+
+  public static void writeDouble(int fieldNumber, double value, Writer writer) {
+    if (Double.compare(value, 0.0) != 0) {
+      writer.writeDouble(fieldNumber, value);
+    }
+  }
+
+  public static void writeFloat(int fieldNumber, float value, Writer writer) {
+    if (Float.compare(value, 0.0f) != 0) {
+      writer.writeFloat(fieldNumber, value);
+    }
+  }
+
+  public static void writeInt64(int fieldNumber, long value, Writer writer) {
+    if (value != 0) {
+      writer.writeInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeUInt64(int fieldNumber, long value, Writer writer) {
+    if (value != 0) {
+      writer.writeUInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeSInt64(int fieldNumber, long value, Writer writer) {
+    if (value != 0) {
+      writer.writeSInt64(fieldNumber, value);
+    }
+  }
+
+  public static void writeFixed64(int fieldNumber, long value, Writer writer) {
+    if (value != 0) {
+      writer.writeFixed64(fieldNumber, value);
+    }
+  }
+
+  public static void writeSFixed64(int fieldNumber, long value, Writer writer) {
+    if (value != 0) {
+      writer.writeSFixed64(fieldNumber, value);
+    }
+  }
+
+  public static void writeInt32(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeUInt32(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeUInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeSInt32(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeSInt32(fieldNumber, value);
+    }
+  }
+
+  public static void writeFixed32(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeFixed32(fieldNumber, value);
+    }
+  }
+
+  public static void writeSFixed32(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeSFixed32(fieldNumber, value);
+    }
+  }
+
+  public static void writeEnum(int fieldNumber, int value, Writer writer) {
+    if (value != 0) {
+      writer.writeEnum(fieldNumber, value);
+    }
+  }
+
+  public static void writeBool(int fieldNumber, boolean value, Writer writer) {
+    if (value) {
+      writer.writeBool(fieldNumber, true);
+    }
+  }
+
+  public static void writeString(int fieldNumber, Object value, Writer writer) {
+    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) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeString(fieldNumber, value);
+    }
+  }
+
+  public static void writeBytes(int fieldNumber, ByteString value, Writer writer) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBytes(fieldNumber, value);
+    }
+  }
+
+  public static void writeMessage(int fieldNumber, Object value, Writer writer) {
+    if (value != null) {
+      writer.writeMessage(fieldNumber, value);
+    }
+  }
+
+  public static void writeDoubleList(
+      int fieldNumber, List<Double> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeDoubleList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFloatList(
+      int fieldNumber, List<Float> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFloatList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeUInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeUInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSInt64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSInt64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFixed64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFixed64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSFixed64List(
+      int fieldNumber, List<Long> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSFixed64List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeUInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeUInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSInt32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSInt32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeFixed32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeFixed32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeSFixed32List(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeSFixed32List(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeEnumList(
+      int fieldNumber, List<Integer> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeEnumList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeBoolList(
+      int fieldNumber, List<Boolean> value, Writer writer, boolean packed) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBoolList(fieldNumber, value, packed);
+    }
+  }
+
+  public static void writeStringList(int fieldNumber, List<String> value, Writer writer) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeStringList(fieldNumber, value);
+    }
+  }
+
+  public static void writeBytesList(int fieldNumber, List<ByteString> value, Writer writer) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeBytesList(fieldNumber, value);
+    }
+  }
+
+  public static void writeMessageList(int fieldNumber, List<?> value, Writer writer) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeMessageList(fieldNumber, value);
+    }
+  }
+
+  public static void writeGroupList(int fieldNumber, List<?> value, Writer writer) {
+    if (value != null && !value.isEmpty()) {
+      writer.writeGroupList(fieldNumber, value);
+    }
+  }
+
+  /**
+   * Used for protobuf-lite (i.e. messages that extend {@link GeneratedMessageLite}). Reads in a
+   * list of messages into a target {@link ProtobufList} field in the message.
+   */
+  public static final <T> void readProtobufMessageList(
+      Object message, long offset, Reader reader, Class<T> targetType) throws IOException {
+    reader.readMessageList(SchemaUtil.<T>mutableProtobufListAt(message, offset), targetType);
+  }
+
+  /**
+   * Used for standard protobuf messages (i.e. messages that extend {@link
+   * com.google.protobuf.GeneratedMessage}). Reads in a list of messages into a target list field in
+   * the message.
+   */
+  public static <T> void readMessageList(
+      Object message, long offset, Reader reader, Class<T> targetType) throws IOException {
+    reader.readMessageList(SchemaUtil.<T>mutableListAt(message, offset), targetType);
+  }
+
+  /**
+   * Used for group types. Reads in a list of group messages into a target list field in the
+   * message.
+   */
+  public static <T> void readGroupList(
+      Object message, long offset, Reader reader, Class<T> targetType) throws IOException {
+    reader.readGroupList(SchemaUtil.<T>mutableListAt(message, offset), targetType);
+  }
+
+  /**
+   * Used for protobuf-lite (i.e. messages that extend GeneratedMessageLite). Converts the {@link
+   * ProtobufList} at the given field position in the message into a mutable list (if it isn't
+   * already).
+   */
+  @SuppressWarnings("unchecked")
+  public static <L> ProtobufList<L> mutableProtobufListAt(Object message, long pos) {
+    ProtobufList<L> list = (ProtobufList<L>) UnsafeUtil.getObject(message, pos);
+    if (!list.isModifiable()) {
+      int size = list.size();
+      list = list.mutableCopyWithCapacity(
+          size == 0 ? AbstractProtobufList.DEFAULT_CAPACITY : size * 2);
+      UnsafeUtil.putObject(message, pos, list);
+    }
+    return list;
+  }
+
+  /** Converts the list at the given field location in the message into a mutable list. */
+  @SuppressWarnings("unchecked")
+  public static <L> List<L> mutableListAt(Object message, long pos) {
+    List<L> list = (List<L>) UnsafeUtil.getObject(message, pos);
+    if (list.isEmpty()) {
+      list =
+          (list instanceof LazyStringList)
+              ? (List<L>) new LazyStringArrayList()
+              : new ArrayList<L>();
+      UnsafeUtil.putObject(message, pos, list);
+    } else if (UNMODIFIABLE_LIST_CLASS.isAssignableFrom(list.getClass())) {
+      list = new ArrayList<L>(list);
+      UnsafeUtil.putObject(message, pos, list);
+    } else if (list instanceof UnmodifiableLazyStringList) {
+      // Convert the list to a mutable list.
+      list = (List<L>) new LazyStringArrayList((List<String>) list);
+      UnsafeUtil.putObject(message, pos, list);
+    }
+    return list;
+  }
+
+  /**
+   * Determines whether to issue tableswitch or lookupswitch for the mergeFrom method.
+   *
+   * @see #shouldUseTableSwitch(int, int, int)
+   */
+  public static boolean shouldUseTableSwitch(List<FieldInfo> fields) {
+    // Determine whether to issue a tableswitch or a lookupswitch
+    // instruction.
+    if (fields.isEmpty()) {
+      return false;
+    }
+
+    int lo = fields.get(0).getFieldNumber();
+    int hi = fields.get(fields.size() - 1).getFieldNumber();
+    return shouldUseTableSwitch(lo, hi, fields.size());
+  }
+
+  /**
+   * 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) {
+    long tableSpaceCost = 4 + ((long) hi - lo + 1); // words
+    long tableTimeCost = 3; // comparisons
+    long lookupSpaceCost = 3 + 2 * (long) numFields;
+    long lookupTimeCost = numFields;
+    return tableSpaceCost + 3 * tableTimeCost <= lookupSpaceCost + 3 * lookupTimeCost;
+  }
+
+  private static Class<?> getAbstractMessageClass() {
+    try {
+      return Class.forName("com.google.protobuf.AbstractMessage");
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  private static Class<?> getGeneratedMessageClass() {
+    try {
+      return Class.forName("com.google.protobuf.GeneratedMessage");
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  private static Class<?> getUnknownFieldSetClass() {
+    try {
+      return Class.forName("com.google.protobuf.UnknownFieldSet");
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  private static long unknownFieldOffset() {
+    try {
+      if (GENERATED_MESSAGE_CLASS != null) {
+        Field field = GENERATED_MESSAGE_CLASS.getDeclaredField("unknownFields");
+        return UnsafeUtil.objectFieldOffset(field);
+      }
+    } catch (Throwable e) {
+      // Do nothing.
+    }
+    return -1;
+  }
+
+  private static long unknownFieldLiteOffset() {
+    try {
+      Field field = GeneratedMessageLite.class.getDeclaredField("unknownFields");
+      return UnsafeUtil.objectFieldOffset(field);
+    } catch (Throwable e) {
+      // Do nothing.
+    }
+    return -1;
+  }
+
+  private static Object defaultUnknownFieldSet() {
+    try {
+      if (UNKNOWN_FIELD_SET_CLASS != null) {
+        Method method = UNKNOWN_FIELD_SET_CLASS.getDeclaredMethod("getDefaultInstance");
+        return method.invoke(null);
+      }
+    } catch (Throwable e) {
+      // Do nothing.
+    }
+    return null;
+  }
+
+  private static long getMemoizedSizeFieldOffset() {
+    try {
+      if (ABSTRACT_MESSAGE_CLASS != null) {
+        Field field = ABSTRACT_MESSAGE_CLASS.getDeclaredField("memoizedSize");
+        return UnsafeUtil.objectFieldOffset(field);
+      }
+    } catch (Throwable e) {
+      // Do nothing.
+    }
+    return -1;
+  }
+
+  private static long getLiteMemoizedSizeFieldOffset() {
+    try {
+      Field field = GeneratedMessageLite.class.getDeclaredField("memoizedSerializedSize");
+      return UnsafeUtil.objectFieldOffset(field);
+    } catch (Throwable e) {
+      // Do nothing.
+    }
+    return -1;
+  }
+}

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

@@ -0,0 +1,155 @@
+// 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.List;
+
+/** A writer that performs serialization of protobuf message fields. */
+@ExperimentalApi
+public interface Writer {
+  /** Writes a field of type {@link FieldType#SFIXED32}. */
+  void writeSFixed32(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#INT64}. */
+  void writeInt64(int fieldNumber, long value);
+
+  /** Writes a field of type {@link FieldType#SFIXED64}. */
+  void writeSFixed64(int fieldNumber, long value);
+
+  /** Writes a field of type {@link FieldType#FLOAT}. */
+  void writeFloat(int fieldNumber, float value);
+
+  /** Writes a field of type {@link FieldType#DOUBLE}. */
+  void writeDouble(int fieldNumber, double value);
+
+  /** Writes a field of type {@link FieldType#ENUM}. */
+  void writeEnum(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#UINT64}. */
+  void writeUInt64(int fieldNumber, long value);
+
+  /** Writes a field of type {@link FieldType#INT32}. */
+  void writeInt32(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#FIXED64}. */
+  void writeFixed64(int fieldNumber, long value);
+
+  /** Writes a field of type {@link FieldType#FIXED32}. */
+  void writeFixed32(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#BOOL}. */
+  void writeBool(int fieldNumber, boolean value);
+
+  /** Writes a field of type {@link FieldType#STRING}. */
+  void writeString(int fieldNumber, String value);
+
+  /** Writes a field of type {@link FieldType#BYTES}. */
+  void writeBytes(int fieldNumber, ByteString value);
+
+  /** Writes a field of type {@link FieldType#UINT32}. */
+  void writeUInt32(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#SINT32}. */
+  void writeSInt32(int fieldNumber, int value);
+
+  /** Writes a field of type {@link FieldType#SINT64}. */
+  void writeSInt64(int fieldNumber, long value);
+
+  /** Writes a field of type {@link FieldType#MESSAGE}. */
+  void writeMessage(int fieldNumber, Object value);
+
+  /**
+   * Writes a field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroup(int fieldNumber, Object value);
+
+  /** Writes a list field of type {@link FieldType#INT32}. */
+  void writeInt32List(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#FIXED32}. */
+  void writeFixed32List(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#INT64}. */
+  void writeInt64List(int fieldNumber, List<Long> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#UINT64}. */
+  void writeUInt64List(int fieldNumber, List<Long> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#FIXED64}. */
+  void writeFixed64List(int fieldNumber, List<Long> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#FLOAT}. */
+  void writeFloatList(int fieldNumber, List<Float> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#DOUBLE}. */
+  void writeDoubleList(int fieldNumber, List<Double> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#ENUM}. */
+  void writeEnumList(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#BOOL}. */
+  void writeBoolList(int fieldNumber, List<Boolean> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#STRING}. */
+  void writeStringList(int fieldNumber, List<String> value);
+
+  /** Writes a list field of type {@link FieldType#BYTES}. */
+  void writeBytesList(int fieldNumber, List<ByteString> value);
+
+  /** Writes a list field of type {@link FieldType#UINT32}. */
+  void writeUInt32List(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#SFIXED32}. */
+  void writeSFixed32List(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#SFIXED64}. */
+  void writeSFixed64List(int fieldNumber, List<Long> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#SINT32}. */
+  void writeSInt32List(int fieldNumber, List<Integer> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#SINT64}. */
+  void writeSInt64List(int fieldNumber, List<Long> value, boolean packed);
+
+  /** Writes a list field of type {@link FieldType#MESSAGE}. */
+  void writeMessageList(int fieldNumber, List<?> value);
+
+  /**
+   * Writes a list field of type {@link FieldType#GROUP}.
+   *
+   * @deprecated groups fields are deprecated.
+   */
+  @Deprecated
+  void writeGroupList(int fieldNumber, List<?> value);
+}