浏览代码

Integrated internal changes from Google

This includes all internal changes from around May 20 to now.
Adam Cozzette 9 年之前
父节点
当前提交
d64a2d9941
共有 100 个文件被更改,包括 9366 次插入4058 次删除
  1. 2 0
      Makefile.am
  2. 94 1
      java/core/src/main/java/com/google/protobuf/AbstractMessage.java
  3. 22 24
      java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
  4. 24 22
      java/core/src/main/java/com/google/protobuf/BooleanArrayList.java
  5. 23 90
      java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
  6. 12 0
      java/core/src/main/java/com/google/protobuf/Descriptors.java
  7. 26 24
      java/core/src/main/java/com/google/protobuf/DoubleArrayList.java
  8. 8 3
      java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java
  9. 95 0
      java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java
  10. 49 8
      java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java
  11. 13 13
      java/core/src/main/java/com/google/protobuf/FieldSet.java
  12. 27 24
      java/core/src/main/java/com/google/protobuf/FloatArrayList.java
  13. 183 19
      java/core/src/main/java/com/google/protobuf/GeneratedMessage.java
  14. 138 100
      java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
  15. 27 24
      java/core/src/main/java/com/google/protobuf/IntArrayList.java
  16. 28 25
      java/core/src/main/java/com/google/protobuf/LongArrayList.java
  17. 113 106
      java/core/src/main/java/com/google/protobuf/MapEntry.java
  18. 104 215
      java/core/src/main/java/com/google/protobuf/MapEntryLite.java
  19. 374 39
      java/core/src/main/java/com/google/protobuf/MapField.java
  20. 60 385
      java/core/src/main/java/com/google/protobuf/MapFieldLite.java
  21. 0 3
      java/core/src/main/java/com/google/protobuf/MessageReflection.java
  22. 708 0
      java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java
  23. 241 0
      java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java
  24. 60 31
      java/core/src/main/java/com/google/protobuf/TextFormat.java
  25. 3 1
      java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
  26. 17 0
      java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java
  27. 210 0
      java/core/src/main/java/com/google/protobuf/UnsafeUtil.java
  28. 69 179
      java/core/src/main/java/com/google/protobuf/Utf8.java
  29. 97 93
      java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java
  30. 8 0
      java/core/src/test/java/com/google/protobuf/DescriptorsTest.java
  31. 76 73
      java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java
  32. 245 0
      java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java
  33. 20 0
      java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java
  34. 76 73
      java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java
  35. 73 70
      java/core/src/test/java/com/google/protobuf/IntArrayListTest.java
  36. 17 0
      java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java
  37. 29 2
      java/core/src/test/java/com/google/protobuf/LiteTest.java
  38. 93 90
      java/core/src/test/java/com/google/protobuf/LongArrayListTest.java
  39. 529 188
      java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java
  40. 574 80
      java/core/src/test/java/com/google/protobuf/MapForProto2Test.java
  41. 564 24
      java/core/src/test/java/com/google/protobuf/MapTest.java
  42. 190 0
      java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java
  43. 155 0
      java/core/src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.java
  44. 3 2
      java/core/src/test/java/com/google/protobuf/TestUtil.java
  45. 4 3
      java/core/src/test/java/com/google/protobuf/TextFormatTest.java
  46. 2 0
      java/core/src/test/proto/com/google/protobuf/field_presence_test.proto
  47. 11 0
      java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto
  48. 11 0
      java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto
  49. 11 1
      java/core/src/test/proto/com/google/protobuf/map_test.proto
  50. 256 0
      java/util/src/main/java/com/google/protobuf/util/Durations.java
  51. 27 21
      java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java
  52. 53 25
      java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java
  53. 253 286
      java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
  54. 116 265
      java/util/src/main/java/com/google/protobuf/util/TimeUtil.java
  55. 349 0
      java/util/src/main/java/com/google/protobuf/util/Timestamps.java
  56. 71 42
      java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java
  57. 358 358
      java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
  58. 36 41
      java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java
  59. 1 0
      java/util/src/test/proto/com/google/protobuf/util/json_test.proto
  60. 2 2
      js/binary/constants.js
  61. 1 2
      js/binary/decoder_test.js
  62. 11 14
      js/binary/reader_test.js
  63. 26 10
      js/binary/writer.js
  64. 96 1
      js/message.js
  65. 30 3
      js/message_test.js
  66. 1 0
      js/test.proto
  67. 29 0
      js/testbinary.proto
  68. 32 10
      python/google/protobuf/descriptor.py
  69. 64 1
      python/google/protobuf/descriptor_pool.py
  70. 5 1
      python/google/protobuf/internal/containers.py
  71. 18 0
      python/google/protobuf/internal/descriptor_pool_test.py
  72. 21 20
      python/google/protobuf/internal/descriptor_test.py
  73. 43 0
      python/google/protobuf/internal/file_options_test.proto
  74. 13 0
      python/google/protobuf/internal/json_format_test.py
  75. 4 0
      python/google/protobuf/internal/message_test.py
  76. 412 212
      python/google/protobuf/internal/text_format_test.py
  77. 384 378
      python/google/protobuf/json_format.py
  78. 269 8
      python/google/protobuf/pyext/descriptor.cc
  79. 6 0
      python/google/protobuf/pyext/descriptor.h
  80. 146 12
      python/google/protobuf/pyext/descriptor_containers.cc
  81. 8 0
      python/google/protobuf/pyext/descriptor_containers.h
  82. 38 0
      python/google/protobuf/pyext/descriptor_pool.cc
  83. 0 1
      python/google/protobuf/pyext/map_container.cc
  84. 9 57
      python/google/protobuf/pyext/message.cc
  85. 5 1
      python/google/protobuf/pyext/message.h
  86. 88 0
      python/google/protobuf/pyext/message_module.cc
  87. 436 158
      python/google/protobuf/text_format.py
  88. 2 0
      python/setup.py
  89. 2 2
      src/google/protobuf/any.pb.cc
  90. 6 2
      src/google/protobuf/any.pb.h
  91. 13 3
      src/google/protobuf/any.proto
  92. 16 16
      src/google/protobuf/api.pb.cc
  93. 18 6
      src/google/protobuf/api.pb.h
  94. 2 9
      src/google/protobuf/arena.cc
  95. 1 1
      src/google/protobuf/arena.h
  96. 0 1
      src/google/protobuf/arena_unittest.cc
  97. 16 11
      src/google/protobuf/compiler/command_line_interface.cc
  98. 1 1
      src/google/protobuf/compiler/cpp/cpp_enum_field.cc
  99. 26 31
      src/google/protobuf/compiler/cpp/cpp_map_field.cc
  100. 28 11
      src/google/protobuf/compiler/cpp/cpp_message.cc

+ 2 - 0
Makefile.am

@@ -209,6 +209,7 @@ java_EXTRA_DIST=
   java/core/src/main/java/com/google/protobuf/Extension.java                       \
   java/core/src/main/java/com/google/protobuf/ExtensionLite.java                   \
   java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java               \
+  java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java        \
   java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java           \
   java/core/src/main/java/com/google/protobuf/FieldSet.java                        \
   java/core/src/main/java/com/google/protobuf/FloatArrayList.java                  \
@@ -273,6 +274,7 @@ java_EXTRA_DIST=
   java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java             \
   java/core/src/test/java/com/google/protobuf/DynamicMessageTest.java              \
   java/core/src/test/java/com/google/protobuf/EnumTest.java                        \
+  java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.java    \
   java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java               \
   java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java              \
   java/core/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java        \

+ 94 - 1
java/core/src/main/java/com/google/protobuf/AbstractMessage.java

@@ -54,12 +54,40 @@ public abstract class AbstractMessage
     // TODO(dweis): Update GeneratedMessage to parameterize with MessageType and BuilderType.
     extends AbstractMessageLite
     implements Message {
-  
+
   @Override
   public boolean isInitialized() {
     return MessageReflection.isInitialized(this);
   }
 
+  /**
+   * Interface for the parent of a Builder that allows the builder to
+   * communicate invalidations back to the parent for use when using nested
+   * builders.
+   */
+  protected interface BuilderParent {
+
+    /**
+     * A builder becomes dirty whenever a field is modified -- including fields
+     * in nested builders -- and becomes clean when build() is called.  Thus,
+     * when a builder becomes dirty, all its parents become dirty as well, and
+     * when it becomes clean, all its children become clean.  The dirtiness
+     * state is used to invalidate certain cached values.
+     * <br>
+     * To this end, a builder calls markDirty() on its parent whenever it
+     * transitions from clean to dirty.  The parent must propagate this call to
+     * its own parent, unless it was already dirty, in which case the
+     * grandparent must necessarily already be dirty as well.  The parent can
+     * only transition back to "clean" after calling build() on all children.
+     */
+    void markDirty();
+  }
+
+  /** Create a nested builder. */
+  protected Message.Builder newBuilderForType(BuilderParent parent) {
+    throw new UnsupportedOperationException("Nested builder is not supported for this type.");
+  }
+
 
   @Override
   public List<String> findInitializationErrors() {
@@ -460,6 +488,31 @@ public abstract class AbstractMessage
           MessageReflection.findMissingFields(message));
     }
 
+    /**
+     * Used to support nested builders and called to mark this builder as clean.
+     * Clean builders will propagate the {@link BuildParent#markDirty()} event
+     * to their parent builders, while dirty builders will not, as their parents
+     * should be dirty already.
+     *
+     * NOTE: Implementations that don't support nested builders don't need to
+     * override this method.
+     */
+    void markClean() {
+      throw new IllegalStateException("Should be overriden by subclasses.");
+    }
+
+    /**
+     * Used to support nested builders and called when this nested builder is
+     * no longer used by its parent builder and should release the reference
+     * to its parent builder.
+     *
+     * NOTE: Implementations that don't support nested builders don't need to
+     * override this method.
+     */
+    void dispose() {
+      throw new IllegalStateException("Should be overriden by subclasses.");
+    }
+
     // ===============================================================
     // The following definitions seem to be required in order to make javac
     // not produce weird errors like:
@@ -550,4 +603,44 @@ public abstract class AbstractMessage
       return super.mergeDelimitedFrom(input, extensionRegistry);
     }
   }
+
+  /**
+   * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1
+   * generated code.
+   */
+  @Deprecated
+  protected static int hashLong(long n) {
+    return (int) (n ^ (n >>> 32));
+  }
+  //
+  /**
+   * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1
+   * generated code.
+   */
+  @Deprecated
+  protected static int hashBoolean(boolean b) {
+    return b ? 1231 : 1237;
+  }
+  //
+  /**
+   * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1
+   * generated code.
+   */
+  @Deprecated
+  protected static int hashEnum(EnumLite e) {
+    return e.getNumber();
+  }
+  //
+  /**
+   * @deprecated from v3.0.0-beta-3+, for compatiblity with v2.5.0 and v2.6.1
+   * generated code.
+   */
+  @Deprecated
+  protected static int hashEnumList(List<? extends EnumLite> list) {
+    int hash = 1;
+    for (EnumLite e : list) {
+      hash = 31 * hash + hashEnum(e);
+    }
+    return hash;
+  }
 }

+ 22 - 24
java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java

@@ -45,10 +45,10 @@ import java.util.Collection;
  */
 public abstract class AbstractMessageLite<
     MessageType extends AbstractMessageLite<MessageType, BuilderType>,
-    BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>> 
+    BuilderType extends AbstractMessageLite.Builder<MessageType, BuilderType>>
         implements MessageLite {
   protected int memoizedHashCode = 0;
-  
+
   @Override
   public ByteString toByteString() {
     try {
@@ -57,9 +57,7 @@ public abstract class AbstractMessageLite<
       writeTo(out.getCodedOutput());
       return out.build();
     } catch (IOException e) {
-      throw new RuntimeException(
-        "Serializing to a ByteString threw an IOException (should " +
-        "never happen).", e);
+      throw new RuntimeException(getSerializingExceptionMessage("ByteString"), e);
     }
   }
 
@@ -72,9 +70,7 @@ public abstract class AbstractMessageLite<
       output.checkNoSpaceLeft();
       return result;
     } catch (IOException e) {
-      throw new RuntimeException(
-        "Serializing to a byte array threw an IOException " +
-        "(should never happen).", e);
+      throw new RuntimeException(getSerializingExceptionMessage("byte array"), e);
     }
   }
 
@@ -109,6 +105,11 @@ public abstract class AbstractMessageLite<
     return new UninitializedMessageException(this);
   }
 
+  private String getSerializingExceptionMessage(String target) {
+    return "Serializing " + getClass().getName() + " to a " + target
+        + " threw an IOException (should never happen).";
+  }
+
   protected static void checkByteStringIsUtf8(ByteString byteString)
       throws IllegalArgumentException {
     if (!byteString.isValidUtf8()) {
@@ -120,7 +121,7 @@ public abstract class AbstractMessageLite<
       final Collection<? super T> list) {
     Builder.addAll(values, list);
   }
-  
+
   /**
    * A partial implementation of the {@link Message.Builder} interface which
    * implements as many methods of that interface as possible in terms of
@@ -156,9 +157,7 @@ public abstract class AbstractMessageLite<
       } catch (InvalidProtocolBufferException e) {
         throw e;
       } catch (IOException e) {
-        throw new RuntimeException(
-          "Reading from a ByteString threw an IOException (should " +
-          "never happen).", e);
+        throw new RuntimeException(getReadingExceptionMessage("ByteString"), e);
       }
     }
 
@@ -174,9 +173,7 @@ public abstract class AbstractMessageLite<
       } catch (InvalidProtocolBufferException e) {
         throw e;
       } catch (IOException e) {
-        throw new RuntimeException(
-          "Reading from a ByteString threw an IOException (should " +
-          "never happen).", e);
+        throw new RuntimeException(getReadingExceptionMessage("ByteString"), e);
       }
     }
 
@@ -197,9 +194,7 @@ public abstract class AbstractMessageLite<
       } catch (InvalidProtocolBufferException e) {
         throw e;
       } catch (IOException e) {
-        throw new RuntimeException(
-          "Reading from a byte array threw an IOException (should " +
-          "never happen).", e);
+        throw new RuntimeException(getReadingExceptionMessage("byte array"), e);
       }
     }
 
@@ -225,9 +220,7 @@ public abstract class AbstractMessageLite<
       } catch (InvalidProtocolBufferException e) {
         throw e;
       } catch (IOException e) {
-        throw new RuntimeException(
-          "Reading from a byte array threw an IOException (should " +
-          "never happen).", e);
+        throw new RuntimeException(getReadingExceptionMessage("byte array"), e);
       }
     }
 
@@ -321,7 +314,7 @@ public abstract class AbstractMessageLite<
       return mergeDelimitedFrom(input,
           ExtensionRegistryLite.getEmptyRegistry());
     }
-    
+
     @Override
     @SuppressWarnings("unchecked") // isInstance takes care of this
     public BuilderType mergeFrom(final MessageLite other) {
@@ -329,12 +322,17 @@ public abstract class AbstractMessageLite<
         throw new IllegalArgumentException(
             "mergeFrom(MessageLite) can only merge messages of the same type.");
       }
-        
+
       return internalMergeFrom((MessageType) other);
     }
-    
+
     protected abstract BuilderType internalMergeFrom(MessageType message);
 
+    private String getReadingExceptionMessage(String target) {
+      return "Reading " + getClass().getName() + " from a " + target
+          + " threw an IOException (should never happen).";
+    }
+
     /**
      * Construct an UninitializedMessageException reporting missing fields in
      * the given message.

+ 24 - 22
java/core/src/main/java/com/google/protobuf/BooleanArrayList.java

@@ -38,21 +38,22 @@ import java.util.RandomAccess;
 
 /**
  * An implementation of {@link BooleanList} on top of a primitive array.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 final class BooleanArrayList
-    extends AbstractProtobufList<Boolean> implements BooleanList, RandomAccess {
-  
+    extends AbstractProtobufList<Boolean>
+    implements BooleanList, RandomAccess {
+
   private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList();
   static {
     EMPTY_LIST.makeImmutable();
   }
-  
+
   public static BooleanArrayList emptyList() {
     return EMPTY_LIST;
   }
-  
+
   /**
    * The backing store for the list.
    */
@@ -72,13 +73,14 @@ final class BooleanArrayList
   }
 
   /**
-   * Constructs a new mutable {@code BooleanArrayList}.
+   * Constructs a new mutable {@code BooleanArrayList}
+   * containing the same elements as {@code other}.
    */
-  private BooleanArrayList(boolean[] array, int size) {
-    this.array = array;
+  private BooleanArrayList(boolean[] other, int size) {
+    array = other;
     this.size = size;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -91,14 +93,14 @@ final class BooleanArrayList
     if (size != other.size) {
       return false;
     }
-    
+
     final boolean[] arr = other.array;
     for (int i = 0; i < size; i++) {
       if (array[i] != arr[i]) {
         return false;
       }
     }
-    
+
     return true;
   }
 
@@ -170,7 +172,7 @@ final class BooleanArrayList
     if (index < 0 || index > size) {
       throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index));
     }
-    
+
     if (size < array.length) {
       // Shift everything over to make room
       System.arraycopy(array, index, array, index + 1, size - index);
@@ -178,10 +180,10 @@ final class BooleanArrayList
       // Resize to 1.5x the size
       int length = ((size * 3) / 2) + 1;
       boolean[] newArray = new boolean[length];
-      
+
       // Copy the first part directly
       System.arraycopy(array, 0, newArray, 0, index);
-      
+
       // Copy the rest shifted over by one to make room
       System.arraycopy(array, index, newArray, index + 1, size - index);
       array = newArray;
@@ -195,38 +197,38 @@ final class BooleanArrayList
   @Override
   public boolean addAll(Collection<? extends Boolean> collection) {
     ensureIsMutable();
-    
+
     if (collection == null) {
       throw new NullPointerException();
     }
-    
+
     // We specialize when adding another BooleanArrayList to avoid boxing elements.
     if (!(collection instanceof BooleanArrayList)) {
       return super.addAll(collection);
     }
-    
+
     BooleanArrayList list = (BooleanArrayList) collection;
     if (list.size == 0) {
       return false;
     }
-    
+
     int overflow = Integer.MAX_VALUE - size;
     if (overflow < list.size) {
       // We can't actually represent a list this large.
       throw new OutOfMemoryError();
     }
-    
+
     int newSize = size + list.size;
     if (newSize > array.length) {
       array = Arrays.copyOf(array, newSize);
     }
-    
+
     System.arraycopy(list.array, 0, array, size, list.size);
     size = newSize;
     modCount++;
     return true;
   }
-  
+
   @Override
   public boolean remove(Object o) {
     ensureIsMutable();
@@ -255,7 +257,7 @@ final class BooleanArrayList
   /**
    * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
    * {@link IndexOutOfBoundsException} if it is not.
-   * 
+   *
    * @param index the index to verify is in range
    */
   private void ensureIndexInRange(int index) {

+ 23 - 90
java/core/src/main/java/com/google/protobuf/CodedOutputStream.java

@@ -36,12 +36,9 @@ import com.google.protobuf.Utf8.UnpairedSurrogateException;
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.lang.reflect.Field;
 import java.nio.BufferOverflowException;
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
-import java.security.AccessController;
-import java.security.PrivilegedExceptionAction;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -59,9 +56,8 @@ import java.util.logging.Logger;
  */
 public abstract class CodedOutputStream extends ByteOutput {
   private static final Logger logger = Logger.getLogger(CodedOutputStream.class.getName());
-  private static final sun.misc.Unsafe UNSAFE = getUnsafe();
-  private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations();
-  private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset();
+  private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = UnsafeUtil.hasUnsafeArrayOperations();
+  private static final long ARRAY_BASE_OFFSET = UnsafeUtil.getArrayBaseOffset();
 
   private static final int FIXED_32_SIZE = 4;
   private static final int FIXED_64_SIZE = 8;
@@ -869,7 +865,7 @@ public abstract class CodedOutputStream extends ByteOutput {
     return computeLengthDelimitedFieldSize(value.getSerializedSize());
   }
 
-  private static int computeLengthDelimitedFieldSize(int fieldLength) {
+  static int computeLengthDelimitedFieldSize(int fieldLength) {
     return computeUInt32SizeNoTag(fieldLength) + fieldLength;
   }
 
@@ -948,6 +944,10 @@ public abstract class CodedOutputStream extends ByteOutput {
     OutOfSpaceException(Throwable cause) {
       super(MESSAGE, cause);
     }
+
+    OutOfSpaceException(String explanationMessage, Throwable cause) {
+      super(MESSAGE + ": " + explanationMessage, cause);
+    }
   }
 
   /**
@@ -1250,8 +1250,8 @@ public abstract class CodedOutputStream extends ByteOutput {
       try {
         buffer[position++] = value;
       } catch (IndexOutOfBoundsException e) {
-        throw new OutOfSpaceException(new IndexOutOfBoundsException(
-            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)));
+        throw new OutOfSpaceException(
+            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
       }
     }
 
@@ -1271,11 +1271,11 @@ public abstract class CodedOutputStream extends ByteOutput {
         long pos = ARRAY_BASE_OFFSET + position;
         while (true) {
           if ((value & ~0x7F) == 0) {
-            UNSAFE.putByte(buffer, pos++, (byte) value);
+            UnsafeUtil.putByte(buffer, pos++, (byte) value);
             position++;
             return;
           } else {
-            UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80));
+            UnsafeUtil.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80));
             position++;
             value >>>= 7;
           }
@@ -1293,8 +1293,7 @@ public abstract class CodedOutputStream extends ByteOutput {
           }
         } catch (IndexOutOfBoundsException e) {
           throw new OutOfSpaceException(
-              new IndexOutOfBoundsException(
-                  String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)));
+              String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
         }
       }
     }
@@ -1308,8 +1307,7 @@ public abstract class CodedOutputStream extends ByteOutput {
         buffer[position++] = (byte) ((value >> 24) & 0xFF);
       } catch (IndexOutOfBoundsException e) {
         throw new OutOfSpaceException(
-            new IndexOutOfBoundsException(
-                String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)));
+            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
       }
     }
 
@@ -1319,11 +1317,11 @@ public abstract class CodedOutputStream extends ByteOutput {
         long pos = ARRAY_BASE_OFFSET + position;
         while (true) {
           if ((value & ~0x7FL) == 0) {
-            UNSAFE.putByte(buffer, pos++, (byte) value);
+            UnsafeUtil.putByte(buffer, pos++, (byte) value);
             position++;
             return;
           } else {
-            UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80));
+            UnsafeUtil.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80));
             position++;
             value >>>= 7;
           }
@@ -1341,8 +1339,7 @@ public abstract class CodedOutputStream extends ByteOutput {
           }
         } catch (IndexOutOfBoundsException e) {
           throw new OutOfSpaceException(
-              new IndexOutOfBoundsException(
-                  String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)));
+              String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
         }
       }
     }
@@ -1360,8 +1357,7 @@ public abstract class CodedOutputStream extends ByteOutput {
         buffer[position++] = (byte) ((int) (value >> 56) & 0xFF);
       } catch (IndexOutOfBoundsException e) {
         throw new OutOfSpaceException(
-            new IndexOutOfBoundsException(
-                String.format("Pos: %d, limit: %d, len: %d", position, limit, 1)));
+            String.format("Pos: %d, limit: %d, len: %d", position, limit, 1), e);
       }
     }
 
@@ -1372,8 +1368,7 @@ public abstract class CodedOutputStream extends ByteOutput {
         position += length;
       } catch (IndexOutOfBoundsException e) {
         throw new OutOfSpaceException(
-            new IndexOutOfBoundsException(
-                String.format("Pos: %d, limit: %d, len: %d", position, limit, length)));
+            String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e);
       }
     }
 
@@ -1390,8 +1385,7 @@ public abstract class CodedOutputStream extends ByteOutput {
         position += length;
       } catch (IndexOutOfBoundsException e) {
         throw new OutOfSpaceException(
-            new IndexOutOfBoundsException(
-                String.format("Pos: %d, limit: %d, len: %d", position, limit, length)));
+            String.format("Pos: %d, limit: %d, len: %d", position, limit, length), e);
       }
     }
 
@@ -1855,10 +1849,10 @@ public abstract class CodedOutputStream extends ByteOutput {
         long pos = originalPos;
         while (true) {
           if ((value & ~0x7F) == 0) {
-            UNSAFE.putByte(buffer, pos++, (byte) value);
+            UnsafeUtil.putByte(buffer, pos++, (byte) value);
             break;
           } else {
-            UNSAFE.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80));
+            UnsafeUtil.putByte(buffer, pos++, (byte) ((value & 0x7F) | 0x80));
             value >>>= 7;
           }
         }
@@ -1890,10 +1884,10 @@ public abstract class CodedOutputStream extends ByteOutput {
         long pos = originalPos;
         while (true) {
           if ((value & ~0x7FL) == 0) {
-            UNSAFE.putByte(buffer, pos++, (byte) value);
+            UnsafeUtil.putByte(buffer, pos++, (byte) value);
             break;
           } else {
-            UNSAFE.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80));
+            UnsafeUtil.putByte(buffer, pos++, (byte) (((int) value & 0x7F) | 0x80));
             value >>>= 7;
           }
         }
@@ -2600,65 +2594,4 @@ public abstract class CodedOutputStream extends ByteOutput {
       position = 0;
     }
   }
-
-  /**
-   * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this
-   * platform.
-   */
-  private static sun.misc.Unsafe getUnsafe() {
-    sun.misc.Unsafe unsafe = null;
-    try {
-      unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() {
-        @Override
-        public sun.misc.Unsafe run() throws Exception {
-          Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
-
-          for (Field f : k.getDeclaredFields()) {
-            f.setAccessible(true);
-            Object x = f.get(null);
-            if (k.isInstance(x)) {
-              return k.cast(x);
-            }
-          }
-          // The sun.misc.Unsafe field does not exist.
-          return null;
-        }
-      });
-    } catch (Throwable e) {
-      // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError
-      // for Unsafe.
-    }
-
-    logger.log(Level.FINEST, "sun.misc.Unsafe: {}",
-        unsafe != null ? "available" : "unavailable");
-    return unsafe;
-  }
-
-  /**
-   * Indicates whether or not unsafe array operations are supported on this platform.
-   */
-  // TODO(nathanmittler): Add support for Android's MemoryBlock.
-  private static boolean supportsUnsafeArrayOperations() {
-    boolean supported = false;
-    if (UNSAFE != null) {
-      try {
-        UNSAFE.getClass().getMethod("arrayBaseOffset", Class.class);
-        UNSAFE.getClass().getMethod("putByte", Object.class, long.class, byte.class);
-        supported = true;
-      } catch (Throwable e) {
-        // Do nothing.
-      }
-    }
-    logger.log(Level.FINEST, "Unsafe array operations: {}",
-        supported ? "available" : "unavailable");
-    return supported;
-  }
-
-  /**
-   * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not
-   * available.
-   */
-  private static <T> int byteArrayBaseOffset() {
-    return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1;
-  }
 }

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

@@ -871,6 +871,10 @@ public final class Descriptors {
         nestedTypes[i].setProto(proto.getNestedType(i));
       }
 
+      for (int i = 0; i < oneofs.length; i++) {
+        oneofs[i].setProto(proto.getOneofDecl(i));
+      }
+
       for (int i = 0; i < enumTypes.length; i++) {
         enumTypes[i].setProto(proto.getEnumType(i));
       }
@@ -2513,6 +2517,10 @@ public final class Descriptors {
 
     public int getFieldCount() { return fieldCount; }
 
+    public OneofOptions getOptions() {
+      return proto.getOptions();
+    }
+
     /** Get a list of this message type's fields. */
     public List<FieldDescriptor> getFields() {
       return Collections.unmodifiableList(Arrays.asList(fields));
@@ -2522,6 +2530,10 @@ public final class Descriptors {
       return fields[index];
     }
 
+    private void setProto(final OneofDescriptorProto proto) {
+      this.proto = proto;
+    }
+
     private OneofDescriptor(final OneofDescriptorProto proto,
                             final FileDescriptor file,
                             final Descriptor parent,

+ 26 - 24
java/core/src/main/java/com/google/protobuf/DoubleArrayList.java

@@ -38,26 +38,27 @@ import java.util.RandomAccess;
 
 /**
  * An implementation of {@link DoubleList} on top of a primitive array.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 final class DoubleArrayList
-    extends AbstractProtobufList<Double> implements DoubleList, RandomAccess {
-  
+    extends AbstractProtobufList<Double>
+    implements DoubleList, RandomAccess {
+
   private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList();
   static {
     EMPTY_LIST.makeImmutable();
   }
-  
+
   public static DoubleArrayList emptyList() {
     return EMPTY_LIST;
   }
-  
+
   /**
    * The backing store for the list.
    */
   private double[] array;
-  
+
   /**
    * The size of the list distinct from the length of the array. That is, it is the number of
    * elements set in the list.
@@ -72,13 +73,14 @@ final class DoubleArrayList
   }
 
   /**
-   * Constructs a new mutable {@code DoubleArrayList} containing the same elements as {@code other}.
+   * Constructs a new mutable {@code DoubleArrayList}
+   * containing the same elements as {@code other}.
    */
-  private DoubleArrayList(double[] array, int size) {
-    this.array = array;
+  private DoubleArrayList(double[] other, int size) {
+    array = other;
     this.size = size;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -91,14 +93,14 @@ final class DoubleArrayList
     if (size != other.size) {
       return false;
     }
-    
+
     final double[] arr = other.array;
     for (int i = 0; i < size; i++) {
       if (array[i] != arr[i]) {
         return false;
       }
     }
-    
+
     return true;
   }
 
@@ -119,7 +121,7 @@ final class DoubleArrayList
     }
     return new DoubleArrayList(Arrays.copyOf(array, capacity), size);
   }
-  
+
   @Override
   public Double get(int index) {
     return getDouble(index);
@@ -171,7 +173,7 @@ final class DoubleArrayList
     if (index < 0 || index > size) {
       throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index));
     }
-    
+
     if (size < array.length) {
       // Shift everything over to make room
       System.arraycopy(array, index, array, index + 1, size - index);
@@ -179,10 +181,10 @@ final class DoubleArrayList
       // Resize to 1.5x the size
       int length = ((size * 3) / 2) + 1;
       double[] newArray = new double[length];
-      
+
       // Copy the first part directly
       System.arraycopy(array, 0, newArray, 0, index);
-      
+
       // Copy the rest shifted over by one to make room
       System.arraycopy(array, index, newArray, index + 1, size - index);
       array = newArray;
@@ -196,38 +198,38 @@ final class DoubleArrayList
   @Override
   public boolean addAll(Collection<? extends Double> collection) {
     ensureIsMutable();
-    
+
     if (collection == null) {
       throw new NullPointerException();
     }
-    
+
     // We specialize when adding another DoubleArrayList to avoid boxing elements.
     if (!(collection instanceof DoubleArrayList)) {
       return super.addAll(collection);
     }
-    
+
     DoubleArrayList list = (DoubleArrayList) collection;
     if (list.size == 0) {
       return false;
     }
-    
+
     int overflow = Integer.MAX_VALUE - size;
     if (overflow < list.size) {
       // We can't actually represent a list this large.
       throw new OutOfMemoryError();
     }
-    
+
     int newSize = size + list.size;
     if (newSize > array.length) {
       array = Arrays.copyOf(array, newSize);
     }
-    
+
     System.arraycopy(list.array, 0, array, size, list.size);
     size = newSize;
     modCount++;
     return true;
   }
-  
+
   @Override
   public boolean remove(Object o) {
     ensureIsMutable();
@@ -256,7 +258,7 @@ final class DoubleArrayList
   /**
    * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
    * {@link IndexOutOfBoundsException} if it is not.
-   * 
+   *
    * @param index the index to verify is in range
    */
   private void ensureIndexInRange(int index) {

+ 8 - 3
java/core/src/main/java/com/google/protobuf/ExtensionRegistry.java

@@ -101,7 +101,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite {
 
   /** Get the unmodifiable singleton empty instance. */
   public static ExtensionRegistry getEmptyRegistry() {
-    return EMPTY;
+    return EMPTY_REGISTRY;
   }
 
 
@@ -243,6 +243,11 @@ public class ExtensionRegistry extends ExtensionRegistryLite {
     add(newExtensionInfo(extension), extension.getExtensionType());
   }
 
+  /** Add an extension from a generated file to the registry. */
+  public void add(final GeneratedMessage.GeneratedExtension<?, ?> extension) {
+    add((Extension<?, ?>) extension);
+  }
+
   static ExtensionInfo newExtensionInfo(final Extension<?, ?> extension) {
     if (extension.getDescriptor().getJavaType() ==
         FieldDescriptor.JavaType.MESSAGE) {
@@ -311,7 +316,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite {
   private final Map<DescriptorIntPair, ExtensionInfo> mutableExtensionsByNumber;
 
   ExtensionRegistry(boolean empty) {
-    super(ExtensionRegistryLite.getEmptyRegistry());
+    super(EMPTY_REGISTRY_LITE);
     this.immutableExtensionsByName =
         Collections.<String, ExtensionInfo>emptyMap();
     this.mutableExtensionsByName =
@@ -321,7 +326,7 @@ public class ExtensionRegistry extends ExtensionRegistryLite {
     this.mutableExtensionsByNumber =
             Collections.<DescriptorIntPair, ExtensionInfo>emptyMap();
   }
-  private static final ExtensionRegistry EMPTY = new ExtensionRegistry(true);
+  static final ExtensionRegistry EMPTY_REGISTRY = new ExtensionRegistry(true);
 
   private void add(
       final ExtensionInfo extension,

+ 95 - 0
java/core/src/main/java/com/google/protobuf/ExtensionRegistryFactory.java

@@ -0,0 +1,95 @@
+// 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.ExtensionRegistryLite.EMPTY_REGISTRY_LITE;
+
+/**
+ * A factory object to create instances of {@link ExtensionRegistryLite}.
+ * 
+ * <p>
+ * This factory detects (via reflection) if the full (non-Lite) protocol buffer libraries
+ * are available, and if so, the instances returned are actually {@link ExtensionRegistry}.
+ */
+final class ExtensionRegistryFactory {
+
+  static final String FULL_REGISTRY_CLASS_NAME = "com.google.protobuf.ExtensionRegistry";
+
+  /* Visible for Testing
+     @Nullable */
+  static final Class<?> EXTENSION_REGISTRY_CLASS = reflectExtensionRegistry();
+
+  /* @Nullable */
+  static Class<?> reflectExtensionRegistry() {
+    try {
+      return Class.forName(FULL_REGISTRY_CLASS_NAME);
+    } catch (ClassNotFoundException e) {
+      // The exception allocation is potentially expensive on Android (where it can be triggered
+      // many times at start up). Is there a way to ameliorate this?
+      return null;
+    }
+  }
+
+  /** Construct a new, empty instance. */
+  public static ExtensionRegistryLite create() {
+    if (EXTENSION_REGISTRY_CLASS != null) {
+      try {
+        return invokeSubclassFactory("newInstance");
+      } catch (Exception e) {
+        // return a Lite registry.
+      }
+    }
+    return new ExtensionRegistryLite();
+  }
+
+  /** Get the unmodifiable singleton empty instance. */
+  public static ExtensionRegistryLite createEmpty() {
+    if (EXTENSION_REGISTRY_CLASS != null) {
+      try {
+        return invokeSubclassFactory("getEmptyRegistry");
+      } catch (Exception e) {
+        // return a Lite registry.
+      }
+    }
+    return EMPTY_REGISTRY_LITE;
+  }
+
+  static boolean isFullRegistry(ExtensionRegistryLite registry) {
+    return EXTENSION_REGISTRY_CLASS != null
+        && EXTENSION_REGISTRY_CLASS.isAssignableFrom(registry.getClass());
+  }
+
+  private static final ExtensionRegistryLite invokeSubclassFactory(String methodName)
+      throws Exception {
+    return (ExtensionRegistryLite) EXTENSION_REGISTRY_CLASS
+        .getMethod(methodName).invoke(null);
+  }
+}

+ 49 - 8
java/core/src/main/java/com/google/protobuf/ExtensionRegistryLite.java

@@ -79,6 +79,22 @@ public class ExtensionRegistryLite {
   // applications. Need to support this feature on smaller granularity.
   private static volatile boolean eagerlyParseMessageSets = false;
 
+  // Visible for testing.
+  static final String EXTENSION_CLASS_NAME = "com.google.protobuf.Extension";
+
+  /* @Nullable */
+  static Class<?> resolveExtensionClass() {
+    try {
+      return Class.forName(EXTENSION_CLASS_NAME);
+    } catch (ClassNotFoundException e) {
+      // See comment in ExtensionRegistryFactory on the potential expense of this.
+      return null;
+    }
+  }
+
+  /* @Nullable */
+  private static final Class<?> extensionClass = resolveExtensionClass();
+
   public static boolean isEagerlyParseMessageSets() {
     return eagerlyParseMessageSets;
   }
@@ -87,14 +103,22 @@ public class ExtensionRegistryLite {
     eagerlyParseMessageSets = isEagerlyParse;
   }
 
-  /** Construct a new, empty instance. */
+  /**
+   * Construct a new, empty instance.
+   * 
+   * <p>
+   * This may be an {@code ExtensionRegistry} if the full (non-Lite) proto libraries are available.
+   */
   public static ExtensionRegistryLite newInstance() {
-    return new ExtensionRegistryLite();
+    return ExtensionRegistryFactory.create();
   }
 
-  /** Get the unmodifiable singleton empty instance. */
+  /**
+   * Get the unmodifiable singleton empty instance of either ExtensionRegistryLite or
+   * {@code ExtensionRegistry} (if the full (non-Lite) proto libraries are available).
+   */
   public static ExtensionRegistryLite getEmptyRegistry() {
-    return EMPTY;
+    return ExtensionRegistryFactory.createEmpty();
   }
 
   /** Returns an unmodifiable view of the registry. */
@@ -128,6 +152,23 @@ public class ExtensionRegistryLite {
       extension);
   }
 
+  /**
+   * Add an extension from a lite generated file to the registry only if it is
+   * a non-lite extension i.e. {@link GeneratedMessageLite.GeneratedExtension}. */
+  public final void add(ExtensionLite<?, ?> extension) {
+    if (GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(extension.getClass())) {
+      add((GeneratedMessageLite.GeneratedExtension<?, ?>) extension);
+    }
+    if (ExtensionRegistryFactory.isFullRegistry(this)) {
+      try {
+        this.getClass().getMethod("add", extensionClass).invoke(this, extension);
+      } catch (Exception e) {
+        throw new IllegalArgumentException(
+            String.format("Could not invoke ExtensionRegistry#add for %s", extension), e);
+      }
+    }
+  }
+
   // =================================================================
   // Private stuff.
 
@@ -139,9 +180,11 @@ public class ExtensionRegistryLite {
         new HashMap<ObjectIntPair,
                     GeneratedMessageLite.GeneratedExtension<?, ?>>();
   }
+  static final ExtensionRegistryLite EMPTY_REGISTRY_LITE =
+      new ExtensionRegistryLite(true);
 
   ExtensionRegistryLite(ExtensionRegistryLite other) {
-    if (other == EMPTY) {
+    if (other == EMPTY_REGISTRY_LITE) {
       this.extensionsByNumber = Collections.emptyMap();
     } else {
       this.extensionsByNumber =
@@ -153,11 +196,9 @@ public class ExtensionRegistryLite {
                     GeneratedMessageLite.GeneratedExtension<?, ?>>
       extensionsByNumber;
 
-  private ExtensionRegistryLite(boolean empty) {
+  ExtensionRegistryLite(boolean empty) {
     this.extensionsByNumber = Collections.emptyMap();
   }
-  private static final ExtensionRegistryLite EMPTY =
-    new ExtensionRegistryLite(true);
 
   /** A (Object, int) pair, used as a map key. */
   private static final class ObjectIntPair {

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

@@ -120,21 +120,21 @@ final class FieldSet<FieldDescriptorType extends
   public boolean isImmutable() {
     return isImmutable;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
       return true;
     }
-    
+
     if (!(o instanceof FieldSet)) {
       return false;
     }
-    
+
     FieldSet<?> other = (FieldSet<?>) o;
-    return other.fields.equals(other.fields);
+    return fields.equals(other.fields);
   }
-  
+
   @Override
   public int hashCode() {
     return fields.hashCode();
@@ -493,7 +493,7 @@ final class FieldSet<FieldDescriptorType extends
   }
 
   /**
-   * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another 
+   * Like {@link Message.Builder#mergeFrom(Message)}, but merges from another
    * {@link FieldSet}.
    */
   public void mergeFrom(final FieldSet<FieldDescriptorType> other) {
@@ -638,10 +638,11 @@ final class FieldSet<FieldDescriptorType extends
    *               {@link Message#getField(Descriptors.FieldDescriptor)} for
    *               this field.
    */
-  private static void writeElement(final CodedOutputStream output,
-                                   final WireFormat.FieldType type,
-                                   final int number,
-                                   final Object value) throws IOException {
+  static void writeElement(
+      final CodedOutputStream output,
+      final WireFormat.FieldType type,
+      final int number,
+      final Object value) throws IOException {
     // Special case for groups, which need a start and end tag; other fields
     // can just use writeTag() and writeFieldNoTag().
     if (type == WireFormat.FieldType.GROUP) {
@@ -804,9 +805,8 @@ final class FieldSet<FieldDescriptorType extends
    *               {@link Message#getField(Descriptors.FieldDescriptor)} for
    *               this field.
    */
-  private static int computeElementSize(
-      final WireFormat.FieldType type,
-      final int number, final Object value) {
+  static int computeElementSize(
+      final WireFormat.FieldType type, final int number, final Object value) {
     int tagSize = CodedOutputStream.computeTagSize(number);
     if (type == WireFormat.FieldType.GROUP) {
       // Only count the end group tag for proto2 messages as for proto1 the end

+ 27 - 24
java/core/src/main/java/com/google/protobuf/FloatArrayList.java

@@ -38,25 +38,27 @@ import java.util.RandomAccess;
 
 /**
  * An implementation of {@link FloatList} on top of a primitive array.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
-final class FloatArrayList extends AbstractProtobufList<Float> implements FloatList, RandomAccess {
-  
+final class FloatArrayList
+    extends AbstractProtobufList<Float>
+    implements FloatList, RandomAccess {
+
   private static final FloatArrayList EMPTY_LIST = new FloatArrayList();
   static {
     EMPTY_LIST.makeImmutable();
   }
-  
+
   public static FloatArrayList emptyList() {
     return EMPTY_LIST;
   }
-  
+
   /**
    * The backing store for the list.
    */
   private float[] array;
-  
+
   /**
    * The size of the list distinct from the length of the array. That is, it is the number of
    * elements set in the list.
@@ -71,13 +73,14 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
   }
 
   /**
-   * Constructs a new mutable {@code FloatArrayList} containing the same elements as {@code other}.
+   * Constructs a new mutable {@code FloatArrayList}
+   * containing the same elements as {@code other}.
    */
-  private FloatArrayList(float[] array, int size) {
-    this.array = array;
+  private FloatArrayList(float[] other, int size) {
+    array = other;
     this.size = size;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -90,14 +93,14 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
     if (size != other.size) {
       return false;
     }
-    
+
     final float[] arr = other.array;
     for (int i = 0; i < size; i++) {
       if (array[i] != arr[i]) {
         return false;
       }
     }
-    
+
     return true;
   }
 
@@ -117,7 +120,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
     }
     return new FloatArrayList(Arrays.copyOf(array, capacity), size);
   }
-  
+
   @Override
   public Float get(int index) {
     return getFloat(index);
@@ -169,7 +172,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
     if (index < 0 || index > size) {
       throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index));
     }
-    
+
     if (size < array.length) {
       // Shift everything over to make room
       System.arraycopy(array, index, array, index + 1, size - index);
@@ -177,10 +180,10 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
       // Resize to 1.5x the size
       int length = ((size * 3) / 2) + 1;
       float[] newArray = new float[length];
-      
+
       // Copy the first part directly
       System.arraycopy(array, 0, newArray, 0, index);
-      
+
       // Copy the rest shifted over by one to make room
       System.arraycopy(array, index, newArray, index + 1, size - index);
       array = newArray;
@@ -194,38 +197,38 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
   @Override
   public boolean addAll(Collection<? extends Float> collection) {
     ensureIsMutable();
-    
+
     if (collection == null) {
       throw new NullPointerException();
     }
-    
+
     // We specialize when adding another FloatArrayList to avoid boxing elements.
     if (!(collection instanceof FloatArrayList)) {
       return super.addAll(collection);
     }
-    
+
     FloatArrayList list = (FloatArrayList) collection;
     if (list.size == 0) {
       return false;
     }
-    
+
     int overflow = Integer.MAX_VALUE - size;
     if (overflow < list.size) {
       // We can't actually represent a list this large.
       throw new OutOfMemoryError();
     }
-    
+
     int newSize = size + list.size;
     if (newSize > array.length) {
       array = Arrays.copyOf(array, newSize);
     }
-    
+
     System.arraycopy(list.array, 0, array, size, list.size);
     size = newSize;
     modCount++;
     return true;
   }
-  
+
   @Override
   public boolean remove(Object o) {
     ensureIsMutable();
@@ -254,7 +257,7 @@ final class FloatArrayList extends AbstractProtobufList<Float> implements FloatL
   /**
    * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
    * {@link IndexOutOfBoundsException} if it is not.
-   * 
+   *
    * @param index the index to verify is in range
    */
   private void ensureIndexInRange(int index) {

+ 183 - 19
java/core/src/main/java/com/google/protobuf/GeneratedMessage.java

@@ -355,31 +355,30 @@ public abstract class GeneratedMessage extends AbstractMessage
     // Noop for messages without extensions.
   }
 
-  protected abstract Message.Builder newBuilderForType(BuilderParent parent);
+  /**
+   * TODO(xiaofeng): remove this after b/29368482 is fixed. We need to move this
+   * interface to AbstractMessage in order to versioning GeneratedMessage but
+   * this move breaks binary compatibility for AppEngine. After AppEngine is
+   * fixed we can exlude this from google3.
+   */
+  protected interface BuilderParent extends AbstractMessage.BuilderParent {}
 
   /**
-   * Interface for the parent of a Builder that allows the builder to
-   * communicate invalidations back to the parent for use when using nested
-   * builders.
+   * TODO(xiaofeng): remove this together with GeneratedMessage.BuilderParent.
    */
-  protected interface BuilderParent {
+  protected abstract Message.Builder newBuilderForType(BuilderParent parent);
 
-    /**
-     * A builder becomes dirty whenever a field is modified -- including fields
-     * in nested builders -- and becomes clean when build() is called.  Thus,
-     * when a builder becomes dirty, all its parents become dirty as well, and
-     * when it becomes clean, all its children become clean.  The dirtiness
-     * state is used to invalidate certain cached values.
-     * <br>
-     * To this end, a builder calls markAsDirty() on its parent whenever it
-     * transitions from clean to dirty.  The parent must propagate this call to
-     * its own parent, unless it was already dirty, in which case the
-     * grandparent must necessarily already be dirty as well.  The parent can
-     * only transition back to "clean" after calling build() on all children.
-     */
-    void markDirty();
+  @Override
+  protected Message.Builder newBuilderForType(final AbstractMessage.BuilderParent parent) {
+    return newBuilderForType(new BuilderParent() {
+      @Override
+      public void markDirty() {
+        parent.markDirty();
+      }
+    });
   }
 
+
   @SuppressWarnings("unchecked")
   public abstract static class Builder <BuilderType extends Builder<BuilderType>>
       extends AbstractMessage.Builder<BuilderType> {
@@ -403,6 +402,7 @@ public abstract class GeneratedMessage extends AbstractMessage
       this.builderParent = builderParent;
     }
 
+    @Override
     void dispose() {
       builderParent = null;
     }
@@ -420,6 +420,7 @@ public abstract class GeneratedMessage extends AbstractMessage
      * Called by the subclass or a builder to notify us that a message was
      * built and may be cached and therefore invalidations are needed.
      */
+    @Override
     protected void markClean() {
       this.isClean = true;
     }
@@ -755,6 +756,33 @@ public abstract class GeneratedMessage extends AbstractMessage
     <Type> Type getExtension(
         ExtensionLite<MessageType, List<Type>> extension,
         int index);
+
+    /** Check if a singular extension is present. */
+    <Type> boolean hasExtension(
+        Extension<MessageType, Type> extension);
+    /** Check if a singular extension is present. */
+    <Type> boolean hasExtension(
+        GeneratedExtension<MessageType, Type> extension);
+    /** Get the number of elements in a repeated extension. */
+    <Type> int getExtensionCount(
+        Extension<MessageType, List<Type>> extension);
+    /** Get the number of elements in a repeated extension. */
+    <Type> int getExtensionCount(
+        GeneratedExtension<MessageType, List<Type>> extension);
+    /** Get the value of an extension. */
+    <Type> Type getExtension(
+        Extension<MessageType, Type> extension);
+    /** Get the value of an extension. */
+    <Type> Type getExtension(
+        GeneratedExtension<MessageType, Type> extension);
+    /** Get one element of a repeated extension. */
+    <Type> Type getExtension(
+        Extension<MessageType, List<Type>> extension,
+        int index);
+    /** Get one element of a repeated extension. */
+    <Type> Type getExtension(
+        GeneratedExtension<MessageType, List<Type>> extension,
+        int index);
   }
 
   /**
@@ -881,6 +909,53 @@ public abstract class GeneratedMessage extends AbstractMessage
           extensions.getRepeatedField(descriptor, index));
     }
 
+    /** Check if a singular extension is present. */
+    @Override
+    public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) {
+      return hasExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Check if a singular extension is present. */
+    @Override
+    public final <Type> boolean hasExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
+      return hasExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get the number of elements in a repeated extension. */
+    @Override
+    public final <Type> int getExtensionCount(
+        final Extension<MessageType, List<Type>> extension) {
+      return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension);
+    }
+    /** Get the number of elements in a repeated extension. */
+    @Override
+    public final <Type> int getExtensionCount(
+        final GeneratedExtension<MessageType, List<Type>> extension) {
+      return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(final Extension<MessageType, Type> extension) {
+      return getExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
+      return getExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get one element of a repeated extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final Extension<MessageType, List<Type>> extension, final int index) {
+      return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index);
+    }
+    /** Get one element of a repeated extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final GeneratedExtension<MessageType, List<Type>> extension, final int index) {
+      return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index);
+    }
+
     /** Called by subclasses to check if all extensions are initialized. */
     protected boolean extensionsAreInitialized() {
       return extensions.isInitialized();
@@ -1269,6 +1344,95 @@ public abstract class GeneratedMessage extends AbstractMessage
       return (BuilderType) this;
     }
 
+    /** Check if a singular extension is present. */
+    @Override
+    public final <Type> boolean hasExtension(final Extension<MessageType, Type> extension) {
+      return hasExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Check if a singular extension is present. */
+    @Override
+    public final <Type> boolean hasExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
+      return hasExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get the number of elements in a repeated extension. */
+    @Override
+    public final <Type> int getExtensionCount(
+        final Extension<MessageType, List<Type>> extension) {
+      return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension);
+    }
+    /** Get the number of elements in a repeated extension. */
+    @Override
+    public final <Type> int getExtensionCount(
+        final GeneratedExtension<MessageType, List<Type>> extension) {
+      return getExtensionCount((ExtensionLite<MessageType, List<Type>>) extension);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(final Extension<MessageType, Type> extension) {
+      return getExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
+      return getExtension((ExtensionLite<MessageType, Type>) extension);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final Extension<MessageType, List<Type>> extension, final int index) {
+      return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index);
+    }
+    /** Get the value of an extension. */
+    @Override
+    public final <Type> Type getExtension(
+        final GeneratedExtension<MessageType, List<Type>> extension, final int index) {
+      return getExtension((ExtensionLite<MessageType, List<Type>>) extension, index);
+    }
+    /** Set the value of an extension. */
+    public final <Type> BuilderType setExtension(
+        final Extension<MessageType, Type> extension, final Type value) {
+      return setExtension((ExtensionLite<MessageType, Type>) extension, value);
+    }
+    /** Set the value of an extension. */
+    public final <Type> BuilderType setExtension(
+        final GeneratedExtension<MessageType, Type> extension, final Type value) {
+      return setExtension((ExtensionLite<MessageType, Type>) extension, value);
+    }
+    /** Set the value of one element of a repeated extension. */
+    public final <Type> BuilderType setExtension(
+        final Extension<MessageType, List<Type>> extension,
+        final int index, final Type value) {
+      return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value);
+    }
+    /** Set the value of one element of a repeated extension. */
+    public final <Type> BuilderType setExtension(
+        final GeneratedExtension<MessageType, List<Type>> extension,
+        final int index, final Type value) {
+      return setExtension((ExtensionLite<MessageType, List<Type>>) extension, index, value);
+    }
+    /** Append a value to a repeated extension. */
+    public final <Type> BuilderType addExtension(
+        final Extension<MessageType, List<Type>> extension, final Type value) {
+      return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value);
+    }
+    /** Append a value to a repeated extension. */
+    public final <Type> BuilderType addExtension(
+        final GeneratedExtension<MessageType, List<Type>> extension, final Type value) {
+      return addExtension((ExtensionLite<MessageType, List<Type>>) extension, value);
+    }
+    /** Clear an extension. */
+    public final <Type> BuilderType clearExtension(
+        final Extension<MessageType, ?> extension) {
+      return clearExtension((ExtensionLite<MessageType, ?>) extension);
+    }
+    /** Clear an extension. */
+    public final <Type> BuilderType clearExtension(
+        final GeneratedExtension<MessageType, ?> extension) {
+      return clearExtension((ExtensionLite<MessageType, ?>) extension);
+    }
+
     /** Called by subclasses to check if all extensions are initialized. */
     protected boolean extensionsAreInitialized() {
       return extensions.isInitialized();

+ 138 - 100
java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java

@@ -59,15 +59,15 @@ import java.util.Map;
  */
 public abstract class GeneratedMessageLite<
     MessageType extends GeneratedMessageLite<MessageType, BuilderType>,
-    BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>> 
+    BuilderType extends GeneratedMessageLite.Builder<MessageType, BuilderType>>
         extends AbstractMessageLite<MessageType, BuilderType> {
 
   /** For use by generated code only. Lazily initialized to reduce allocations. */
   protected UnknownFieldSetLite unknownFields = UnknownFieldSetLite.getDefaultInstance();
-  
+
   /** For use by generated code only.  */
   protected int memoizedSerializedSize = -1;
-  
+
   @Override
   @SuppressWarnings("unchecked") // Guaranteed by runtime.
   public final Parser<MessageType> getParserForType() {
@@ -113,7 +113,7 @@ public abstract class GeneratedMessageLite<
     }
     return memoizedHashCode;
   }
-  
+
   @SuppressWarnings("unchecked") // Guaranteed by runtime
   int hashCode(HashCodeVisitor visitor) {
     if (memoizedHashCode == 0) {
@@ -125,18 +125,18 @@ public abstract class GeneratedMessageLite<
     }
     return memoizedHashCode;
   }
-  
+
   @SuppressWarnings("unchecked") // Guaranteed by isInstance + runtime
   @Override
   public boolean equals(Object other) {
     if (this == other) {
       return true;
     }
-    
+
     if (!getDefaultInstanceForType().getClass().isInstance(other)) {
       return false;
     }
-    
+
     try {
       visit(EqualsVisitor.INSTANCE, (MessageType) other);
     } catch (NotEqualsException e) {
@@ -144,7 +144,7 @@ public abstract class GeneratedMessageLite<
     }
     return true;
   }
-  
+
   /**
    * Same as {@link #equals(Object)} but throws {@code NotEqualsException}.
    */
@@ -153,7 +153,7 @@ public abstract class GeneratedMessageLite<
     if (this == other) {
       return true;
     }
-    
+
     if (!getDefaultInstanceForType().getClass().isInstance(other)) {
       return false;
     }
@@ -161,7 +161,7 @@ public abstract class GeneratedMessageLite<
     visit(visitor, (MessageType) other);
     return true;
   }
-  
+
   // The general strategy for unknown fields is to use an UnknownFieldSetLite that is treated as
   // mutable during the parsing constructor and immutable after. This allows us to avoid
   // any unnecessary intermediary allocations while reducing the generated code size.
@@ -174,10 +174,10 @@ public abstract class GeneratedMessageLite<
       unknownFields = UnknownFieldSetLite.newInstance();
     }
   }
-  
+
   /**
    * Called by subclasses to parse an unknown field. For use by generated code only.
-   * 
+   *
    * @return {@code true} unless the tag is an end-group tag.
    */
   protected boolean parseUnknownField(int tag, CodedInputStream input) throws IOException {
@@ -185,7 +185,7 @@ public abstract class GeneratedMessageLite<
     if (WireFormat.getTagWireType(tag) == WireFormat.WIRETYPE_END_GROUP) {
       return false;
     }
-    
+
     ensureUnknownFieldsInitialized();
     return unknownFields.mergeFieldFrom(tag, input);
   }
@@ -197,7 +197,7 @@ public abstract class GeneratedMessageLite<
     ensureUnknownFieldsInitialized();
     unknownFields.mergeVarintField(tag, value);
   }
-  
+
   /**
    * Called by subclasses to parse an unknown field. For use by generated code only.
    */
@@ -205,7 +205,7 @@ public abstract class GeneratedMessageLite<
     ensureUnknownFieldsInitialized();
     unknownFields.mergeLengthDelimitedField(fieldNumber, value);
   }
-  
+
   /**
    * Called by subclasses to complete parsing. For use by generated code only.
    */
@@ -292,7 +292,7 @@ public abstract class GeneratedMessageLite<
     dynamicMethod(MethodToInvoke.VISIT, visitor, other);
     unknownFields = visitor.visitUnknownFields(unknownFields, other.unknownFields);
   }
-  
+
   /**
    * Merge some unknown fields into the {@link UnknownFieldSetLite} for this
    * message.
@@ -359,9 +359,9 @@ public abstract class GeneratedMessageLite<
       if (isBuilt) {
         return instance;
       }
-      
+
       instance.makeImmutable();
-      
+
       isBuilt = true;
       return instance;
     }
@@ -374,24 +374,24 @@ public abstract class GeneratedMessageLite<
       }
       return result;
     }
-    
+
     @Override
     protected BuilderType internalMergeFrom(MessageType message) {
       return mergeFrom(message);
     }
-    
+
     /** All subclasses implement this. */
     public BuilderType mergeFrom(MessageType message) {
       copyOnWrite();
       instance.visit(MergeFromVisitor.INSTANCE, message);
       return (BuilderType) this;
     }
-    
+
     @Override
     public MessageType getDefaultInstanceForType() {
       return defaultInstance;
     }
-    
+
     @Override
     public BuilderType mergeFrom(
         com.google.protobuf.CodedInputStream input,
@@ -466,12 +466,12 @@ public abstract class GeneratedMessageLite<
       super.visit(visitor, other);
       extensions = visitor.visitExtensions(extensions, other.extensions);
     }
-    
+
     /**
      * Parse an unknown field or an extension. For use by generated code only.
-     * 
+     *
      * <p>For use by generated code only.
-     * 
+     *
      * @return {@code true} unless the tag is an end-group tag.
      */
     protected <MessageType extends MessageLite> boolean parseUnknownField(
@@ -590,7 +590,7 @@ public abstract class GeneratedMessageLite<
 
       return true;
     }
-    
+
     private void verifyExtensionContainingType(
         final GeneratedExtension<MessageType, ?> extension) {
       if (extension.getContainingTypeDefaultInstance() !=
@@ -607,7 +607,7 @@ public abstract class GeneratedMessageLite<
     public final <Type> boolean hasExtension(final ExtensionLite<MessageType, Type> extension) {
       GeneratedExtension<MessageType, Type> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       return extensions.hasField(extensionLite.descriptor);
     }
@@ -618,7 +618,7 @@ public abstract class GeneratedMessageLite<
         final ExtensionLite<MessageType, List<Type>> extension) {
       GeneratedExtension<MessageType, List<Type>> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       return extensions.getRepeatedFieldCount(extensionLite.descriptor);
     }
@@ -629,7 +629,7 @@ public abstract class GeneratedMessageLite<
     public final <Type> Type getExtension(final ExtensionLite<MessageType, Type> extension) {
       GeneratedExtension<MessageType, Type> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       final Object value = extensions.getField(extensionLite.descriptor);
       if (value == null) {
@@ -660,7 +660,7 @@ public abstract class GeneratedMessageLite<
     @Override
     protected final void makeImmutable() {
       super.makeImmutable();
-      
+
       extensions.makeImmutable();
     }
 
@@ -734,7 +734,7 @@ public abstract class GeneratedMessageLite<
       implements ExtendableMessageOrBuilder<MessageType, BuilderType> {
     protected ExtendableBuilder(MessageType defaultInstance) {
       super(defaultInstance);
-      
+
       // TODO(dweis): This is kind of an unnecessary clone since we construct a
       //     new instance in the parent constructor which makes the extensions
       //     immutable. This extra allocation shouldn't matter in practice
@@ -753,7 +753,7 @@ public abstract class GeneratedMessageLite<
       if (!isBuilt) {
         return;
       }
-      
+
       super.copyOnWrite();
       instance.extensions = instance.extensions.clone();
     }
@@ -814,14 +814,14 @@ public abstract class GeneratedMessageLite<
     public BuilderType clone() {
       return super.clone();
     }
-    
+
     /** Set the value of an extension. */
     public final <Type> BuilderType setExtension(
         final ExtensionLite<MessageType, Type> extension,
         final Type value) {
       GeneratedExtension<MessageType, Type> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       copyOnWrite();
       instance.extensions.setField(extensionLite.descriptor, extensionLite.toFieldSetType(value));
@@ -834,7 +834,7 @@ public abstract class GeneratedMessageLite<
         final int index, final Type value) {
       GeneratedExtension<MessageType, List<Type>> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       copyOnWrite();
       instance.extensions.setRepeatedField(
@@ -848,7 +848,7 @@ public abstract class GeneratedMessageLite<
         final Type value) {
       GeneratedExtension<MessageType, List<Type>> extensionLite =
           checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       copyOnWrite();
       instance.extensions.addRepeatedField(
@@ -860,7 +860,7 @@ public abstract class GeneratedMessageLite<
     public final <Type> BuilderType clearExtension(
         final ExtensionLite<MessageType, ?> extension) {
       GeneratedExtension<MessageType, ?> extensionLite = checkIsLite(extension);
-      
+
       verifyExtensionContainingType(extensionLite);
       copyOnWrite();
       instance.extensions.clearField(extensionLite.descriptor);
@@ -1157,7 +1157,7 @@ public abstract class GeneratedMessageLite<
     public static SerializedForm of(MessageLite message) {
       return new SerializedForm(message);
     }
-    
+
     private static final long serialVersionUID = 0L;
 
     private final String messageClassName;
@@ -1191,7 +1191,7 @@ public abstract class GeneratedMessageLite<
       } catch (ClassNotFoundException e) {
         throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e);
       } catch (NoSuchFieldException e) {
-        throw new RuntimeException("Unable to find DEFAULT_INSTANCE in " + messageClassName, e);
+        return readResolveFallback();
       } catch (SecurityException e) {
         throw new RuntimeException("Unable to call DEFAULT_INSTANCE in " + messageClassName, e);
       } catch (IllegalAccessException e) {
@@ -1200,8 +1200,35 @@ public abstract class GeneratedMessageLite<
         throw new RuntimeException("Unable to understand proto buffer", e);
       }
     }
+
+    /**
+     * @deprecated from v3.0.0-beta-3+, for compatibility with v2.5.0 and v2.6.1 generated code.
+     */
+    @Deprecated
+    private Object readResolveFallback() throws ObjectStreamException {
+      try {
+        Class<?> messageClass = Class.forName(messageClassName);
+        java.lang.reflect.Field defaultInstanceField =
+            messageClass.getDeclaredField("defaultInstance");
+        defaultInstanceField.setAccessible(true);
+        MessageLite defaultInstance = (MessageLite) defaultInstanceField.get(null);
+        return defaultInstance.newBuilderForType()
+            .mergeFrom(asBytes)
+            .buildPartial();
+      } catch (ClassNotFoundException e) {
+        throw new RuntimeException("Unable to find proto buffer class: " + messageClassName, e);
+      } catch (NoSuchFieldException e) {
+        throw new RuntimeException("Unable to find defaultInstance in " + messageClassName, e);
+      } catch (SecurityException e) {
+        throw new RuntimeException("Unable to call defaultInstance in " + messageClassName, e);
+      } catch (IllegalAccessException e) {
+        throw new RuntimeException("Unable to call parsePartialFrom", e);
+      } catch (InvalidProtocolBufferException e) {
+        throw new RuntimeException("Unable to understand proto buffer", e);
+      }
+    }
   }
-  
+
   /**
    * Checks that the {@link Extension} is Lite and returns it as a
    * {@link GeneratedExtension}.
@@ -1215,7 +1242,7 @@ public abstract class GeneratedMessageLite<
     if (!extension.isLite()) {
       throw new IllegalArgumentException("Expected a lite extension.");
     }
-    
+
     return (GeneratedExtension<MessageType, T>) extension;
   }
 
@@ -1227,8 +1254,8 @@ public abstract class GeneratedMessageLite<
   protected static final <T extends GeneratedMessageLite<T, ?>> boolean isInitialized(
       T message, boolean shouldMemoize) {
     return message.dynamicMethod(MethodToInvoke.IS_INITIALIZED, shouldMemoize) != null;
-  } 
-  
+  }
+
   protected static final <T extends GeneratedMessageLite<T, ?>> void makeImmutable(T message) {
     message.dynamicMethod(MethodToInvoke.MAKE_IMMUTABLE);
   }
@@ -1246,7 +1273,7 @@ public abstract class GeneratedMessageLite<
   protected static LongList emptyLongList() {
     return LongArrayList.emptyList();
   }
-  
+
   protected static LongList mutableCopy(LongList list) {
     int size = list.size();
     return list.mutableCopyWithCapacity(
@@ -1256,7 +1283,7 @@ public abstract class GeneratedMessageLite<
   protected static FloatList emptyFloatList() {
     return FloatArrayList.emptyList();
   }
-  
+
   protected static FloatList mutableCopy(FloatList list) {
     int size = list.size();
     return list.mutableCopyWithCapacity(
@@ -1266,7 +1293,7 @@ public abstract class GeneratedMessageLite<
   protected static DoubleList emptyDoubleList() {
     return DoubleArrayList.emptyList();
   }
-  
+
   protected static DoubleList mutableCopy(DoubleList list) {
     int size = list.size();
     return list.mutableCopyWithCapacity(
@@ -1276,7 +1303,7 @@ public abstract class GeneratedMessageLite<
   protected static BooleanList emptyBooleanList() {
     return BooleanArrayList.emptyList();
   }
-  
+
   protected static BooleanList mutableCopy(BooleanList list) {
     int size = list.size();
     return list.mutableCopyWithCapacity(
@@ -1286,7 +1313,7 @@ public abstract class GeneratedMessageLite<
   protected static <E> ProtobufList<E> emptyProtobufList() {
     return ProtobufArrayList.emptyList();
   }
-  
+
   protected static <E> ProtobufList<E> mutableCopy(ProtobufList<E> list) {
     int size = list.size();
     return list.mutableCopyWithCapacity(
@@ -1300,20 +1327,20 @@ public abstract class GeneratedMessageLite<
    */
   protected static class DefaultInstanceBasedParser<T extends GeneratedMessageLite<T, ?>>
       extends AbstractParser<T> {
-    
+
     private T defaultInstance;
-    
+
     public DefaultInstanceBasedParser(T defaultInstance) {
       this.defaultInstance = defaultInstance;
     }
-    
+
     @Override
     public T parsePartialFrom(CodedInputStream input, ExtensionRegistryLite extensionRegistry)
         throws InvalidProtocolBufferException {
       return GeneratedMessageLite.parsePartialFrom(defaultInstance, input, extensionRegistry);
     }
   }
-  
+
   /**
    * A static helper method for parsing a partial from input using the extension registry and the
    * instance.
@@ -1335,14 +1362,14 @@ public abstract class GeneratedMessageLite<
     }
     return result;
   }
-  
+
   protected static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
       T defaultInstance,
       CodedInputStream input)
       throws InvalidProtocolBufferException {
     return parsePartialFrom(defaultInstance, input, ExtensionRegistryLite.getEmptyRegistry());
   }
-  
+
   /**
    * Helper method to check if message is initialized.
    *
@@ -1373,7 +1400,7 @@ public abstract class GeneratedMessageLite<
       throws InvalidProtocolBufferException {
     return checkMessageInitialized(parsePartialFrom(defaultInstance, data, extensionRegistry));
   }
-  
+
   // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the
   // ByteString.
   private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
@@ -1393,7 +1420,7 @@ public abstract class GeneratedMessageLite<
       throw e;
     }
   }
-  
+
   // This is a special case since we want to verify that the last tag is 0. We assume we exhaust the
   // ByteString.
   private static <T extends GeneratedMessageLite<T, ?>> T parsePartialFrom(
@@ -1477,7 +1504,7 @@ public abstract class GeneratedMessageLite<
     return checkMessageInitialized(
         parsePartialDelimitedFrom(defaultInstance, input, extensionRegistry));
   }
-  
+
   private static <T extends GeneratedMessageLite<T, ?>> T parsePartialDelimitedFrom(
       T defaultInstance,
       InputStream input,
@@ -1530,13 +1557,12 @@ public abstract class GeneratedMessageLite<
     Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other);
     Object visitOneofMessage(boolean minePresent, Object mine, Object other);
     void visitOneofNotSet(boolean minePresent);
-    
+
     /**
      * Message fields use null sentinals.
      */
     <T extends MessageLite> T visitMessage(T mine, T other);
-    LazyFieldLite visitLazyMessage(
-        boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other);
+    LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other);
 
     <T> ProtobufList<T> visitList(ProtobufList<T> mine, ProtobufList<T> other);
     BooleanList visitBooleanList(BooleanList mine, BooleanList other);
@@ -1686,7 +1712,7 @@ public abstract class GeneratedMessageLite<
       }
       throw NOT_EQUALS;
     }
-    
+
     @Override
     public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
       if (minePresent && ((GeneratedMessageLite<?, ?>) mine).equals(this, (MessageLite) other)) {
@@ -1694,7 +1720,7 @@ public abstract class GeneratedMessageLite<
       }
       throw NOT_EQUALS;
     }
-    
+
     @Override
     public void visitOneofNotSet(boolean minePresent) {
       if (minePresent) {
@@ -1716,13 +1742,17 @@ public abstract class GeneratedMessageLite<
 
       return mine;
     }
-    
+
     @Override
     public LazyFieldLite visitLazyMessage(
-        boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) {
-      if (!minePresent && !otherPresent) {
-        return mine;
-      } else if (minePresent && otherPresent && mine.equals(other)) {
+        LazyFieldLite mine, LazyFieldLite other) {
+      if (mine == null && other == null) {
+        return null;
+      }
+      if (mine == null || other == null) {
+        throw NOT_EQUALS;
+      }
+      if (mine.equals(other)) {
         return mine;
       }
       throw NOT_EQUALS;
@@ -1813,7 +1843,7 @@ public abstract class GeneratedMessageLite<
     // The caller must ensure that the visitor is invoked parameterized with this and this such that
     // other is this. This is required due to how oneof cases are handled. See the class comment
     // on Visitor for more information.
-    
+
     private int hashCode = 0;
 
     @Override
@@ -1909,7 +1939,7 @@ public abstract class GeneratedMessageLite<
       hashCode = (53 * hashCode) + mine.hashCode();
       return mine;
     }
-    
+
     @Override
     public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
       return visitMessage((MessageLite) mine, (MessageLite) other);
@@ -1918,7 +1948,7 @@ public abstract class GeneratedMessageLite<
     @Override
     public void visitOneofNotSet(boolean minePresent) {
       if (minePresent) {
-        throw new IllegalStateException(); // Can't happen if other == this. 
+        throw new IllegalStateException(); // Can't happen if other == this.
       }
     }
 
@@ -1939,9 +1969,14 @@ public abstract class GeneratedMessageLite<
     }
 
     @Override
-    public LazyFieldLite visitLazyMessage(
-        boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) {
-      hashCode = (53 * hashCode) + mine.hashCode();
+    public LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other) {
+      final int protoHash;
+      if (mine != null) {
+        protoHash = mine.hashCode();
+      } else {
+        protoHash = 37;
+      }
+      hashCode = (53 * hashCode) + protoHash;
       return mine;
     }
 
@@ -1996,7 +2031,7 @@ public abstract class GeneratedMessageLite<
       hashCode = (53 * hashCode) + mine.hashCode();
       return mine;
     }
-    
+
     @Override
     public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) {
       hashCode = (53 * hashCode) + mine.hashCode();
@@ -2064,7 +2099,7 @@ public abstract class GeneratedMessageLite<
 
     @Override
     public Object visitOneofDouble(boolean minePresent, Object mine, Object other) {
-      return other;      
+      return other;
     }
 
     @Override
@@ -2074,29 +2109,26 @@ public abstract class GeneratedMessageLite<
 
     @Override
     public Object visitOneofLong(boolean minePresent, Object mine, Object other) {
-      return other;      
+      return other;
     }
 
     @Override
     public Object visitOneofString(boolean minePresent, Object mine, Object other) {
-      return other;      
+      return other;
     }
 
     @Override
     public Object visitOneofByteString(boolean minePresent, Object mine, Object other) {
-      return other;      
+      return other;
     }
 
     @Override
     public Object visitOneofLazyMessage(boolean minePresent, Object mine, Object other) {
-      if (minePresent) {
-        LazyFieldLite lazy = (LazyFieldLite) mine;
-        lazy.merge((LazyFieldLite) other);
-        return lazy;
-      }
-      return other;
+      LazyFieldLite lazy = minePresent ? (LazyFieldLite) mine : new LazyFieldLite();
+      lazy.merge((LazyFieldLite) other);
+      return lazy;
     }
-    
+
     @Override
     public Object visitOneofMessage(boolean minePresent, Object mine, Object other) {
       if (minePresent) {
@@ -2104,7 +2136,7 @@ public abstract class GeneratedMessageLite<
       }
       return other;
     }
-    
+
     @Override
     public void visitOneofNotSet(boolean minePresent) {
       return;
@@ -2121,12 +2153,13 @@ public abstract class GeneratedMessageLite<
     }
 
     @Override
-    public LazyFieldLite visitLazyMessage(
-        boolean minePresent, LazyFieldLite mine, boolean otherPresent, LazyFieldLite other) {
-      // LazyFieldLite's are never null so we can just copy across. Necessary to avoid leakage
-      // from builder into immutable message.
-      // TODO(dweis): Change to null sentinels?
-      mine.merge(other);
+    public LazyFieldLite visitLazyMessage(LazyFieldLite mine, LazyFieldLite other) {
+      if (other != null) {
+        if (mine == null) {
+          mine = new LazyFieldLite();
+        }
+        mine.merge(other);
+      }
       return mine;
     }
 
@@ -2140,7 +2173,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2154,7 +2187,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2168,7 +2201,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2182,7 +2215,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2196,7 +2229,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2210,7 +2243,7 @@ public abstract class GeneratedMessageLite<
         }
         mine.addAll(other);
       }
-      
+
       return size > 0 ? mine : other;
     }
 
@@ -2232,10 +2265,15 @@ public abstract class GeneratedMessageLite<
       return other == UnknownFieldSetLite.getDefaultInstance()
           ? mine : UnknownFieldSetLite.mutableCopyOf(mine, other);
     }
-    
+
     @Override
     public <K, V> MapFieldLite<K, V> visitMap(MapFieldLite<K, V> mine, MapFieldLite<K, V> other) {
-      mine.mergeFrom(other);
+      if (!other.isEmpty()) {
+        if (!mine.isMutable()) {
+          mine = mine.mutableCopy();
+        }
+        mine.mergeFrom(other);
+      }
       return mine;
     }
   }

+ 27 - 24
java/core/src/main/java/com/google/protobuf/IntArrayList.java

@@ -38,25 +38,27 @@ import java.util.RandomAccess;
 
 /**
  * An implementation of {@link IntList} on top of a primitive array.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
-final class IntArrayList extends AbstractProtobufList<Integer> implements IntList, RandomAccess {
-  
+final class IntArrayList
+    extends AbstractProtobufList<Integer>
+    implements IntList, RandomAccess {
+
   private static final IntArrayList EMPTY_LIST = new IntArrayList();
   static {
     EMPTY_LIST.makeImmutable();
   }
-  
+
   public static IntArrayList emptyList() {
     return EMPTY_LIST;
   }
-  
+
   /**
    * The backing store for the list.
    */
   private int[] array;
-  
+
   /**
    * The size of the list distinct from the length of the array. That is, it is the number of
    * elements set in the list.
@@ -71,13 +73,14 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
   }
 
   /**
-   * Constructs a new mutable {@code IntArrayList} containing the same elements as {@code other}.
+   * Constructs a new mutable {@code IntArrayList}
+   * containing the same elements as {@code other}.
    */
-  private IntArrayList(int[] array, int size) {
-    this.array = array;
+  private IntArrayList(int[] other, int size) {
+    array = other;
     this.size = size;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
@@ -90,14 +93,14 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
     if (size != other.size) {
       return false;
     }
-    
+
     final int[] arr = other.array;
     for (int i = 0; i < size; i++) {
       if (array[i] != arr[i]) {
         return false;
       }
     }
-    
+
     return true;
   }
 
@@ -117,7 +120,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
     }
     return new IntArrayList(Arrays.copyOf(array, capacity), size);
   }
-  
+
   @Override
   public Integer get(int index) {
     return getInt(index);
@@ -169,7 +172,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
     if (index < 0 || index > size) {
       throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index));
     }
-    
+
     if (size < array.length) {
       // Shift everything over to make room
       System.arraycopy(array, index, array, index + 1, size - index);
@@ -177,10 +180,10 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
       // Resize to 1.5x the size
       int length = ((size * 3) / 2) + 1;
       int[] newArray = new int[length];
-      
+
       // Copy the first part directly
       System.arraycopy(array, 0, newArray, 0, index);
-      
+
       // Copy the rest shifted over by one to make room
       System.arraycopy(array, index, newArray, index + 1, size - index);
       array = newArray;
@@ -194,38 +197,38 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
   @Override
   public boolean addAll(Collection<? extends Integer> collection) {
     ensureIsMutable();
-    
+
     if (collection == null) {
       throw new NullPointerException();
     }
-    
+
     // We specialize when adding another IntArrayList to avoid boxing elements.
     if (!(collection instanceof IntArrayList)) {
       return super.addAll(collection);
     }
-    
+
     IntArrayList list = (IntArrayList) collection;
     if (list.size == 0) {
       return false;
     }
-    
+
     int overflow = Integer.MAX_VALUE - size;
     if (overflow < list.size) {
       // We can't actually represent a list this large.
       throw new OutOfMemoryError();
     }
-    
+
     int newSize = size + list.size;
     if (newSize > array.length) {
       array = Arrays.copyOf(array, newSize);
     }
-    
+
     System.arraycopy(list.array, 0, array, size, list.size);
     size = newSize;
     modCount++;
     return true;
   }
-  
+
   @Override
   public boolean remove(Object o) {
     ensureIsMutable();
@@ -254,7 +257,7 @@ final class IntArrayList extends AbstractProtobufList<Integer> implements IntLis
   /**
    * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
    * {@link IndexOutOfBoundsException} if it is not.
-   * 
+   *
    * @param index the index to verify is in range
    */
   private void ensureIndexInRange(int index) {

+ 28 - 25
java/core/src/main/java/com/google/protobuf/LongArrayList.java

@@ -38,25 +38,27 @@ import java.util.RandomAccess;
 
 /**
  * An implementation of {@link LongList} on top of a primitive array.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
-final class LongArrayList extends AbstractProtobufList<Long> implements LongList, RandomAccess {
-  
+final class LongArrayList
+    extends AbstractProtobufList<Long>
+    implements LongList, RandomAccess {
+
   private static final LongArrayList EMPTY_LIST = new LongArrayList();
   static {
     EMPTY_LIST.makeImmutable();
   }
-  
+
   public static LongArrayList emptyList() {
     return EMPTY_LIST;
   }
-  
+
   /**
    * The backing store for the list.
    */
   private long[] array;
-  
+
   /**
    * The size of the list distinct from the length of the array. That is, it is the number of
    * elements set in the list.
@@ -71,33 +73,34 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
   }
 
   /**
-   * Constructs a new mutable {@code LongArrayList} containing the same elements as {@code other}.
+   * Constructs a new mutable {@code LongArrayList}
+   * containing the same elements as {@code other}.
    */
-  private LongArrayList(long[] array, int size) {
-    this.array = array;
+  private LongArrayList(long[] other, int size) {
+    array = other;
     this.size = size;
   }
-  
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
       return true;
     }
-    if (!(o instanceof IntArrayList)) {
+    if (!(o instanceof LongArrayList)) {
       return super.equals(o);
     }
     LongArrayList other = (LongArrayList) o;
     if (size != other.size) {
       return false;
     }
-    
+
     final long[] arr = other.array;
     for (int i = 0; i < size; i++) {
       if (array[i] != arr[i]) {
         return false;
       }
     }
-    
+
     return true;
   }
 
@@ -117,7 +120,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
     }
     return new LongArrayList(Arrays.copyOf(array, capacity), size);
   }
-  
+
   @Override
   public Long get(int index) {
     return getLong(index);
@@ -169,7 +172,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
     if (index < 0 || index > size) {
       throw new IndexOutOfBoundsException(makeOutOfBoundsExceptionMessage(index));
     }
-    
+
     if (size < array.length) {
       // Shift everything over to make room
       System.arraycopy(array, index, array, index + 1, size - index);
@@ -177,10 +180,10 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
       // Resize to 1.5x the size
       int length = ((size * 3) / 2) + 1;
       long[] newArray = new long[length];
-      
+
       // Copy the first part directly
       System.arraycopy(array, 0, newArray, 0, index);
-      
+
       // Copy the rest shifted over by one to make room
       System.arraycopy(array, index, newArray, index + 1, size - index);
       array = newArray;
@@ -194,38 +197,38 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
   @Override
   public boolean addAll(Collection<? extends Long> collection) {
     ensureIsMutable();
-    
+
     if (collection == null) {
       throw new NullPointerException();
     }
-    
+
     // We specialize when adding another LongArrayList to avoid boxing elements.
     if (!(collection instanceof LongArrayList)) {
       return super.addAll(collection);
     }
-    
+
     LongArrayList list = (LongArrayList) collection;
     if (list.size == 0) {
       return false;
     }
-    
+
     int overflow = Integer.MAX_VALUE - size;
     if (overflow < list.size) {
       // We can't actually represent a list this large.
       throw new OutOfMemoryError();
     }
-    
+
     int newSize = size + list.size;
     if (newSize > array.length) {
       array = Arrays.copyOf(array, newSize);
     }
-    
+
     System.arraycopy(list.array, 0, array, size, list.size);
     size = newSize;
     modCount++;
     return true;
   }
-  
+
   @Override
   public boolean remove(Object o) {
     ensureIsMutable();
@@ -254,7 +257,7 @@ final class LongArrayList extends AbstractProtobufList<Long> implements LongList
   /**
    * Ensures that the provided {@code index} is within the range of {@code [0, size]}. Throws an
    * {@link IndexOutOfBoundsException} if it is not.
-   * 
+   *
    * @param index the index to verify is in range
    */
   private void ensureIndexInRange(int index) {

+ 113 - 106
java/core/src/main/java/com/google/protobuf/MapEntry.java

@@ -41,63 +41,83 @@ import java.util.TreeMap;
 
 /**
  * Implements MapEntry messages.
- * 
+ *
  * In reflection API, map fields will be treated as repeated message fields and
  * each map entry is accessed as a message. This MapEntry class is used to
  * represent these map entry messages in reflection API.
- * 
+ *
  * Protobuf internal. Users shouldn't use this class.
  */
 public final class MapEntry<K, V> extends AbstractMessage {
-  private static class Metadata<K, V> {
-    public final Descriptor descriptor;  
-    public final MapEntry<K, V> defaultInstance;
-    public final AbstractParser<MapEntry<K, V>> parser;
-    
+
+  private static final class Metadata<K, V> extends MapEntryLite.Metadata<K, V> {
+
+    public final Descriptor descriptor;
+    public final Parser<MapEntry<K, V>> parser;
+
     public Metadata(
-        final Descriptor descriptor, final MapEntry<K, V> defaultInstance) {
+        Descriptor descriptor,
+        MapEntry<K, V> defaultInstance,
+        WireFormat.FieldType keyType,
+        WireFormat.FieldType valueType) {
+      super(keyType, defaultInstance.key, valueType, defaultInstance.value);
       this.descriptor = descriptor;
-      this.defaultInstance = defaultInstance;
-      final Metadata<K, V> thisMetadata = this;
       this.parser = new AbstractParser<MapEntry<K, V>>() {
-        private final Parser<MapEntryLite<K, V>> dataParser =
-            defaultInstance.data.getParserForType();
+
         @Override
         public MapEntry<K, V> parsePartialFrom(
             CodedInputStream input, ExtensionRegistryLite extensionRegistry)
             throws InvalidProtocolBufferException {
-          MapEntryLite<K, V> data =
-              dataParser.parsePartialFrom(input, extensionRegistry);
-          return new MapEntry<K, V>(thisMetadata, data);
+          return new MapEntry<K, V>(Metadata.this, input, extensionRegistry);
         }
-        
       };
     }
   }
-  
+
+  private final K key;
+  private final V value;
   private final Metadata<K, V> metadata;
-  private final MapEntryLite<K, V> data;
-  
+
   /** Create a default MapEntry instance. */
-  private MapEntry(Descriptor descriptor,
+  private MapEntry(
+      Descriptor descriptor,
       WireFormat.FieldType keyType, K defaultKey,
       WireFormat.FieldType valueType, V defaultValue) {
-    this.data = MapEntryLite.newDefaultInstance(
-        keyType, defaultKey, valueType, defaultValue);
-    this.metadata = new Metadata<K, V>(descriptor, this); 
+    this.key = defaultKey;
+    this.value = defaultValue;
+    this.metadata = new Metadata<K, V>(descriptor, this, keyType, valueType);
   }
-  
-  /** Create a new MapEntry message. */
-  private MapEntry(Metadata<K, V> metadata, MapEntryLite<K, V> data) {
+
+  /** Create a MapEntry with the provided key and value. */
+  private MapEntry(Metadata metadata, K key, V value) {
+    this.key = key;
+    this.value = value;
     this.metadata = metadata;
-    this.data = data;
   }
-  
+
+  /** Parsing constructor. */
+  private MapEntry(
+      Metadata<K, V> metadata,
+      CodedInputStream input,
+      ExtensionRegistryLite extensionRegistry)
+      throws InvalidProtocolBufferException {
+    try {
+      this.metadata = metadata;
+      Map.Entry<K, V> entry = MapEntryLite.parseEntry(input, metadata, extensionRegistry);
+      this.key = entry.getKey();
+      this.value = entry.getValue();
+    } catch (InvalidProtocolBufferException e) {
+      throw e.setUnfinishedMessage(this);
+    } catch (IOException e) {
+      throw new InvalidProtocolBufferException(e.getMessage()).setUnfinishedMessage(this);
+    }
+  }
+
   /**
    * Create a default MapEntry instance. A default MapEntry instance should be
    * created only once for each map entry message type. Generated code should
    * store the created default instance and use it later to create new MapEntry
-   * messages of the same type. 
+   * messages of the same type.
    */
   public static <K, V> MapEntry<K, V> newDefaultInstance(
       Descriptor descriptor,
@@ -106,30 +126,38 @@ public final class MapEntry<K, V> extends AbstractMessage {
     return new MapEntry<K, V>(
         descriptor, keyType, defaultKey, valueType, defaultValue);
   }
-  
+
   public K getKey() {
-    return data.getKey();
+    return key;
   }
-  
+
   public V getValue() {
-    return data.getValue();
+    return value;
   }
-  
+
+  private volatile int cachedSerializedSize = -1;
+
   @Override
   public int getSerializedSize() {
-    return data.getSerializedSize();
+    if (cachedSerializedSize != -1) {
+      return cachedSerializedSize;
+    }
+
+    int size = MapEntryLite.computeSerializedSize(metadata, key, value);
+    cachedSerializedSize = size;
+    return size;
   }
-  
+
   @Override
   public void writeTo(CodedOutputStream output) throws IOException {
-    data.writeTo(output);
+    MapEntryLite.writeTo(output, metadata, key, value);
   }
-  
+
   @Override
   public boolean isInitialized() {
-    return data.isInitialized();
+    return isInitialized(metadata, value);
   }
-  
+
   @Override
   public Parser<MapEntry<K, V>> getParserForType() {
     return metadata.parser;
@@ -139,15 +167,15 @@ public final class MapEntry<K, V> extends AbstractMessage {
   public Builder<K, V> newBuilderForType() {
     return new Builder<K, V>(metadata);
   }
-  
+
   @Override
   public Builder<K, V> toBuilder() {
-    return new Builder<K, V>(metadata, data);
+    return new Builder<K, V>(metadata, key, value);
   }
 
   @Override
   public MapEntry<K, V> getDefaultInstanceForType() {
-    return metadata.defaultInstance;
+    return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue);
   }
 
   @Override
@@ -157,8 +185,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
   @Override
   public Map<FieldDescriptor, Object> getAllFields() {
-    final TreeMap<FieldDescriptor, Object> result =
-        new TreeMap<FieldDescriptor, Object>();
+    TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>();
     for (final FieldDescriptor field : metadata.descriptor.getFields()) {
       if (hasField(field)) {
         result.put(field, getField(field));
@@ -166,12 +193,12 @@ public final class MapEntry<K, V> extends AbstractMessage {
     }
     return Collections.unmodifiableMap(result);
   }
-  
+
   private void checkFieldDescriptor(FieldDescriptor field) {
     if (field.getContainingType() != metadata.descriptor) {
       throw new RuntimeException(
           "Wrong FieldDescriptor \"" + field.getFullName()
-          + "\" used in message \"" + metadata.descriptor.getFullName()); 
+          + "\" used in message \"" + metadata.descriptor.getFullName());
     }
   }
 
@@ -217,56 +244,44 @@ public final class MapEntry<K, V> extends AbstractMessage {
   public static class Builder<K, V>
       extends AbstractMessage.Builder<Builder<K, V>> {
     private final Metadata<K, V> metadata;
-    private MapEntryLite<K, V> data;
-    private MapEntryLite.Builder<K, V> dataBuilder;
-    
+    private K key;
+    private V value;
+
     private Builder(Metadata<K, V> metadata) {
-      this.metadata = metadata;
-      this.data = metadata.defaultInstance.data;
-      this.dataBuilder = null;
+      this(metadata, metadata.defaultKey, metadata.defaultValue);
     }
-    
-    private Builder(Metadata<K, V> metadata, MapEntryLite<K, V> data) {
+
+    private Builder(Metadata<K, V> metadata, K key, V value) {
       this.metadata = metadata;
-      this.data = data;
-      this.dataBuilder = null;
+      this.key = key;
+      this.value = value;
     }
-    
+
     public K getKey() {
-      return dataBuilder == null ? data.getKey() : dataBuilder.getKey();
+      return key;
     }
-    
+
     public V getValue() {
-      return dataBuilder == null ? data.getValue() : dataBuilder.getValue();
-    }
-    
-    private void ensureMutable() {
-      if (dataBuilder == null) {
-        dataBuilder = data.toBuilder();
-      }
+      return value;
     }
-    
+
     public Builder<K, V> setKey(K key) {
-      ensureMutable();
-      dataBuilder.setKey(key);
+      this.key = key;
       return this;
     }
-    
+
     public Builder<K, V> clearKey() {
-      ensureMutable();
-      dataBuilder.clearKey();
+      this.key = metadata.defaultKey;
       return this;
     }
-    
+
     public Builder<K, V> setValue(V value) {
-      ensureMutable();
-      dataBuilder.setValue(value);
+      this.value = value;
       return this;
     }
-    
+
     public Builder<K, V> clearValue() {
-      ensureMutable();
-      dataBuilder.clearValue();
+      this.value = metadata.defaultValue;
       return this;
     }
 
@@ -281,29 +296,24 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
     @Override
     public MapEntry<K, V> buildPartial() {
-      if (dataBuilder != null) {
-        data = dataBuilder.buildPartial();
-        dataBuilder = null;
-      }
-      return new MapEntry<K, V>(metadata, data);
+      return new MapEntry<K, V>(metadata, key, value);
     }
 
     @Override
     public Descriptor getDescriptorForType() {
       return metadata.descriptor;
     }
-    
+
     private void checkFieldDescriptor(FieldDescriptor field) {
       if (field.getContainingType() != metadata.descriptor) {
         throw new RuntimeException(
             "Wrong FieldDescriptor \"" + field.getFullName()
-            + "\" used in message \"" + metadata.descriptor.getFullName()); 
+            + "\" used in message \"" + metadata.descriptor.getFullName());
       }
     }
 
     @Override
-    public com.google.protobuf.Message.Builder newBuilderForField(
-        FieldDescriptor field) {
+    public Message.Builder newBuilderForField(FieldDescriptor field) {
       checkFieldDescriptor(field);;
       // This method should be called for message fields and in a MapEntry
       // message only the value field can possibly be a message field.
@@ -312,7 +322,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
         throw new RuntimeException(
             "\"" + field.getFullName() + "\" is not a message value field.");
       }
-      return ((Message) data.getValue()).newBuilderForType();
+      return ((Message) value).newBuilderForType();
     }
 
     @SuppressWarnings("unchecked")
@@ -362,22 +372,17 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
     @Override
     public MapEntry<K, V> getDefaultInstanceForType() {
-      return metadata.defaultInstance;
+      return new MapEntry<K, V>(metadata, metadata.defaultKey, metadata.defaultValue);
     }
 
     @Override
     public boolean isInitialized() {
-      if (dataBuilder != null) {
-        return dataBuilder.isInitialized();
-      } else {
-        return data.isInitialized();
-      }
+      return MapEntry.isInitialized(metadata, value);
     }
 
     @Override
     public Map<FieldDescriptor, Object> getAllFields() {
-      final TreeMap<FieldDescriptor, Object> result =
-          new TreeMap<FieldDescriptor, Object>();
+      final TreeMap<FieldDescriptor, Object> result = new TreeMap<FieldDescriptor, Object>();
       for (final FieldDescriptor field : metadata.descriptor.getFields()) {
         if (hasField(field)) {
           result.put(field, getField(field));
@@ -398,8 +403,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
       Object result = field.getNumber() == 1 ? getKey() : getValue();
       // Convert enums to EnumValueDescriptor.
       if (field.getType() == FieldDescriptor.Type.ENUM) {
-        result = field.getEnumType().findValueByNumberCreatingIfUnknown(
-            (java.lang.Integer) result);
+        result = field.getEnumType().findValueByNumberCreatingIfUnknown((Integer) result);
       }
       return result;
     }
@@ -409,13 +413,13 @@ public final class MapEntry<K, V> extends AbstractMessage {
       throw new RuntimeException(
           "There is no repeated field in a map entry message.");
     }
-    
+
     @Override
     public Object getRepeatedField(FieldDescriptor field, int index) {
       throw new RuntimeException(
           "There is no repeated field in a map entry message.");
     }
-    
+
     @Override
     public UnknownFieldSet getUnknownFields() {
       return UnknownFieldSet.getDefaultInstance();
@@ -423,11 +427,14 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
     @Override
     public Builder<K, V> clone() {
-      if (dataBuilder == null) {
-        return new Builder<K, V>(metadata, data);
-      } else {
-        return new Builder<K, V>(metadata, dataBuilder.build());
-      }
+      return new Builder(metadata, key, value);
     }
   }
+
+  private static <V> boolean isInitialized(Metadata metadata, V value) {
+    if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) {
+      return ((MessageLite) value).isInitialized();
+    }
+    return true;
+  }
 }

+ 104 - 215
java/core/src/main/java/com/google/protobuf/MapEntryLite.java

@@ -31,80 +31,74 @@
 package com.google.protobuf;
 
 import java.io.IOException;
+import java.util.AbstractMap;
+import java.util.Map;
 
 /**
  * Implements the lite version of map entry messages.
- * 
+ *
  * This class serves as an utility class to help do serialization/parsing of
  * map entries. It's used in generated code and also in the full version
  * MapEntry message.
- * 
+ *
  * Protobuf internal. Users shouldn't use.
  */
-public class MapEntryLite<K, V>
-    extends AbstractMessageLite<MapEntryLite<K, V>, MapEntryLite.Builder<K, V>> {
-  private static class Metadata<K, V> {
-    public final MapEntryLite<K, V> defaultInstance;
+public class MapEntryLite<K, V> {
+
+  static class Metadata<K, V> {
     public final WireFormat.FieldType keyType;
+    public final K defaultKey;
     public final WireFormat.FieldType valueType;
-    public final Parser<MapEntryLite<K, V>> parser;
+    public final V defaultValue;
+
     public Metadata(
-        MapEntryLite<K, V> defaultInstance,
-        WireFormat.FieldType keyType,
-        WireFormat.FieldType valueType) {
-      this.defaultInstance = defaultInstance; 
+        WireFormat.FieldType keyType, K defaultKey,
+        WireFormat.FieldType valueType, V defaultValue) {
       this.keyType = keyType;
+      this.defaultKey = defaultKey;
       this.valueType = valueType;
-      final Metadata<K, V> finalThis = this;
-      this.parser = new AbstractParser<MapEntryLite<K, V>>() {
-        @Override
-        public MapEntryLite<K, V> parsePartialFrom(
-            CodedInputStream input, ExtensionRegistryLite extensionRegistry)
-            throws InvalidProtocolBufferException {
-          return new MapEntryLite<K, V>(finalThis, input, extensionRegistry);
-        }
-      };
+      this.defaultValue = defaultValue;
     }
   }
-  
+
   private static final int KEY_FIELD_NUMBER = 1;
   private static final int VALUE_FIELD_NUMBER = 2;
-  
+
   private final Metadata<K, V> metadata;
   private final K key;
   private final V value;
-  
+
   /** Creates a default MapEntryLite message instance. */
   private MapEntryLite(
       WireFormat.FieldType keyType, K defaultKey,
       WireFormat.FieldType valueType, V defaultValue) {
-    this.metadata = new Metadata<K, V>(this, keyType, valueType);
+    this.metadata = new Metadata<K, V>(keyType, defaultKey, valueType, defaultValue);
     this.key = defaultKey;
     this.value = defaultValue;
   }
-  
+
   /** Creates a new MapEntryLite message. */
   private MapEntryLite(Metadata<K, V> metadata, K key, V value) {
     this.metadata = metadata;
     this.key = key;
     this.value = value;
   }
-  
+
   public K getKey() {
     return key;
   }
-  
+
   public V getValue() {
     return value;
   }
 
   /**
    * Creates a default MapEntryLite message instance.
-   * 
+   *
    * This method is used by generated code to create the default instance for
    * a map entry message. The created default instance should be used to create
    * new map entry messages of the same type. For each map entry message, only
-   * one default instance should be created. 
+   * one default instance should be created.
    */
   public static <K, V> MapEntryLite<K, V> newDefaultInstance(
       WireFormat.FieldType keyType, K defaultKey,
@@ -112,80 +106,20 @@ public class MapEntryLite<K, V>
     return new MapEntryLite<K, V>(
         keyType, defaultKey, valueType, defaultValue);
   }
-  
-  @Override
-  public void writeTo(CodedOutputStream output) throws IOException {
-    writeField(KEY_FIELD_NUMBER, metadata.keyType, key, output);
-    writeField(VALUE_FIELD_NUMBER, metadata.valueType, value, output);
-  }
 
-  private void writeField(
-      int number, WireFormat.FieldType type, Object value,
-      CodedOutputStream output) throws IOException {
-    output.writeTag(number, type.getWireType());
-    FieldSet.writeElementNoTag(output, type, value);
+  static <K, V> void writeTo(CodedOutputStream output, Metadata<K, V> metadata, K key, V value)
+      throws IOException {
+    FieldSet.writeElement(output, metadata.keyType, KEY_FIELD_NUMBER, key);
+    FieldSet.writeElement(output, metadata.valueType, VALUE_FIELD_NUMBER, value);
   }
 
-  private volatile int cachedSerializedSize = -1;
-  @Override
-  public int getSerializedSize() {
-    if (cachedSerializedSize != -1) {
-      return cachedSerializedSize;
-    }
-    int size = 0;
-    size += getFieldSize(KEY_FIELD_NUMBER, metadata.keyType, key);
-    size += getFieldSize(VALUE_FIELD_NUMBER, metadata.valueType, value);
-    cachedSerializedSize = size;
-    return size;
+  static <K, V> int computeSerializedSize(Metadata<K, V> metadata, K key, V value) {
+    return FieldSet.computeElementSize(metadata.keyType, KEY_FIELD_NUMBER, key)
+        + FieldSet.computeElementSize(metadata.valueType, VALUE_FIELD_NUMBER, value);
   }
 
-  private int getFieldSize(
-      int number, WireFormat.FieldType type, Object value) {
-    return CodedOutputStream.computeTagSize(number)
-        + FieldSet.computeElementSizeNoTag(type, value);
-  }
-  
-  /** Parsing constructor. */
-  private MapEntryLite(
-      Metadata<K, V> metadata,
-      CodedInputStream input,
-      ExtensionRegistryLite extensionRegistry)
-      throws InvalidProtocolBufferException {
-    try {
-      K key = metadata.defaultInstance.key;
-      V value = metadata.defaultInstance.value;
-      while (true) {
-        int tag = input.readTag();
-        if (tag == 0) {
-          break;
-        }
-        if (tag == WireFormat.makeTag(
-                KEY_FIELD_NUMBER, metadata.keyType.getWireType())) {
-          key = mergeField(
-              input, extensionRegistry, metadata.keyType, key);
-        } else if (tag == WireFormat.makeTag(
-                       VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) {
-          value = mergeField(
-              input, extensionRegistry, metadata.valueType, value);
-        } else {
-          if (!input.skipField(tag)) {
-            break;
-          }
-        }
-      }
-      this.metadata = metadata;
-      this.key = key;
-      this.value = value;
-    } catch (InvalidProtocolBufferException e) {
-      throw e.setUnfinishedMessage(this);
-    } catch (IOException e) {
-      throw new InvalidProtocolBufferException(e.getMessage())
-          .setUnfinishedMessage(this);
-    }
-  }
-  
   @SuppressWarnings("unchecked")
-  private <T> T mergeField(
+  static <T> T parseField(
       CodedInputStream input, ExtensionRegistryLite extensionRegistry,
       WireFormat.FieldType type, T value) throws IOException {
     switch (type) {
@@ -202,136 +136,91 @@ public class MapEntryLite<K, V>
     }
   }
 
-  @Override
-  public Parser<MapEntryLite<K, V>> getParserForType() {
-    return metadata.parser;
-  }
-
-  @Override
-  public Builder<K, V> newBuilderForType() {
-    return new Builder<K, V>(metadata);
-  }
-
-  @Override
-  public Builder<K, V> toBuilder() {
-    return new Builder<K, V>(metadata, key, value);
+  /**
+   * Serializes the provided key and value as though they were wrapped by a {@link MapEntryLite}
+   * to the output stream. This helper method avoids allocation of a {@link MapEntryLite}
+   * built with a key and value and is called from generated code directly.
+   */
+  public void serializeTo(CodedOutputStream output, int fieldNumber, K key, V value)
+      throws IOException {
+    output.writeTag(fieldNumber, WireFormat.WIRETYPE_LENGTH_DELIMITED);
+    output.writeUInt32NoTag(computeSerializedSize(metadata, key, value));
+    writeTo(output, metadata, key, value);
   }
 
-  @Override
-  public MapEntryLite<K, V> getDefaultInstanceForType() {
-    return metadata.defaultInstance;
+  /**
+   * Computes the message size for the provided key and value as though they were wrapped
+   * by a {@link MapEntryLite}. This helper method avoids allocation of a {@link MapEntryLite}
+   * built with a key and value and is called from generated code directly.
+   */
+  public int computeMessageSize(int fieldNumber, K key, V value) {
+    return CodedOutputStream.computeTagSize(fieldNumber)
+        + CodedOutputStream.computeLengthDelimitedFieldSize(
+            computeSerializedSize(metadata, key, value));
   }
 
-  @Override
-  public boolean isInitialized() {
-    if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) {
-      return ((MessageLite) value).isInitialized();
+  /**
+   * Parses an entry off of the input as a {@link Map.Entry}. This helper requires an allocation
+   * so using {@link #parseInto} is preferred if possible.
+   */
+  public Map.Entry<K, V> parseEntry(ByteString bytes, ExtensionRegistryLite extensionRegistry)
+      throws IOException {
+    return parseEntry(bytes.newCodedInput(), metadata, extensionRegistry);
+  }
+
+  static <K, V> Map.Entry<K, V> parseEntry(
+      CodedInputStream input, Metadata<K, V> metadata, ExtensionRegistryLite extensionRegistry)
+          throws IOException{
+    K key = metadata.defaultKey;
+    V value = metadata.defaultValue;
+    while (true) {
+      int tag = input.readTag();
+      if (tag == 0) {
+        break;
+      }
+      if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) {
+        key = parseField(input, extensionRegistry, metadata.keyType, key);
+      } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) {
+        value = parseField(input, extensionRegistry, metadata.valueType, value);
+      } else {
+        if (!input.skipField(tag)) {
+          break;
+        }
+      }
     }
-    return true;
+    return new AbstractMap.SimpleImmutableEntry<K, V>(key, value);
   }
 
   /**
-   * Builder used to create {@link MapEntryLite} messages.
+   * Parses an entry off of the input into the map. This helper avoids allocaton of a
+   * {@link MapEntryLite} by parsing directly into the provided {@link MapFieldLite}.
    */
-  public static class Builder<K, V>
-      extends AbstractMessageLite.Builder<MapEntryLite<K, V>, Builder<K, V>> {
-    private final Metadata<K, V> metadata;
-    private K key;
-    private V value;
-    
-    private Builder(Metadata<K, V> metadata) {
-      this.metadata = metadata;
-      this.key = metadata.defaultInstance.key;
-      this.value = metadata.defaultInstance.value;
-    }
-    
-    public K getKey() {
-      return key;
-    }
-    
-    public V getValue() {
-      return value;
-    }
-    
-    public Builder<K, V> setKey(K key) {
-      this.key = key;
-      return this;
-    }
-    
-    public Builder<K, V> setValue(V value) {
-      this.value = value;
-      return this;
-    }
-    
-    public Builder<K, V> clearKey() {
-      this.key = metadata.defaultInstance.key;
-      return this;
-    }
-    
-    public Builder<K, V> clearValue() {
-      this.value = metadata.defaultInstance.value;
-      return this;
-    }
-
-    @Override
-    public Builder<K, V> clear() {
-      this.key = metadata.defaultInstance.key;
-      this.value = metadata.defaultInstance.value;
-      return this;
-    }
-
-    @Override
-    public MapEntryLite<K, V> build() {
-      MapEntryLite<K, V> result = buildPartial();
-      if (!result.isInitialized()) {
-        throw newUninitializedMessageException(result);
+  public void parseInto(
+      MapFieldLite<K, V> map, CodedInputStream input, ExtensionRegistryLite extensionRegistry)
+          throws IOException {
+    int length = input.readRawVarint32();
+    final int oldLimit = input.pushLimit(length);
+    K key = metadata.defaultKey;
+    V value = metadata.defaultValue;
+
+    while (true) {
+      int tag = input.readTag();
+      if (tag == 0) {
+        break;
       }
-      return result;
-    }
-
-    @Override
-    public MapEntryLite<K, V> buildPartial() {
-      return new MapEntryLite<K, V>(metadata, key, value);
-    }
-
-    @Override
-    public MessageLite getDefaultInstanceForType() {
-      return metadata.defaultInstance;
-    }
-
-    @Override
-    public boolean isInitialized() {
-      if (metadata.valueType.getJavaType() == WireFormat.JavaType.MESSAGE) {
-        return ((MessageLite) value).isInitialized();
+      if (tag == WireFormat.makeTag(KEY_FIELD_NUMBER, metadata.keyType.getWireType())) {
+        key = parseField(input, extensionRegistry, metadata.keyType, key);
+      } else if (tag == WireFormat.makeTag(VALUE_FIELD_NUMBER, metadata.valueType.getWireType())) {
+        value = parseField(input, extensionRegistry, metadata.valueType, value);
+      } else {
+        if (!input.skipField(tag)) {
+          break;
+        }
       }
-      return true;
-    }
-
-    private Builder(Metadata<K, V> metadata, K key, V value) {
-      this.metadata = metadata;
-      this.key = key;
-      this.value = value;
-    }
-    
-    @Override
-    public Builder<K, V> clone() {
-      return new Builder<K, V>(metadata, key, value);
     }
 
-    @Override
-    public Builder<K, V> mergeFrom(
-        CodedInputStream input, ExtensionRegistryLite extensionRegistry)
-        throws IOException {
-      MapEntryLite<K, V> entry =
-          new MapEntryLite<K, V>(metadata, input, extensionRegistry);
-      this.key = entry.key;
-      this.value = entry.value;
-      return this;
-    }
-
-    @Override
-    protected Builder<K, V> internalMergeFrom(MapEntryLite<K, V> message) {
-      throw new UnsupportedOperationException();
-    }
+    input.checkLastTagWas(0);
+    input.popLimit(oldLimit);
+    map.put(key, value);
   }
 }

+ 374 - 39
java/core/src/main/java/com/google/protobuf/MapField.java

@@ -30,25 +30,26 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.MapFieldLite.MutatabilityAwareMap;
-
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * Internal representation of map fields in generated messages.
- * 
+ *
  * This class supports accessing the map field as a {@link Map} to be used in
  * generated API and also supports accessing the field as a {@link List} to be
  * used in reflection API. It keeps track of where the data is currently stored
- * and do necessary conversions between map and list.  
- * 
+ * and do necessary conversions between map and list.
+ *
  * This class is a protobuf implementation detail. Users shouldn't use this
  * class directly.
- * 
+ *
  * THREAD-SAFETY NOTE: Read-only access is thread-safe. Users can call getMap()
  * and getList() concurrently in multiple threads. If write-access is needed,
  * all access must be synchronized.
@@ -56,21 +57,21 @@ import java.util.Map;
 public class MapField<K, V> implements MutabilityOracle {
   /**
    * Indicates where the data of this map field is currently stored.
-   * 
+   *
    * MAP: Data is stored in mapData.
    * LIST: Data is stored in listData.
    * BOTH: mapData and listData have the same data.
    *
    * When the map field is accessed (through generated API or reflection API),
    * it will shift between these 3 modes:
-   * 
+   *
    *          getMap()   getList()   getMutableMap()   getMutableList()
    *   MAP      MAP        BOTH          MAP               LIST
    *   LIST     BOTH       LIST          MAP               LIST
    *   BOTH     BOTH       BOTH          MAP               LIST
-   *   
+   *
    * As the map field changes its mode, the list/map reference returned in a
-   * previous method call may be invalidated. 
+   * previous method call may be invalidated.
    */
   private enum StorageMode {MAP, LIST, BOTH}
 
@@ -78,26 +79,26 @@ public class MapField<K, V> implements MutabilityOracle {
   private volatile StorageMode mode;
   private MutatabilityAwareMap<K, V> mapData;
   private List<Message> listData;
-  
+
   // Convert between a map entry Message and a key-value pair.
   private static interface Converter<K, V> {
     Message convertKeyAndValueToMessage(K key, V value);
     void convertMessageToKeyAndValue(Message message, Map<K, V> map);
-    
+
     Message getMessageDefaultInstance();
   }
-  
+
   private static class ImmutableMessageConverter<K, V> implements Converter<K, V> {
     private final MapEntry<K, V> defaultEntry;
     public ImmutableMessageConverter(MapEntry<K, V> defaultEntry) {
       this.defaultEntry = defaultEntry;
     }
-    
+
     @Override
     public Message convertKeyAndValueToMessage(K key, V value) {
       return defaultEntry.newBuilderForType().setKey(key).setValue(value).buildPartial();
     }
-    
+
     @Override
     public void convertMessageToKeyAndValue(Message message, Map<K, V> map) {
       MapEntry<K, V> entry = (MapEntry<K, V>) message;
@@ -109,10 +110,10 @@ public class MapField<K, V> implements MutabilityOracle {
       return defaultEntry;
     }
   }
-  
+
 
   private final Converter<K, V> converter;
-  
+
   private MapField(
       Converter<K, V> converter,
       StorageMode mode,
@@ -123,34 +124,34 @@ public class MapField<K, V> implements MutabilityOracle {
     this.mapData = new MutatabilityAwareMap<K, V>(this, mapData);
     this.listData = null;
   }
-    
+
   private MapField(
       MapEntry<K, V> defaultEntry,
       StorageMode mode,
       Map<K, V> mapData) {
     this(new ImmutableMessageConverter<K, V>(defaultEntry), mode, mapData);
   }
-  
-  
+
+
   /** Returns an immutable empty MapField. */
   public static <K, V> MapField<K, V> emptyMapField(
       MapEntry<K, V> defaultEntry) {
     return new MapField<K, V>(
         defaultEntry, StorageMode.MAP, Collections.<K, V>emptyMap());
   }
-  
-  
+
+
   /** Creates a new mutable empty MapField. */
   public static <K, V> MapField<K, V> newMapField(MapEntry<K, V> defaultEntry) {
     return new MapField<K, V>(
         defaultEntry, StorageMode.MAP, new LinkedHashMap<K, V>());
   }
-  
-  
+
+
   private Message convertKeyAndValueToMessage(K key, V value) {
     return converter.convertKeyAndValueToMessage(key, value);
   }
-  
+
   @SuppressWarnings("unchecked")
   private void convertMessageToKeyAndValue(Message message, Map<K, V> map) {
     converter.convertMessageToKeyAndValue(message, map);
@@ -173,7 +174,7 @@ public class MapField<K, V> implements MutabilityOracle {
     }
     return new MutatabilityAwareMap<K, V>(this, mapData);
   }
-  
+
   /** Returns the content of this MapField as a read-only Map. */
   public Map<K, V> getMap() {
     if (mode == StorageMode.LIST) {
@@ -186,7 +187,7 @@ public class MapField<K, V> implements MutabilityOracle {
     }
     return Collections.unmodifiableMap(mapData);
   }
-  
+
   /** Gets a mutable Map view of this MapField. */
   public Map<K, V> getMutableMap() {
     if (mode != StorageMode.MAP) {
@@ -194,20 +195,20 @@ public class MapField<K, V> implements MutabilityOracle {
         mapData = convertListToMap(listData);
       }
       listData = null;
-      mode = StorageMode.MAP; 
+      mode = StorageMode.MAP;
     }
     return mapData;
   }
-  
+
   public void mergeFrom(MapField<K, V> other) {
     getMutableMap().putAll(MapFieldLite.copy(other.getMap()));
   }
-  
+
   public void clear() {
     mapData = new MutatabilityAwareMap<K, V>(this, new LinkedHashMap<K, V>());
     mode = StorageMode.MAP;
   }
-  
+
   @SuppressWarnings("unchecked")
   @Override
   public boolean equals(Object object) {
@@ -217,18 +218,18 @@ public class MapField<K, V> implements MutabilityOracle {
     MapField<K, V> other = (MapField<K, V>) object;
     return MapFieldLite.<K, V>equals(getMap(), other.getMap());
   }
-  
+
   @Override
   public int hashCode() {
     return MapFieldLite.<K, V>calculateHashCodeForMap(getMap());
   }
-  
+
   /** Returns a deep copy of this MapField. */
   public MapField<K, V> copy() {
     return new MapField<K, V>(
         converter, StorageMode.MAP, MapFieldLite.copy(getMap()));
   }
-  
+
   /** Gets the content of this MapField as a read-only List. */
   List<Message> getList() {
     if (mode == StorageMode.MAP) {
@@ -241,7 +242,7 @@ public class MapField<K, V> implements MutabilityOracle {
     }
     return Collections.unmodifiableList(listData);
   }
-  
+
   /** Gets a mutable List view of this MapField. */
   List<Message> getMutableList() {
     if (mode != StorageMode.LIST) {
@@ -253,7 +254,7 @@ public class MapField<K, V> implements MutabilityOracle {
     }
     return listData;
   }
-  
+
   /**
    * Gets the default instance of the message stored in the list view of this
    * map field.
@@ -261,7 +262,7 @@ public class MapField<K, V> implements MutabilityOracle {
   Message getMapEntryMessageDefaultInstance() {
     return converter.getMessageDefaultInstance();
   }
-  
+
   /**
    * Makes this list immutable. All subsequent modifications will throw an
    * {@link UnsupportedOperationException}.
@@ -269,14 +270,14 @@ public class MapField<K, V> implements MutabilityOracle {
   public void makeImmutable() {
     isMutable = false;
   }
-  
+
   /**
    * Returns whether this field can be modified.
    */
   public boolean isMutable() {
     return isMutable;
   }
-  
+
   /* (non-Javadoc)
    * @see com.google.protobuf.MutabilityOracle#ensureMutable()
    */
@@ -286,4 +287,338 @@ public class MapField<K, V> implements MutabilityOracle {
       throw new UnsupportedOperationException();
     }
   }
+
+  /**
+   * An internal map that checks for mutability before delegating.
+   */
+  private static class MutatabilityAwareMap<K, V> implements Map<K, V> {
+    private final MutabilityOracle mutabilityOracle;
+    private final Map<K, V> delegate;
+
+    MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) {
+      this.mutabilityOracle = mutabilityOracle;
+      this.delegate = delegate;
+    }
+
+    @Override
+    public int size() {
+      return delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return delegate.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+      return delegate.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+      return delegate.containsValue(value);
+    }
+
+    @Override
+    public V get(Object key) {
+      return delegate.get(key);
+    }
+
+    @Override
+    public V put(K key, V value) {
+      mutabilityOracle.ensureMutable();
+      return delegate.put(key, value);
+    }
+
+    @Override
+    public V remove(Object key) {
+      mutabilityOracle.ensureMutable();
+      return delegate.remove(key);
+    }
+
+    @Override
+    public void putAll(Map<? extends K, ? extends V> m) {
+      mutabilityOracle.ensureMutable();
+      delegate.putAll(m);
+    }
+
+    @Override
+    public void clear() {
+      mutabilityOracle.ensureMutable();
+      delegate.clear();
+    }
+
+    @Override
+    public Set<K> keySet() {
+      return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet());
+    }
+
+    @Override
+    public Collection<V> values() {
+      return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values());
+    }
+
+    @Override
+    public Set<java.util.Map.Entry<K, V>> entrySet() {
+      return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet());
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return delegate.equals(o);
+    }
+
+    @Override
+    public int hashCode() {
+      return delegate.hashCode();
+    }
+
+    @Override
+    public String toString() {
+      return delegate.toString();
+    }
+
+    /**
+     * An internal collection that checks for mutability before delegating.
+     */
+    private static class MutatabilityAwareCollection<E> implements Collection<E> {
+      private final MutabilityOracle mutabilityOracle;
+      private final Collection<E> delegate;
+
+      MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) {
+        this.mutabilityOracle = mutabilityOracle;
+        this.delegate = delegate;
+      }
+
+      @Override
+      public int size() {
+        return delegate.size();
+      }
+
+      @Override
+      public boolean isEmpty() {
+        return delegate.isEmpty();
+      }
+
+      @Override
+      public boolean contains(Object o) {
+        return delegate.contains(o);
+      }
+
+      @Override
+      public Iterator<E> iterator() {
+        return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator());
+      }
+
+      @Override
+      public Object[] toArray() {
+        return delegate.toArray();
+      }
+
+      @Override
+      public <T> T[] toArray(T[] a) {
+        return delegate.toArray(a);
+      }
+
+      @Override
+      public boolean add(E e) {
+        // Unsupported operation in the delegate.
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public boolean remove(Object o) {
+        mutabilityOracle.ensureMutable();
+        return delegate.remove(o);
+      }
+
+      @Override
+      public boolean containsAll(Collection<?> c) {
+        return delegate.containsAll(c);
+      }
+
+      @Override
+      public boolean addAll(Collection<? extends E> c) {
+        // Unsupported operation in the delegate.
+        throw new UnsupportedOperationException();
+      }
+
+      @Override
+      public boolean removeAll(Collection<?> c) {
+        mutabilityOracle.ensureMutable();
+        return delegate.removeAll(c);
+      }
+
+      @Override
+      public boolean retainAll(Collection<?> c) {
+        mutabilityOracle.ensureMutable();
+        return delegate.retainAll(c);
+      }
+
+      @Override
+      public void clear() {
+        mutabilityOracle.ensureMutable();
+        delegate.clear();
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        return delegate.equals(o);
+      }
+
+      @Override
+      public int hashCode() {
+        return delegate.hashCode();
+      }
+
+      @Override
+      public String toString() {
+        return delegate.toString();
+      }
+    }
+
+    /**
+     * An internal set that checks for mutability before delegating.
+     */
+    private static class MutatabilityAwareSet<E> implements Set<E> {
+      private final MutabilityOracle mutabilityOracle;
+      private final Set<E> delegate;
+
+      MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) {
+        this.mutabilityOracle = mutabilityOracle;
+        this.delegate = delegate;
+      }
+
+      @Override
+      public int size() {
+        return delegate.size();
+      }
+
+      @Override
+      public boolean isEmpty() {
+        return delegate.isEmpty();
+      }
+
+      @Override
+      public boolean contains(Object o) {
+        return delegate.contains(o);
+      }
+
+      @Override
+      public Iterator<E> iterator() {
+        return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator());
+      }
+
+      @Override
+      public Object[] toArray() {
+        return delegate.toArray();
+      }
+
+      @Override
+      public <T> T[] toArray(T[] a) {
+        return delegate.toArray(a);
+      }
+
+      @Override
+      public boolean add(E e) {
+        mutabilityOracle.ensureMutable();
+        return delegate.add(e);
+      }
+
+      @Override
+      public boolean remove(Object o) {
+        mutabilityOracle.ensureMutable();
+        return delegate.remove(o);
+      }
+
+      @Override
+      public boolean containsAll(Collection<?> c) {
+        return delegate.containsAll(c);
+      }
+
+      @Override
+      public boolean addAll(Collection<? extends E> c) {
+        mutabilityOracle.ensureMutable();
+        return delegate.addAll(c);
+      }
+
+      @Override
+      public boolean retainAll(Collection<?> c) {
+        mutabilityOracle.ensureMutable();
+        return delegate.retainAll(c);
+      }
+
+      @Override
+      public boolean removeAll(Collection<?> c) {
+        mutabilityOracle.ensureMutable();
+        return delegate.removeAll(c);
+      }
+
+      @Override
+      public void clear() {
+        mutabilityOracle.ensureMutable();
+        delegate.clear();
+      }
+
+      @Override
+      public boolean equals(Object o) {
+        return delegate.equals(o);
+      }
+
+      @Override
+      public int hashCode() {
+        return delegate.hashCode();
+      }
+
+      @Override
+      public String toString() {
+        return delegate.toString();
+      }
+    }
+
+    /**
+     * An internal iterator that checks for mutability before delegating.
+     */
+    private static class MutatabilityAwareIterator<E> implements Iterator<E> {
+      private final MutabilityOracle mutabilityOracle;
+      private final Iterator<E> delegate;
+
+      MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) {
+        this.mutabilityOracle = mutabilityOracle;
+        this.delegate = delegate;
+      }
+
+      @Override
+      public boolean hasNext() {
+        return delegate.hasNext();
+      }
+
+      @Override
+      public E next() {
+        return delegate.next();
+      }
+
+      @Override
+      public void remove() {
+        mutabilityOracle.ensureMutable();
+        delegate.remove();
+      }
+
+      @Override
+      public boolean equals(Object obj) {
+        return delegate.equals(obj);
+      }
+
+      @Override
+      public int hashCode() {
+        return delegate.hashCode();
+      }
+
+      @Override
+      public String toString() {
+        return delegate.toString();
+      }
+    }
+  }
 }

+ 60 - 385
java/core/src/main/java/com/google/protobuf/MapFieldLite.java

@@ -33,71 +33,85 @@ package com.google.protobuf;
 import com.google.protobuf.Internal.EnumLite;
 
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
 /**
  * Internal representation of map fields in generated lite-runtime messages.
- * 
+ *
  * This class is a protobuf implementation detail. Users shouldn't use this
  * class directly.
  */
-public final class MapFieldLite<K, V> implements MutabilityOracle {
-  private MutatabilityAwareMap<K, V> mapData;
+public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> {
+
   private boolean isMutable;
-  
+
+  private MapFieldLite() {
+    this.isMutable = true;
+  }
+
   private MapFieldLite(Map<K, V> mapData) {
-    this.mapData = new MutatabilityAwareMap<K, V>(this, mapData);
+    super(mapData);
     this.isMutable = true;
   }
-  
+
   @SuppressWarnings({"rawtypes", "unchecked"})
-  private static final MapFieldLite EMPTY_MAP_FIELD =
-      new MapFieldLite(Collections.emptyMap());
+  private static final MapFieldLite EMPTY_MAP_FIELD = new MapFieldLite(Collections.emptyMap());
   static {
     EMPTY_MAP_FIELD.makeImmutable();
   }
-  
+
   /** Returns an singleton immutable empty MapFieldLite instance. */
   @SuppressWarnings({"unchecked", "cast"})
   public static <K, V> MapFieldLite<K, V> emptyMapField() {
     return (MapFieldLite<K, V>) EMPTY_MAP_FIELD;
   }
-  
-  /** Creates a new MapFieldLite instance. */
-  public static <K, V> MapFieldLite<K, V> newMapField() {
-    return new MapFieldLite<K, V>(new LinkedHashMap<K, V>());
+
+  public void mergeFrom(MapFieldLite<K, V> other) {
+    ensureMutable();
+    if (!other.isEmpty()) {
+      putAll(other);
+    }
+  }
+
+  @SuppressWarnings({"unchecked", "cast"})
+  @Override public Set<Map.Entry<K, V>> entrySet() {
+    return isEmpty() ? Collections.<Map.Entry<K, V>>emptySet() : super.entrySet();
+  }
+
+  @Override public void clear() {
+    ensureMutable();
+    clear();
   }
-  
-  /** Gets the content of this MapField as a read-only Map. */
-  public Map<K, V> getMap() {
-    return Collections.unmodifiableMap(mapData);
+
+  @Override public V put(K key, V value) {
+    ensureMutable();
+    return super.put(key, value);
   }
-  
-  /** Gets a mutable Map view of this MapField. */
-  public Map<K, V> getMutableMap() {
-    return mapData;
+
+  public V put(Map.Entry<K, V> entry) {
+    return put(entry.getKey(), entry.getValue());
   }
-  
-  public void mergeFrom(MapFieldLite<K, V> other) {
-    mapData.putAll(copy(other.mapData));
+
+  @Override public void putAll(Map<? extends K, ? extends V> m) {
+    ensureMutable();
+    super.putAll(m);
   }
-  
-  public void clear() {
-    mapData.clear();
+
+  @Override public V remove(Object key) {
+    ensureMutable();
+    return super.remove(key);
   }
-  
+
   private static boolean equals(Object a, Object b) {
     if (a instanceof byte[] && b instanceof byte[]) {
       return Arrays.equals((byte[]) a, (byte[]) b);
     }
     return a.equals(b);
   }
-  
+
   /**
    * Checks whether two {@link Map}s are equal. We don't use the default equals
    * method of {@link Map} because it compares by identity not by content for
@@ -120,20 +134,16 @@ public final class MapFieldLite<K, V> implements MutabilityOracle {
     }
     return true;
   }
-  
+
   /**
    * Checks whether two map fields are equal.
    */
   @SuppressWarnings("unchecked")
   @Override
   public boolean equals(Object object) {
-    if (!(object instanceof MapFieldLite)) {
-      return false;
-    }
-    MapFieldLite<K, V> other = (MapFieldLite<K, V>) object;
-    return equals(mapData, other.mapData);
+    return (object instanceof Map) && equals(this, (Map<K, V>) object);
   }
-  
+
   private static int calculateHashCodeForObject(Object a) {
     if (a instanceof byte[]) {
       return Internal.hashCode((byte[]) a);
@@ -156,14 +166,14 @@ public final class MapFieldLite<K, V> implements MutabilityOracle {
       result += calculateHashCodeForObject(entry.getKey())
           ^ calculateHashCodeForObject(entry.getValue());
     }
-    return result;    
+    return result;
   }
-  
+
   @Override
   public int hashCode() {
-    return calculateHashCodeForMap(mapData);
+    return calculateHashCodeForMap(this);
   }
-  
+
   private static Object copy(Object object) {
     if (object instanceof byte[]) {
       byte[] data = (byte[]) object;
@@ -171,7 +181,7 @@ public final class MapFieldLite<K, V> implements MutabilityOracle {
     }
     return object;
   }
-  
+
   /**
    * Makes a deep copy of a {@link Map}. Immutable objects in the map will be
    * shared (e.g., integers, strings, immutable messages) and mutable ones will
@@ -185,12 +195,12 @@ public final class MapFieldLite<K, V> implements MutabilityOracle {
     }
     return result;
   }
-  
+
   /** Returns a deep copy of this map field. */
-  public MapFieldLite<K, V> copy() {
-    return new MapFieldLite<K, V>(copy(mapData));
+  public MapFieldLite<K, V> mutableCopy() {
+    return isEmpty() ? new MapFieldLite<K, V>() : new MapFieldLite<K, V>(this);
   }
-  
+
   /**
    * Makes this field immutable. All subsequent modifications will throw an
    * {@link UnsupportedOperationException}.
@@ -198,352 +208,17 @@ public final class MapFieldLite<K, V> implements MutabilityOracle {
   public void makeImmutable() {
     isMutable = false;
   }
-  
+
   /**
    * Returns whether this field can be modified.
    */
   public boolean isMutable() {
     return isMutable;
   }
-  
-  @Override
-  public void ensureMutable() {
-    if (!isMutable()) {
-      throw new UnsupportedOperationException();
-    }
-  }
-
-  /**
-   * An internal map that checks for mutability before delegating.
-   */
-  static class MutatabilityAwareMap<K, V> implements Map<K, V> {    
-    private final MutabilityOracle mutabilityOracle;
-    private final Map<K, V> delegate;
-    
-    MutatabilityAwareMap(MutabilityOracle mutabilityOracle, Map<K, V> delegate) {
-      this.mutabilityOracle = mutabilityOracle;
-      this.delegate = delegate;
-    }
-
-    @Override
-    public int size() {
-      return delegate.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-      return delegate.isEmpty();
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-      return delegate.containsKey(key);
-    }
-
-    @Override
-    public boolean containsValue(Object value) {
-      return delegate.containsValue(value);
-    }
-
-    @Override
-    public V get(Object key) {
-      return delegate.get(key);
-    }
-
-    @Override
-    public V put(K key, V value) {
-      mutabilityOracle.ensureMutable();
-      return delegate.put(key, value);
-    }
-
-    @Override
-    public V remove(Object key) {
-      mutabilityOracle.ensureMutable();
-      return delegate.remove(key);
-    }
-
-    @Override
-    public void putAll(Map<? extends K, ? extends V> m) {
-      mutabilityOracle.ensureMutable();
-      delegate.putAll(m);
-    }
-
-    @Override
-    public void clear() {
-      mutabilityOracle.ensureMutable();
-      delegate.clear();
-    }
-
-    @Override
-    public Set<K> keySet() {
-      return new MutatabilityAwareSet<K>(mutabilityOracle, delegate.keySet());
-    }
-
-    @Override
-    public Collection<V> values() {
-      return new MutatabilityAwareCollection<V>(mutabilityOracle, delegate.values());
-    }
-
-    @Override
-    public Set<java.util.Map.Entry<K, V>> entrySet() {
-      return new MutatabilityAwareSet<Entry<K, V>>(mutabilityOracle, delegate.entrySet());
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      return delegate.equals(o);
-    }
-
-    @Override
-    public int hashCode() {
-      return delegate.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return delegate.toString();
-    }
-  }
-
-  /**
-   * An internal collection that checks for mutability before delegating.
-   */
-  private static class MutatabilityAwareCollection<E> implements Collection<E> {
-    private final MutabilityOracle mutabilityOracle;
-    private final Collection<E> delegate;
-    
-    MutatabilityAwareCollection(MutabilityOracle mutabilityOracle, Collection<E> delegate) {
-      this.mutabilityOracle = mutabilityOracle;
-      this.delegate = delegate;
-    }
 
-    @Override
-    public int size() {
-      return delegate.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-      return delegate.isEmpty();
-    }
-
-    @Override
-    public boolean contains(Object o) {
-      return delegate.contains(o);
-    }
-
-    @Override
-    public Iterator<E> iterator() {
-      return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator());
-    }
-
-    @Override
-    public Object[] toArray() {
-      return delegate.toArray();
-    }
-
-    @Override
-    public <T> T[] toArray(T[] a) {
-      return delegate.toArray(a);
-    }
-
-    @Override
-    public boolean add(E e) {
-      // Unsupported operation in the delegate.
-      throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean remove(Object o) {
-      mutabilityOracle.ensureMutable();
-      return delegate.remove(o);
-    }
-
-    @Override
-    public boolean containsAll(Collection<?> c) {
-      return delegate.containsAll(c);
-    }
-
-    @Override
-    public boolean addAll(Collection<? extends E> c) {
-      // Unsupported operation in the delegate.
+  private void ensureMutable() {
+    if (!isMutable()) {
       throw new UnsupportedOperationException();
     }
-
-    @Override
-    public boolean removeAll(Collection<?> c) {
-      mutabilityOracle.ensureMutable();
-      return delegate.removeAll(c);
-    }
-
-    @Override
-    public boolean retainAll(Collection<?> c) {
-      mutabilityOracle.ensureMutable();
-      return delegate.retainAll(c);
-    }
-
-    @Override
-    public void clear() {
-      mutabilityOracle.ensureMutable();
-      delegate.clear();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      return delegate.equals(o);
-    }
-
-    @Override
-    public int hashCode() {
-      return delegate.hashCode();
-    }
-    
-    @Override
-    public String toString() {
-      return delegate.toString();
-    }
-  }
-
-  /**
-   * An internal set that checks for mutability before delegating.
-   */
-  private static class MutatabilityAwareSet<E> implements Set<E> {
-    private final MutabilityOracle mutabilityOracle;
-    private final Set<E> delegate;
-    
-    MutatabilityAwareSet(MutabilityOracle mutabilityOracle, Set<E> delegate) {
-      this.mutabilityOracle = mutabilityOracle;
-      this.delegate = delegate;
-    }
-
-    @Override
-    public int size() {
-      return delegate.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-      return delegate.isEmpty();
-    }
-
-    @Override
-    public boolean contains(Object o) {
-      return delegate.contains(o);
-    }
-
-    @Override
-    public Iterator<E> iterator() {
-      return new MutatabilityAwareIterator<E>(mutabilityOracle, delegate.iterator());
-    }
-
-    @Override
-    public Object[] toArray() {
-      return delegate.toArray();
-    }
-
-    @Override
-    public <T> T[] toArray(T[] a) {
-      return delegate.toArray(a);
-    }
-
-    @Override
-    public boolean add(E e) {
-      mutabilityOracle.ensureMutable();
-      return delegate.add(e);
-    }
-
-    @Override
-    public boolean remove(Object o) {
-      mutabilityOracle.ensureMutable();
-      return delegate.remove(o);
-    }
-
-    @Override
-    public boolean containsAll(Collection<?> c) {
-      return delegate.containsAll(c);
-    }
-
-    @Override
-    public boolean addAll(Collection<? extends E> c) {
-      mutabilityOracle.ensureMutable();
-      return delegate.addAll(c);
-    }
-
-    @Override
-    public boolean retainAll(Collection<?> c) {
-      mutabilityOracle.ensureMutable();
-      return delegate.retainAll(c);
-    }
-
-    @Override
-    public boolean removeAll(Collection<?> c) {
-      mutabilityOracle.ensureMutable();
-      return delegate.removeAll(c);
-    }
-
-    @Override
-    public void clear() {
-      mutabilityOracle.ensureMutable();
-      delegate.clear();
-    }
-
-    @Override
-    public boolean equals(Object o) {
-      return delegate.equals(o);
-    }
-
-    @Override
-    public int hashCode() {
-      return delegate.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return delegate.toString();
-    }
-  }
-  
-  /**
-   * An internal iterator that checks for mutability before delegating.
-   */
-  private static class MutatabilityAwareIterator<E> implements Iterator<E> {
-    private final MutabilityOracle mutabilityOracle;
-    private final Iterator<E> delegate;
-    
-    MutatabilityAwareIterator(MutabilityOracle mutabilityOracle, Iterator<E> delegate) {
-      this.mutabilityOracle = mutabilityOracle;
-      this.delegate = delegate;
-    }
-
-    @Override
-    public boolean hasNext() {
-      return delegate.hasNext();
-    }
-
-    @Override
-    public E next() {
-      return delegate.next();
-    }
-
-    @Override
-    public void remove() {
-      mutabilityOracle.ensureMutable();
-      delegate.remove();
-    }
-    
-    @Override
-    public boolean equals(Object obj) {
-      return delegate.equals(obj);
-    }
-    
-    @Override
-    public int hashCode() {
-      return delegate.hashCode();
-    }
-
-    @Override
-    public String toString() {
-      return delegate.toString();
-    }
   }
 }

+ 0 - 3
java/core/src/main/java/com/google/protobuf/MessageReflection.java

@@ -364,7 +364,6 @@ class MessageReflection {
      * Finishes the merge and returns the underlying object.
      */
     Object finish();
-    
   }
 
   static class BuilderAdapter implements MergeTarget {
@@ -549,7 +548,6 @@ class MessageReflection {
     public Object finish() {
       return builder.buildPartial();
     }
-    
   }
 
 
@@ -713,7 +711,6 @@ class MessageReflection {
       throw new UnsupportedOperationException(
           "finish() called on FieldSet object");
     }
-    
   }
 
   /**

+ 708 - 0
java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java

@@ -0,0 +1,708 @@
+// 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.AbstractList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * {@code RepeatedFieldBuilderV3} implements a structure that a protocol
+ * message uses to hold a repeated field of other protocol messages. It supports
+ * the classical use case of adding immutable {@link Message}'s to the
+ * repeated field and is highly optimized around this (no extra memory
+ * allocations and sharing of immutable arrays).
+ * <br>
+ * It also supports the additional use case of adding a {@link Message.Builder}
+ * to the repeated field and deferring conversion of that {@code Builder}
+ * to an immutable {@code Message}. In this way, it's possible to maintain
+ * a tree of {@code Builder}'s that acts as a fully read/write data
+ * structure.
+ * <br>
+ * Logically, one can think of a tree of builders as converting the entire tree
+ * to messages when build is called on the root or when any method is called
+ * that desires a Message instead of a Builder. In terms of the implementation,
+ * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3}
+ * classes cache messages that were created so that messages only need to be
+ * created when some change occurred in its builder or a builder for one of its
+ * descendants.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class RepeatedFieldBuilderV3
+    <MType extends AbstractMessage,
+     BType extends AbstractMessage.Builder,
+     IType extends MessageOrBuilder>
+    implements AbstractMessage.BuilderParent {
+
+  // Parent to send changes to.
+  private AbstractMessage.BuilderParent parent;
+
+  // List of messages. Never null. It may be immutable, in which case
+  // isMessagesListMutable will be false. See note below.
+  private List<MType> messages;
+
+  // Whether messages is an mutable array that can be modified.
+  private boolean isMessagesListMutable;
+
+  // List of builders. May be null, in which case, no nested builders were
+  // created. If not null, entries represent the builder for that index.
+  private List<SingleFieldBuilderV3<MType, BType, IType>> builders;
+
+  // Here are the invariants for messages and builders:
+  // 1. messages is never null and its count corresponds to the number of items
+  //    in the repeated field.
+  // 2. If builders is non-null, messages and builders MUST always
+  //    contain the same number of items.
+  // 3. Entries in either array can be null, but for any index, there MUST be
+  //    either a Message in messages or a builder in builders.
+  // 4. If the builder at an index is non-null, the builder is
+  //    authoritative. This is the case where a Builder was set on the index.
+  //    Any message in the messages array MUST be ignored.
+  // t. If the builder at an index is null, the message in the messages
+  //    list is authoritative. This is the case where a Message (not a Builder)
+  //    was set directly for an index.
+
+  // Indicates that we've built a message and so we are now obligated
+  // to dispatch dirty invalidations. See AbstractMessage.BuilderListener.
+  private boolean isClean;
+
+  // A view of this builder that exposes a List interface of messages. This is
+  // initialized on demand. This is fully backed by this object and all changes
+  // are reflected in it. Access to any item converts it to a message if it
+  // was a builder.
+  private MessageExternalList<MType, BType, IType> externalMessageList;
+
+  // A view of this builder that exposes a List interface of builders. This is
+  // initialized on demand. This is fully backed by this object and all changes
+  // are reflected in it. Access to any item converts it to a builder if it
+  // was a message.
+  private BuilderExternalList<MType, BType, IType> externalBuilderList;
+
+  // A view of this builder that exposes a List interface of the interface
+  // implemented by messages and builders. This is initialized on demand. This
+  // is fully backed by this object and all changes are reflected in it.
+  // Access to any item returns either a builder or message depending on
+  // what is most efficient.
+  private MessageOrBuilderExternalList<MType, BType, IType>
+      externalMessageOrBuilderList;
+
+  /**
+   * Constructs a new builder with an empty list of messages.
+   *
+   * @param messages the current list of messages
+   * @param isMessagesListMutable Whether the messages list is mutable
+   * @param parent a listener to notify of changes
+   * @param isClean whether the builder is initially marked clean
+   */
+  public RepeatedFieldBuilderV3(
+      List<MType> messages,
+      boolean isMessagesListMutable,
+      AbstractMessage.BuilderParent parent,
+      boolean isClean) {
+    this.messages = messages;
+    this.isMessagesListMutable = isMessagesListMutable;
+    this.parent = parent;
+    this.isClean = isClean;
+  }
+
+  public void dispose() {
+    // Null out parent so we stop sending it invalidations.
+    parent = null;
+  }
+
+  /**
+   * Ensures that the list of messages is mutable so it can be updated. If it's
+   * immutable, a copy is made.
+   */
+  private void ensureMutableMessageList() {
+    if (!isMessagesListMutable) {
+      messages = new ArrayList<MType>(messages);
+      isMessagesListMutable = true;
+    }
+  }
+
+  /**
+   * Ensures that the list of builders is not null. If it's null, the list is
+   * created and initialized to be the same size as the messages list with
+   * null entries.
+   */
+  private void ensureBuilders() {
+    if (this.builders == null) {
+      this.builders =
+          new ArrayList<SingleFieldBuilderV3<MType, BType, IType>>(
+              messages.size());
+      for (int i = 0; i < messages.size(); i++) {
+        builders.add(null);
+      }
+    }
+  }
+
+  /**
+   * Gets the count of items in the list.
+   *
+   * @return the count of items in the list.
+   */
+  public int getCount() {
+    return messages.size();
+  }
+
+  /**
+   * Gets whether the list is empty.
+   *
+   * @return whether the list is empty
+   */
+  public boolean isEmpty() {
+    return messages.isEmpty();
+  }
+
+  /**
+   * Get the message at the specified index. If the message is currently stored
+   * as a {@code Builder}, it is converted to a {@code Message} by
+   * calling {@link Message.Builder#buildPartial} on it.
+   *
+   * @param index the index of the message to get
+   * @return the message for the specified index
+   */
+  public MType getMessage(int index) {
+    return getMessage(index, false);
+  }
+
+  /**
+   * Get the message at the specified index. If the message is currently stored
+   * as a {@code Builder}, it is converted to a {@code Message} by
+   * calling {@link Message.Builder#buildPartial} on it.
+   *
+   * @param index the index of the message to get
+   * @param forBuild this is being called for build so we want to make sure
+   *     we SingleFieldBuilderV3.build to send dirty invalidations
+   * @return the message for the specified index
+   */
+  private MType getMessage(int index, boolean forBuild) {
+    if (this.builders == null) {
+      // We don't have any builders -- return the current Message.
+      // This is the case where no builder was created, so we MUST have a
+      // Message.
+      return messages.get(index);
+    }
+
+    SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index);
+    if (builder == null) {
+      // We don't have a builder -- return the current message.
+      // This is the case where no builder was created for the entry at index,
+      // so we MUST have a message.
+      return messages.get(index);
+
+    } else {
+      return forBuild ? builder.build() : builder.getMessage();
+    }
+  }
+
+  /**
+   * Gets a builder for the specified index. If no builder has been created for
+   * that index, a builder is created on demand by calling
+   * {@link Message#toBuilder}.
+   *
+   * @param index the index of the message to get
+   * @return The builder for that index
+   */
+  public BType getBuilder(int index) {
+    ensureBuilders();
+    SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index);
+    if (builder == null) {
+      MType message = messages.get(index);
+      builder = new SingleFieldBuilderV3<MType, BType, IType>(
+          message, this, isClean);
+      builders.set(index, builder);
+    }
+    return builder.getBuilder();
+  }
+
+  /**
+   * Gets the base class interface for the specified index. This may either be
+   * a builder or a message. It will return whatever is more efficient.
+   *
+   * @param index the index of the message to get
+   * @return the message or builder for the index as the base class interface
+   */
+  @SuppressWarnings("unchecked")
+  public IType getMessageOrBuilder(int index) {
+    if (this.builders == null) {
+      // We don't have any builders -- return the current Message.
+      // This is the case where no builder was created, so we MUST have a
+      // Message.
+      return (IType) messages.get(index);
+    }
+
+    SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(index);
+    if (builder == null) {
+      // We don't have a builder -- return the current message.
+      // This is the case where no builder was created for the entry at index,
+      // so we MUST have a message.
+      return (IType) messages.get(index);
+
+    } else {
+      return builder.getMessageOrBuilder();
+    }
+  }
+
+  /**
+   * Sets a  message at the specified index replacing the existing item at
+   * that index.
+   *
+   * @param index the index to set.
+   * @param message the message to set
+   * @return the builder
+   */
+  public RepeatedFieldBuilderV3<MType, BType, IType> setMessage(
+      int index, MType message) {
+    if (message == null) {
+      throw new NullPointerException();
+    }
+    ensureMutableMessageList();
+    messages.set(index, message);
+    if (builders != null) {
+      SingleFieldBuilderV3<MType, BType, IType> entry =
+          builders.set(index, null);
+      if (entry != null) {
+        entry.dispose();
+      }
+    }
+    onChanged();
+    incrementModCounts();
+    return this;
+  }
+
+  /**
+   * Appends the specified element to the end of this list.
+   *
+   * @param message the message to add
+   * @return the builder
+   */
+  public RepeatedFieldBuilderV3<MType, BType, IType> addMessage(
+      MType message) {
+    if (message == null) {
+      throw new NullPointerException();
+    }
+    ensureMutableMessageList();
+    messages.add(message);
+    if (builders != null) {
+      builders.add(null);
+    }
+    onChanged();
+    incrementModCounts();
+    return this;
+  }
+
+  /**
+   * Inserts the specified message at the specified position in this list.
+   * Shifts the element currently at that position (if any) and any subsequent
+   * elements to the right (adds one to their indices).
+   *
+   * @param index the index at which to insert the message
+   * @param message the message to add
+   * @return the builder
+   */
+  public RepeatedFieldBuilderV3<MType, BType, IType> addMessage(
+      int index, MType message) {
+    if (message == null) {
+      throw new NullPointerException();
+    }
+    ensureMutableMessageList();
+    messages.add(index, message);
+    if (builders != null) {
+      builders.add(index, null);
+    }
+    onChanged();
+    incrementModCounts();
+    return this;
+  }
+
+  /**
+   * Appends all of the messages in the specified collection to the end of
+   * this list, in the order that they are returned by the specified
+   * collection's iterator.
+   *
+   * @param values the messages to add
+   * @return the builder
+   */
+  public RepeatedFieldBuilderV3<MType, BType, IType> addAllMessages(
+      Iterable<? extends MType> values) {
+    for (final MType value : values) {
+      if (value == null) {
+        throw new NullPointerException();
+      }
+    }
+
+    // If we can inspect the size, we can more efficiently add messages.
+    int size = -1;
+    if (values instanceof Collection) {
+      @SuppressWarnings("unchecked") final
+      Collection<MType> collection = (Collection<MType>) values;
+      if (collection.size() == 0) {
+        return this;
+      }
+      size = collection.size();
+    }
+    ensureMutableMessageList();
+
+    if (size >= 0 && messages instanceof ArrayList) {
+      ((ArrayList<MType>) messages)
+          .ensureCapacity(messages.size() + size);
+    }
+
+    for (MType value : values) {
+      addMessage(value);
+    }
+
+    onChanged();
+    incrementModCounts();
+    return this;
+  }
+
+  /**
+   * Appends a new builder to the end of this list and returns the builder.
+   *
+   * @param message the message to add which is the basis of the builder
+   * @return the new builder
+   */
+  public BType addBuilder(MType message) {
+    ensureMutableMessageList();
+    ensureBuilders();
+    SingleFieldBuilderV3<MType, BType, IType> builder =
+        new SingleFieldBuilderV3<MType, BType, IType>(
+            message, this, isClean);
+    messages.add(null);
+    builders.add(builder);
+    onChanged();
+    incrementModCounts();
+    return builder.getBuilder();
+  }
+
+  /**
+   * Inserts a new builder at the specified position in this list.
+   * Shifts the element currently at that position (if any) and any subsequent
+   * elements to the right (adds one to their indices).
+   *
+   * @param index the index at which to insert the builder
+   * @param message the message to add which is the basis of the builder
+   * @return the builder
+   */
+  public BType addBuilder(int index, MType message) {
+    ensureMutableMessageList();
+    ensureBuilders();
+    SingleFieldBuilderV3<MType, BType, IType> builder =
+        new SingleFieldBuilderV3<MType, BType, IType>(
+            message, this, isClean);
+    messages.add(index, null);
+    builders.add(index, builder);
+    onChanged();
+    incrementModCounts();
+    return builder.getBuilder();
+  }
+
+  /**
+   * Removes the element at the specified position in this list. Shifts any
+   * subsequent elements to the left (subtracts one from their indices).
+   * Returns the element that was removed from the list.
+   *
+   * @param index the index at which to remove the message
+   */
+  public void remove(int index) {
+    ensureMutableMessageList();
+    messages.remove(index);
+    if (builders != null) {
+      SingleFieldBuilderV3<MType, BType, IType> entry =
+          builders.remove(index);
+      if (entry != null) {
+        entry.dispose();
+      }
+    }
+    onChanged();
+    incrementModCounts();
+  }
+
+  /**
+   * Removes all of the elements from this list.
+   * The list will be empty after this call returns.
+   */
+  public void clear() {
+    messages = Collections.emptyList();
+    isMessagesListMutable = false;
+    if (builders != null) {
+      for (SingleFieldBuilderV3<MType, BType, IType> entry :
+          builders) {
+        if (entry != null) {
+          entry.dispose();
+        }
+      }
+      builders = null;
+    }
+    onChanged();
+    incrementModCounts();
+  }
+
+  /**
+   * Builds the list of messages from the builder and returns them.
+   *
+   * @return an immutable list of messages
+   */
+  public List<MType> build() {
+    // Now that build has been called, we are required to dispatch
+    // invalidations.
+    isClean = true;
+
+    if (!isMessagesListMutable && builders == null) {
+      // We still have an immutable list and we never created a builder.
+      return messages;
+    }
+
+    boolean allMessagesInSync = true;
+    if (!isMessagesListMutable) {
+      // We still have an immutable list. Let's see if any of them are out
+      // of sync with their builders.
+      for (int i = 0; i < messages.size(); i++) {
+        Message message = messages.get(i);
+        SingleFieldBuilderV3<MType, BType, IType> builder = builders.get(i);
+        if (builder != null) {
+          if (builder.build() != message) {
+            allMessagesInSync = false;
+            break;
+          }
+        }
+      }
+      if (allMessagesInSync) {
+        // Immutable list is still in sync.
+        return messages;
+      }
+    }
+
+    // Need to make sure messages is up to date
+    ensureMutableMessageList();
+    for (int i = 0; i < messages.size(); i++) {
+      messages.set(i, getMessage(i, true));
+    }
+
+    // We're going to return our list as immutable so we mark that we can
+    // no longer update it.
+    messages = Collections.unmodifiableList(messages);
+    isMessagesListMutable = false;
+    return messages;
+  }
+
+  /**
+   * Gets a view of the builder as a list of messages. The returned list is live
+   * and will reflect any changes to the underlying builder.
+   *
+   * @return the messages in the list
+   */
+  public List<MType> getMessageList() {
+    if (externalMessageList == null) {
+      externalMessageList =
+          new MessageExternalList<MType, BType, IType>(this);
+    }
+    return externalMessageList;
+  }
+
+  /**
+   * Gets a view of the builder as a list of builders. This returned list is
+   * live and will reflect any changes to the underlying builder.
+   *
+   * @return the builders in the list
+   */
+  public List<BType> getBuilderList() {
+    if (externalBuilderList == null) {
+      externalBuilderList =
+          new BuilderExternalList<MType, BType, IType>(this);
+    }
+    return externalBuilderList;
+  }
+
+  /**
+   * Gets a view of the builder as a list of MessageOrBuilders. This returned
+   * list is live and will reflect any changes to the underlying builder.
+   *
+   * @return the builders in the list
+   */
+  public List<IType> getMessageOrBuilderList() {
+    if (externalMessageOrBuilderList == null) {
+      externalMessageOrBuilderList =
+          new MessageOrBuilderExternalList<MType, BType, IType>(this);
+    }
+    return externalMessageOrBuilderList;
+  }
+
+  /**
+   * Called when a the builder or one of its nested children has changed
+   * and any parent should be notified of its invalidation.
+   */
+  private void onChanged() {
+    if (isClean && parent != null) {
+      parent.markDirty();
+
+      // Don't keep dispatching invalidations until build is called again.
+      isClean = false;
+    }
+  }
+
+  @Override
+  public void markDirty() {
+    onChanged();
+  }
+
+  /**
+   * Increments the mod counts so that an ConcurrentModificationException can
+   * be thrown if calling code tries to modify the builder while its iterating
+   * the list.
+   */
+  private void incrementModCounts() {
+    if (externalMessageList != null) {
+      externalMessageList.incrementModCount();
+    }
+    if (externalBuilderList != null) {
+      externalBuilderList.incrementModCount();
+    }
+    if (externalMessageOrBuilderList != null) {
+      externalMessageOrBuilderList.incrementModCount();
+    }
+  }
+
+  /**
+   * Provides a live view of the builder as a list of messages.
+   *
+   * @param <MType> the type of message for the field
+   * @param <BType> the type of builder for the field
+   * @param <IType> the common interface for the message and the builder
+   */
+  private static class MessageExternalList<
+      MType extends AbstractMessage,
+      BType extends AbstractMessage.Builder,
+      IType extends MessageOrBuilder>
+      extends AbstractList<MType> implements List<MType> {
+
+    RepeatedFieldBuilderV3<MType, BType, IType> builder;
+
+    MessageExternalList(
+        RepeatedFieldBuilderV3<MType, BType, IType> builder) {
+      this.builder = builder;
+    }
+
+    @Override
+    public int size() {
+      return this.builder.getCount();
+    }
+
+    @Override
+    public MType get(int index) {
+      return builder.getMessage(index);
+    }
+
+    void incrementModCount() {
+      modCount++;
+    }
+  }
+
+  /**
+   * Provides a live view of the builder as a list of builders.
+   *
+   * @param <MType> the type of message for the field
+   * @param <BType> the type of builder for the field
+   * @param <IType> the common interface for the message and the builder
+   */
+  private static class BuilderExternalList<
+      MType extends AbstractMessage,
+      BType extends AbstractMessage.Builder,
+      IType extends MessageOrBuilder>
+      extends AbstractList<BType> implements List<BType> {
+
+    RepeatedFieldBuilderV3<MType, BType, IType> builder;
+
+    BuilderExternalList(
+        RepeatedFieldBuilderV3<MType, BType, IType> builder) {
+      this.builder = builder;
+    }
+
+    @Override
+    public int size() {
+      return this.builder.getCount();
+    }
+
+    @Override
+    public BType get(int index) {
+      return builder.getBuilder(index);
+    }
+
+    void incrementModCount() {
+      modCount++;
+    }
+  }
+
+  /**
+   * Provides a live view of the builder as a list of builders.
+   *
+   * @param <MType> the type of message for the field
+   * @param <BType> the type of builder for the field
+   * @param <IType> the common interface for the message and the builder
+   */
+  private static class MessageOrBuilderExternalList<
+      MType extends AbstractMessage,
+      BType extends AbstractMessage.Builder,
+      IType extends MessageOrBuilder>
+      extends AbstractList<IType> implements List<IType> {
+
+    RepeatedFieldBuilderV3<MType, BType, IType> builder;
+
+    MessageOrBuilderExternalList(
+        RepeatedFieldBuilderV3<MType, BType, IType> builder) {
+      this.builder = builder;
+    }
+
+    @Override
+    public int size() {
+      return this.builder.getCount();
+    }
+
+    @Override
+    public IType get(int index) {
+      return builder.getMessageOrBuilder(index);
+    }
+
+    void incrementModCount() {
+      modCount++;
+    }
+  }
+}

+ 241 - 0
java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java

@@ -0,0 +1,241 @@
+// 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;
+
+/**
+ * {@code SingleFieldBuilderV3} implements a structure that a protocol
+ * message uses to hold a single field of another protocol message. It supports
+ * the classical use case of setting an immutable {@link Message} as the value
+ * of the field and is highly optimized around this.
+ * <br>
+ * It also supports the additional use case of setting a {@link Message.Builder}
+ * as the field and deferring conversion of that {@code Builder}
+ * to an immutable {@code Message}. In this way, it's possible to maintain
+ * a tree of {@code Builder}'s that acts as a fully read/write data
+ * structure.
+ * <br>
+ * Logically, one can think of a tree of builders as converting the entire tree
+ * to messages when build is called on the root or when any method is called
+ * that desires a Message instead of a Builder. In terms of the implementation,
+ * the {@code SingleFieldBuilderV3} and {@code RepeatedFieldBuilderV3}
+ * classes cache messages that were created so that messages only need to be
+ * created when some change occurred in its builder or a builder for one of its
+ * descendants.
+ *
+ * @param <MType> the type of message for the field
+ * @param <BType> the type of builder for the field
+ * @param <IType> the common interface for the message and the builder
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class SingleFieldBuilderV3
+    <MType extends AbstractMessage,
+     BType extends AbstractMessage.Builder,
+     IType extends MessageOrBuilder>
+    implements AbstractMessage.BuilderParent {
+
+  // Parent to send changes to.
+  private AbstractMessage.BuilderParent parent;
+
+  // Invariant: one of builder or message fields must be non-null.
+
+  // If set, this is the case where we are backed by a builder. In this case,
+  // message field represents a cached message for the builder (or null if
+  // there is no cached message).
+  private BType builder;
+
+  // If builder is non-null, this represents a cached message from the builder.
+  // If builder is null, this is the authoritative message for the field.
+  private MType message;
+
+  // Indicates that we've built a message and so we are now obligated
+  // to dispatch dirty invalidations. See AbstractMessage.BuilderListener.
+  private boolean isClean;
+
+  public SingleFieldBuilderV3(
+      MType message,
+      AbstractMessage.BuilderParent parent,
+      boolean isClean) {
+    if (message == null) {
+      throw new NullPointerException();
+    }
+    this.message = message;
+    this.parent = parent;
+    this.isClean = isClean;
+  }
+
+  public void dispose() {
+    // Null out parent so we stop sending it invalidations.
+    parent = null;
+  }
+
+  /**
+   * Get the message for the field. If the message is currently stored
+   * as a {@code Builder}, it is converted to a {@code Message} by
+   * calling {@link Message.Builder#buildPartial} on it. If no message has
+   * been set, returns the default instance of the message.
+   *
+   * @return the message for the field
+   */
+  @SuppressWarnings("unchecked")
+  public MType getMessage() {
+    if (message == null) {
+      // If message is null, the invariant is that we must be have a builder.
+      message = (MType) builder.buildPartial();
+    }
+    return message;
+  }
+
+  /**
+   * Builds the message and returns it.
+   *
+   * @return the message
+   */
+  public MType build() {
+    // Now that build has been called, we are required to dispatch
+    // invalidations.
+    isClean = true;
+    return getMessage();
+  }
+
+  /**
+   * Gets a builder for the field. If no builder has been created yet, a
+   * builder is created on demand by calling {@link Message#toBuilder}.
+   *
+   * @return The builder for the field
+   */
+  @SuppressWarnings("unchecked")
+  public BType getBuilder() {
+    if (builder == null) {
+      // builder.mergeFrom() on a fresh builder
+      // does not create any sub-objects with independent clean/dirty states,
+      // therefore setting the builder itself to clean without actually calling
+      // build() cannot break any invariants.
+      builder = (BType) message.newBuilderForType(this);
+      builder.mergeFrom(message); // no-op if message is the default message
+      builder.markClean();
+    }
+    return builder;
+  }
+
+  /**
+   * Gets the base class interface for the field. This may either be a builder
+   * or a message. It will return whatever is more efficient.
+   *
+   * @return the message or builder for the field as the base class interface
+   */
+  @SuppressWarnings("unchecked")
+  public IType getMessageOrBuilder() {
+    if (builder != null) {
+      return  (IType) builder;
+    } else {
+      return (IType) message;
+    }
+  }
+
+  /**
+   * Sets a  message for the field replacing any existing value.
+   *
+   * @param message the message to set
+   * @return the builder
+   */
+  public SingleFieldBuilderV3<MType, BType, IType> setMessage(
+      MType message) {
+    if (message == null) {
+      throw new NullPointerException();
+    }
+    this.message = message;
+    if (builder != null) {
+      builder.dispose();
+      builder = null;
+    }
+    onChanged();
+    return this;
+  }
+
+  /**
+   * Merges the field from another field.
+   *
+   * @param value the value to merge from
+   * @return the builder
+   */
+  public SingleFieldBuilderV3<MType, BType, IType> mergeFrom(
+      MType value) {
+    if (builder == null && message == message.getDefaultInstanceForType()) {
+      message = value;
+    } else {
+      getBuilder().mergeFrom(value);
+    }
+    onChanged();
+    return this;
+  }
+
+  /**
+   * Clears the value of the field.
+   *
+   * @return the builder
+   */
+  @SuppressWarnings("unchecked")
+  public SingleFieldBuilderV3<MType, BType, IType> clear() {
+    message = (MType) (message != null ?
+        message.getDefaultInstanceForType() :
+        builder.getDefaultInstanceForType());
+    if (builder != null) {
+      builder.dispose();
+      builder = null;
+    }
+    onChanged();
+    return this;
+  }
+
+  /**
+   * Called when a the builder or one of its nested children has changed
+   * and any parent should be notified of its invalidation.
+   */
+  private void onChanged() {
+    // If builder is null, this is the case where onChanged is being called
+    // from setMessage or clear.
+    if (builder != null) {
+      message = null;
+    }
+    if (isClean && parent != null) {
+      parent.markDirty();
+
+      // Don't keep dispatching invalidations until build is called again.
+      isClean = false;
+    }
+  }
+
+  @Override
+  public void markDirty() {
+    onChanged();
+  }
+}

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

@@ -661,6 +661,14 @@ public final class TextFormat {
       nextToken();
     }
 
+    int getPreviousLine() {
+      return previousLine;
+    }
+
+    int getPreviousColumn() {
+      return previousColumn;
+    }
+
     int getLine() {
       return line;
     }
@@ -1374,6 +1382,28 @@ public final class TextFormat {
       return text;
     }
 
+    // Check both unknown fields and unknown extensions and log warming messages
+    // or throw exceptions according to the flag.
+    private void checkUnknownFields(final List<String> unknownFields)
+        throws ParseException {
+      if (unknownFields.isEmpty()) {
+        return;
+      }
+
+      StringBuilder msg = new StringBuilder("Input contains unknown fields and/or extensions:");
+      for (String field : unknownFields) {
+        msg.append('\n').append(field);
+      }
+
+      if (allowUnknownFields) {
+          logger.warning(msg.toString());
+      } else {
+        String[] lineColumn = unknownFields.get(0).split(":");
+        throw new ParseException(Integer.valueOf(lineColumn[0]),
+            Integer.valueOf(lineColumn[1]), msg.toString());
+      }
+    }
+
     /**
      * Parse a text-format message from {@code input} and merge the contents
      * into {@code builder}.  Extensions will be recognized if they are
@@ -1387,9 +1417,13 @@ public final class TextFormat {
       MessageReflection.BuilderAdapter target =
           new MessageReflection.BuilderAdapter(builder);
 
+      List<String> unknownFields = new ArrayList<String>();
+
       while (!tokenizer.atEnd()) {
-        mergeField(tokenizer, extensionRegistry, target);
+        mergeField(tokenizer, extensionRegistry, target, unknownFields);
       }
+
+      checkUnknownFields(unknownFields);
     }
 
 
@@ -1399,9 +1433,11 @@ public final class TextFormat {
      */
     private void mergeField(final Tokenizer tokenizer,
                             final ExtensionRegistry extensionRegistry,
-                            final MessageReflection.MergeTarget target)
+                            final MessageReflection.MergeTarget target,
+                            List<String> unknownFields)
                             throws ParseException {
-      mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder);
+      mergeField(tokenizer, extensionRegistry, target, parseInfoTreeBuilder,
+                 unknownFields);
     }
 
     /**
@@ -1411,7 +1447,8 @@ public final class TextFormat {
     private void mergeField(final Tokenizer tokenizer,
                             final ExtensionRegistry extensionRegistry,
                             final MessageReflection.MergeTarget target,
-                            TextFormatParseInfoTree.Builder parseTreeBuilder)
+                            TextFormatParseInfoTree.Builder parseTreeBuilder,
+                            List<String> unknownFields)
                             throws ParseException {
       FieldDescriptor field = null;
       int startLine = tokenizer.getLine();
@@ -1432,13 +1469,9 @@ public final class TextFormat {
             extensionRegistry, name.toString());
 
         if (extension == null) {
-          if (!allowUnknownFields) {
-            throw tokenizer.parseExceptionPreviousToken(
-              "Extension \"" + name + "\" not found in the ExtensionRegistry.");
-          } else {
-            logger.warning(
-              "Extension \"" + name + "\" not found in the ExtensionRegistry.");
-          }
+          unknownFields.add((tokenizer.getPreviousLine() + 1) + ":" +
+              (tokenizer.getPreviousColumn() + 1) + ":\t" +
+              type.getFullName() + ".[" + name + "]");
         } else {
           if (extension.descriptor.getContainingType() != type) {
             throw tokenizer.parseExceptionPreviousToken(
@@ -1473,16 +1506,9 @@ public final class TextFormat {
         }
 
         if (field == null) {
-          if (!allowUnknownFields) {
-            throw tokenizer.unknownFieldParseExceptionPreviousToken(
-              name,
-              "Message type \"" + type.getFullName()
-              + "\" has no field named \"" + name + "\".");
-          } else {
-            logger.warning(
-              "Message type \"" + type.getFullName()
-              + "\" has no field named \"" + name + "\".");
-          }
+          unknownFields.add((tokenizer.getPreviousLine() + 1) + ":" +
+              (tokenizer.getPreviousColumn() + 1) + ":\t" +
+              type.getFullName() + "." + name);
         }
       }
 
@@ -1511,15 +1537,15 @@ public final class TextFormat {
           TextFormatParseInfoTree.Builder childParseTreeBuilder =
               parseTreeBuilder.getBuilderForSubMessageField(field);
           consumeFieldValues(tokenizer, extensionRegistry, target, field, extension,
-              childParseTreeBuilder);
+              childParseTreeBuilder, unknownFields);
         } else {
           consumeFieldValues(tokenizer, extensionRegistry, target, field, extension,
-              parseTreeBuilder);
+              parseTreeBuilder, unknownFields);
         }
       } else {
         tokenizer.consume(":");  // required
-        consumeFieldValues(
-            tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder);
+        consumeFieldValues(tokenizer, extensionRegistry, target, field,
+            extension, parseTreeBuilder, unknownFields);
       }
 
       if (parseTreeBuilder != null) {
@@ -1544,14 +1570,15 @@ public final class TextFormat {
         final MessageReflection.MergeTarget target,
         final FieldDescriptor field,
         final ExtensionRegistry.ExtensionInfo extension,
-        final TextFormatParseInfoTree.Builder parseTreeBuilder)
+        final TextFormatParseInfoTree.Builder parseTreeBuilder,
+        List<String> unknownFields)
         throws ParseException {
       // Support specifying repeated field values as a comma-separated list.
       // Ex."foo: [1, 2, 3]"
       if (field.isRepeated() && tokenizer.tryConsume("[")) {
         while (true) {
           consumeFieldValue(tokenizer, extensionRegistry, target, field, extension,
-              parseTreeBuilder);
+              parseTreeBuilder, unknownFields);
           if (tokenizer.tryConsume("]")) {
             // End of list.
             break;
@@ -1559,8 +1586,8 @@ public final class TextFormat {
           tokenizer.consume(",");
         }
       } else {
-        consumeFieldValue(
-            tokenizer, extensionRegistry, target, field, extension, parseTreeBuilder);
+        consumeFieldValue(tokenizer, extensionRegistry, target, field,
+            extension, parseTreeBuilder, unknownFields);
       }
     }
 
@@ -1574,7 +1601,8 @@ public final class TextFormat {
         final MessageReflection.MergeTarget target,
         final FieldDescriptor field,
         final ExtensionRegistry.ExtensionInfo extension,
-        final TextFormatParseInfoTree.Builder parseTreeBuilder)
+        final TextFormatParseInfoTree.Builder parseTreeBuilder,
+        List<String> unknownFields)
         throws ParseException {
       Object value = null;
 
@@ -1596,7 +1624,8 @@ public final class TextFormat {
             throw tokenizer.parseException(
               "Expected \"" + endToken + "\".");
           }
-          mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder);
+          mergeField(tokenizer, extensionRegistry, subField, parseTreeBuilder,
+              unknownFields);
         }
 
         value = subField.finish();

+ 3 - 1
java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java

@@ -57,6 +57,7 @@ import java.util.TreeMap;
  * @author kenton@google.com Kenton Varda
  */
 public final class UnknownFieldSet implements MessageLite {
+
   private UnknownFieldSet() {}
 
   /** Create a new {@link Builder}. */
@@ -130,7 +131,8 @@ public final class UnknownFieldSet implements MessageLite {
   @Override
   public void writeTo(final CodedOutputStream output) throws IOException {
     for (final Map.Entry<Integer, Field> entry : fields.entrySet()) {
-      entry.getValue().writeTo(entry.getKey(), output);
+      Field field = entry.getValue();
+      field.writeTo(entry.getKey(), output);
     }
   }
 

+ 17 - 0
java/core/src/main/java/com/google/protobuf/UnsafeByteOperations.java

@@ -42,6 +42,23 @@ import java.nio.ByteBuffer;
  * guaranteed that the buffer backing the {@link ByteString} will never change! Mutation of a
  * {@link ByteString} can lead to unexpected and undesirable consequences in your application,
  * and will likely be difficult to debug. Proceed with caution!
+ *
+ * <p>This can have a number of significant side affects that have
+ * spooky-action-at-a-distance-like behavior. In particular, if the bytes value changes out from
+ * under a Protocol Buffer:
+ * <ul>
+ * <li>serialization may throw
+ * <li>serialization may succeed but the wrong bytes may be written out
+ * <li>messages are no longer threadsafe
+ * <li>hashCode may be incorrect
+ *   <ul>
+ *   <li>can result in a permanent memory leak when used as a key in a long-lived HashMap
+ *   <li> the semantics of many programs may be violated if this is the case
+ *   </ul>
+ * </ul>
+ * Each of these issues will occur in parts of the code base that are entirely distinct from the
+ * parts of the code base modifying the buffer. In fact, both parts of the code base may be correct
+ * - it is the bridging with the unsafe operations that was in error!
  */
 @ExperimentalApi
 public final class UnsafeByteOperations {

+ 210 - 0
java/core/src/main/java/com/google/protobuf/UnsafeUtil.java

@@ -0,0 +1,210 @@
+// 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 sun.misc.Unsafe;
+
+import java.lang.reflect.Field;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.security.AccessController;
+import java.security.PrivilegedExceptionAction;
+
+/**
+ * Utility class for working with unsafe operations.
+ */
+// TODO(nathanmittler): Add support for Android Memory/MemoryBlock
+final class UnsafeUtil {
+  private static final sun.misc.Unsafe UNSAFE = getUnsafe();
+  private static final boolean HAS_UNSAFE_BYTEBUFFER_OPERATIONS =
+      supportsUnsafeByteBufferOperations();
+  private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations();
+  private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset();
+  private static final long BUFFER_ADDRESS_OFFSET = fieldOffset(field(Buffer.class, "address"));
+
+  private UnsafeUtil() {
+  }
+
+  static boolean hasUnsafeArrayOperations() {
+    return HAS_UNSAFE_ARRAY_OPERATIONS;
+  }
+
+  static boolean hasUnsafeByteBufferOperations() {
+    return HAS_UNSAFE_BYTEBUFFER_OPERATIONS;
+  }
+
+  static long getArrayBaseOffset() {
+    return ARRAY_BASE_OFFSET;
+  }
+
+  static byte getByte(byte[] target, long offset) {
+    return UNSAFE.getByte(target, offset);
+  }
+
+  static void putByte(byte[] target, long offset, byte value) {
+    UNSAFE.putByte(target, offset, value);
+  }
+
+  static void copyMemory(
+      byte[] src, long srcOffset, byte[] target, long targetOffset, long length) {
+    UNSAFE.copyMemory(src, srcOffset, target, targetOffset, length);
+  }
+
+  static long getLong(byte[] target, long offset) {
+    return UNSAFE.getLong(target, offset);
+  }
+
+  static byte getByte(long address) {
+    return UNSAFE.getByte(address);
+  }
+
+  static void putByte(long address, byte value) {
+    UNSAFE.putByte(address, value);
+  }
+
+  static long getLong(long address) {
+    return UNSAFE.getLong(address);
+  }
+
+  static void copyMemory(long srcAddress, long targetAddress, long length) {
+    UNSAFE.copyMemory(srcAddress, targetAddress, length);
+  }
+
+  /**
+   * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}.
+   */
+  static long addressOffset(ByteBuffer buffer) {
+    return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET);
+  }
+
+  /**
+   * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this platform.
+   */
+  private static sun.misc.Unsafe getUnsafe() {
+    sun.misc.Unsafe unsafe = null;
+    try {
+      unsafe =
+          AccessController.doPrivileged(
+              new PrivilegedExceptionAction<Unsafe>() {
+                @Override
+                public sun.misc.Unsafe run() throws Exception {
+                  Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
+
+                  for (Field f : k.getDeclaredFields()) {
+                    f.setAccessible(true);
+                    Object x = f.get(null);
+                    if (k.isInstance(x)) {
+                      return k.cast(x);
+                    }
+                  }
+                  // The sun.misc.Unsafe field does not exist.
+                  return null;
+                }
+              });
+    } catch (Throwable e) {
+      // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError
+      // for Unsafe.
+    }
+    return unsafe;
+  }
+
+  /**
+   * Indicates whether or not unsafe array operations are supported on this platform.
+   */
+  private static boolean supportsUnsafeArrayOperations() {
+    boolean supported = false;
+    if (UNSAFE != null) {
+      try {
+        Class<?> clazz = UNSAFE.getClass();
+        clazz.getMethod("arrayBaseOffset", Class.class);
+        clazz.getMethod("getByte", Object.class, long.class);
+        clazz.getMethod("putByte", Object.class, long.class, byte.class);
+        clazz.getMethod("getLong", Object.class, long.class);
+        clazz.getMethod(
+            "copyMemory", Object.class, long.class, Object.class, long.class, long.class);
+        supported = true;
+      } catch (Throwable e) {
+        // Do nothing.
+      }
+    }
+    return supported;
+  }
+
+  private static boolean supportsUnsafeByteBufferOperations() {
+    boolean supported = false;
+    if (UNSAFE != null) {
+      try {
+        Class<?> clazz = UNSAFE.getClass();
+        clazz.getMethod("objectFieldOffset", Field.class);
+        clazz.getMethod("getByte", long.class);
+        clazz.getMethod("getLong", Object.class, long.class);
+        clazz.getMethod("putByte", long.class, byte.class);
+        clazz.getMethod("getLong", long.class);
+        clazz.getMethod("copyMemory", long.class, long.class, long.class);
+        supported = true;
+      } catch (Throwable e) {
+        // Do nothing.
+      }
+    }
+    return supported;
+  }
+
+  /**
+   * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not available.
+   */
+  private static int byteArrayBaseOffset() {
+    return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1;
+  }
+
+  /**
+   * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
+   * available.
+   */
+  private static long fieldOffset(Field field) {
+    return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field);
+  }
+
+  /**
+   * Gets the field with the given name within the class, or {@code null} if not found. If found,
+   * the field is made accessible.
+   */
+  private static Field field(Class<?> clazz, String fieldName) {
+    Field field;
+    try {
+      field = clazz.getDeclaredField(fieldName);
+      field.setAccessible(true);
+    } catch (Throwable t) {
+      // Failed to access the fields.
+      field = null;
+    }
+    return field;
+  }
+}

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

@@ -30,18 +30,16 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.UnsafeUtil.addressOffset;
+import static com.google.protobuf.UnsafeUtil.getArrayBaseOffset;
+import static com.google.protobuf.UnsafeUtil.hasUnsafeArrayOperations;
+import static com.google.protobuf.UnsafeUtil.hasUnsafeByteBufferOperations;
 import static java.lang.Character.MAX_SURROGATE;
 import static java.lang.Character.MIN_SURROGATE;
 import static java.lang.Character.isSurrogatePair;
 import static java.lang.Character.toCodePoint;
 
-import java.lang.reflect.Field;
-import java.nio.Buffer;
 import java.nio.ByteBuffer;
-import java.security.AccessController;
-import java.security.PrivilegedExceptionAction;
-import java.util.logging.Level;
-import java.util.logging.Logger;
 
 /**
  * A set of low-level, high-performance static utility methods related
@@ -79,7 +77,6 @@ import java.util.logging.Logger;
  */
 // TODO(nathanmittler): Copy changes in this class back to Guava
 final class Utf8 {
-  private static final Logger logger = Logger.getLogger(Utf8.class.getName());
 
   /**
    * UTF-8 is a runtime hot spot so we attempt to provide heavily optimized implementations
@@ -237,7 +234,7 @@ final class Utf8 {
   // fallback to more lenient behavior.
   
   static class UnpairedSurrogateException extends IllegalArgumentException {
-    private UnpairedSurrogateException(int index, int length) {
+    UnpairedSurrogateException(int index, int length) {
       super("Unpaired surrogate at index " + index + " of " + length);
     }
   }
@@ -991,23 +988,11 @@ final class Utf8 {
    * {@link Processor} that uses {@code sun.misc.Unsafe} where possible to improve performance.
    */
   static final class UnsafeProcessor extends Processor {
-    private static final sun.misc.Unsafe UNSAFE = getUnsafe();
-    private static final long BUFFER_ADDRESS_OFFSET =
-        fieldOffset(field(Buffer.class, "address"));
-    private static final int ARRAY_BASE_OFFSET = byteArrayBaseOffset();
-
-    /**
-     * We only use Unsafe operations if we have access to direct {@link ByteBuffer}'s address
-     * and the array base offset is a multiple of 8 (needed by Unsafe.getLong()).
-     */
-    private static final boolean AVAILABLE =
-        BUFFER_ADDRESS_OFFSET != -1 && ARRAY_BASE_OFFSET % 8 == 0;
-
     /**
      * Indicates whether or not all required unsafe operations are supported on this platform.
      */
     static boolean isAvailable() {
-      return AVAILABLE;
+      return hasUnsafeArrayOperations() && hasUnsafeByteBufferOperations();
     }
 
     @Override
@@ -1016,8 +1001,8 @@ final class Utf8 {
         throw new ArrayIndexOutOfBoundsException(
             String.format("Array length=%d, index=%d, limit=%d", bytes.length, index, limit));
       }
-      long offset = ARRAY_BASE_OFFSET + index;
-      final long offsetLimit = ARRAY_BASE_OFFSET + limit;
+      long offset = getArrayBaseOffset() + index;
+      final long offsetLimit = getArrayBaseOffset() + limit;
       if (state != COMPLETE) {
         // The previous decoding operation was incomplete (or malformed).
         // We look for a well-formed sequence consisting of bytes from
@@ -1038,7 +1023,7 @@ final class Utf8 {
           // leading position and overlong 2-byte form.
           if (byte1 < (byte) 0xC2
               // byte2 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else if (byte1 < (byte) 0xF0) {
@@ -1047,7 +1032,7 @@ final class Utf8 {
           // Get byte2 from saved state or array
           int byte2 = (byte) ~(state >> 8);
           if (byte2 == 0) {
-            byte2 = UNSAFE.getByte(bytes, offset++);
+            byte2 = UnsafeUtil.getByte(bytes, offset++);
             if (offset >= offsetLimit) {
               return incompleteStateFor(byte1, byte2);
             }
@@ -1058,7 +1043,7 @@ final class Utf8 {
               // illegal surrogate codepoint?
               || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
               // byte3 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else {
@@ -1068,7 +1053,7 @@ final class Utf8 {
           int byte2 = (byte) ~(state >> 8);
           int byte3 = 0;
           if (byte2 == 0) {
-            byte2 = UNSAFE.getByte(bytes, offset++);
+            byte2 = UnsafeUtil.getByte(bytes, offset++);
             if (offset >= offsetLimit) {
               return incompleteStateFor(byte1, byte2);
             }
@@ -1076,7 +1061,7 @@ final class Utf8 {
             byte3 = (byte) (state >> 16);
           }
           if (byte3 == 0) {
-            byte3 = UNSAFE.getByte(bytes, offset++);
+            byte3 = UnsafeUtil.getByte(bytes, offset++);
             if (offset >= offsetLimit) {
               return incompleteStateFor(byte1, byte2, byte3);
             }
@@ -1095,7 +1080,7 @@ final class Utf8 {
               // byte3 trailing-byte test
               || byte3 > (byte) 0xBF
               // byte4 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         }
@@ -1134,7 +1119,7 @@ final class Utf8 {
           // leading position and overlong 2-byte form.
           if (byte1 < (byte) 0xC2
               // byte2 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else if (byte1 < (byte) 0xF0) {
@@ -1143,7 +1128,7 @@ final class Utf8 {
           // Get byte2 from saved state or array
           int byte2 = (byte) ~(state >> 8);
           if (byte2 == 0) {
-            byte2 = UNSAFE.getByte(address++);
+            byte2 = UnsafeUtil.getByte(address++);
             if (address >= addressLimit) {
               return incompleteStateFor(byte1, byte2);
             }
@@ -1154,7 +1139,7 @@ final class Utf8 {
               // illegal surrogate codepoint?
               || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
               // byte3 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else {
@@ -1164,7 +1149,7 @@ final class Utf8 {
           int byte2 = (byte) ~(state >> 8);
           int byte3 = 0;
           if (byte2 == 0) {
-            byte2 = UNSAFE.getByte(address++);
+            byte2 = UnsafeUtil.getByte(address++);
             if (address >= addressLimit) {
               return incompleteStateFor(byte1, byte2);
             }
@@ -1172,7 +1157,7 @@ final class Utf8 {
             byte3 = (byte) (state >> 16);
           }
           if (byte3 == 0) {
-            byte3 = UNSAFE.getByte(address++);
+            byte3 = UnsafeUtil.getByte(address++);
             if (address >= addressLimit) {
               return incompleteStateFor(byte1, byte2, byte3);
             }
@@ -1191,7 +1176,7 @@ final class Utf8 {
               // byte3 trailing-byte test
               || byte3 > (byte) 0xBF
               // byte4 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         }
@@ -1202,7 +1187,7 @@ final class Utf8 {
 
     @Override
     int encodeUtf8(final CharSequence in, final byte[] out, final int offset, final int length) {
-      long outIx = ARRAY_BASE_OFFSET + offset;
+      long outIx = getArrayBaseOffset() + offset;
       final long outLimit = outIx + length;
       final int inLimit = in.length();
       if (inLimit > length || out.length - length < offset) {
@@ -1215,25 +1200,25 @@ final class Utf8 {
       // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
       int inIx = 0;
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
-        UNSAFE.putByte(out, outIx++, (byte) c);
+        UnsafeUtil.putByte(out, outIx++, (byte) c);
       }
       if (inIx == inLimit) {
         // We're done, it was ASCII encoded.
-        return (int) (outIx - ARRAY_BASE_OFFSET);
+        return (int) (outIx - getArrayBaseOffset());
       }
 
       for (char c; inIx < inLimit; ++inIx) {
         c = in.charAt(inIx);
         if (c < 0x80 && outIx < outLimit) {
-          UNSAFE.putByte(out, outIx++, (byte) c);
+          UnsafeUtil.putByte(out, outIx++, (byte) c);
         } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes
-          UNSAFE.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6)));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
+          UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 6) | (c >>> 6)));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
         } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) {
           // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
-          UNSAFE.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12)));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
+          UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 5) | (c >>> 12)));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & c)));
         } else if (outIx <= outLimit - 4L) {
           // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
           // bytes
@@ -1242,10 +1227,10 @@ final class Utf8 {
             throw new UnpairedSurrogateException((inIx - 1), inLimit);
           }
           int codePoint = toCodePoint(c, low);
-          UNSAFE.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
-          UNSAFE.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint)));
+          UnsafeUtil.putByte(out, outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
+          UnsafeUtil.putByte(out, outIx++, (byte) (0x80 | (0x3F & codePoint)));
         } else {
           if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE)
               && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) {
@@ -1258,7 +1243,7 @@ final class Utf8 {
       }
 
       // All bytes have been encoded.
-      return (int) (outIx - ARRAY_BASE_OFFSET);
+      return (int) (outIx - getArrayBaseOffset());
     }
 
     @Override
@@ -1277,7 +1262,7 @@ final class Utf8 {
       // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
       int inIx = 0;
       for (char c; inIx < inLimit && (c = in.charAt(inIx)) < 0x80; ++inIx) {
-        UNSAFE.putByte(outIx++, (byte) c);
+        UnsafeUtil.putByte(outIx++, (byte) c);
       }
       if (inIx == inLimit) {
         // We're done, it was ASCII encoded.
@@ -1288,15 +1273,15 @@ final class Utf8 {
       for (char c; inIx < inLimit; ++inIx) {
         c = in.charAt(inIx);
         if (c < 0x80 && outIx < outLimit) {
-          UNSAFE.putByte(outIx++, (byte) c);
+          UnsafeUtil.putByte(outIx++, (byte) c);
         } else if (c < 0x800 && outIx <= outLimit - 2L) { // 11 bits, two UTF-8 bytes
-          UNSAFE.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6)));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
+          UnsafeUtil.putByte(outIx++, (byte) ((0xF << 6) | (c >>> 6)));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
         } else if ((c < MIN_SURROGATE || MAX_SURROGATE < c) && outIx <= outLimit - 3L) {
           // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
-          UNSAFE.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12)));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
+          UnsafeUtil.putByte(outIx++, (byte) ((0xF << 5) | (c >>> 12)));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (c >>> 6))));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & c)));
         } else if (outIx <= outLimit - 4L) {
           // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8
           // bytes
@@ -1305,10 +1290,10 @@ final class Utf8 {
             throw new UnpairedSurrogateException((inIx - 1), inLimit);
           }
           int codePoint = toCodePoint(c, low);
-          UNSAFE.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
-          UNSAFE.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint)));
+          UnsafeUtil.putByte(outIx++, (byte) ((0xF << 4) | (codePoint >>> 18)));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 12))));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & (codePoint >>> 6))));
+          UnsafeUtil.putByte(outIx++, (byte) (0x80 | (0x3F & codePoint)));
         } else {
           if ((MIN_SURROGATE <= c && c <= MAX_SURROGATE)
               && (inIx + 1 == inLimit || !isSurrogatePair(c, in.charAt(inIx + 1)))) {
@@ -1349,7 +1334,7 @@ final class Utf8 {
       // we're 8-byte aligned.
       final int unaligned = (int) offset & 7;
       for (int j = unaligned; j > 0; j--) {
-        if (UNSAFE.getByte(bytes, offset++) < 0) {
+        if (UnsafeUtil.getByte(bytes, offset++) < 0) {
           return unaligned - j;
         }
       }
@@ -1358,7 +1343,7 @@ final class Utf8 {
       // To speed things up further, we're reading longs instead of bytes so we use a mask to
       // determine if any byte in the current long is non-ASCII.
       remaining -= unaligned;
-      for (; remaining >= 8 && (UNSAFE.getLong(bytes, offset) & ASCII_MASK_LONG) == 0;
+      for (; remaining >= 8 && (UnsafeUtil.getLong(bytes, offset) & ASCII_MASK_LONG) == 0;
           offset += 8, remaining -= 8) {}
       return maxChars - remaining;
     }
@@ -1379,7 +1364,7 @@ final class Utf8 {
       // be read before we're 8-byte aligned.
       final int unaligned = (int) address & 7;
       for (int j = unaligned; j > 0; j--) {
-        if (UNSAFE.getByte(address++) < 0) {
+        if (UnsafeUtil.getByte(address++) < 0) {
           return unaligned - j;
         }
       }
@@ -1388,7 +1373,7 @@ final class Utf8 {
       // To speed things up further, we're reading longs instead of bytes so we use a mask to
       // determine if any byte in the current long is non-ASCII.
       remaining -= unaligned;
-      for (; remaining >= 8 && (UNSAFE.getLong(address) & ASCII_MASK_LONG) == 0;
+      for (; remaining >= 8 && (UnsafeUtil.getLong(address) & ASCII_MASK_LONG) == 0;
           address += 8, remaining -= 8) {}
       return maxChars - remaining;
     }
@@ -1404,7 +1389,7 @@ final class Utf8 {
         // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold?
         // Maybe after seeing a few in a row that are ASCII, go back to fast mode?
         int byte1 = 0;
-        for (; remaining > 0 && (byte1 = UNSAFE.getByte(bytes, offset++)) >= 0; --remaining) {
+        for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(bytes, offset++)) >= 0; --remaining) {
         }
         if (remaining == 0) {
           return COMPLETE;
@@ -1423,7 +1408,7 @@ final class Utf8 {
           // Simultaneously checks for illegal trailing-byte in
           // leading position and overlong 2-byte form.
           if (byte1 < (byte) 0xC2
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else if (byte1 < (byte) 0xF0) {
@@ -1435,13 +1420,13 @@ final class Utf8 {
           remaining -= 2;
 
           final int byte2;
-          if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF
+          if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF
               // overlong? 5 most significant bits must not all be zero
               || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0)
               // check for illegal surrogate codepoints
               || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
               // byte3 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else {
@@ -1453,16 +1438,16 @@ final class Utf8 {
           remaining -= 3;
 
           final int byte2;
-          if ((byte2 = UNSAFE.getByte(bytes, offset++)) > (byte) 0xBF
+          if ((byte2 = UnsafeUtil.getByte(bytes, offset++)) > (byte) 0xBF
               // Check that 1 <= plane <= 16.  Tricky optimized form of:
               // if (byte1 > (byte) 0xF4 ||
               //     byte1 == (byte) 0xF0 && byte2 < (byte) 0x90 ||
               //     byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F)
               || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0
               // byte3 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF
               // byte4 trailing-byte test
-              || UNSAFE.getByte(bytes, offset++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(bytes, offset++) > (byte) 0xBF) {
             return MALFORMED;
           }
         }
@@ -1480,7 +1465,7 @@ final class Utf8 {
         // TODO(nathanmittler): Consider checking 8 bytes at a time after some threshold?
         // Maybe after seeing a few in a row that are ASCII, go back to fast mode?
         int byte1 = 0;
-        for (; remaining > 0 && (byte1 = UNSAFE.getByte(address++)) >= 0; --remaining) {
+        for (; remaining > 0 && (byte1 = UnsafeUtil.getByte(address++)) >= 0; --remaining) {
         }
         if (remaining == 0) {
           return COMPLETE;
@@ -1498,7 +1483,7 @@ final class Utf8 {
 
           // Simultaneously checks for illegal trailing-byte in
           // leading position and overlong 2-byte form.
-          if (byte1 < (byte) 0xC2 || UNSAFE.getByte(address++) > (byte) 0xBF) {
+          if (byte1 < (byte) 0xC2 || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else if (byte1 < (byte) 0xF0) {
@@ -1510,14 +1495,14 @@ final class Utf8 {
           }
           remaining -= 2;
 
-          final byte byte2 = UNSAFE.getByte(address++);
+          final byte byte2 = UnsafeUtil.getByte(address++);
           if (byte2 > (byte) 0xBF
               // overlong? 5 most significant bits must not all be zero
               || (byte1 == (byte) 0xE0 && byte2 < (byte) 0xA0)
               // check for illegal surrogate codepoints
               || (byte1 == (byte) 0xED && byte2 >= (byte) 0xA0)
               // byte3 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         } else {
@@ -1529,7 +1514,7 @@ final class Utf8 {
           }
           remaining -= 3;
 
-          final byte byte2 = UNSAFE.getByte(address++);
+          final byte byte2 = UnsafeUtil.getByte(address++);
           if (byte2 > (byte) 0xBF
               // Check that 1 <= plane <= 16.  Tricky optimized form of:
               // if (byte1 > (byte) 0xF4 ||
@@ -1537,9 +1522,9 @@ final class Utf8 {
               //     byte1 == (byte) 0xF4 && byte2 > (byte) 0x8F)
               || (((byte1 << 28) + (byte2 - (byte) 0x90)) >> 30) != 0
               // byte3 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF
               // byte4 trailing-byte test
-              || UNSAFE.getByte(address++) > (byte) 0xBF) {
+              || UnsafeUtil.getByte(address++) > (byte) 0xBF) {
             return MALFORMED;
           }
         }
@@ -1553,11 +1538,11 @@ final class Utf8 {
           return incompleteStateFor(byte1);
         }
         case 1: {
-          return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset));
+          return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset));
         }
         case 2: {
-          return incompleteStateFor(byte1, UNSAFE.getByte(bytes, offset),
-              UNSAFE.getByte(bytes, offset + 1));
+          return incompleteStateFor(byte1, UnsafeUtil.getByte(bytes, offset),
+              UnsafeUtil.getByte(bytes, offset + 1));
         }
         default: {
           throw new AssertionError();
@@ -1571,112 +1556,17 @@ final class Utf8 {
           return incompleteStateFor(byte1);
         }
         case 1: {
-          return incompleteStateFor(byte1, UNSAFE.getByte(address));
+          return incompleteStateFor(byte1, UnsafeUtil.getByte(address));
         }
         case 2: {
-          return incompleteStateFor(byte1, UNSAFE.getByte(address), UNSAFE.getByte(address + 1));
+          return incompleteStateFor(byte1, UnsafeUtil.getByte(address),
+              UnsafeUtil.getByte(address + 1));
         }
         default: {
           throw new AssertionError();
         }
       }
     }
-
-    /**
-     * Gets the field with the given name within the class, or {@code null} if not found. If
-     * found, the field is made accessible.
-     */
-    private static Field field(Class<?> clazz, String fieldName) {
-      Field field;
-      try {
-        field = clazz.getDeclaredField(fieldName);
-        field.setAccessible(true);
-      } catch (Throwable t) {
-        // Failed to access the fields.
-        field = null;
-      }
-      logger.log(Level.FINEST, "{0}.{1}: {2}",
-          new Object[] {clazz.getName(), fieldName, (field != null ? "available" : "unavailable")});
-      return field;
-    }
-
-    /**
-     * Returns the offset of the provided field, or {@code -1} if {@code sun.misc.Unsafe} is not
-     * available.
-     */
-    private static long fieldOffset(Field field) {
-      return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field);
-    }
-
-    /**
-     * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not
-     * available.
-     */
-    private static <T> int byteArrayBaseOffset() {
-      return UNSAFE == null ? -1 : UNSAFE.arrayBaseOffset(byte[].class);
-    }
-
-    /**
-     * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}.
-     */
-    private static long addressOffset(ByteBuffer buffer) {
-      return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET);
-    }
-
-    /**
-     * Gets the {@code sun.misc.Unsafe} instance, or {@code null} if not available on this
-     * platform.
-     */
-    private static sun.misc.Unsafe getUnsafe() {
-      sun.misc.Unsafe unsafe = null;
-      try {
-        unsafe = AccessController.doPrivileged(new PrivilegedExceptionAction<sun.misc.Unsafe>() {
-          @Override
-          public sun.misc.Unsafe run() throws Exception {
-            Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
-
-            // Check that this platform supports all of the required unsafe methods.
-            checkRequiredMethods(k);
-
-            for (Field f : k.getDeclaredFields()) {
-              f.setAccessible(true);
-              Object x = f.get(null);
-              if (k.isInstance(x)) {
-                return k.cast(x);
-              }
-            }
-            // The sun.misc.Unsafe field does not exist.
-            return null;
-          }
-        });
-      } catch (Throwable e) {
-        // Catching Throwable here due to the fact that Google AppEngine raises NoClassDefFoundError
-        // for Unsafe.
-      }
-
-      logger.log(Level.FINEST, "sun.misc.Unsafe: {}",
-          unsafe != null ? "available" : "unavailable");
-      return unsafe;
-    }
-
-    /**
-     * Verifies that all required methods of {@code sun.misc.Unsafe} are available on this platform.
-     */
-    private static void checkRequiredMethods(Class<sun.misc.Unsafe> clazz)
-        throws NoSuchMethodException, SecurityException {
-      // Needed for Unsafe byte[] access
-      clazz.getMethod("arrayBaseOffset", Class.class);
-      clazz.getMethod("getByte", Object.class, long.class);
-      clazz.getMethod("putByte", Object.class, long.class, byte.class);
-      clazz.getMethod("getLong", Object.class, long.class);
-
-      // Needed for Unsafe Direct ByteBuffer access
-      clazz.getMethod("objectFieldOffset", Field.class);
-      clazz.getMethod("getByte", long.class);
-      clazz.getMethod("getLong", Object.class, long.class);
-      clazz.getMethod("putByte", long.class, byte.class);
-      clazz.getMethod("getLong", long.class);
-    }
   }
 
   private Utf8() {}

+ 97 - 93
java/core/src/test/java/com/google/protobuf/BooleanArrayListTest.java

@@ -40,30 +40,31 @@ import java.util.Iterator;
 
 /**
  * Tests for {@link BooleanArrayList}.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 public class BooleanArrayListTest extends TestCase {
-  
-  private static final BooleanArrayList UNARY_LIST = newImmutableBooleanArrayList(true);
+
+  private static final BooleanArrayList UNARY_LIST =
+      newImmutableBooleanArrayList(true);
   private static final BooleanArrayList TERTIARY_LIST =
-      newImmutableBooleanArrayList(true, true, false);
-  
+      newImmutableBooleanArrayList(true, false, true);
+
   private BooleanArrayList list;
-  
+
   @Override
   protected void setUp() throws Exception {
     list = new BooleanArrayList();
   }
-  
+
   public void testEmptyListReturnsSameInstance() {
     assertSame(BooleanArrayList.emptyList(), BooleanArrayList.emptyList());
   }
-  
+
   public void testEmptyListIsImmutable() {
     assertImmutable(BooleanArrayList.emptyList());
   }
-  
+
   public void testMakeImmutable() {
     list.addBoolean(true);
     list.addBoolean(false);
@@ -72,16 +73,16 @@ public class BooleanArrayListTest extends TestCase {
     list.makeImmutable();
     assertImmutable(list);
   }
-  
+
   public void testModificationWithIteration() {
-    list.addAll(asList(true, false, false, true));
+    list.addAll(asList(true, false, true, false));
     Iterator<Boolean> iterator = list.iterator();
     assertEquals(4, list.size());
     assertEquals(true, (boolean) list.get(0));
     assertEquals(true, (boolean) iterator.next());
     list.set(0, true);
     assertEquals(false, (boolean) iterator.next());
-    
+
     list.remove(0);
     try {
       iterator.next();
@@ -89,7 +90,7 @@ public class BooleanArrayListTest extends TestCase {
     } catch (ConcurrentModificationException e) {
       // expected
     }
-    
+
     iterator = list.iterator();
     list.add(0, false);
     try {
@@ -99,19 +100,19 @@ public class BooleanArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGet() {
     assertEquals(true, (boolean) TERTIARY_LIST.get(0));
-    assertEquals(true, (boolean) TERTIARY_LIST.get(1));
-    assertEquals(false, (boolean) TERTIARY_LIST.get(2));
-    
+    assertEquals(false, (boolean) TERTIARY_LIST.get(1));
+    assertEquals(true, (boolean) TERTIARY_LIST.get(2));
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -119,19 +120,19 @@ public class BooleanArrayListTest extends TestCase {
       // expected
     }
   }
-  
-  public void testGetInt() {
+
+  public void testGetBoolean() {
     assertEquals(true, TERTIARY_LIST.getBoolean(0));
-    assertEquals(true, TERTIARY_LIST.getBoolean(1));
-    assertEquals(false, TERTIARY_LIST.getBoolean(2));
-    
+    assertEquals(false, TERTIARY_LIST.getBoolean(1));
+    assertEquals(true, TERTIARY_LIST.getBoolean(2));
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -139,7 +140,7 @@ public class BooleanArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSize() {
     assertEquals(0, BooleanArrayList.emptyList().size());
     assertEquals(1, UNARY_LIST.size());
@@ -150,26 +151,26 @@ public class BooleanArrayListTest extends TestCase {
     list.addBoolean(false);
     list.addBoolean(false);
     assertEquals(4, list.size());
-    
+
     list.remove(0);
     assertEquals(3, list.size());
-    
+
     list.add(true);
     assertEquals(4, list.size());
   }
-  
+
   public void testSet() {
     list.addBoolean(false);
     list.addBoolean(false);
-    
+
     assertEquals(false, (boolean) list.set(0, true));
     assertEquals(true, list.getBoolean(0));
 
     assertEquals(false, (boolean) list.set(1, false));
     assertEquals(false, list.getBoolean(1));
-    
+
     try {
-      list.set(-1, true);
+      list.set(-1, false);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
@@ -182,17 +183,17 @@ public class BooleanArrayListTest extends TestCase {
       // expected
     }
   }
-  
-  public void testSetInt() {
+
+  public void testSetBoolean() {
     list.addBoolean(true);
     list.addBoolean(true);
-    
+
     assertEquals(true, list.setBoolean(0, false));
     assertEquals(false, list.getBoolean(0));
 
     assertEquals(true, list.setBoolean(1, false));
     assertEquals(false, list.getBoolean(1));
-    
+
     try {
       list.setBoolean(-1, false);
       fail();
@@ -201,76 +202,78 @@ public class BooleanArrayListTest extends TestCase {
     }
 
     try {
-      list.setBoolean(2, true);
+      list.setBoolean(2, false);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   public void testAdd() {
     assertEquals(0, list.size());
 
-    assertTrue(list.add(true));
-    assertEquals(asList(true), list);
-
     assertTrue(list.add(false));
+    assertEquals(asList(false), list);
+
+    assertTrue(list.add(true));
     list.add(0, false);
-    assertEquals(asList(false, true, false), list);
-    
-    list.add(0, false);
+    assertEquals(asList(false, false, true), list);
+
     list.add(0, true);
+    list.add(0, false);
     // Force a resize by getting up to 11 elements.
     for (int i = 0; i < 6; i++) {
-      list.add(true);
+      list.add(i % 2 == 0);
     }
-    assertEquals(asList(true, false, false, true, false, true, true, true, true, true, true), list);
-    
+    assertEquals(
+        asList(false, true, false, false, true, true, false, true, false, true, false),
+        list);
+
     try {
-      list.add(-1, false);
+      list.add(-1, true);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.add(4, true);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
-  public void testAddInt() {
-    assertEquals(0, list.size());
 
-    list.addBoolean(true);
-    assertEquals(asList(true), list);
+  public void testAddBoolean() {
+    assertEquals(0, list.size());
 
     list.addBoolean(false);
-    assertEquals(asList(true, false), list);
+    assertEquals(asList(false), list);
+
+    list.addBoolean(true);
+    assertEquals(asList(false, true), list);
   }
-  
+
   public void testAddAll() {
     assertEquals(0, list.size());
 
-    assertTrue(list.addAll(Collections.singleton(false)));
+    assertTrue(list.addAll(Collections.singleton(true)));
     assertEquals(1, list.size());
-    assertEquals(false, (boolean) list.get(0));
-    assertEquals(false, list.getBoolean(0));
-    
-    assertTrue(list.addAll(asList(true, false, false, false, true)));
-    assertEquals(asList(false, true, false, false, false, true), list);
-    
+    assertEquals(true, (boolean) list.get(0));
+    assertEquals(true, list.getBoolean(0));
+
+    assertTrue(list.addAll(asList(false, true, false, true, false)));
+    assertEquals(asList(true, false, true, false, true, false), list);
+
     assertTrue(list.addAll(TERTIARY_LIST));
-    assertEquals(asList(false, true, false, false, false, true, true, true, false), list);
+    assertEquals(asList(true, false, true, false, true, false, true, false, true), list);
 
     assertFalse(list.addAll(Collections.<Boolean>emptyList()));
     assertFalse(list.addAll(BooleanArrayList.emptyList()));
   }
-  
+
   public void testRemove() {
     list.addAll(TERTIARY_LIST);
     assertEquals(true, (boolean) list.remove(0));
-    assertEquals(asList(true, false), list);
+    assertEquals(asList(false, true), list);
 
     assertTrue(list.remove(Boolean.TRUE));
     assertEquals(asList(false), list);
@@ -280,92 +283,93 @@ public class BooleanArrayListTest extends TestCase {
 
     assertEquals(false, (boolean) list.remove(0));
     assertEquals(asList(), list);
-    
+
     try {
       list.remove(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.remove(0);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   private void assertImmutable(BooleanArrayList list) {
+
     try {
-      list.add(false);
+      list.add(true);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.add(0, true);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.<Boolean>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
-      list.addAll(Collections.singletonList(false));
+      list.addAll(Collections.singletonList(true));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(new BooleanArrayList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.singleton(true));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.<Boolean>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
-      list.addBoolean(true);
+      list.addBoolean(false);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.clear();
       fail();
@@ -379,63 +383,63 @@ public class BooleanArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.remove(new Object());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.<Boolean>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.singleton(Boolean.TRUE));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.<Boolean>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
-      list.retainAll(Collections.singleton(Boolean.TRUE));
+      list.removeAll(Collections.singleton(Boolean.TRUE));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
-      list.set(0, true);
+      list.set(0, false);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.setBoolean(0, false);
       fail();
@@ -443,7 +447,7 @@ public class BooleanArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   private static BooleanArrayList newImmutableBooleanArrayList(boolean... elements) {
     BooleanArrayList list = new BooleanArrayList();
     for (boolean element : elements) {

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

@@ -382,6 +382,14 @@ public class DescriptorsTest extends TestCase {
     assertEquals(Long.valueOf(8765432109L),
       field.getOptions().getExtension(UnittestCustomOptions.fieldOpt1));
 
+    OneofDescriptor oneof = descriptor.getOneofs().get(0);
+    assertNotNull(oneof);
+
+    assertTrue(
+      oneof.getOptions().hasExtension(UnittestCustomOptions.oneofOpt1));
+    assertEquals(Integer.valueOf(-99),
+      oneof.getOptions().getExtension(UnittestCustomOptions.oneofOpt1));
+
     EnumDescriptor enumType =
       UnittestCustomOptions.TestMessageWithCustomOptions.AnEnum.getDescriptor();
 

+ 76 - 73
java/core/src/test/java/com/google/protobuf/DoubleArrayListTest.java

@@ -40,39 +40,40 @@ import java.util.Iterator;
 
 /**
  * Tests for {@link DoubleArrayList}.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 public class DoubleArrayListTest extends TestCase {
-  
-  private static final DoubleArrayList UNARY_LIST = newImmutableDoubleArrayList(1);
+
+  private static final DoubleArrayList UNARY_LIST =
+      newImmutableDoubleArrayList(1);
   private static final DoubleArrayList TERTIARY_LIST =
       newImmutableDoubleArrayList(1, 2, 3);
-  
+
   private DoubleArrayList list;
-  
+
   @Override
   protected void setUp() throws Exception {
     list = new DoubleArrayList();
   }
-  
+
   public void testEmptyListReturnsSameInstance() {
     assertSame(DoubleArrayList.emptyList(), DoubleArrayList.emptyList());
   }
-  
+
   public void testEmptyListIsImmutable() {
     assertImmutable(DoubleArrayList.emptyList());
   }
-  
+
   public void testMakeImmutable() {
-    list.addDouble(2);
+    list.addDouble(3);
     list.addDouble(4);
-    list.addDouble(6);
-    list.addDouble(8);
+    list.addDouble(5);
+    list.addDouble(7);
     list.makeImmutable();
     assertImmutable(list);
   }
-  
+
   public void testModificationWithIteration() {
     list.addAll(asList(1D, 2D, 3D, 4D));
     Iterator<Double> iterator = list.iterator();
@@ -81,7 +82,7 @@ public class DoubleArrayListTest extends TestCase {
     assertEquals(1D, (double) iterator.next());
     list.set(0, 1D);
     assertEquals(2D, (double) iterator.next());
-    
+
     list.remove(0);
     try {
       iterator.next();
@@ -89,7 +90,7 @@ public class DoubleArrayListTest extends TestCase {
     } catch (ConcurrentModificationException e) {
       // expected
     }
-    
+
     iterator = list.iterator();
     list.add(0, 0D);
     try {
@@ -99,19 +100,19 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGet() {
     assertEquals(1D, (double) TERTIARY_LIST.get(0));
     assertEquals(2D, (double) TERTIARY_LIST.get(1));
     assertEquals(3D, (double) TERTIARY_LIST.get(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -119,19 +120,19 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
-  public void testGetInt() {
+
+  public void testGetDouble() {
     assertEquals(1D, TERTIARY_LIST.getDouble(0));
     assertEquals(2D, TERTIARY_LIST.getDouble(1));
     assertEquals(3D, TERTIARY_LIST.getDouble(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -139,35 +140,35 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSize() {
     assertEquals(0, DoubleArrayList.emptyList().size());
     assertEquals(1, UNARY_LIST.size());
     assertEquals(3, TERTIARY_LIST.size());
 
-    list.addDouble(2);
+    list.addDouble(3);
     list.addDouble(4);
     list.addDouble(6);
     list.addDouble(8);
     assertEquals(4, list.size());
-    
+
     list.remove(0);
     assertEquals(3, list.size());
-    
-    list.add(16D);
+
+    list.add(17D);
     assertEquals(4, list.size());
   }
-  
+
   public void testSet() {
     list.addDouble(2);
     list.addDouble(4);
-    
-    assertEquals(2D, (double) list.set(0, 0D));
-    assertEquals(0D, list.getDouble(0));
+
+    assertEquals(2D, (double) list.set(0, 3D));
+    assertEquals(3D, list.getDouble(0));
 
     assertEquals(4D, (double) list.set(1, 0D));
     assertEquals(0D, list.getDouble(1));
-    
+
     try {
       list.set(-1, 0D);
       fail();
@@ -182,17 +183,17 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
-  public void testSetInt() {
-    list.addDouble(2);
-    list.addDouble(4);
-    
-    assertEquals(2D, list.setDouble(0, 0));
+
+  public void testSetDouble() {
+    list.addDouble(1);
+    list.addDouble(3);
+
+    assertEquals(1D, list.setDouble(0, 0));
     assertEquals(0D, list.getDouble(0));
 
-    assertEquals(4D, list.setDouble(1, 0));
+    assertEquals(3D, list.setDouble(1, 0));
     assertEquals(0D, list.getDouble(1));
-    
+
     try {
       list.setDouble(-1, 0);
       fail();
@@ -207,7 +208,7 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testAdd() {
     assertEquals(0, list.size());
 
@@ -217,29 +218,31 @@ public class DoubleArrayListTest extends TestCase {
     assertTrue(list.add(3D));
     list.add(0, 4D);
     assertEquals(asList(4D, 2D, 3D), list);
-    
+
     list.add(0, 1D);
     list.add(0, 0D);
     // Force a resize by getting up to 11 elements.
     for (int i = 0; i < 6; i++) {
       list.add(Double.valueOf(5 + i));
     }
-    assertEquals(asList(0D, 1D, 4D, 2D, 3D, 5D, 6D, 7D, 8D, 9D, 10D), list);
-    
+    assertEquals(
+        asList(0D, 1D, 4D, 2D, 3D, 5D, 6D, 7D, 8D, 9D, 10D),
+        list);
+
     try {
       list.add(-1, 5D);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.add(4, 5D);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
-  public void testAddInt() {
+
+  public void testAddDouble() {
     assertEquals(0, list.size());
 
     list.addDouble(2);
@@ -248,7 +251,7 @@ public class DoubleArrayListTest extends TestCase {
     list.addDouble(3);
     assertEquals(asList(2D, 3D), list);
   }
-  
+
   public void testAddAll() {
     assertEquals(0, list.size());
 
@@ -256,17 +259,17 @@ public class DoubleArrayListTest extends TestCase {
     assertEquals(1, list.size());
     assertEquals(1D, (double) list.get(0));
     assertEquals(1D, list.getDouble(0));
-    
+
     assertTrue(list.addAll(asList(2D, 3D, 4D, 5D, 6D)));
     assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D), list);
-    
+
     assertTrue(list.addAll(TERTIARY_LIST));
     assertEquals(asList(1D, 2D, 3D, 4D, 5D, 6D, 1D, 2D, 3D), list);
 
     assertFalse(list.addAll(Collections.<Double>emptyList()));
     assertFalse(list.addAll(DoubleArrayList.emptyList()));
   }
-  
+
   public void testRemove() {
     list.addAll(TERTIARY_LIST);
     assertEquals(1D, (double) list.remove(0));
@@ -280,96 +283,96 @@ public class DoubleArrayListTest extends TestCase {
 
     assertEquals(2D, (double) list.remove(0));
     assertEquals(asList(), list);
-    
+
     try {
       list.remove(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.remove(0);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   private void assertImmutable(DoubleArrayList list) {
     if (list.contains(1D)) {
       throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
     }
-    
+
     try {
       list.add(1D);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.add(0, 1D);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.<Double>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.singletonList(1D));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(new DoubleArrayList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.singleton(1D));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.<Double>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addDouble(0);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.clear();
       fail();
@@ -383,28 +386,28 @@ public class DoubleArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.remove(new Object());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.<Double>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.singleton(1D));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(UNARY_LIST);
       fail();
@@ -418,28 +421,28 @@ public class DoubleArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.singleton(1D));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.set(0, 0D);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.setDouble(0, 0);
       fail();
@@ -447,7 +450,7 @@ public class DoubleArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   private static DoubleArrayList newImmutableDoubleArrayList(double... elements) {
     DoubleArrayList list = new DoubleArrayList();
     for (double element : elements) {

+ 245 - 0
java/core/src/test/java/com/google/protobuf/ExtensionRegistryFactoryTest.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 protobuf_unittest.NonNestedExtension;
+import protobuf_unittest.NonNestedExtensionLite;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+import java.lang.reflect.Method;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for {@link ExtensionRegistryFactory} and the {@link ExtensionRegistry} instances it
+ * creates.
+ * 
+ * <p>This test simulates the runtime behaviour of the ExtensionRegistryFactory by delegating test
+ * definitions to two inner classes {@link InnerTest} and {@link InnerLiteTest}, the latter of
+ * which is executed using a custom ClassLoader, simulating the ProtoLite environment.
+ * 
+ * <p>The test mechanism employed here is based on the pattern in
+ * {@code com.google.common.util.concurrent.AbstractFutureFallbackAtomicHelperTest}
+ */
+public class ExtensionRegistryFactoryTest extends TestCase {
+
+  // A classloader which blacklists some non-Lite classes.
+  private static final ClassLoader LITE_CLASS_LOADER = getLiteOnlyClassLoader();
+
+  /**
+   * Defines the set of test methods which will be run.
+   */
+  static interface RegistryTests {
+    void testCreate();
+    void testEmpty();
+    void testIsFullRegistry();
+    void testAdd();
+  }
+
+  /**
+   * Test implementations for the non-Lite usage of ExtensionRegistryFactory.
+   */
+  public static class InnerTest implements RegistryTests {
+
+    @Override
+    public void testCreate() {
+      ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
+
+      assertEquals(registry.getClass(), ExtensionRegistry.class);
+    }
+
+    @Override
+    public void testEmpty() {
+      ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty();
+
+      assertEquals(emptyRegistry.getClass(), ExtensionRegistry.class);
+      assertEquals(emptyRegistry, ExtensionRegistry.EMPTY_REGISTRY);
+    }
+
+    @Override
+    public void testIsFullRegistry() {
+      ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
+      assertTrue(ExtensionRegistryFactory.isFullRegistry(registry));
+    }
+
+    @Override
+    public void testAdd() {
+      ExtensionRegistryLite registry1 = ExtensionRegistryLite.newInstance();
+      NonNestedExtensionLite.registerAllExtensions(registry1);
+      registry1.add(NonNestedExtensionLite.nonNestedExtensionLite);
+
+      ExtensionRegistryLite registry2 = ExtensionRegistryLite.newInstance();
+      NonNestedExtension.registerAllExtensions((ExtensionRegistry) registry2);
+      registry2.add(NonNestedExtension.nonNestedExtension);
+
+      ExtensionRegistry fullRegistry1 = (ExtensionRegistry) registry1;
+      ExtensionRegistry fullRegistry2 = (ExtensionRegistry) registry2;
+
+      assertTrue("Test is using a non-lite extension",
+          GeneratedMessageLite.GeneratedExtension.class.isAssignableFrom(
+              NonNestedExtensionLite.nonNestedExtensionLite.getClass()));
+      assertNull("Extension is not registered in masqueraded full registry",
+          fullRegistry1.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension"));
+      GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?>
+      extension = registry1.findLiteExtensionByNumber(
+          NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1);
+      assertNotNull("Extension registered in lite registry", extension);
+
+      assertTrue("Test is using a non-lite extension",
+          GeneratedMessage.GeneratedExtension.class.isAssignableFrom(
+          NonNestedExtension.nonNestedExtension.getClass()));
+      assertNotNull("Extension is registered in masqueraded full registry",
+          fullRegistry2.findImmutableExtensionByName("protobuf_unittest.nonNestedExtension"));
+    }
+  }
+
+  /**
+   * Test implementations for the Lite usage of ExtensionRegistryFactory.
+   */
+  public static final class InnerLiteTest implements RegistryTests {
+
+    @Override
+    public void testCreate() {
+      ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
+
+      assertEquals(registry.getClass(), ExtensionRegistryLite.class);
+    }
+
+    @Override
+    public void testEmpty() {
+      ExtensionRegistryLite emptyRegistry = ExtensionRegistryFactory.createEmpty();
+
+      assertEquals(emptyRegistry.getClass(), ExtensionRegistryLite.class);
+      assertEquals(emptyRegistry, ExtensionRegistryLite.EMPTY_REGISTRY_LITE);
+    }
+
+    @Override
+    public void testIsFullRegistry() {
+      ExtensionRegistryLite registry = ExtensionRegistryFactory.create();
+      assertFalse(ExtensionRegistryFactory.isFullRegistry(registry));
+    }
+
+    @Override
+    public void testAdd() {
+      ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
+      NonNestedExtensionLite.registerAllExtensions(registry);
+      GeneratedMessageLite.GeneratedExtension<NonNestedExtensionLite.MessageLiteToBeExtended, ?>
+          extension = registry.findLiteExtensionByNumber(
+              NonNestedExtensionLite.MessageLiteToBeExtended.getDefaultInstance(), 1);
+      assertNotNull("Extension is registered in Lite registry", extension);
+    }
+  }
+
+  /**
+   * Defines a suite of tests which the JUnit3 runner retrieves by reflection.
+   */
+  public static Test suite() { 
+    TestSuite suite = new TestSuite();
+    for (Method method : RegistryTests.class.getMethods()) {
+      suite.addTest(TestSuite.createTest(ExtensionRegistryFactoryTest.class, method.getName()));
+    }
+    return suite;
+  }
+
+  /**
+   * Sequentially runs first the Lite and then the non-Lite test variant via classloader
+   * manipulation.
+   */
+  @Override
+  public void runTest() throws Exception {
+    ClassLoader storedClassLoader = Thread.currentThread().getContextClassLoader();
+    Thread.currentThread().setContextClassLoader(LITE_CLASS_LOADER);
+    try {
+      runTestMethod(LITE_CLASS_LOADER, InnerLiteTest.class);
+    } finally {
+      Thread.currentThread().setContextClassLoader(storedClassLoader);
+    }
+    try {
+      runTestMethod(storedClassLoader, InnerTest.class);
+    } finally {
+      Thread.currentThread().setContextClassLoader(storedClassLoader);
+    }
+  }
+
+  private void runTestMethod(ClassLoader classLoader, Class<? extends RegistryTests> testClass)
+      throws Exception {
+    classLoader.loadClass(ExtensionRegistryFactory.class.getName());
+    Class<?> test = classLoader.loadClass(testClass.getName());
+    String testName = getName();
+    test.getMethod(testName).invoke(test.newInstance());
+  }
+
+  /**
+   * Constructs a custom ClassLoader blacklisting the classes which are inspected in the SUT
+   * to determine the Lite/non-Lite runtime.
+   */
+  private static ClassLoader getLiteOnlyClassLoader() {
+    ClassLoader testClassLoader = ExtensionRegistryFactoryTest.class.getClassLoader();
+    final Set<String> classNamesNotInLite =
+        Collections.unmodifiableSet(
+            new HashSet<String>(
+                Arrays.asList(
+                    ExtensionRegistryFactory.FULL_REGISTRY_CLASS_NAME,
+                    ExtensionRegistry.EXTENSION_CLASS_NAME)));
+
+    // Construct a URLClassLoader delegating to the system ClassLoader, and looking up classes
+    // in jar files based on the URLs already configured for this test's UrlClassLoader.
+    // Certain classes throw a ClassNotFoundException by design.
+    return new URLClassLoader(((URLClassLoader) testClassLoader).getURLs(),
+        ClassLoader.getSystemClassLoader()) {
+      @Override
+      public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+        if (classNamesNotInLite.contains(name)) {
+          throw new ClassNotFoundException("Class deliberately blacklisted by test.");
+        }
+        Class<?> loadedClass = null;
+        try {
+          loadedClass = findLoadedClass(name);
+          if (loadedClass == null) {
+            loadedClass = findClass(name);
+            if (resolve) {
+              resolveClass(loadedClass);
+            }
+          }
+        } catch (ClassNotFoundException e) {
+          loadedClass = super.loadClass(name, resolve);
+        }
+        return loadedClass;
+      }
+    };
+  }
+}

+ 20 - 0
java/core/src/test/java/com/google/protobuf/FieldPresenceTest.java

@@ -152,6 +152,26 @@ public class FieldPresenceTest extends TestCase {
     assertFalse(message1.equals(message2));
   }
 
+  public void testLazyField() throws Exception {
+    // Test default constructed message.
+    TestAllTypes.Builder builder = TestAllTypes.newBuilder();
+    TestAllTypes message = builder.build();
+    assertFalse(message.hasOptionalLazyMessage());
+    assertEquals(0, message.getSerializedSize());
+    assertEquals(ByteString.EMPTY, message.toByteString());
+
+    // Set default instance to the field.
+    builder.setOptionalLazyMessage(TestAllTypes.NestedMessage.getDefaultInstance());
+    message = builder.build();
+    assertTrue(message.hasOptionalLazyMessage());
+    assertEquals(2, message.getSerializedSize());
+
+    // Test parse zero-length from wire sets the presence.
+    TestAllTypes parsed = TestAllTypes.parseFrom(message.toByteString());
+    assertTrue(parsed.hasOptionalLazyMessage());
+    assertEquals(message.getOptionalLazyMessage(), parsed.getOptionalLazyMessage());
+  }
+
   public void testFieldPresence() {
     // Optional non-message fields set to their default value are treated the
     // same way as not set.

+ 76 - 73
java/core/src/test/java/com/google/protobuf/FloatArrayListTest.java

@@ -40,39 +40,40 @@ import java.util.Iterator;
 
 /**
  * Tests for {@link FloatArrayList}.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 public class FloatArrayListTest extends TestCase {
-  
-  private static final FloatArrayList UNARY_LIST = newImmutableFloatArrayList(1);
+
+  private static final FloatArrayList UNARY_LIST =
+      newImmutableFloatArrayList(1);
   private static final FloatArrayList TERTIARY_LIST =
       newImmutableFloatArrayList(1, 2, 3);
-  
+
   private FloatArrayList list;
-  
+
   @Override
   protected void setUp() throws Exception {
     list = new FloatArrayList();
   }
-  
+
   public void testEmptyListReturnsSameInstance() {
     assertSame(FloatArrayList.emptyList(), FloatArrayList.emptyList());
   }
-  
+
   public void testEmptyListIsImmutable() {
     assertImmutable(FloatArrayList.emptyList());
   }
-  
+
   public void testMakeImmutable() {
-    list.addFloat(2);
+    list.addFloat(3);
     list.addFloat(4);
-    list.addFloat(6);
-    list.addFloat(8);
+    list.addFloat(5);
+    list.addFloat(7);
     list.makeImmutable();
     assertImmutable(list);
   }
-  
+
   public void testModificationWithIteration() {
     list.addAll(asList(1F, 2F, 3F, 4F));
     Iterator<Float> iterator = list.iterator();
@@ -81,7 +82,7 @@ public class FloatArrayListTest extends TestCase {
     assertEquals(1F, (float) iterator.next());
     list.set(0, 1F);
     assertEquals(2F, (float) iterator.next());
-    
+
     list.remove(0);
     try {
       iterator.next();
@@ -89,7 +90,7 @@ public class FloatArrayListTest extends TestCase {
     } catch (ConcurrentModificationException e) {
       // expected
     }
-    
+
     iterator = list.iterator();
     list.add(0, 0F);
     try {
@@ -99,19 +100,19 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGet() {
     assertEquals(1F, (float) TERTIARY_LIST.get(0));
     assertEquals(2F, (float) TERTIARY_LIST.get(1));
     assertEquals(3F, (float) TERTIARY_LIST.get(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -119,19 +120,19 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGetFloat() {
     assertEquals(1F, TERTIARY_LIST.getFloat(0));
     assertEquals(2F, TERTIARY_LIST.getFloat(1));
     assertEquals(3F, TERTIARY_LIST.getFloat(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -139,35 +140,35 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSize() {
     assertEquals(0, FloatArrayList.emptyList().size());
     assertEquals(1, UNARY_LIST.size());
     assertEquals(3, TERTIARY_LIST.size());
 
-    list.addFloat(2);
+    list.addFloat(3);
     list.addFloat(4);
     list.addFloat(6);
     list.addFloat(8);
     assertEquals(4, list.size());
-    
+
     list.remove(0);
     assertEquals(3, list.size());
-    
-    list.add(16F);
+
+    list.add(17F);
     assertEquals(4, list.size());
   }
-  
+
   public void testSet() {
     list.addFloat(2);
     list.addFloat(4);
-    
-    assertEquals(2F, (float) list.set(0, 0F));
-    assertEquals(0F, list.getFloat(0));
+
+    assertEquals(2F, (float) list.set(0, 3F));
+    assertEquals(3F, list.getFloat(0));
 
     assertEquals(4F, (float) list.set(1, 0F));
     assertEquals(0F, list.getFloat(1));
-    
+
     try {
       list.set(-1, 0F);
       fail();
@@ -182,17 +183,17 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSetFloat() {
-    list.addFloat(2);
-    list.addFloat(4);
-    
-    assertEquals(2F, list.setFloat(0, 0));
+    list.addFloat(1);
+    list.addFloat(3);
+
+    assertEquals(1F, list.setFloat(0, 0));
     assertEquals(0F, list.getFloat(0));
 
-    assertEquals(4F, list.setFloat(1, 0));
+    assertEquals(3F, list.setFloat(1, 0));
     assertEquals(0F, list.getFloat(1));
-    
+
     try {
       list.setFloat(-1, 0);
       fail();
@@ -207,7 +208,7 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testAdd() {
     assertEquals(0, list.size());
 
@@ -217,28 +218,30 @@ public class FloatArrayListTest extends TestCase {
     assertTrue(list.add(3F));
     list.add(0, 4F);
     assertEquals(asList(4F, 2F, 3F), list);
-    
+
     list.add(0, 1F);
     list.add(0, 0F);
     // Force a resize by getting up to 11 elements.
     for (int i = 0; i < 6; i++) {
       list.add(Float.valueOf(5 + i));
     }
-    assertEquals(asList(0F, 1F, 4F, 2F, 3F, 5F, 6F, 7F, 8F, 9F, 10F), list);
-    
+    assertEquals(
+        asList(0F, 1F, 4F, 2F, 3F, 5F, 6F, 7F, 8F, 9F, 10F),
+        list);
+
     try {
       list.add(-1, 5F);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.add(4, 5F);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   public void testAddFloat() {
     assertEquals(0, list.size());
 
@@ -248,7 +251,7 @@ public class FloatArrayListTest extends TestCase {
     list.addFloat(3);
     assertEquals(asList(2F, 3F), list);
   }
-  
+
   public void testAddAll() {
     assertEquals(0, list.size());
 
@@ -256,17 +259,17 @@ public class FloatArrayListTest extends TestCase {
     assertEquals(1, list.size());
     assertEquals(1F, (float) list.get(0));
     assertEquals(1F, list.getFloat(0));
-    
+
     assertTrue(list.addAll(asList(2F, 3F, 4F, 5F, 6F)));
     assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F), list);
-    
+
     assertTrue(list.addAll(TERTIARY_LIST));
     assertEquals(asList(1F, 2F, 3F, 4F, 5F, 6F, 1F, 2F, 3F), list);
 
     assertFalse(list.addAll(Collections.<Float>emptyList()));
     assertFalse(list.addAll(FloatArrayList.emptyList()));
   }
-  
+
   public void testRemove() {
     list.addAll(TERTIARY_LIST);
     assertEquals(1F, (float) list.remove(0));
@@ -280,96 +283,96 @@ public class FloatArrayListTest extends TestCase {
 
     assertEquals(2F, (float) list.remove(0));
     assertEquals(asList(), list);
-    
+
     try {
       list.remove(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.remove(0);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   private void assertImmutable(FloatArrayList list) {
     if (list.contains(1F)) {
       throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
     }
-    
+
     try {
       list.add(1F);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.add(0, 1F);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.<Float>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.singletonList(1F));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(new FloatArrayList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.singleton(1F));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.<Float>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addFloat(0);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.clear();
       fail();
@@ -383,63 +386,63 @@ public class FloatArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.remove(new Object());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.<Float>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.singleton(1F));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.<Float>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.singleton(1F));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.set(0, 0F);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.setFloat(0, 0);
       fail();
@@ -447,10 +450,10 @@ public class FloatArrayListTest extends TestCase {
       // expected
     }
   }
-  
-  private static FloatArrayList newImmutableFloatArrayList(int... elements) {
+
+  private static FloatArrayList newImmutableFloatArrayList(float... elements) {
     FloatArrayList list = new FloatArrayList();
-    for (int element : elements) {
+    for (float element : elements) {
       list.addFloat(element);
     }
     list.makeImmutable();

+ 73 - 70
java/core/src/test/java/com/google/protobuf/IntArrayListTest.java

@@ -40,35 +40,36 @@ import java.util.Iterator;
 
 /**
  * Tests for {@link IntArrayList}.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 public class IntArrayListTest extends TestCase {
-  
-  private static final IntArrayList UNARY_LIST = newImmutableIntArrayList(1);
+
+  private static final IntArrayList UNARY_LIST =
+      newImmutableIntArrayList(1);
   private static final IntArrayList TERTIARY_LIST =
       newImmutableIntArrayList(1, 2, 3);
-  
+
   private IntArrayList list;
-  
+
   @Override
   protected void setUp() throws Exception {
     list = new IntArrayList();
   }
-  
+
   public void testEmptyListReturnsSameInstance() {
     assertSame(IntArrayList.emptyList(), IntArrayList.emptyList());
   }
-  
+
   public void testEmptyListIsImmutable() {
     assertImmutable(IntArrayList.emptyList());
   }
-  
+
   public void testMakeImmutable() {
-    list.addInt(2);
+    list.addInt(3);
     list.addInt(4);
-    list.addInt(6);
-    list.addInt(8);
+    list.addInt(5);
+    list.addInt(7);
     list.makeImmutable();
     assertImmutable(list);
   }
@@ -81,7 +82,7 @@ public class IntArrayListTest extends TestCase {
     assertEquals(1, (int) iterator.next());
     list.set(0, 1);
     assertEquals(2, (int) iterator.next());
-    
+
     list.remove(0);
     try {
       iterator.next();
@@ -99,19 +100,19 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGet() {
     assertEquals(1, (int) TERTIARY_LIST.get(0));
     assertEquals(2, (int) TERTIARY_LIST.get(1));
     assertEquals(3, (int) TERTIARY_LIST.get(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -119,19 +120,19 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGetInt() {
     assertEquals(1, TERTIARY_LIST.getInt(0));
     assertEquals(2, TERTIARY_LIST.getInt(1));
     assertEquals(3, TERTIARY_LIST.getInt(2));
-    
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -139,35 +140,35 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSize() {
     assertEquals(0, IntArrayList.emptyList().size());
     assertEquals(1, UNARY_LIST.size());
     assertEquals(3, TERTIARY_LIST.size());
 
-    list.addInt(2);
+    list.addInt(3);
     list.addInt(4);
     list.addInt(6);
     list.addInt(8);
     assertEquals(4, list.size());
-    
+
     list.remove(0);
     assertEquals(3, list.size());
-    
-    list.add(16);
+
+    list.add(17);
     assertEquals(4, list.size());
   }
-  
+
   public void testSet() {
     list.addInt(2);
     list.addInt(4);
-    
-    assertEquals(2, (int) list.set(0, 0));
-    assertEquals(0, list.getInt(0));
+
+    assertEquals(2, (int) list.set(0, 3));
+    assertEquals(3, list.getInt(0));
 
     assertEquals(4, (int) list.set(1, 0));
     assertEquals(0, list.getInt(1));
-    
+
     try {
       list.set(-1, 0);
       fail();
@@ -182,17 +183,17 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSetInt() {
-    list.addInt(2);
-    list.addInt(4);
-    
-    assertEquals(2, list.setInt(0, 0));
+    list.addInt(1);
+    list.addInt(3);
+
+    assertEquals(1, list.setInt(0, 0));
     assertEquals(0, list.getInt(0));
 
-    assertEquals(4, list.setInt(1, 0));
+    assertEquals(3, list.setInt(1, 0));
     assertEquals(0, list.getInt(1));
-    
+
     try {
       list.setInt(-1, 0);
       fail();
@@ -207,7 +208,7 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testAdd() {
     assertEquals(0, list.size());
 
@@ -217,28 +218,30 @@ public class IntArrayListTest extends TestCase {
     assertTrue(list.add(3));
     list.add(0, 4);
     assertEquals(asList(4, 2, 3), list);
-    
+
     list.add(0, 1);
     list.add(0, 0);
     // Force a resize by getting up to 11 elements.
     for (int i = 0; i < 6; i++) {
-      list.add(5 + i);
+      list.add(Integer.valueOf(5 + i));
     }
-    assertEquals(asList(0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10), list);
-    
+    assertEquals(
+        asList(0, 1, 4, 2, 3, 5, 6, 7, 8, 9, 10),
+        list);
+
     try {
       list.add(-1, 5);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.add(4, 5);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   public void testAddInt() {
     assertEquals(0, list.size());
 
@@ -248,7 +251,7 @@ public class IntArrayListTest extends TestCase {
     list.addInt(3);
     assertEquals(asList(2, 3), list);
   }
-  
+
   public void testAddAll() {
     assertEquals(0, list.size());
 
@@ -256,17 +259,17 @@ public class IntArrayListTest extends TestCase {
     assertEquals(1, list.size());
     assertEquals(1, (int) list.get(0));
     assertEquals(1, list.getInt(0));
-    
+
     assertTrue(list.addAll(asList(2, 3, 4, 5, 6)));
     assertEquals(asList(1, 2, 3, 4, 5, 6), list);
-    
+
     assertTrue(list.addAll(TERTIARY_LIST));
     assertEquals(asList(1, 2, 3, 4, 5, 6, 1, 2, 3), list);
 
     assertFalse(list.addAll(Collections.<Integer>emptyList()));
     assertFalse(list.addAll(IntArrayList.emptyList()));
   }
-  
+
   public void testRemove() {
     list.addAll(TERTIARY_LIST);
     assertEquals(1, (int) list.remove(0));
@@ -280,96 +283,96 @@ public class IntArrayListTest extends TestCase {
 
     assertEquals(2, (int) list.remove(0));
     assertEquals(asList(), list);
-    
+
     try {
       list.remove(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.remove(0);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   private void assertImmutable(IntArrayList list) {
     if (list.contains(1)) {
       throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
     }
-    
+
     try {
       list.add(1);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.add(0, 1);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.<Integer>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.singletonList(1));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(new IntArrayList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.singleton(1));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.<Integer>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addInt(0);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.clear();
       fail();
@@ -383,63 +386,63 @@ public class IntArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.remove(new Object());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.<Integer>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.singleton(1));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.<Integer>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.singleton(1));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.set(0, 0);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.setInt(0, 0);
       fail();
@@ -447,7 +450,7 @@ public class IntArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   private static IntArrayList newImmutableIntArrayList(int... elements) {
     IntArrayList list = new IntArrayList();
     for (int element : elements) {

+ 17 - 0
java/core/src/test/java/com/google/protobuf/LazyMessageLiteTest.java

@@ -251,6 +251,23 @@ public class LazyMessageLiteTest extends TestCase {
     assertEquals(42, merged.getOneofInner().getNumWithDefault());
   }
 
+  // Regression test for b/28198805.
+  public void testMergeOneofMessages() throws Exception {
+    LazyInnerMessageLite inner = LazyInnerMessageLite.newBuilder().build();
+    LazyMessageLite outer = LazyMessageLite.newBuilder().setOneofInner(inner).build();
+    ByteString data1 = outer.toByteString();
+
+    // The following should not alter the content of the 'outer' message.
+    LazyMessageLite.Builder merged = LazyMessageLite.newBuilder().mergeFrom(outer);
+    LazyInnerMessageLite anotherInner = LazyInnerMessageLite.newBuilder().setNum(12345).build();
+    merged.setOneofInner(anotherInner);
+
+    // Check that the 'outer' stays the same.
+    ByteString data2 = outer.toByteString();
+    assertEquals(data1, data2);
+    assertEquals(0, outer.getOneofInner().getNum());
+  }
+
   public void testSerialize() throws InvalidProtocolBufferException {
     LazyNestedInnerMessageLite nested = LazyNestedInnerMessageLite.newBuilder()
         .setNum(3)

+ 29 - 2
java/core/src/test/java/com/google/protobuf/LiteTest.java

@@ -1630,7 +1630,7 @@ public class LiteTest extends TestCase {
       fail();
     } catch (InvalidProtocolBufferException expected) {}
   }
-  
+
   public void testMergeFrom_sanity() throws Exception {
     TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build();
     byte[] bytes = one.toByteArray();
@@ -1642,7 +1642,19 @@ public class LiteTest extends TestCase {
     assertEquals(two, one);
     assertEquals(one.hashCode(), two.hashCode());
   }
-  
+
+  public void testMergeFromNoLazyFieldSharing() throws Exception {
+    TestAllTypesLite.Builder sourceBuilder = TestAllTypesLite.newBuilder().setOptionalLazyMessage(
+        TestAllTypesLite.NestedMessage.newBuilder().setBb(1));
+    TestAllTypesLite.Builder targetBuilder =
+        TestAllTypesLite.newBuilder().mergeFrom(sourceBuilder.build());
+    assertEquals(1, sourceBuilder.getOptionalLazyMessage().getBb());
+    // now change the sourceBuilder, and target value shouldn't be affected.
+    sourceBuilder.setOptionalLazyMessage(
+        TestAllTypesLite.NestedMessage.newBuilder().setBb(2));
+    assertEquals(1, targetBuilder.getOptionalLazyMessage().getBb());
+  }
+
   public void testEquals_notEqual() throws Exception {
     TestAllTypesLite one = TestUtilLite.getAllLiteSetBuilder().build();
     byte[] bytes = one.toByteArray();
@@ -2202,6 +2214,21 @@ public class LiteTest extends TestCase {
     assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndUnknownFields);
     assertEqualsAndHashCodeAreFalse(fooWithValueAndExtension, fooWithValueAndUnknownFields);
   }
+
+  public void testEqualsAndHashCodeWithExtensions() throws InvalidProtocolBufferException {
+    Foo fooWithOnlyValue = Foo.newBuilder()
+        .setValue(1)
+        .build();
+
+    Foo fooWithValueAndExtension = fooWithOnlyValue.toBuilder()
+        .setValue(1)
+        .setExtension(Bar.fooExt, Bar.newBuilder()
+            .setName("name")
+            .build())
+        .build();
+
+    assertEqualsAndHashCodeAreFalse(fooWithOnlyValue, fooWithValueAndExtension);
+  }
   
   // Test to ensure we avoid a class cast exception with oneofs.
   public void testEquals_oneOfMessages() {

+ 93 - 90
java/core/src/test/java/com/google/protobuf/LongArrayListTest.java

@@ -40,48 +40,49 @@ import java.util.Iterator;
 
 /**
  * Tests for {@link LongArrayList}.
- * 
+ *
  * @author dweis@google.com (Daniel Weis)
  */
 public class LongArrayListTest extends TestCase {
-  
-  private static final LongArrayList UNARY_LIST = newImmutableLongArrayList(1);
+
+  private static final LongArrayList UNARY_LIST =
+      newImmutableLongArrayList(1);
   private static final LongArrayList TERTIARY_LIST =
       newImmutableLongArrayList(1, 2, 3);
-  
+
   private LongArrayList list;
-  
+
   @Override
   protected void setUp() throws Exception {
     list = new LongArrayList();
   }
-  
+
   public void testEmptyListReturnsSameInstance() {
     assertSame(LongArrayList.emptyList(), LongArrayList.emptyList());
   }
-  
+
   public void testEmptyListIsImmutable() {
     assertImmutable(LongArrayList.emptyList());
   }
-  
+
   public void testMakeImmutable() {
-    list.addLong(2);
+    list.addLong(3);
     list.addLong(4);
-    list.addLong(6);
-    list.addLong(8);
+    list.addLong(5);
+    list.addLong(7);
     list.makeImmutable();
     assertImmutable(list);
   }
-  
+
   public void testModificationWithIteration() {
     list.addAll(asList(1L, 2L, 3L, 4L));
     Iterator<Long> iterator = list.iterator();
     assertEquals(4, list.size());
-    assertEquals(1, (long) list.get(0));
-    assertEquals(1, (long) iterator.next());
+    assertEquals(1L, (long) list.get(0));
+    assertEquals(1L, (long) iterator.next());
     list.set(0, 1L);
-    assertEquals(2, (long) iterator.next());
-    
+    assertEquals(2L, (long) iterator.next());
+
     list.remove(0);
     try {
       iterator.next();
@@ -89,7 +90,7 @@ public class LongArrayListTest extends TestCase {
     } catch (ConcurrentModificationException e) {
       // expected
     }
-    
+
     iterator = list.iterator();
     list.add(0, 0L);
     try {
@@ -99,19 +100,19 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGet() {
-    assertEquals(1, (long) TERTIARY_LIST.get(0));
-    assertEquals(2, (long) TERTIARY_LIST.get(1));
-    assertEquals(3, (long) TERTIARY_LIST.get(2));
-    
+    assertEquals(1L, (long) TERTIARY_LIST.get(0));
+    assertEquals(2L, (long) TERTIARY_LIST.get(1));
+    assertEquals(3L, (long) TERTIARY_LIST.get(2));
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -119,19 +120,19 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testGetLong() {
-    assertEquals(1, TERTIARY_LIST.getLong(0));
-    assertEquals(2, TERTIARY_LIST.getLong(1));
-    assertEquals(3, TERTIARY_LIST.getLong(2));
-    
+    assertEquals(1L, TERTIARY_LIST.getLong(0));
+    assertEquals(2L, TERTIARY_LIST.getLong(1));
+    assertEquals(3L, TERTIARY_LIST.getLong(2));
+
     try {
       TERTIARY_LIST.get(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       TERTIARY_LIST.get(3);
       fail();
@@ -139,35 +140,35 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSize() {
     assertEquals(0, LongArrayList.emptyList().size());
     assertEquals(1, UNARY_LIST.size());
     assertEquals(3, TERTIARY_LIST.size());
 
-    list.addLong(2);
+    list.addLong(3);
     list.addLong(4);
     list.addLong(6);
     list.addLong(8);
     assertEquals(4, list.size());
-    
+
     list.remove(0);
     assertEquals(3, list.size());
-    
-    list.add(16L);
+
+    list.add(17L);
     assertEquals(4, list.size());
   }
-  
+
   public void testSet() {
     list.addLong(2);
     list.addLong(4);
-    
-    assertEquals(2, (long) list.set(0, 0L));
-    assertEquals(0, list.getLong(0));
 
-    assertEquals(4, (long) list.set(1, 0L));
-    assertEquals(0, list.getLong(1));
-    
+    assertEquals(2L, (long) list.set(0, 3L));
+    assertEquals(3L, list.getLong(0));
+
+    assertEquals(4L, (long) list.set(1, 0L));
+    assertEquals(0L, list.getLong(1));
+
     try {
       list.set(-1, 0L);
       fail();
@@ -182,17 +183,17 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testSetLong() {
-    list.addLong(2);
-    list.addLong(4);
-    
-    assertEquals(2, list.setLong(0, 0));
-    assertEquals(0, list.getLong(0));
+    list.addLong(1);
+    list.addLong(3);
+
+    assertEquals(1L, list.setLong(0, 0));
+    assertEquals(0L, list.getLong(0));
+
+    assertEquals(3L, list.setLong(1, 0));
+    assertEquals(0L, list.getLong(1));
 
-    assertEquals(4, list.setLong(1, 0));
-    assertEquals(0, list.getLong(1));
-    
     try {
       list.setLong(-1, 0);
       fail();
@@ -207,7 +208,7 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   public void testAdd() {
     assertEquals(0, list.size());
 
@@ -217,28 +218,30 @@ public class LongArrayListTest extends TestCase {
     assertTrue(list.add(3L));
     list.add(0, 4L);
     assertEquals(asList(4L, 2L, 3L), list);
-    
+
     list.add(0, 1L);
     list.add(0, 0L);
     // Force a resize by getting up to 11 elements.
     for (int i = 0; i < 6; i++) {
       list.add(Long.valueOf(5 + i));
     }
-    assertEquals(asList(0L, 1L, 4L, 2L, 3L, 5L, 6L, 7L, 8L, 9L, 10L), list);
-    
+    assertEquals(
+        asList(0L, 1L, 4L, 2L, 3L, 5L, 6L, 7L, 8L, 9L, 10L),
+        list);
+
     try {
       list.add(-1, 5L);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.add(4, 5L);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   public void testAddLong() {
     assertEquals(0, list.size());
 
@@ -248,128 +251,128 @@ public class LongArrayListTest extends TestCase {
     list.addLong(3);
     assertEquals(asList(2L, 3L), list);
   }
-  
+
   public void testAddAll() {
     assertEquals(0, list.size());
 
     assertTrue(list.addAll(Collections.singleton(1L)));
     assertEquals(1, list.size());
-    assertEquals(1, (long) list.get(0));
-    assertEquals(1, list.getLong(0));
-    
+    assertEquals(1L, (long) list.get(0));
+    assertEquals(1L, list.getLong(0));
+
     assertTrue(list.addAll(asList(2L, 3L, 4L, 5L, 6L)));
     assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L), list);
-    
+
     assertTrue(list.addAll(TERTIARY_LIST));
     assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L, 1L, 2L, 3L), list);
 
     assertFalse(list.addAll(Collections.<Long>emptyList()));
     assertFalse(list.addAll(LongArrayList.emptyList()));
   }
-  
+
   public void testRemove() {
     list.addAll(TERTIARY_LIST);
-    assertEquals(1, (long) list.remove(0));
+    assertEquals(1L, (long) list.remove(0));
     assertEquals(asList(2L, 3L), list);
 
-    assertTrue(list.remove(3L));
+    assertTrue(list.remove(Long.valueOf(3)));
     assertEquals(asList(2L), list);
 
-    assertFalse(list.remove(3L));
+    assertFalse(list.remove(Long.valueOf(3)));
     assertEquals(asList(2L), list);
 
-    assertEquals(2, (long) list.remove(0));
+    assertEquals(2L, (long) list.remove(0));
     assertEquals(asList(), list);
-    
+
     try {
       list.remove(-1);
       fail();
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
-    
+
     try {
       list.remove(0);
     } catch (IndexOutOfBoundsException e) {
       // expected
     }
   }
-  
+
   private void assertImmutable(LongArrayList list) {
     if (list.contains(1L)) {
       throw new RuntimeException("Cannot test the immutability of lists that contain 1.");
     }
-    
+
     try {
       list.add(1L);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.add(0, 1L);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.<Long>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(Collections.singletonList(1L));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(new LongArrayList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.singleton(1L));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addAll(0, Collections.<Long>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.addLong(0);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.clear();
       fail();
@@ -383,63 +386,63 @@ public class LongArrayListTest extends TestCase {
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.remove(new Object());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.<Long>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(Collections.singleton(1L));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.removeAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.<Long>emptyList());
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(Collections.singleton(1L));
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.retainAll(UNARY_LIST);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.set(0, 0L);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
-    
+
     try {
       list.setLong(0, 0);
       fail();
@@ -447,7 +450,7 @@ public class LongArrayListTest extends TestCase {
       // expected
     }
   }
-  
+
   private static LongArrayList newImmutableLongArrayList(long... elements) {
     LongArrayList list = new LongArrayList();
     for (long element : elements) {

+ 529 - 188
java/core/src/test/java/com/google/protobuf/MapForProto2LiteTest.java

@@ -30,12 +30,16 @@
 
 package com.google.protobuf;
 
+import map_lite_test.MapForProto2TestProto.BizarroTestMap;
 import map_lite_test.MapForProto2TestProto.TestMap;
 import map_lite_test.MapForProto2TestProto.TestMap.MessageValue;
+import map_lite_test.MapForProto2TestProto.TestMapOrBuilder;
 import map_lite_test.MapForProto2TestProto.TestUnknownEnumValue;
 
 import junit.framework.TestCase;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -44,34 +48,40 @@ import java.util.Map;
 /**
  * Unit tests for map fields.
  */
-public class MapForProto2LiteTest extends TestCase {
+public final class MapForProto2LiteTest extends TestCase {
+
   private void setMapValues(TestMap.Builder builder) {
-    builder.getMutableInt32ToInt32Field().put(1, 11);
-    builder.getMutableInt32ToInt32Field().put(2, 22);
-    builder.getMutableInt32ToInt32Field().put(3, 33);
-
-    builder.getMutableInt32ToStringField().put(1, "11");
-    builder.getMutableInt32ToStringField().put(2, "22");
-    builder.getMutableInt32ToStringField().put(3, "33");
-    
-    builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11"));
-    builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22"));
-    builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33"));
-    
-    builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO);
-    builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR);
-    builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ);
-    
-    builder.getMutableInt32ToMessageField().put(
-        1, MessageValue.newBuilder().setValue(11).build());
-    builder.getMutableInt32ToMessageField().put(
-        2, MessageValue.newBuilder().setValue(22).build());
-    builder.getMutableInt32ToMessageField().put(
-        3, MessageValue.newBuilder().setValue(33).build());
-    
-    builder.getMutableStringToInt32Field().put("1", 11);
-    builder.getMutableStringToInt32Field().put("2", 22);
-    builder.getMutableStringToInt32Field().put("3", 33);
+    builder
+        .putInt32ToInt32Field(1, 11)
+        .putInt32ToInt32Field(2, 22)
+        .putInt32ToInt32Field(3, 33)
+
+        .putInt32ToStringField(1, "11")
+        .putInt32ToStringField(2, "22")
+        .putInt32ToStringField(3, "33")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("11"))
+        .putInt32ToBytesField(2, TestUtil.toBytes("22"))
+        .putInt32ToBytesField(3, TestUtil.toBytes("33"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.FOO)
+        .putInt32ToEnumField(2, TestMap.EnumValue.BAR)
+        .putInt32ToEnumField(3, TestMap.EnumValue.BAZ)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build())
+        .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build())
+        .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build())
+
+        .putStringToInt32Field("1", 11)
+        .putStringToInt32Field("2", 22)
+        .putStringToInt32Field("3", 33);
+  }
+
+  public void testSetMapValues() {
+    TestMap.Builder mapBuilder = TestMap.newBuilder();
+    setMapValues(mapBuilder);
+    TestMap map = mapBuilder.build();
+    assertMapValuesSet(map);
   }
 
   private void copyMapValues(TestMap source, TestMap.Builder destination) {
@@ -94,22 +104,22 @@ public class MapForProto2LiteTest extends TestCase {
     assertEquals("11", message.getInt32ToStringField().get(1));
     assertEquals("22", message.getInt32ToStringField().get(2));
     assertEquals("33", message.getInt32ToStringField().get(3));
-    
+
     assertEquals(3, message.getInt32ToBytesField().size());
     assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1));
     assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2));
     assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
-    
+
     assertEquals(3, message.getInt32ToEnumField().size());
     assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1));
     assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2));
     assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
-    
+
     assertEquals(3, message.getInt32ToMessageField().size());
     assertEquals(11, message.getInt32ToMessageField().get(1).getValue());
     assertEquals(22, message.getInt32ToMessageField().get(2).getValue());
     assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
-    
+
     assertEquals(3, message.getStringToInt32Field().size());
     assertEquals(11, message.getStringToInt32Field().get("1").intValue());
     assertEquals(22, message.getStringToInt32Field().get("2").intValue());
@@ -117,31 +127,42 @@ public class MapForProto2LiteTest extends TestCase {
   }
 
   private void updateMapValues(TestMap.Builder builder) {
-    builder.getMutableInt32ToInt32Field().put(1, 111);
-    builder.getMutableInt32ToInt32Field().remove(2);
-    builder.getMutableInt32ToInt32Field().put(4, 44);
-
-    builder.getMutableInt32ToStringField().put(1, "111");
-    builder.getMutableInt32ToStringField().remove(2);
-    builder.getMutableInt32ToStringField().put(4, "44");
-    
-    builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111"));
-    builder.getMutableInt32ToBytesField().remove(2);
-    builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44"));
-    
-    builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR);
-    builder.getMutableInt32ToEnumField().remove(2);
-    builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX);
-    
-    builder.getMutableInt32ToMessageField().put(
-        1, MessageValue.newBuilder().setValue(111).build());
-    builder.getMutableInt32ToMessageField().remove(2);
-    builder.getMutableInt32ToMessageField().put(
-        4, MessageValue.newBuilder().setValue(44).build());
-    
-    builder.getMutableStringToInt32Field().put("1", 111);
-    builder.getMutableStringToInt32Field().remove("2");
-    builder.getMutableStringToInt32Field().put("4", 44);
+    builder
+        .putInt32ToInt32Field(1, 111)
+        .removeInt32ToInt32Field(2)
+        .putInt32ToInt32Field(4, 44)
+
+        .putInt32ToStringField(1, "111")
+        .removeInt32ToStringField(2)
+        .putInt32ToStringField(4, "44")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("111"))
+        .removeInt32ToBytesField(2)
+        .putInt32ToBytesField(4, TestUtil.toBytes("44"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.BAR)
+        .removeInt32ToEnumField(2)
+        .putInt32ToEnumField(4, TestMap.EnumValue.QUX)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build())
+        .removeInt32ToMessageField(2)
+        .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build())
+
+        .putStringToInt32Field("1", 111)
+        .removeStringToInt32Field("2")
+        .putStringToInt32Field("4", 44);
+  }
+
+  public void testUpdateMapValues() {
+    TestMap.Builder mapBuilder = TestMap.newBuilder();
+    setMapValues(mapBuilder);
+    TestMap map = mapBuilder.build();
+    assertMapValuesSet(map);
+
+    mapBuilder = map.toBuilder();
+    updateMapValues(mapBuilder);
+    map = mapBuilder.build();
+    assertMapValuesUpdated(map);
   }
 
   private void assertMapValuesUpdated(TestMap message) {
@@ -154,188 +175,149 @@ public class MapForProto2LiteTest extends TestCase {
     assertEquals("111", message.getInt32ToStringField().get(1));
     assertEquals("33", message.getInt32ToStringField().get(3));
     assertEquals("44", message.getInt32ToStringField().get(4));
-    
+
     assertEquals(3, message.getInt32ToBytesField().size());
     assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1));
     assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
     assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4));
-    
+
     assertEquals(3, message.getInt32ToEnumField().size());
     assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1));
     assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
     assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4));
-    
+
     assertEquals(3, message.getInt32ToMessageField().size());
     assertEquals(111, message.getInt32ToMessageField().get(1).getValue());
     assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
     assertEquals(44, message.getInt32ToMessageField().get(4).getValue());
-    
+
     assertEquals(3, message.getStringToInt32Field().size());
     assertEquals(111, message.getStringToInt32Field().get("1").intValue());
     assertEquals(33, message.getStringToInt32Field().get("3").intValue());
     assertEquals(44, message.getStringToInt32Field().get("4").intValue());
   }
 
-  private void assertMapValuesCleared(TestMap message) {
-    assertEquals(0, message.getInt32ToInt32Field().size());
-    assertEquals(0, message.getInt32ToStringField().size());
-    assertEquals(0, message.getInt32ToBytesField().size());
-    assertEquals(0, message.getInt32ToEnumField().size());
-    assertEquals(0, message.getInt32ToMessageField().size());
-    assertEquals(0, message.getStringToInt32Field().size());
+  private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(0, testMapOrBuilder.getStringToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount());
   }
 
   public void testSanityCopyOnWrite() throws InvalidProtocolBufferException {
     // Since builders are implemented as a thin wrapper around a message
     // instance, we attempt to verify that we can't cause the builder to modify
     // a produced message.
-    
+
     TestMap.Builder builder = TestMap.newBuilder();
     TestMap message = builder.build();
-    Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field();
-    intMap.put(1, 2);
+    builder.putInt32ToInt32Field(1, 2);
     assertTrue(message.getInt32ToInt32Field().isEmpty());
     message = builder.build();
-    try {
-      intMap.put(2, 3);
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
     assertEquals(newMap(1, 2), message.getInt32ToInt32Field());
     assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
-    builder.getMutableInt32ToInt32Field().put(2, 3);
+    builder.putInt32ToInt32Field(2, 3);
     assertEquals(newMap(1, 2), message.getInt32ToInt32Field());
     assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field());
   }
-  
-  public void testMutableMapLifecycle() {
+
+  public void testGetMapIsImmutable() {
     TestMap.Builder builder = TestMap.newBuilder();
-    Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field();
-    intMap.put(1, 2);
-    assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+
+    setMapValues(builder);
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+  }
+
+  private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) {
+    assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2);
+    assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2");
+    assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2"));
+    assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO);
+    assertImmutable(
+        testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance());
+    assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2);
+  }
+
+  private <K, V> void assertImmutable(Map<K, V> map, K key, V value) {
     try {
-      intMap.put(2, 3);
+      map.put(key, value);
       fail();
     } catch (UnsupportedOperationException e) {
       // expected
     }
+    if (!map.isEmpty()) {
+      try {
+        map.entrySet().remove(map.entrySet().iterator().next());
+        fail();
+      } catch (UnsupportedOperationException e) {
+        // expected
+      }
+    }
+  }
+
+  public void testMutableMapLifecycle() {
+    TestMap.Builder builder = TestMap.newBuilder()
+        .putInt32ToInt32Field(1, 2);
+    assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
     assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
-    builder.getMutableInt32ToInt32Field().put(2, 3);
+    builder.putInt32ToInt32Field(2, 3);
     assertEquals(newMap(1, 2, 2, 3), builder.getInt32ToInt32Field());
 
-    Map<Integer, TestMap.EnumValue> enumMap = builder.getMutableInt32ToEnumField();
-    enumMap.put(1, TestMap.EnumValue.BAR);
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.BAR);
     assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.build().getInt32ToEnumField());
-    try {
-      enumMap.put(2, TestMap.EnumValue.FOO);
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
     assertEquals(newMap(1, TestMap.EnumValue.BAR), builder.getInt32ToEnumField());
-    builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.FOO);
+    builder.putInt32ToEnumField(2, TestMap.EnumValue.FOO);
     assertEquals(
         newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO),
         builder.getInt32ToEnumField());
-    
-    Map<Integer, String> stringMap = builder.getMutableInt32ToStringField();
-    stringMap.put(1, "1");
+
+    builder.putInt32ToStringField(1, "1");
     assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField());
-    try {
-      stringMap.put(2, "2");
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
     assertEquals(newMap(1, "1"), builder.getInt32ToStringField());
-    builder.getMutableInt32ToStringField().put(2, "2");
-    assertEquals(
-        newMap(1, "1", 2, "2"),
-        builder.getInt32ToStringField());
-    
-    Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField();
-    messageMap.put(1, TestMap.MessageValue.getDefaultInstance());
+    builder.putInt32ToStringField(2, "2");
+    assertEquals(newMap(1, "1", 2, "2"), builder.getInt32ToStringField());
+
+    builder.putInt32ToMessageField(1, TestMap.MessageValue.getDefaultInstance());
     assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()),
         builder.build().getInt32ToMessageField());
-    try {
-      messageMap.put(2, TestMap.MessageValue.getDefaultInstance());
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
     assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()),
         builder.getInt32ToMessageField());
-    builder.getMutableInt32ToMessageField().put(2, TestMap.MessageValue.getDefaultInstance());
+    builder.putInt32ToMessageField(2, TestMap.MessageValue.getDefaultInstance());
     assertEquals(
         newMap(1, TestMap.MessageValue.getDefaultInstance(),
             2, TestMap.MessageValue.getDefaultInstance()),
         builder.getInt32ToMessageField());
   }
 
-  public void testMutableMapLifecycle_collections() {
-    TestMap.Builder builder = TestMap.newBuilder();
-    Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field();
-    intMap.put(1, 2);
-    assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
-    try {
-      intMap.remove(2);
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    try {
-      intMap.entrySet().remove(new Object());
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    try {
-      intMap.entrySet().iterator().remove();
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    try {
-      intMap.keySet().remove(new Object());
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    try {
-      intMap.values().remove(new Object());
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    try {
-      intMap.values().iterator().remove();
-      fail();
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
-    assertEquals(newMap(1, 2), intMap);
-    assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
-    assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
-  }
-
   public void testGettersAndSetters() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     TestMap message = builder.build();
     assertMapValuesCleared(message);
-    
+
     builder = message.toBuilder();
     setMapValues(builder);
     message = builder.build();
     assertMapValuesSet(message);
-    
+
     builder = message.toBuilder();
     updateMapValues(builder);
     message = builder.build();
     assertMapValuesUpdated(message);
-    
+
     builder = message.toBuilder();
     builder.clear();
+    assertMapValuesCleared(builder);
     message = builder.build();
     assertMapValuesCleared(message);
   }
@@ -344,12 +326,52 @@ public class MapForProto2LiteTest extends TestCase {
     TestMap.Builder sourceBuilder = TestMap.newBuilder();
     setMapValues(sourceBuilder);
     TestMap source = sourceBuilder.build();
+    assertMapValuesSet(source);
 
     TestMap.Builder destination = TestMap.newBuilder();
     copyMapValues(source, destination);
     assertMapValuesSet(destination.build());
   }
 
+  public void testPutChecksNullKeysAndValues() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToMessageField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putStringToInt32Field(null, 1);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+  }
+
   public void testSerializeAndParse() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     setMapValues(builder);
@@ -357,14 +379,14 @@ public class MapForProto2LiteTest extends TestCase {
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesSet(message);
-    
+
     builder = message.toBuilder();
     updateMapValues(builder);
     message = builder.build();
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesUpdated(message);
-    
+
     builder = message.toBuilder();
     builder.clear();
     message = builder.build();
@@ -372,12 +394,61 @@ public class MapForProto2LiteTest extends TestCase {
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesCleared(message);
   }
-  
+
+  private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream);
+    bizarroMap.writeTo(output);
+    output.flush();
+    return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray()));
+  }
+
+  public void testParseError() throws Exception {
+    ByteString bytes = TestUtil.toBytes("SOME BYTES");
+    String stringKey = "a string key";
+
+    TestMap map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToInt32Field(5, bytes)
+        .build());
+    assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToStringField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToStringFieldOrDefault(0, null), "");
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToBytesField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToEnumField(stringKey, bytes)
+        .build());
+    assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO);
+
+    try {
+      tryParseTestMap(BizarroTestMap.newBuilder()
+          .putInt32ToMessageField(stringKey, bytes)
+          .build());
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertTrue(expected.getUnfinishedMessage() instanceof TestMap);
+      map = (TestMap) expected.getUnfinishedMessage();
+      assertTrue(map.getInt32ToMessageField().isEmpty());
+    }
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putStringToInt32Field(stringKey, bytes)
+        .build());
+    assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0);
+  }
+
   public void testMergeFrom() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     setMapValues(builder);
     TestMap message = builder.build();
-    
+
     TestMap.Builder other = TestMap.newBuilder();
     other.mergeFrom(message);
     assertMapValuesSet(other.build());
@@ -386,26 +457,26 @@ public class MapForProto2LiteTest extends TestCase {
   public void testEqualsAndHashCode() throws Exception {
     // Test that generated equals() and hashCode() will disregard the order
     // of map entries when comparing/hashing map fields.
-    
+
     // We can't control the order of elements in a HashMap. The best we can do
     // here is to add elements in different order.
-    TestMap.Builder b1 = TestMap.newBuilder();
-    b1.getMutableInt32ToInt32Field().put(1, 2);
-    b1.getMutableInt32ToInt32Field().put(3, 4);
-    b1.getMutableInt32ToInt32Field().put(5, 6);
+    TestMap.Builder b1 = TestMap.newBuilder()
+        .putInt32ToInt32Field(1, 2)
+        .putInt32ToInt32Field(3, 4)
+        .putInt32ToInt32Field(5, 6);
     TestMap m1 = b1.build();
-    
-    TestMap.Builder b2 = TestMap.newBuilder();
-    b2.getMutableInt32ToInt32Field().put(5, 6);
-    b2.getMutableInt32ToInt32Field().put(1, 2);
-    b2.getMutableInt32ToInt32Field().put(3, 4);
+
+    TestMap.Builder b2 = TestMap.newBuilder()
+        .putInt32ToInt32Field(5, 6)
+        .putInt32ToInt32Field(1, 2)
+        .putInt32ToInt32Field(3, 4);
     TestMap m2 = b2.build();
-    
+
     assertEquals(m1, m2);
     assertEquals(m1.hashCode(), m2.hashCode());
-    
+
     // Make sure we did compare map fields.
-    b2.getMutableInt32ToInt32Field().put(1, 0);
+    b2.putInt32ToInt32Field(1, 0);
     m2 = b2.build();
     assertFalse(m1.equals(m2));
     // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed
@@ -413,10 +484,9 @@ public class MapForProto2LiteTest extends TestCase {
   }
 
   public void testUnknownEnumValues() throws Exception {
-    TestUnknownEnumValue.Builder builder =
-        TestUnknownEnumValue.newBuilder();
-    builder.getMutableInt32ToInt32Field().put(1, 1);
-    builder.getMutableInt32ToInt32Field().put(2, 54321);
+    TestUnknownEnumValue.Builder builder = TestUnknownEnumValue.newBuilder()
+        .putInt32ToInt32Field(1, 1)
+        .putInt32ToInt32Field(2, 54321);
     ByteString data = builder.build().toByteString();
 
     TestMap message = TestMap.parseFrom(data);
@@ -442,17 +512,288 @@ public class MapForProto2LiteTest extends TestCase {
     assertEquals(Arrays.asList("1", "2", "3"),
         new ArrayList<String>(message.getStringToInt32Field().keySet()));
   }
-  
+
   private static <K, V> Map<K, V> newMap(K key1, V value1) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);
     return map;
   }
-  
+
   private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);
     map.put(key2, value2);
     return map;
   }
+
+  public void testGetMap() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    TestMap message = builder.build();
+    assertEquals(
+        message.getStringToInt32Field(),
+        message.getStringToInt32FieldMap());
+    assertEquals(
+        message.getInt32ToBytesField(),
+        message.getInt32ToBytesFieldMap());
+    assertEquals(
+        message.getInt32ToEnumField(),
+        message.getInt32ToEnumFieldMap());
+    assertEquals(
+        message.getInt32ToMessageField(),
+        message.getInt32ToMessageFieldMap());
+  }
+
+  public void testContains() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    assertMapContainsSetValues(builder);
+    assertMapContainsSetValues(builder.build());
+  }
+
+  private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) {
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3));
+    assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToStringField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1));
+
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("1"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("2"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("3"));
+    assertFalse(testMapOrBuilder.containsStringToInt32Field("-1"));
+  }
+
+  public void testCount() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+
+    setMapValues(builder);
+    assertMapCounts(3, builder);
+
+    TestMap message = builder.build();
+    assertMapCounts(3, message);
+
+    builder = message.toBuilder().putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+    assertEquals(4, builder.build().getInt32ToInt32FieldCount());
+
+    // already present - should be unchanged
+    builder.putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+  }
+
+  private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetOrDefault() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValues(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11));
+    assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11));
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11"));
+    assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null));
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null));
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null));
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null));
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11));
+    assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11));
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testGetOrThrow() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValues(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToStringFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1"));
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow("-1");
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testPut() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    builder.putInt32ToInt32Field(1, 11);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+
+    builder.putInt32ToStringField(1, "a");
+    assertEquals("a", builder.getInt32ToStringFieldOrThrow(1));
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToBytesField(1, TestUtil.toBytes("11"));
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO);
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putStringToInt32Field("a", 1);
+    assertEquals(1, builder.getStringToInt32FieldOrThrow("a"));
+    try {
+      builder.putStringToInt32Field(null, -1);
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testRemove() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValues(builder);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToInt32Field(1);
+      assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1));
+    }
+
+    assertEquals("11", builder.getInt32ToStringFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToStringField(1);
+      assertNull(builder.getInt32ToStringFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToBytesField(1);
+      assertNull(builder.getInt32ToBytesFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToEnumField(1);
+      assertNull(builder.getInt32ToEnumFieldOrDefault(1, null));
+    }
+
+    assertEquals(11, builder.getStringToInt32FieldOrThrow("1"));
+    for (int times = 0; times < 2; times++) {
+      builder.removeStringToInt32Field("1");
+      assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1));
+    }
+
+    try {
+      builder.removeStringToInt32Field(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
 }

+ 574 - 80
java/core/src/test/java/com/google/protobuf/MapForProto2Test.java

@@ -31,13 +31,17 @@
 package com.google.protobuf;
 
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import map_test.MapForProto2TestProto.BizarroTestMap;
 import map_test.MapForProto2TestProto.TestMap;
 import map_test.MapForProto2TestProto.TestMap.MessageValue;
 import map_test.MapForProto2TestProto.TestMap.MessageWithRequiredFields;
+import map_test.MapForProto2TestProto.TestMapOrBuilder;
 import map_test.MapForProto2TestProto.TestRecursiveMap;
 import map_test.MapForProto2TestProto.TestUnknownEnumValue;
 import junit.framework.TestCase;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -48,7 +52,8 @@ import java.util.Map;
  * Unit tests for map fields in proto2 protos.
  */
 public class MapForProto2Test extends TestCase {
-  private void setMapValues(TestMap.Builder builder) {
+
+  private void setMapValuesUsingMutableMap(TestMap.Builder builder) {
     builder.getMutableInt32ToInt32Field().put(1, 11);
     builder.getMutableInt32ToInt32Field().put(2, 22);
     builder.getMutableInt32ToInt32Field().put(3, 33);
@@ -56,27 +61,67 @@ public class MapForProto2Test extends TestCase {
     builder.getMutableInt32ToStringField().put(1, "11");
     builder.getMutableInt32ToStringField().put(2, "22");
     builder.getMutableInt32ToStringField().put(3, "33");
-    
+
     builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("11"));
     builder.getMutableInt32ToBytesField().put(2, TestUtil.toBytes("22"));
     builder.getMutableInt32ToBytesField().put(3, TestUtil.toBytes("33"));
-    
+
     builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.FOO);
     builder.getMutableInt32ToEnumField().put(2, TestMap.EnumValue.BAR);
     builder.getMutableInt32ToEnumField().put(3, TestMap.EnumValue.BAZ);
-    
+
     builder.getMutableInt32ToMessageField().put(
         1, MessageValue.newBuilder().setValue(11).build());
     builder.getMutableInt32ToMessageField().put(
         2, MessageValue.newBuilder().setValue(22).build());
     builder.getMutableInt32ToMessageField().put(
         3, MessageValue.newBuilder().setValue(33).build());
-    
+
     builder.getMutableStringToInt32Field().put("1", 11);
     builder.getMutableStringToInt32Field().put("2", 22);
     builder.getMutableStringToInt32Field().put("3", 33);
   }
 
+  private void setMapValuesUsingAccessors(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 11)
+        .putInt32ToInt32Field(2, 22)
+        .putInt32ToInt32Field(3, 33)
+
+        .putInt32ToStringField(1, "11")
+        .putInt32ToStringField(2, "22")
+        .putInt32ToStringField(3, "33")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("11"))
+        .putInt32ToBytesField(2, TestUtil.toBytes("22"))
+        .putInt32ToBytesField(3, TestUtil.toBytes("33"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.FOO)
+        .putInt32ToEnumField(2, TestMap.EnumValue.BAR)
+        .putInt32ToEnumField(3, TestMap.EnumValue.BAZ)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build())
+        .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build())
+        .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build())
+
+        .putStringToInt32Field("1", 11)
+        .putStringToInt32Field("2", 22)
+        .putStringToInt32Field("3", 33);
+  }
+
+  public void testSetMapValues() {
+    TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(usingMutableMapBuilder);
+    TestMap usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesSet(usingMutableMap);
+
+    TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder();
+    setMapValuesUsingAccessors(usingAccessorsBuilder);
+    TestMap usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesSet(usingAccessors);
+    assertEquals(usingAccessors, usingMutableMap);
+  }
+
   private void copyMapValues(TestMap source, TestMap.Builder destination) {
     destination
         .putAllInt32ToInt32Field(source.getInt32ToInt32Field())
@@ -87,7 +132,7 @@ public class MapForProto2Test extends TestCase {
         .putAllStringToInt32Field(source.getStringToInt32Field());
   }
 
-  private void assertMapValuesSet(TestMap message) {
+  private void assertMapValuesSet(TestMapOrBuilder message) {
     assertEquals(3, message.getInt32ToInt32Field().size());
     assertEquals(11, message.getInt32ToInt32Field().get(1).intValue());
     assertEquals(22, message.getInt32ToInt32Field().get(2).intValue());
@@ -97,29 +142,29 @@ public class MapForProto2Test extends TestCase {
     assertEquals("11", message.getInt32ToStringField().get(1));
     assertEquals("22", message.getInt32ToStringField().get(2));
     assertEquals("33", message.getInt32ToStringField().get(3));
-    
+
     assertEquals(3, message.getInt32ToBytesField().size());
     assertEquals(TestUtil.toBytes("11"), message.getInt32ToBytesField().get(1));
     assertEquals(TestUtil.toBytes("22"), message.getInt32ToBytesField().get(2));
     assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
-    
+
     assertEquals(3, message.getInt32ToEnumField().size());
     assertEquals(TestMap.EnumValue.FOO, message.getInt32ToEnumField().get(1));
     assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(2));
     assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
-    
+
     assertEquals(3, message.getInt32ToMessageField().size());
     assertEquals(11, message.getInt32ToMessageField().get(1).getValue());
     assertEquals(22, message.getInt32ToMessageField().get(2).getValue());
     assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
-    
+
     assertEquals(3, message.getStringToInt32Field().size());
     assertEquals(11, message.getStringToInt32Field().get("1").intValue());
     assertEquals(22, message.getStringToInt32Field().get("2").intValue());
     assertEquals(33, message.getStringToInt32Field().get("3").intValue());
   }
 
-  private void updateMapValues(TestMap.Builder builder) {
+  private void updateMapValuesUsingMutableMap(TestMap.Builder builder) {
     builder.getMutableInt32ToInt32Field().put(1, 111);
     builder.getMutableInt32ToInt32Field().remove(2);
     builder.getMutableInt32ToInt32Field().put(4, 44);
@@ -127,26 +172,78 @@ public class MapForProto2Test extends TestCase {
     builder.getMutableInt32ToStringField().put(1, "111");
     builder.getMutableInt32ToStringField().remove(2);
     builder.getMutableInt32ToStringField().put(4, "44");
-    
+
     builder.getMutableInt32ToBytesField().put(1, TestUtil.toBytes("111"));
     builder.getMutableInt32ToBytesField().remove(2);
     builder.getMutableInt32ToBytesField().put(4, TestUtil.toBytes("44"));
-    
+
     builder.getMutableInt32ToEnumField().put(1, TestMap.EnumValue.BAR);
     builder.getMutableInt32ToEnumField().remove(2);
     builder.getMutableInt32ToEnumField().put(4, TestMap.EnumValue.QUX);
-    
+
     builder.getMutableInt32ToMessageField().put(
         1, MessageValue.newBuilder().setValue(111).build());
     builder.getMutableInt32ToMessageField().remove(2);
     builder.getMutableInt32ToMessageField().put(
         4, MessageValue.newBuilder().setValue(44).build());
-    
+
     builder.getMutableStringToInt32Field().put("1", 111);
     builder.getMutableStringToInt32Field().remove("2");
     builder.getMutableStringToInt32Field().put("4", 44);
   }
 
+  private void updateMapValuesUsingAccessors(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 111)
+        .removeInt32ToInt32Field(2)
+        .putInt32ToInt32Field(4, 44)
+
+        .putInt32ToStringField(1, "111")
+        .removeInt32ToStringField(2)
+        .putInt32ToStringField(4, "44")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("111"))
+        .removeInt32ToBytesField(2)
+        .putInt32ToBytesField(4, TestUtil.toBytes("44"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.BAR)
+        .removeInt32ToEnumField(2)
+        .putInt32ToEnumField(4, TestMap.EnumValue.QUX)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build())
+        .removeInt32ToMessageField(2)
+        .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build())
+
+        .putStringToInt32Field("1", 111)
+        .removeStringToInt32Field("2")
+        .putStringToInt32Field("4", 44);
+  }
+
+  public void testUpdateMapValues() {
+    TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(usingMutableMapBuilder);
+    TestMap usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesSet(usingMutableMap);
+
+    TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder();
+    setMapValuesUsingAccessors(usingAccessorsBuilder);
+    TestMap usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesSet(usingAccessors);
+    assertEquals(usingAccessors, usingMutableMap);
+
+    usingMutableMapBuilder = usingMutableMap.toBuilder();
+    updateMapValuesUsingMutableMap(usingMutableMapBuilder);
+    usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesUpdated(usingMutableMap);
+
+    usingAccessorsBuilder = usingAccessors.toBuilder();
+    updateMapValuesUsingAccessors(usingAccessorsBuilder);
+    usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesUpdated(usingAccessors);
+
+    assertEquals(usingAccessors, usingMutableMap);
+  }
+
   private void assertMapValuesUpdated(TestMap message) {
     assertEquals(3, message.getInt32ToInt32Field().size());
     assertEquals(111, message.getInt32ToInt32Field().get(1).intValue());
@@ -157,37 +254,72 @@ public class MapForProto2Test extends TestCase {
     assertEquals("111", message.getInt32ToStringField().get(1));
     assertEquals("33", message.getInt32ToStringField().get(3));
     assertEquals("44", message.getInt32ToStringField().get(4));
-    
+
     assertEquals(3, message.getInt32ToBytesField().size());
     assertEquals(TestUtil.toBytes("111"), message.getInt32ToBytesField().get(1));
     assertEquals(TestUtil.toBytes("33"), message.getInt32ToBytesField().get(3));
     assertEquals(TestUtil.toBytes("44"), message.getInt32ToBytesField().get(4));
-    
+
     assertEquals(3, message.getInt32ToEnumField().size());
     assertEquals(TestMap.EnumValue.BAR, message.getInt32ToEnumField().get(1));
     assertEquals(TestMap.EnumValue.BAZ, message.getInt32ToEnumField().get(3));
     assertEquals(TestMap.EnumValue.QUX, message.getInt32ToEnumField().get(4));
-    
+
     assertEquals(3, message.getInt32ToMessageField().size());
     assertEquals(111, message.getInt32ToMessageField().get(1).getValue());
     assertEquals(33, message.getInt32ToMessageField().get(3).getValue());
     assertEquals(44, message.getInt32ToMessageField().get(4).getValue());
-    
+
     assertEquals(3, message.getStringToInt32Field().size());
     assertEquals(111, message.getStringToInt32Field().get("1").intValue());
     assertEquals(33, message.getStringToInt32Field().get("3").intValue());
     assertEquals(44, message.getStringToInt32Field().get("4").intValue());
   }
 
-  private void assertMapValuesCleared(TestMap message) {
-    assertEquals(0, message.getInt32ToInt32Field().size());
-    assertEquals(0, message.getInt32ToStringField().size());
-    assertEquals(0, message.getInt32ToBytesField().size());
-    assertEquals(0, message.getInt32ToEnumField().size());
-    assertEquals(0, message.getInt32ToMessageField().size());
-    assertEquals(0, message.getStringToInt32Field().size());
+  private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(0, testMapOrBuilder.getStringToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetMapIsImmutable() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+
+    setMapValuesUsingAccessors(builder);
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+  }
+
+  private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) {
+    assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2);
+    assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2");
+    assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2"));
+    assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO);
+    assertImmutable(
+        testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance());
+    assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2);
+  }
+
+  private <K, V> void assertImmutable(Map<K, V> map, K key, V value) {
+    try {
+      map.put(key, value);
+      fail();
+    } catch (UnsupportedOperationException e) {
+      // expected
+    }
   }
-  
+
   public void testMutableMapLifecycle() {
     TestMap.Builder builder = TestMap.newBuilder();
     Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field();
@@ -217,7 +349,7 @@ public class MapForProto2Test extends TestCase {
     assertEquals(
         newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO),
         builder.getInt32ToEnumField());
-    
+
     Map<Integer, String> stringMap = builder.getMutableInt32ToStringField();
     stringMap.put(1, "1");
     assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField());
@@ -232,7 +364,7 @@ public class MapForProto2Test extends TestCase {
     assertEquals(
         newMap(1, "1", 2, "2"),
         builder.getInt32ToStringField());
-    
+
     Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField();
     messageMap.put(1, TestMap.MessageValue.getDefaultInstance());
     assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()),
@@ -302,48 +434,91 @@ public class MapForProto2Test extends TestCase {
     TestMap.Builder builder = TestMap.newBuilder();
     TestMap message = builder.build();
     assertMapValuesCleared(message);
-    
+
     builder = message.toBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertMapValuesSet(message);
-    
+
     builder = message.toBuilder();
-    updateMapValues(builder);
+    updateMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertMapValuesUpdated(message);
-    
+
     builder = message.toBuilder();
     builder.clear();
+    assertMapValuesCleared(builder);
     message = builder.build();
     assertMapValuesCleared(message);
   }
 
   public void testPutAll() throws Exception {
     TestMap.Builder sourceBuilder = TestMap.newBuilder();
-    setMapValues(sourceBuilder);
+    setMapValuesUsingMutableMap(sourceBuilder);
     TestMap source = sourceBuilder.build();
+    assertMapValuesSet(source);
 
     TestMap.Builder destination = TestMap.newBuilder();
     copyMapValues(source, destination);
     assertMapValuesSet(destination.build());
+
+    assertEquals(3, destination.getInt32ToEnumFieldCount());
+  }
+
+  public void testPutChecksNullKeysAndValues() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToMessageField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putStringToInt32Field(null, 1);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
   }
 
   public void testSerializeAndParse() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesSet(message);
-    
+
     builder = message.toBuilder();
-    updateMapValues(builder);
+    updateMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesUpdated(message);
-    
+
     builder = message.toBuilder();
     builder.clear();
     message = builder.build();
@@ -351,12 +526,61 @@ public class MapForProto2Test extends TestCase {
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesCleared(message);
   }
-  
+
+  private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream);
+    bizarroMap.writeTo(output);
+    output.flush();
+    return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray()));
+  }
+
+  public void testParseError() throws Exception {
+    ByteString bytes = TestUtil.toBytes("SOME BYTES");
+    String stringKey = "a string key";
+
+    TestMap map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToInt32Field(5, bytes)
+        .build());
+    assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToStringField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToStringFieldOrDefault(0, null), "");
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToBytesField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToEnumField(stringKey, bytes)
+        .build());
+    assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO);
+
+    try {
+      tryParseTestMap(BizarroTestMap.newBuilder()
+          .putInt32ToMessageField(stringKey, bytes)
+          .build());
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertTrue(expected.getUnfinishedMessage() instanceof TestMap);
+      map = (TestMap) expected.getUnfinishedMessage();
+      assertTrue(map.getInt32ToMessageField().isEmpty());
+    }
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putStringToInt32Field(stringKey, bytes)
+        .build());
+    assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0);
+  }
+
   public void testMergeFrom() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
-    
+
     TestMap.Builder other = TestMap.newBuilder();
     other.mergeFrom(message);
     assertMapValuesSet(other.build());
@@ -365,7 +589,7 @@ public class MapForProto2Test extends TestCase {
   public void testEqualsAndHashCode() throws Exception {
     // Test that generated equals() and hashCode() will disregard the order
     // of map entries when comparing/hashing map fields.
-    
+
     // We can't control the order of elements in a HashMap. The best we can do
     // here is to add elements in different order.
     TestMap.Builder b1 = TestMap.newBuilder();
@@ -373,16 +597,16 @@ public class MapForProto2Test extends TestCase {
     b1.getMutableInt32ToInt32Field().put(3, 4);
     b1.getMutableInt32ToInt32Field().put(5, 6);
     TestMap m1 = b1.build();
-    
+
     TestMap.Builder b2 = TestMap.newBuilder();
     b2.getMutableInt32ToInt32Field().put(5, 6);
     b2.getMutableInt32ToInt32Field().put(1, 2);
     b2.getMutableInt32ToInt32Field().put(3, 4);
     TestMap m2 = b2.build();
-    
+
     assertEquals(m1, m2);
     assertEquals(m1.hashCode(), m2.hashCode());
-    
+
     // Make sure we did compare map fields.
     b2.getMutableInt32ToInt32Field().put(1, 0);
     m2 = b2.build();
@@ -390,26 +614,26 @@ public class MapForProto2Test extends TestCase {
     // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed
     // to be different.
   }
-  
-  
+
+
   // The following methods are used to test reflection API.
-  
+
   private static FieldDescriptor f(String name) {
     return TestMap.getDescriptor().findFieldByName(name);
   }
-  
+
   private static Object getFieldValue(Message mapEntry, String name) {
     FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name);
     return mapEntry.getField(field);
   }
-  
+
   private static Message.Builder setFieldValue(
       Message.Builder mapEntry, String name, Object value) {
     FieldDescriptor field = mapEntry.getDescriptorForType().findFieldByName(name);
     mapEntry.setField(field, value);
     return mapEntry;
   }
-  
+
   private static void assertHasMapValues(Message message, String name, Map<?, ?> values) {
     FieldDescriptor field = f(name);
     for (Object entry : (List<?>) message.getField(field)) {
@@ -428,7 +652,7 @@ public class MapForProto2Test extends TestCase {
       assertEquals(value, values.get(key));
     }
   }
-  
+
   private static <KeyType, ValueType>
   Message newMapEntry(Message.Builder builder, String name, KeyType key, ValueType value) {
     FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name);
@@ -439,7 +663,7 @@ public class MapForProto2Test extends TestCase {
     entryBuilder.setField(valueField, value);
     return entryBuilder.build();
   }
-  
+
   private static void setMapValues(Message.Builder builder, String name, Map<?, ?> values) {
     List<Message> entryList = new ArrayList<Message>();
     for (Map.Entry<?, ?> entry : values.entrySet()) {
@@ -448,9 +672,8 @@ public class MapForProto2Test extends TestCase {
     FieldDescriptor field = builder.getDescriptorForType().findFieldByName(name);
     builder.setField(field, entryList);
   }
-  
-  private static <KeyType, ValueType>
-  Map<KeyType, ValueType> mapForValues(
+
+  private static <KeyType, ValueType> Map<KeyType, ValueType> mapForValues(
       KeyType key1, ValueType value1, KeyType key2, ValueType value2) {
     Map<KeyType, ValueType> map = new HashMap<KeyType, ValueType>();
     map.put(key1, value1);
@@ -476,14 +699,14 @@ public class MapForProto2Test extends TestCase {
         mapForValues(
             11, MessageValue.newBuilder().setValue(22).build(),
             33, MessageValue.newBuilder().setValue(44).build()));
-    
+
     // Test clearField()
     builder.clearField(f("int32_to_int32_field"));
     builder.clearField(f("int32_to_message_field"));
     message = builder.build();
     assertEquals(0, message.getInt32ToInt32Field().size());
     assertEquals(0, message.getInt32ToMessageField().size());
-    
+
     // Test setField()
     setMapValues(builder, "int32_to_int32_field",
         mapForValues(11, 22, 33, 44));
@@ -496,7 +719,7 @@ public class MapForProto2Test extends TestCase {
     assertEquals(44, message.getInt32ToInt32Field().get(33).intValue());
     assertEquals(222, message.getInt32ToMessageField().get(111).getValue());
     assertEquals(444, message.getInt32ToMessageField().get(333).getValue());
-    
+
     // Test addRepeatedField
     builder.addRepeatedField(f("int32_to_int32_field"),
         newMapEntry(builder, "int32_to_int32_field", 55, 66));
@@ -516,7 +739,7 @@ public class MapForProto2Test extends TestCase {
     message = builder.build();
     assertEquals(55, message.getInt32ToInt32Field().get(55).intValue());
     assertEquals(555, message.getInt32ToMessageField().get(555).getValue());
-    
+
     // Test setRepeatedField
     for (int i = 0; i < builder.getRepeatedFieldCount(f("int32_to_int32_field")); i++) {
       Message mapEntry = (Message) builder.getRepeatedField(f("int32_to_int32_field"), i);
@@ -533,35 +756,35 @@ public class MapForProto2Test extends TestCase {
     assertEquals(33, message.getInt32ToInt32Field().get(44).intValue());
     assertEquals(55, message.getInt32ToInt32Field().get(55).intValue());
   }
-  
+
   public void testTextFormat() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
-    
+
     String textData = TextFormat.printToString(message);
-    
+
     builder = TestMap.newBuilder();
     TextFormat.merge(textData, builder);
     message = builder.build();
-    
+
     assertMapValuesSet(message);
   }
-  
+
   public void testDynamicMessage() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
-    
+
     Message dynamicDefaultInstance =
         DynamicMessage.getDefaultInstance(TestMap.getDescriptor());
     Message dynamicMessage = dynamicDefaultInstance
         .newBuilderForType().mergeFrom(message.toByteString()).build();
-    
+
     assertEquals(message, dynamicMessage);
     assertEquals(message.hashCode(), dynamicMessage.hashCode());
   }
-  
+
   public void testReflectionEqualsAndHashCode() throws Exception {
     // Test that generated equals() and hashCode() will disregard the order
     // of map entries when comparing/hashing map fields.
@@ -570,22 +793,22 @@ public class MapForProto2Test extends TestCase {
     Message dynamicDefaultInstance =
         DynamicMessage.getDefaultInstance(TestMap.getDescriptor());
     FieldDescriptor field = f("int32_to_int32_field");
-    
+
     Message.Builder b1 = dynamicDefaultInstance.newBuilderForType();
     b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 1, 2));
     b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 3, 4));
     b1.addRepeatedField(field, newMapEntry(b1, "int32_to_int32_field", 5, 6));
     Message m1 = b1.build();
-    
+
     Message.Builder b2 = dynamicDefaultInstance.newBuilderForType();
     b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 5, 6));
     b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 1, 2));
     b2.addRepeatedField(field, newMapEntry(b2, "int32_to_int32_field", 3, 4));
     Message m2 = b2.build();
-    
+
     assertEquals(m1, m2);
     assertEquals(m1.hashCode(), m2.hashCode());
-    
+
     // Make sure we did compare map fields.
     b2.setRepeatedField(field, 0, newMapEntry(b1, "int32_to_int32_field", 0, 0));
     m2 = b2.build();
@@ -593,7 +816,7 @@ public class MapForProto2Test extends TestCase {
     // Don't check m1.hashCode() != m2.hashCode() because it's not guaranteed
     // to be different.
   }
-  
+
   public void testUnknownEnumValues() throws Exception {
     TestUnknownEnumValue.Builder builder =
         TestUnknownEnumValue.newBuilder();
@@ -646,13 +869,266 @@ public class MapForProto2Test extends TestCase {
 
   public void testIterationOrder() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
 
     assertEquals(Arrays.asList("1", "2", "3"),
         new ArrayList<String>(message.getStringToInt32Field().keySet()));
   }
 
+  public void testContains() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(builder);
+    assertMapContainsSetValues(builder);
+    assertMapContainsSetValues(builder.build());
+  }
+
+  private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) {
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3));
+    assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToStringField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1));
+
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("1"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("2"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("3"));
+    assertFalse(testMapOrBuilder.containsStringToInt32Field("-1"));
+  }
+
+  public void testCount() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+
+    setMapValuesUsingMutableMap(builder);
+    assertMapCounts(3, builder);
+
+    TestMap message = builder.build();
+    assertMapCounts(3, message);
+
+    builder = message.toBuilder().putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+    assertEquals(4, builder.build().getInt32ToInt32FieldCount());
+
+    // already present - should be unchanged
+    builder.putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+  }
+
+  private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetOrDefault() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValuesUsingAccessors(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11));
+    assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11));
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11"));
+    assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null));
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null));
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null));
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null));
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11));
+    assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11));
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testGetOrThrow() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValuesUsingAccessors(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToStringFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1"));
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow("-1");
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testPut() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    builder.putInt32ToInt32Field(1, 11);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+
+    builder.putInt32ToStringField(1, "a");
+    assertEquals("a", builder.getInt32ToStringFieldOrThrow(1));
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToBytesField(1, TestUtil.toBytes("11"));
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO);
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putStringToInt32Field("a", 1);
+    assertEquals(1, builder.getStringToInt32FieldOrThrow("a"));
+    try {
+      builder.putStringToInt32Field(null, -1);
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testRemove() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(builder);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToInt32Field(1);
+      assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1));
+    }
+
+    assertEquals("11", builder.getInt32ToStringFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToStringField(1);
+      assertNull(builder.getInt32ToStringFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToBytesField(1);
+      assertNull(builder.getInt32ToBytesFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToEnumField(1);
+      assertNull(builder.getInt32ToEnumFieldOrDefault(1, null));
+    }
+
+    assertEquals(11, builder.getStringToInt32FieldOrThrow("1"));
+    for (int times = 0; times < 2; times++) {
+      builder.removeStringToInt32Field("1");
+      assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1));
+    }
+
+    try {
+      builder.removeStringToInt32Field(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
   // Regression test for b/20494788
   public void testMapInitializationOrder() throws Exception {
     assertEquals("RedactAllTypes", map_test.RedactAllTypes
@@ -666,18 +1142,36 @@ public class MapForProto2Test extends TestCase {
         message.getDescriptorForType().findFieldByName("map_field"), 0);
     assertEquals(2, mapEntry.getAllFields().size());
   }
-  
+
   private static <K, V> Map<K, V> newMap(K key1, V value1) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);
     return map;
   }
-  
+
   private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);
     map.put(key2, value2);
     return map;
   }
-}
 
+  public void testGetMap() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingAccessors(builder);
+    assertMapValuesSet(builder);
+    TestMap message = builder.build();
+    assertEquals(
+        message.getStringToInt32Field(),
+        message.getStringToInt32FieldMap());
+    assertEquals(
+        message.getInt32ToBytesField(),
+        message.getInt32ToBytesFieldMap());
+    assertEquals(
+        message.getInt32ToEnumField(),
+        message.getInt32ToEnumFieldMap());
+    assertEquals(
+        message.getInt32ToMessageField(),
+        message.getInt32ToMessageFieldMap());
+  }
+}

+ 564 - 24
java/core/src/test/java/com/google/protobuf/MapTest.java

@@ -30,15 +30,20 @@
 
 package com.google.protobuf;
 
+
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumDescriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import map_test.MapTestProto.BizarroTestMap;
 import map_test.MapTestProto.TestMap;
 import map_test.MapTestProto.TestMap.MessageValue;
+import map_test.MapTestProto.TestMapOrBuilder;
 import map_test.MapTestProto.TestOnChangeEventPropagation;
 import junit.framework.TestCase;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -49,7 +54,8 @@ import java.util.Map;
  * Unit tests for map fields.
  */
 public class MapTest extends TestCase {
-  private void setMapValues(TestMap.Builder builder) {
+
+  private void setMapValuesUsingMutableMap(TestMap.Builder builder) {
     builder.getMutableInt32ToInt32Field().put(1, 11);
     builder.getMutableInt32ToInt32Field().put(2, 22);
     builder.getMutableInt32ToInt32Field().put(3, 33);
@@ -78,6 +84,46 @@ public class MapTest extends TestCase {
     builder.getMutableStringToInt32Field().put("3", 33);
   }
 
+  private void setMapValuesUsingAccessors(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 11)
+        .putInt32ToInt32Field(2, 22)
+        .putInt32ToInt32Field(3, 33)
+
+        .putInt32ToStringField(1, "11")
+        .putInt32ToStringField(2, "22")
+        .putInt32ToStringField(3, "33")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("11"))
+        .putInt32ToBytesField(2, TestUtil.toBytes("22"))
+        .putInt32ToBytesField(3, TestUtil.toBytes("33"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.FOO)
+        .putInt32ToEnumField(2, TestMap.EnumValue.BAR)
+        .putInt32ToEnumField(3, TestMap.EnumValue.BAZ)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(11).build())
+        .putInt32ToMessageField(2, MessageValue.newBuilder().setValue(22).build())
+        .putInt32ToMessageField(3, MessageValue.newBuilder().setValue(33).build())
+
+        .putStringToInt32Field("1", 11)
+        .putStringToInt32Field("2", 22)
+        .putStringToInt32Field("3", 33);
+  }
+
+  public void testSetMapValues() {
+    TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(usingMutableMapBuilder);
+    TestMap usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesSet(usingMutableMap);
+
+    TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder();
+    setMapValuesUsingAccessors(usingAccessorsBuilder);
+    TestMap usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesSet(usingAccessors);
+    assertEquals(usingAccessors, usingMutableMap);
+  }
+
   private void copyMapValues(TestMap source, TestMap.Builder destination) {
     destination
         .putAllInt32ToInt32Field(source.getInt32ToInt32Field())
@@ -120,7 +166,7 @@ public class MapTest extends TestCase {
     assertEquals(33, message.getStringToInt32Field().get("3").intValue());
   }
 
-  private void updateMapValues(TestMap.Builder builder) {
+  private void updateMapValuesUsingMutableMap(TestMap.Builder builder) {
     builder.getMutableInt32ToInt32Field().put(1, 111);
     builder.getMutableInt32ToInt32Field().remove(2);
     builder.getMutableInt32ToInt32Field().put(4, 44);
@@ -148,6 +194,58 @@ public class MapTest extends TestCase {
     builder.getMutableStringToInt32Field().put("4", 44);
   }
 
+  private void updateMapValuesUsingAccessors(TestMap.Builder builder) {
+    builder
+        .putInt32ToInt32Field(1, 111)
+        .removeInt32ToInt32Field(2)
+        .putInt32ToInt32Field(4, 44)
+
+        .putInt32ToStringField(1, "111")
+        .removeInt32ToStringField(2)
+        .putInt32ToStringField(4, "44")
+
+        .putInt32ToBytesField(1, TestUtil.toBytes("111"))
+        .removeInt32ToBytesField(2)
+        .putInt32ToBytesField(4, TestUtil.toBytes("44"))
+
+        .putInt32ToEnumField(1, TestMap.EnumValue.BAR)
+        .removeInt32ToEnumField(2)
+        .putInt32ToEnumField(4, TestMap.EnumValue.QUX)
+
+        .putInt32ToMessageField(1, MessageValue.newBuilder().setValue(111).build())
+        .removeInt32ToMessageField(2)
+        .putInt32ToMessageField(4, MessageValue.newBuilder().setValue(44).build())
+
+        .putStringToInt32Field("1", 111)
+        .removeStringToInt32Field("2")
+        .putStringToInt32Field("4", 44);
+  }
+
+  public void testUpdateMapValues() {
+    TestMap.Builder usingMutableMapBuilder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(usingMutableMapBuilder);
+    TestMap usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesSet(usingMutableMap);
+
+    TestMap.Builder usingAccessorsBuilder = TestMap.newBuilder();
+    setMapValuesUsingAccessors(usingAccessorsBuilder);
+    TestMap usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesSet(usingAccessors);
+    assertEquals(usingAccessors, usingMutableMap);
+
+    usingMutableMapBuilder = usingMutableMap.toBuilder();
+    updateMapValuesUsingMutableMap(usingMutableMapBuilder);
+    usingMutableMap = usingMutableMapBuilder.build();
+    assertMapValuesUpdated(usingMutableMap);
+
+    usingAccessorsBuilder = usingAccessors.toBuilder();
+    updateMapValuesUsingAccessors(usingAccessorsBuilder);
+    usingAccessors = usingAccessorsBuilder.build();
+    assertMapValuesUpdated(usingAccessors);
+
+    assertEquals(usingAccessors, usingMutableMap);
+  }
+
   private void assertMapValuesUpdated(TestMap message) {
     assertEquals(3, message.getInt32ToInt32Field().size());
     assertEquals(111, message.getInt32ToInt32Field().get(1).intValue());
@@ -180,15 +278,50 @@ public class MapTest extends TestCase {
     assertEquals(44, message.getStringToInt32Field().get("4").intValue());
   }
 
-  private void assertMapValuesCleared(TestMap message) {
-    assertEquals(0, message.getInt32ToInt32Field().size());
-    assertEquals(0, message.getInt32ToStringField().size());
-    assertEquals(0, message.getInt32ToBytesField().size());
-    assertEquals(0, message.getInt32ToEnumField().size());
-    assertEquals(0, message.getInt32ToMessageField().size());
-    assertEquals(0, message.getStringToInt32Field().size());
+  private void assertMapValuesCleared(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageField().size());
+    assertEquals(0, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(0, testMapOrBuilder.getStringToInt32Field().size());
+    assertEquals(0, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetMapIsImmutable() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+
+    setMapValuesUsingAccessors(builder);
+    assertMapsAreImmutable(builder);
+    assertMapsAreImmutable(builder.build());
+  }
+
+  private void assertMapsAreImmutable(TestMapOrBuilder testMapOrBuilder) {
+    assertImmutable(testMapOrBuilder.getInt32ToInt32Field(), 1, 2);
+    assertImmutable(testMapOrBuilder.getInt32ToStringField(), 1, "2");
+    assertImmutable(testMapOrBuilder.getInt32ToBytesField(), 1, TestUtil.toBytes("2"));
+    assertImmutable(testMapOrBuilder.getInt32ToEnumField(), 1, TestMap.EnumValue.FOO);
+    assertImmutable(
+        testMapOrBuilder.getInt32ToMessageField(), 1, MessageValue.getDefaultInstance());
+    assertImmutable(testMapOrBuilder.getStringToInt32Field(), "1", 2);
   }
-  
+
+  private <K, V> void assertImmutable(Map<K, V> map, K key, V value) {
+    try {
+      map.put(key, value);
+      fail();
+    } catch (UnsupportedOperationException e) {
+      // expected
+    }
+  }
+
   public void testMutableMapLifecycle() {
     TestMap.Builder builder = TestMap.newBuilder();
     Map<Integer, Integer> intMap = builder.getMutableInt32ToInt32Field();
@@ -218,7 +351,7 @@ public class MapTest extends TestCase {
     assertEquals(
         newMap(1, TestMap.EnumValue.BAR, 2, TestMap.EnumValue.FOO),
         builder.getInt32ToEnumField());
-    
+
     Map<Integer, String> stringMap = builder.getMutableInt32ToStringField();
     stringMap.put(1, "1");
     assertEquals(newMap(1, "1"), builder.build().getInt32ToStringField());
@@ -233,7 +366,7 @@ public class MapTest extends TestCase {
     assertEquals(
         newMap(1, "1", 2, "2"),
         builder.getInt32ToStringField());
-    
+
     Map<Integer, TestMap.MessageValue> messageMap = builder.getMutableInt32ToMessageField();
     messageMap.put(1, TestMap.MessageValue.getDefaultInstance());
     assertEquals(newMap(1, TestMap.MessageValue.getDefaultInstance()),
@@ -298,32 +431,34 @@ public class MapTest extends TestCase {
     assertEquals(newMap(1, 2), builder.getInt32ToInt32Field());
     assertEquals(newMap(1, 2), builder.build().getInt32ToInt32Field());
   }
-  
+
   public void testGettersAndSetters() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     TestMap message = builder.build();
     assertMapValuesCleared(message);
 
     builder = message.toBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertMapValuesSet(message);
 
     builder = message.toBuilder();
-    updateMapValues(builder);
+    updateMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertMapValuesUpdated(message);
 
     builder = message.toBuilder();
     builder.clear();
+    assertMapValuesCleared(builder);
     message = builder.build();
     assertMapValuesCleared(message);
   }
 
   public void testPutAll() throws Exception {
     TestMap.Builder sourceBuilder = TestMap.newBuilder();
-    setMapValues(sourceBuilder);
+    setMapValuesUsingMutableMap(sourceBuilder);
     TestMap source = sourceBuilder.build();
+    assertMapValuesSet(source);
 
     TestMap.Builder destination = TestMap.newBuilder();
     copyMapValues(source, destination);
@@ -344,18 +479,76 @@ public class MapTest extends TestCase {
     assertEquals(0, destination.getInt32ToEnumFieldValue().get(0).intValue());
     assertEquals(1, destination.getInt32ToEnumFieldValue().get(1).intValue());
     assertEquals(1000, destination.getInt32ToEnumFieldValue().get(2).intValue());
+    assertEquals(3, destination.getInt32ToEnumFieldCount());
+  }
+
+  public void testPutForUnknownEnumValues() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder()
+        .putInt32ToEnumFieldValue(0, 0)
+        .putInt32ToEnumFieldValue(1, 1);
+
+    try {
+      builder.putInt32ToEnumFieldValue(2, 1000);  // unknown value.
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    TestMap message = builder.build();
+    assertEquals(0, message.getInt32ToEnumFieldValueOrThrow(0));
+    assertEquals(1, message.getInt32ToEnumFieldValueOrThrow(1));
+    assertEquals(2, message.getInt32ToEnumFieldCount());
+  }
+
+  public void testPutChecksNullKeysAndValues() throws Exception {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putInt32ToMessageField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
+
+    try {
+      builder.putStringToInt32Field(null, 1);
+      fail();
+    } catch (NullPointerException e) {
+      // expected.
+    }
   }
 
   public void testSerializeAndParse() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
     assertMapValuesSet(message);
 
     builder = message.toBuilder();
-    updateMapValues(builder);
+    updateMapValuesUsingMutableMap(builder);
     message = builder.build();
     assertEquals(message.getSerializedSize(), message.toByteString().size());
     message = TestMap.parser().parseFrom(message.toByteString());
@@ -369,9 +562,58 @@ public class MapTest extends TestCase {
     assertMapValuesCleared(message);
   }
 
+  private TestMap tryParseTestMap(BizarroTestMap bizarroMap) throws IOException {
+    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+    CodedOutputStream output = CodedOutputStream.newInstance(byteArrayOutputStream);
+    bizarroMap.writeTo(output);
+    output.flush();
+    return TestMap.parser().parseFrom(ByteString.copyFrom(byteArrayOutputStream.toByteArray()));
+  }
+
+  public void testParseError() throws Exception {
+    ByteString bytes = TestUtil.toBytes("SOME BYTES");
+    String stringKey = "a string key";
+
+    TestMap map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToInt32Field(5, bytes)
+        .build());
+    assertEquals(map.getInt32ToInt32FieldOrDefault(5, -1), 0);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToStringField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToStringFieldOrDefault(0, null), "");
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToBytesField(stringKey, 5)
+        .build());
+    assertEquals(map.getInt32ToBytesFieldOrDefault(0, null), ByteString.EMPTY);
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putInt32ToEnumField(stringKey, bytes)
+        .build());
+    assertEquals(map.getInt32ToEnumFieldOrDefault(0, null), TestMap.EnumValue.FOO);
+
+    try {
+      tryParseTestMap(BizarroTestMap.newBuilder()
+          .putInt32ToMessageField(stringKey, bytes)
+          .build());
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertTrue(expected.getUnfinishedMessage() instanceof TestMap);
+      map = (TestMap) expected.getUnfinishedMessage();
+      assertTrue(map.getInt32ToMessageField().isEmpty());
+    }
+
+    map = tryParseTestMap(BizarroTestMap.newBuilder()
+        .putStringToInt32Field(stringKey, bytes)
+        .build());
+    assertEquals(map.getStringToInt32FieldOrDefault(stringKey, -1), 0);
+  }
+
   public void testMergeFrom() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
 
     TestMap.Builder other = TestMap.newBuilder();
@@ -629,7 +871,7 @@ public class MapTest extends TestCase {
 
   public void testTextFormat() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
 
     String textData = TextFormat.printToString(message);
@@ -643,7 +885,7 @@ public class MapTest extends TestCase {
 
   public void testDynamicMessage() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
 
     Message dynamicDefaultInstance =
@@ -760,19 +1002,317 @@ public class MapTest extends TestCase {
 
   public void testIterationOrder() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
-    setMapValues(builder);
+    setMapValuesUsingMutableMap(builder);
     TestMap message = builder.build();
 
     assertEquals(Arrays.asList("1", "2", "3"),
         new ArrayList<String>(message.getStringToInt32Field().keySet()));
   }
-  
+
+  public void testGetMap() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(builder);
+    TestMap message = builder.build();
+    assertEquals(
+        message.getStringToInt32Field(),
+        message.getStringToInt32FieldMap());
+    assertEquals(
+        message.getInt32ToBytesField(),
+        message.getInt32ToBytesFieldMap());
+    assertEquals(
+        message.getInt32ToEnumField(),
+        message.getInt32ToEnumFieldMap());
+    assertEquals(
+        message.getInt32ToEnumFieldValue(),
+        message.getInt32ToEnumFieldValueMap());
+    assertEquals(
+        message.getInt32ToMessageField(),
+        message.getInt32ToMessageFieldMap());
+  }
+
+  public void testContains() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(builder);
+    assertMapContainsSetValues(builder);
+    assertMapContainsSetValues(builder.build());
+  }
+
+  private void assertMapContainsSetValues(TestMapOrBuilder testMapOrBuilder) {
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(1));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(2));
+    assertTrue(testMapOrBuilder.containsInt32ToInt32Field(3));
+    assertFalse(testMapOrBuilder.containsInt32ToInt32Field(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToStringField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToStringField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToBytesField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToBytesField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToEnumField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToEnumField(-1));
+
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(1));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(2));
+    assertTrue(testMapOrBuilder.containsInt32ToMessageField(3));
+    assertFalse(testMapOrBuilder.containsInt32ToMessageField(-1));
+
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("1"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("2"));
+    assertTrue(testMapOrBuilder.containsStringToInt32Field("3"));
+    assertFalse(testMapOrBuilder.containsStringToInt32Field("-1"));
+  }
+
+  public void testCount() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+
+    setMapValuesUsingMutableMap(builder);
+    assertMapCounts(3, builder);
+
+    TestMap message = builder.build();
+    assertMapCounts(3, message);
+
+    builder = message.toBuilder().putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+    assertEquals(4, builder.build().getInt32ToInt32FieldCount());
+
+    // already present - should be unchanged
+    builder.putInt32ToInt32Field(4, 44);
+    assertEquals(4, builder.getInt32ToInt32FieldCount());
+  }
+
+  private void assertMapCounts(int expectedCount, TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToInt32FieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToStringFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToBytesFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToEnumFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getInt32ToMessageFieldCount());
+    assertEquals(expectedCount, testMapOrBuilder.getStringToInt32FieldCount());
+  }
+
+  public void testGetOrDefault() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValuesUsingAccessors(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrDefault(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(1, -11));
+    assertEquals(-11, testMapOrBuilder.getInt32ToInt32FieldOrDefault(-1, -11));
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrDefault(1, "-11"));
+    assertNull("-11", testMapOrBuilder.getInt32ToStringFieldOrDefault(-1, null));
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToBytesFieldOrDefault(-1, null));
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToEnumFieldOrDefault(-1, null));
+
+    assertEquals(
+        TestMap.EnumValue.BAR.getNumber(),
+        (int) testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(2, -1));
+    assertEquals(-1, testMapOrBuilder.getInt32ToEnumFieldValueOrDefault(-1000, -1));
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrDefault(1, null));
+    assertNull(testMapOrBuilder.getInt32ToMessageFieldOrDefault(-1, null));
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrDefault("1", -11));
+    assertEquals(-11, testMapOrBuilder.getStringToInt32FieldOrDefault("-1", -11));
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrDefault(null, -11);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testGetOrThrow() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    assertMapCounts(0, builder);
+    setMapValuesUsingAccessors(builder);
+    doTestGetOrDefault(builder);
+    doTestGetOrDefault(builder.build());
+  }
+
+  public void doTestGetOrThrow(TestMapOrBuilder testMapOrBuilder) {
+    assertEquals(11, testMapOrBuilder.getInt32ToInt32FieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToInt32FieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals("11", testMapOrBuilder.getInt32ToStringFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToStringFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestUtil.toBytes("11"), testMapOrBuilder.getInt32ToBytesFieldOrThrow(1));
+
+    try {
+      testMapOrBuilder.getInt32ToBytesFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, testMapOrBuilder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(
+        TestMap.EnumValue.BAR.getNumber(), testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(2));
+    try {
+      testMapOrBuilder.getInt32ToEnumFieldValueOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(MessageValue.newBuilder().setValue(11).build(),
+        testMapOrBuilder.getInt32ToMessageFieldOrThrow(1));
+    try {
+      testMapOrBuilder.getInt32ToMessageFieldOrThrow(-1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    assertEquals(11, testMapOrBuilder.getStringToInt32FieldOrThrow("1"));
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow("-1");
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    try {
+      testMapOrBuilder.getStringToInt32FieldOrThrow(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testPut() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    builder.putInt32ToInt32Field(1, 11);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+
+    builder.putInt32ToStringField(1, "a");
+    assertEquals("a", builder.getInt32ToStringFieldOrThrow(1));
+    try {
+      builder.putInt32ToStringField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToBytesField(1, TestUtil.toBytes("11"));
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    try {
+      builder.putInt32ToBytesField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToEnumField(1, TestMap.EnumValue.FOO);
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    try {
+      builder.putInt32ToEnumField(1, null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+
+    builder.putInt32ToEnumFieldValue(1, TestMap.EnumValue.BAR.getNumber());
+    assertEquals(
+        TestMap.EnumValue.BAR.getNumber(), builder.getInt32ToEnumFieldValueOrThrow(1));
+    try {
+      builder.putInt32ToEnumFieldValue(1, -1);
+      fail();
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+
+    builder.putStringToInt32Field("a", 1);
+    assertEquals(1, builder.getStringToInt32FieldOrThrow("a"));
+    try {
+      builder.putStringToInt32Field(null, -1);
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
+  public void testRemove() {
+    TestMap.Builder builder = TestMap.newBuilder();
+    setMapValuesUsingMutableMap(builder);
+    assertEquals(11, builder.getInt32ToInt32FieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToInt32Field(1);
+      assertEquals(-1, builder.getInt32ToInt32FieldOrDefault(1, -1));
+    }
+
+    assertEquals("11", builder.getInt32ToStringFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToStringField(1);
+      assertNull(builder.getInt32ToStringFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestUtil.toBytes("11"), builder.getInt32ToBytesFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToBytesField(1);
+      assertNull(builder.getInt32ToBytesFieldOrDefault(1, null));
+    }
+
+    assertEquals(TestMap.EnumValue.FOO, builder.getInt32ToEnumFieldOrThrow(1));
+    for (int times = 0; times < 2; times++) {
+      builder.removeInt32ToEnumField(1);
+      assertNull(builder.getInt32ToEnumFieldOrDefault(1, null));
+    }
+
+    assertEquals(11, builder.getStringToInt32FieldOrThrow("1"));
+    for (int times = 0; times < 2; times++) {
+      builder.removeStringToInt32Field("1");
+      assertEquals(-1, builder.getStringToInt32FieldOrDefault("1", -1));
+    }
+
+    try {
+      builder.removeStringToInt32Field(null);
+      fail();
+    } catch (NullPointerException e) {
+      // expected
+    }
+  }
+
   private static <K, V> Map<K, V> newMap(K key1, V value1) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);
     return map;
   }
-  
+
   private static <K, V> Map<K, V> newMap(K key1, V value1, K key2, V value2) {
     Map<K, V> map = new HashMap<K, V>();
     map.put(key1, value1);

+ 190 - 0
java/core/src/test/java/com/google/protobuf/RepeatedFieldBuilderV3Test.java

@@ -0,0 +1,190 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+import protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
+
+import junit.framework.TestCase;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Tests for {@link RepeatedFieldBuilderV3}. This tests basic functionality.
+ * More extensive testing is provided via other tests that exercise the
+ * builder.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class RepeatedFieldBuilderV3Test extends TestCase {
+
+  public void testBasicUse() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent);
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+    assertEquals(0, builder.getMessage(0).getOptionalInt32());
+    assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+    List<TestAllTypes> list = builder.build();
+    assertEquals(2, list.size());
+    assertEquals(0, list.get(0).getOptionalInt32());
+    assertEquals(1, list.get(1).getOptionalInt32());
+    assertIsUnmodifiable(list);
+
+    // Make sure it doesn't change.
+    List<TestAllTypes> list2 = builder.build();
+    assertSame(list, list2);
+    assertEquals(0, mockParent.getInvalidationCount());
+  }
+
+  public void testGoingBackAndForth() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent);
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+    assertEquals(0, builder.getMessage(0).getOptionalInt32());
+    assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+    // Convert to list
+    List<TestAllTypes> list = builder.build();
+    assertEquals(2, list.size());
+    assertEquals(0, list.get(0).getOptionalInt32());
+    assertEquals(1, list.get(1).getOptionalInt32());
+    assertIsUnmodifiable(list);
+
+    // Update 0th item
+    assertEquals(0, mockParent.getInvalidationCount());
+    builder.getBuilder(0).setOptionalString("foo");
+    assertEquals(1, mockParent.getInvalidationCount());
+    list = builder.build();
+    assertEquals(2, list.size());
+    assertEquals(0, list.get(0).getOptionalInt32());
+      assertEquals("foo", list.get(0).getOptionalString());
+    assertEquals(1, list.get(1).getOptionalInt32());
+    assertIsUnmodifiable(list);
+    assertEquals(1, mockParent.getInvalidationCount());
+  }
+
+  public void testVariousMethods() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent);
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(2).build());
+    builder.addBuilder(0, TestAllTypes.getDefaultInstance())
+        .setOptionalInt32(0);
+    builder.addBuilder(TestAllTypes.getDefaultInstance()).setOptionalInt32(3);
+
+    assertEquals(0, builder.getMessage(0).getOptionalInt32());
+    assertEquals(1, builder.getMessage(1).getOptionalInt32());
+    assertEquals(2, builder.getMessage(2).getOptionalInt32());
+    assertEquals(3, builder.getMessage(3).getOptionalInt32());
+
+    assertEquals(0, mockParent.getInvalidationCount());
+    List<TestAllTypes> messages = builder.build();
+    assertEquals(4, messages.size());
+    assertSame(messages, builder.build()); // expect same list
+
+    // Remove a message.
+    builder.remove(2);
+    assertEquals(1, mockParent.getInvalidationCount());
+    assertEquals(3, builder.getCount());
+    assertEquals(0, builder.getMessage(0).getOptionalInt32());
+    assertEquals(1, builder.getMessage(1).getOptionalInt32());
+    assertEquals(3, builder.getMessage(2).getOptionalInt32());
+
+    // Remove a builder.
+    builder.remove(0);
+    assertEquals(1, mockParent.getInvalidationCount());
+    assertEquals(2, builder.getCount());
+    assertEquals(1, builder.getMessage(0).getOptionalInt32());
+    assertEquals(3, builder.getMessage(1).getOptionalInt32());
+
+    // Test clear.
+    builder.clear();
+    assertEquals(1, mockParent.getInvalidationCount());
+    assertEquals(0, builder.getCount());
+    assertTrue(builder.isEmpty());
+  }
+
+  public void testLists() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder = newRepeatedFieldBuilderV3(mockParent);
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(1).build());
+    builder.addMessage(0,
+        TestAllTypes.newBuilder().setOptionalInt32(0).build());
+    assertEquals(0, builder.getMessage(0).getOptionalInt32());
+    assertEquals(1, builder.getMessage(1).getOptionalInt32());
+
+    // Use list of builders.
+    List<TestAllTypes.Builder> builders = builder.getBuilderList();
+    assertEquals(0, builders.get(0).getOptionalInt32());
+    assertEquals(1, builders.get(1).getOptionalInt32());
+    builders.get(0).setOptionalInt32(10);
+    builders.get(1).setOptionalInt32(11);
+
+    // Use list of protos
+    List<TestAllTypes> protos = builder.getMessageList();
+    assertEquals(10, protos.get(0).getOptionalInt32());
+    assertEquals(11, protos.get(1).getOptionalInt32());
+
+    // Add an item to the builders and verify it's updated in both
+    builder.addMessage(TestAllTypes.newBuilder().setOptionalInt32(12).build());
+    assertEquals(3, builders.size());
+    assertEquals(3, protos.size());
+  }
+
+  private void assertIsUnmodifiable(List<?> list) {
+    if (list == Collections.emptyList()) {
+      // OKAY -- Need to check this b/c EmptyList allows you to call clear.
+    } else {
+      try {
+        list.clear();
+        fail("List wasn't immutable");
+      } catch (UnsupportedOperationException e) {
+        // good
+      }
+    }
+  }
+
+  private RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+      TestAllTypesOrBuilder>
+      newRepeatedFieldBuilderV3(GeneratedMessage.BuilderParent parent) {
+    return new RepeatedFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder>(Collections.<TestAllTypes>emptyList(), false,
+        parent, false);
+  }
+}

+ 155 - 0
java/core/src/test/java/com/google/protobuf/SingleFieldBuilderV3Test.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 protobuf_unittest.UnittestProto.TestAllTypes;
+import protobuf_unittest.UnittestProto.TestAllTypesOrBuilder;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link SingleFieldBuilderV3}. This tests basic functionality.
+ * More extensive testing is provided via other tests that exercise the
+ * builder.
+ *
+ * @author jonp@google.com (Jon Perlow)
+ */
+public class SingleFieldBuilderV3Test extends TestCase {
+
+  public void testBasicUseAndInvalidations() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder =
+        new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+            TestAllTypesOrBuilder>(
+            TestAllTypes.getDefaultInstance(),
+            mockParent,
+            false);
+    assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+    assertEquals(TestAllTypes.getDefaultInstance(),
+        builder.getBuilder().buildPartial());
+    assertEquals(0, mockParent.getInvalidationCount());
+
+    builder.getBuilder().setOptionalInt32(10);
+    assertEquals(0, mockParent.getInvalidationCount());
+    TestAllTypes message = builder.build();
+    assertEquals(10, message.getOptionalInt32());
+
+    // Test that we receive invalidations now that build has been called.
+    assertEquals(0, mockParent.getInvalidationCount());
+    builder.getBuilder().setOptionalInt32(20);
+    assertEquals(1, mockParent.getInvalidationCount());
+
+    // Test that we don't keep getting invalidations on every change
+    builder.getBuilder().setOptionalInt32(30);
+    assertEquals(1, mockParent.getInvalidationCount());
+
+  }
+
+  public void testSetMessage() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder =
+        new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+            TestAllTypesOrBuilder>(
+            TestAllTypes.getDefaultInstance(),
+            mockParent,
+            false);
+    builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+    assertEquals(0, builder.getMessage().getOptionalInt32());
+
+    // Update message using the builder
+    builder.getBuilder().setOptionalInt32(1);
+    assertEquals(0, mockParent.getInvalidationCount());
+    assertEquals(1, builder.getBuilder().getOptionalInt32());
+    assertEquals(1, builder.getMessage().getOptionalInt32());
+    builder.build();
+    builder.getBuilder().setOptionalInt32(2);
+    assertEquals(2, builder.getBuilder().getOptionalInt32());
+    assertEquals(2, builder.getMessage().getOptionalInt32());
+
+    // Make sure message stays cached
+    assertSame(builder.getMessage(), builder.getMessage());
+  }
+
+  public void testClear() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder =
+        new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+            TestAllTypesOrBuilder>(
+            TestAllTypes.getDefaultInstance(),
+            mockParent,
+            false);
+    builder.setMessage(TestAllTypes.newBuilder().setOptionalInt32(0).build());
+    assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+    builder.clear();
+    assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+
+    builder.getBuilder().setOptionalInt32(1);
+    assertNotSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+    builder.clear();
+    assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+  }
+
+  public void testMerge() {
+    TestUtil.MockBuilderParent mockParent = new TestUtil.MockBuilderParent();
+    SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+        TestAllTypesOrBuilder> builder =
+        new SingleFieldBuilderV3<TestAllTypes, TestAllTypes.Builder,
+            TestAllTypesOrBuilder>(
+            TestAllTypes.getDefaultInstance(),
+            mockParent,
+            false);
+
+    // Merge into default field.
+    builder.mergeFrom(TestAllTypes.getDefaultInstance());
+    assertSame(TestAllTypes.getDefaultInstance(), builder.getMessage());
+
+    // Merge into non-default field on existing builder.
+    builder.getBuilder().setOptionalInt32(2);
+    builder.mergeFrom(TestAllTypes.newBuilder()
+        .setOptionalDouble(4.0)
+        .buildPartial());
+    assertEquals(2, builder.getMessage().getOptionalInt32());
+    assertEquals(4.0, builder.getMessage().getOptionalDouble());
+
+    // Merge into non-default field on existing message
+    builder.setMessage(TestAllTypes.newBuilder()
+        .setOptionalInt32(10)
+        .buildPartial());
+    builder.mergeFrom(TestAllTypes.newBuilder()
+        .setOptionalDouble(5.0)
+        .buildPartial());
+    assertEquals(10, builder.getMessage().getOptionalInt32());
+    assertEquals(5.0, builder.getMessage().getOptionalDouble());
+  }
+}

+ 3 - 2
java/core/src/test/java/com/google/protobuf/TestUtil.java

@@ -3764,7 +3764,8 @@ public final class TestUtil {
 
   private static File getTestDataDir() {
     // Search each parent directory looking for "src/google/protobuf".
-    File ancestor = new File(".");
+    File ancestor = new File(System.getProperty("protobuf.dir", "."));
+    String initialPath = ancestor.getAbsolutePath();
     try {
       ancestor = ancestor.getCanonicalFile();
     } catch (IOException e) {
@@ -3781,7 +3782,7 @@ public final class TestUtil {
     throw new RuntimeException(
       "Could not find golden files.  This test must be run from within the " +
       "protobuf source package so that it can read test data files from the " +
-      "C++ source tree.");
+      "C++ source tree: " + initialPath);
   }
 
   /**

+ 4 - 3
java/core/src/test/java/com/google/protobuf/TextFormatTest.java

@@ -522,15 +522,16 @@ public class TextFormatTest extends TestCase {
       "optional_string: \"ueoauaoe\n" +
       "optional_int32: 123");
     assertParseError(
-      "1:2: Extension \"nosuchext\" not found in the ExtensionRegistry.",
+      "1:2: Input contains unknown fields and/or extensions:\n" +
+      "1:2:\tprotobuf_unittest.TestAllTypes.[nosuchext]",
       "[nosuchext]: 123");
     assertParseError(
       "1:20: Extension \"protobuf_unittest.optional_int32_extension\" does " +
         "not extend message type \"protobuf_unittest.TestAllTypes\".",
       "[protobuf_unittest.optional_int32_extension]: 123");
     assertParseError(
-      "1:1: Message type \"protobuf_unittest.TestAllTypes\" has no field " +
-        "named \"nosuchfield\".",
+      "1:1: Input contains unknown fields and/or extensions:\n" +
+      "1:1:\tprotobuf_unittest.TestAllTypes.nosuchfield",
       "nosuchfield: 123");
     assertParseError(
       "1:21: Expected \">\".",

+ 2 - 0
java/core/src/test/proto/com/google/protobuf/field_presence_test.proto

@@ -54,6 +54,7 @@ message TestAllTypes {
   NestedEnum optional_nested_enum = 4;
   NestedMessage optional_nested_message = 5;
   protobuf_unittest.TestRequired optional_proto2_message = 6;
+  NestedMessage optional_lazy_message = 7 [lazy=true];
 
   oneof oneof_field {
     int32 oneof_int32 = 11;
@@ -81,6 +82,7 @@ message TestOptionalFieldsOnly {
   TestAllTypes.NestedEnum optional_nested_enum = 4;
   TestAllTypes.NestedMessage optional_nested_message = 5;
   protobuf_unittest.TestRequired optional_proto2_message = 6;
+  TestAllTypes.NestedMessage optional_lazy_message = 7 [lazy=true];
 }
 
 message TestRepeatedFieldsOnly {

+ 11 - 0
java/core/src/test/proto/com/google/protobuf/map_for_proto2_lite_test.proto

@@ -70,6 +70,17 @@ message TestRecursiveMap {
   optional int32 value = 1;
   map<int32, TestRecursiveMap> recursive_map_field = 2;
 }
+
+
+// a decoy of TestMap for testing parsing errors
+message BizarroTestMap {
+  map<int32, bytes> int32_to_int32_field = 1; // same key type, different value
+  map<string, int32> int32_to_string_field = 2; // different key and value types
+  map<string, int32> int32_to_bytes_field = 3; // different key types, same value
+  map<string, bytes> int32_to_enum_field = 4; // different key and value types
+  map<string, bytes> int32_to_message_field = 5; // different key and value types
+  map<string, bytes> string_to_int32_field = 6;  // same key type, different value
+}
 package map_for_proto2_lite_test;
 option java_package = "map_lite_test";
 option optimize_for = LITE_RUNTIME;

+ 11 - 0
java/core/src/test/proto/com/google/protobuf/map_for_proto2_test.proto

@@ -72,3 +72,14 @@ message TestRecursiveMap {
   optional int32 value = 1;
   map<int32, TestRecursiveMap> recursive_map_field = 2;
 }
+
+
+// a decoy of TestMap for testing parsing errors
+message BizarroTestMap {
+  map<int32, bytes> int32_to_int32_field = 1; // same key type, different value
+  map<string, int32> int32_to_string_field = 2; // different key and value types
+  map<string, int32> int32_to_bytes_field = 3; // different key types, same value
+  map<string, bytes> int32_to_enum_field = 4; // different key and value types
+  map<string, bytes> int32_to_message_field = 5; // different key and value types
+  map<string, bytes> string_to_int32_field = 6;  // same key type, different value
+}

+ 11 - 1
java/core/src/test/proto/com/google/protobuf/map_test.proto

@@ -55,9 +55,19 @@ message TestMap {
   map<string, int32>       string_to_int32_field = 6;
 }
 
-// Used to test that a nested bulider containing map fields will properly
+// Used to test that a nested builder containing map fields will properly
 // propagate the onChange event and mark its parent dirty when a change
 // is made to a map field.
 message TestOnChangeEventPropagation {
   TestMap optional_message = 1;
 }
+
+// a decoy of TestMap for testing parsing errors
+message BizarroTestMap {
+  map<int32, bytes> int32_to_int32_field = 1; // same key type, different value
+  map<string, int32> int32_to_string_field = 2; // different key and value types
+  map<string, int32> int32_to_bytes_field = 3; // different key types, same value
+  map<string, bytes> int32_to_enum_field = 4; // different key and value types
+  map<string, bytes> int32_to_message_field = 5; // different key and value types
+  map<string, bytes> string_to_int32_field = 6;  // same key type, different value
+}

+ 256 - 0
java/util/src/main/java/com/google/protobuf/util/Durations.java

@@ -0,0 +1,256 @@
+// 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.util;
+
+import static com.google.protobuf.util.Timestamps.MICROS_PER_SECOND;
+import static com.google.protobuf.util.Timestamps.MILLIS_PER_SECOND;
+import static com.google.protobuf.util.Timestamps.NANOS_PER_MICROSECOND;
+import static com.google.protobuf.util.Timestamps.NANOS_PER_MILLISECOND;
+import static com.google.protobuf.util.Timestamps.NANOS_PER_SECOND;
+
+import com.google.protobuf.Duration;
+
+import java.text.ParseException;
+
+/**
+ * Utilities to help create/manipulate {@code protobuf/duration.proto}.
+ */
+public final class Durations {
+  static final long DURATION_SECONDS_MIN = -315576000000L;
+  static final long DURATION_SECONDS_MAX = 315576000000L;
+
+  // TODO(kak): Do we want to expose Duration constants for MAX/MIN?
+
+  private Durations() {}
+
+  /**
+   * Returns true if the given {@link Duration} is valid. The {@code seconds} value must be in the
+   * range [-315,576,000,000, +315,576,000,000]. The {@code nanos} value must be in the range
+   * [-999,999,999, +999,999,999].
+   *
+   * <p>Note: Durations less than one second are represented with a 0 {@code seconds} field and a
+   * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value
+   * for the {@code nanos} field must be of the same sign as the {@code seconds} field.
+   */
+  public static boolean isValid(Duration duration) {
+    return isValid(duration.getSeconds(), duration.getNanos());
+  }
+
+  /**
+   * Returns true if the given number of seconds and nanos is a valid {@link Duration}. The
+   * {@code seconds} value must be in the range [-315,576,000,000, +315,576,000,000]. The
+   * {@code nanos} value must be in the range [-999,999,999, +999,999,999].
+   *
+   * <p>Note: Durations less than one second are represented with a 0 {@code seconds} field and a
+   * positive or negative {@code nanos} field. For durations of one second or more, a non-zero value
+   * for the {@code nanos} field must be of the same sign as the {@code seconds} field.
+   */
+  public static boolean isValid(long seconds, long nanos) {
+    if (seconds < DURATION_SECONDS_MIN || seconds > DURATION_SECONDS_MAX) {
+      return false;
+    }
+    if (nanos < -999999999L || nanos >= NANOS_PER_SECOND) {
+      return false;
+    }
+    if (seconds < 0 || nanos < 0) {
+      if (seconds > 0 || nanos > 0) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not
+   * a valid {@link Duration}.
+   */
+  private static void checkValid(long seconds, int nanos) {
+    if (!isValid(seconds, nanos)) {
+      throw new IllegalArgumentException(String.format(
+          "Duration is not valid. See proto definition for valid values. "
+          + "Seconds (%s) must be in range [-315,576,000,000, +315,576,000,000]."
+          + "Nanos (%s) must be in range [-999,999,999, +999,999,999]. "
+          + "Nanos must have the same sign as seconds", seconds, nanos));
+    }
+  }
+
+  /**
+   * Convert Duration to string format. The string format will contains 3, 6,
+   * or 9 fractional digits depending on the precision required to represent
+   * the exact Duration value. For example: "1s", "1.010s", "1.000000100s",
+   * "-3.100s" The range that can be represented by Duration is from
+   * -315,576,000,000 to +315,576,000,000 inclusive (in seconds).
+   *
+   * @return The string representation of the given duration.
+   * @throws IllegalArgumentException if the given duration is not in the valid
+   *         range.
+   */
+  public static String toString(Duration duration) {
+    long seconds = duration.getSeconds();
+    int nanos = duration.getNanos();
+    checkValid(seconds, nanos);
+
+    StringBuilder result = new StringBuilder();
+    if (seconds < 0 || nanos < 0) {
+      result.append("-");
+      seconds = -seconds;
+      nanos = -nanos;
+    }
+    result.append(seconds);
+    if (nanos != 0) {
+      result.append(".");
+      result.append(Timestamps.formatNanos(nanos));
+    }
+    result.append("s");
+    return result.toString();
+  }
+
+  /**
+   * Parse from a string to produce a duration.
+   *
+   * @return A Duration parsed from the string.
+   * @throws ParseException if parsing fails.
+   */
+  public static Duration parse(String value) throws ParseException {
+    // Must ended with "s".
+    if (value.isEmpty() || value.charAt(value.length() - 1) != 's') {
+      throw new ParseException("Invalid duration string: " + value, 0);
+    }
+    boolean negative = false;
+    if (value.charAt(0) == '-') {
+      negative = true;
+      value = value.substring(1);
+    }
+    String secondValue = value.substring(0, value.length() - 1);
+    String nanoValue = "";
+    int pointPosition = secondValue.indexOf('.');
+    if (pointPosition != -1) {
+      nanoValue = secondValue.substring(pointPosition + 1);
+      secondValue = secondValue.substring(0, pointPosition);
+    }
+    long seconds = Long.parseLong(secondValue);
+    int nanos = nanoValue.isEmpty() ? 0 : Timestamps.parseNanos(nanoValue);
+    if (seconds < 0) {
+      throw new ParseException("Invalid duration string: " + value, 0);
+    }
+    if (negative) {
+      seconds = -seconds;
+      nanos = -nanos;
+    }
+    try {
+      return normalizedDuration(seconds, nanos);
+    } catch (IllegalArgumentException e) {
+      throw new ParseException("Duration value is out of range.", 0);
+    }
+  }
+
+  /**
+   * Create a Duration from the number of milliseconds.
+   */
+  public static Duration fromMillis(long milliseconds) {
+    return normalizedDuration(
+        milliseconds / MILLIS_PER_SECOND,
+        (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
+  }
+
+  /**
+   * Convert a Duration to the number of milliseconds.The result will be
+   * rounded towards 0 to the nearest millisecond. E.g., if the duration
+   * represents -1 nanosecond, it will be rounded to 0.
+   */
+  public static long toMillis(Duration duration) {
+    return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos() / NANOS_PER_MILLISECOND;
+  }
+
+  /**
+   * Create a Duration from the number of microseconds.
+   */
+  public static Duration fromMicros(long microseconds) {
+    return normalizedDuration(
+        microseconds / MICROS_PER_SECOND,
+        (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
+  }
+
+  /**
+   * Convert a Duration to the number of microseconds.The result will be
+   * rounded towards 0 to the nearest microseconds. E.g., if the duration
+   * represents -1 nanosecond, it will be rounded to 0.
+   */
+  public static long toMicros(Duration duration) {
+    return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos() / NANOS_PER_MICROSECOND;
+  }
+
+  /**
+   * Create a Duration from the number of nanoseconds.
+   */
+  public static Duration fromNanos(long nanoseconds) {
+    return normalizedDuration(
+        nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND));
+  }
+
+  /**
+   * Convert a Duration to the number of nanoseconds.
+   */
+  public static long toNanos(Duration duration) {
+    return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos();
+  }
+
+  /**
+   * Add two durations.
+   */
+  public static Duration add(Duration d1, Duration d2) {
+    return normalizedDuration(d1.getSeconds() + d2.getSeconds(), d1.getNanos() + d2.getNanos());
+  }
+
+  /**
+   * Subtract a duration from another.
+   */
+  public static Duration subtract(Duration d1, Duration d2) {
+    return normalizedDuration(d1.getSeconds() - d2.getSeconds(), d1.getNanos() - d2.getNanos());
+  }
+
+  static Duration normalizedDuration(long seconds, int nanos) {
+    if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
+      seconds += nanos / NANOS_PER_SECOND;
+      nanos %= NANOS_PER_SECOND;
+    }
+    if (seconds > 0 && nanos < 0) {
+      nanos += NANOS_PER_SECOND;
+      seconds -= 1;
+    }
+    if (seconds < 0 && nanos > 0) {
+      nanos -= NANOS_PER_SECOND;
+      seconds += 1;
+    }
+    checkValid(seconds, nanos);
+    return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+  }
+}

+ 27 - 21
java/util/src/main/java/com/google/protobuf/util/FieldMaskTree.java

@@ -38,6 +38,7 @@ import com.google.protobuf.Message;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map.Entry;
+import java.util.SortedMap;
 import java.util.TreeMap;
 import java.util.logging.Logger;
 
@@ -59,22 +60,26 @@ import java.util.logging.Logger;
  * intersection to two FieldMasks and traverse all fields specified by the
  * FieldMask in a message tree.
  */
-class FieldMaskTree {
+final class FieldMaskTree {
   private static final Logger logger = Logger.getLogger(FieldMaskTree.class.getName());
 
   private static final String FIELD_PATH_SEPARATOR_REGEX = "\\.";
 
-  private static class Node {
-    public TreeMap<String, Node> children = new TreeMap<String, Node>();
+  private static final class Node {
+    final SortedMap<String, Node> children = new TreeMap<String, Node>();
   }
 
   private final Node root = new Node();
 
-  /** Creates an empty FieldMaskTree. */
-  public FieldMaskTree() {}
+  /**
+   * Creates an empty FieldMaskTree.
+   */
+  FieldMaskTree() {}
 
-  /** Creates a FieldMaskTree for a given FieldMask. */
-  public FieldMaskTree(FieldMask mask) {
+  /**
+   * Creates a FieldMaskTree for a given FieldMask.
+   */
+  FieldMaskTree(FieldMask mask) {
     mergeFromFieldMask(mask);
   }
 
@@ -93,7 +98,7 @@ class FieldMaskTree {
    * Likewise, if the field path to add is a sub-path of an existing leaf node,
    * nothing will be changed in the tree.
    */
-  public FieldMaskTree addFieldPath(String path) {
+  FieldMaskTree addFieldPath(String path) {
     String[] parts = path.split(FIELD_PATH_SEPARATOR_REGEX);
     if (parts.length == 0) {
       return this;
@@ -124,15 +129,17 @@ class FieldMaskTree {
   /**
    * Merges all field paths in a FieldMask into this tree.
    */
-  public FieldMaskTree mergeFromFieldMask(FieldMask mask) {
+  FieldMaskTree mergeFromFieldMask(FieldMask mask) {
     for (String path : mask.getPathsList()) {
       addFieldPath(path);
     }
     return this;
   }
 
-  /** Converts this tree to a FieldMask. */
-  public FieldMask toFieldMask() {
+  /**
+   * Converts this tree to a FieldMask.
+   */
+  FieldMask toFieldMask() {
     if (root.children.isEmpty()) {
       return FieldMask.getDefaultInstance();
     }
@@ -141,7 +148,9 @@ class FieldMaskTree {
     return FieldMask.newBuilder().addAllPaths(paths).build();
   }
 
-  /** Gathers all field paths in a sub-tree. */
+  /**
+   * Gathers all field paths in a sub-tree.
+   */
   private void getFieldPaths(Node node, String path, List<String> paths) {
     if (node.children.isEmpty()) {
       paths.add(path);
@@ -154,10 +163,9 @@ class FieldMaskTree {
   }
 
   /**
-   * Adds the intersection of this tree with the given {@code path} to
-   * {@code output}.
+   * Adds the intersection of this tree with the given {@code path} to {@code output}.
    */
-  public void intersectFieldPath(String path, FieldMaskTree output) {
+  void intersectFieldPath(String path, FieldMaskTree output) {
     if (root.children.isEmpty()) {
       return;
     }
@@ -188,11 +196,9 @@ class FieldMaskTree {
   }
 
   /**
-   * Merges all fields specified by this FieldMaskTree from {@code source} to
-   * {@code destination}.
+   * Merges all fields specified by this FieldMaskTree from {@code source} to {@code destination}.
    */
-  public void merge(
-      Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) {
+  void merge(Message source, Message.Builder destination, FieldMaskUtil.MergeOptions options) {
     if (source.getDescriptorForType() != destination.getDescriptorForType()) {
       throw new IllegalArgumentException("Cannot merge messages of different types.");
     }
@@ -202,8 +208,8 @@ class FieldMaskTree {
     merge(root, "", source, destination, options);
   }
 
-  /** Merges all fields specified by a sub-tree from {@code source} to
-   * {@code destination}.
+  /**
+   * Merges all fields specified by a sub-tree from {@code source} to {@code destination}.
    */
   private void merge(
       Node node,

+ 53 - 25
java/util/src/main/java/com/google/protobuf/util/FieldMaskUtil.java

@@ -32,6 +32,9 @@ package com.google.protobuf.util;
 
 import static com.google.common.base.Preconditions.checkArgument;
 
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
 import com.google.common.primitives.Ints;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
@@ -39,7 +42,9 @@ import com.google.protobuf.FieldMask;
 import com.google.protobuf.Internal;
 import com.google.protobuf.Message;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Utility helper functions to work with {@link com.google.protobuf.FieldMask}.
@@ -48,7 +53,7 @@ public class FieldMaskUtil {
   private static final String FIELD_PATH_SEPARATOR = ",";
   private static final String FIELD_PATH_SEPARATOR_REGEX = ",";
   private static final String FIELD_SEPARATOR_REGEX = "\\.";
-  
+
   private FieldMaskUtil() {}
 
   /**
@@ -78,19 +83,17 @@ public class FieldMaskUtil {
    */
   public static FieldMask fromString(String value) {
     // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
-    return fromStringList(
-        null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
+    return fromStringList(null, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
   }
 
   /**
    * Parses from a string to a FieldMask and validates all field paths.
-   * 
+   *
    * @throws IllegalArgumentException if any of the field path is invalid.
    */
   public static FieldMask fromString(Class<? extends Message> type, String value) {
     // TODO(xiaofeng): Consider using com.google.common.base.Splitter here instead.
-    return fromStringList(
-        type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
+    return fromStringList(type, Arrays.asList(value.split(FIELD_PATH_SEPARATOR_REGEX)));
   }
 
   /**
@@ -99,8 +102,7 @@ public class FieldMaskUtil {
    * @throws IllegalArgumentException if any of the field path is not valid.
    */
   // TODO(xiaofeng): Consider renaming fromStrings()
-  public static FieldMask fromStringList(
-      Class<? extends Message> type, Iterable<String> paths) {
+  public static FieldMask fromStringList(Class<? extends Message> type, Iterable<String> paths) {
     FieldMask.Builder builder = FieldMask.newBuilder();
     for (String path : paths) {
       if (path.isEmpty()) {
@@ -108,8 +110,7 @@ public class FieldMaskUtil {
         continue;
       }
       if (type != null && !isValid(type, path)) {
-        throw new IllegalArgumentException(
-            path + " is not a valid path for " + type);
+        throw new IllegalArgumentException(path + " is not a valid path for " + type);
       }
       builder.addPaths(path);
     }
@@ -145,16 +146,46 @@ public class FieldMaskUtil {
     return builder.build();
   }
 
+  /**
+   * Converts a field mask to a Proto3 JSON string, that is converting from snake case to camel
+   * case and joining all paths into one string with commas.
+   */
+  public static String toJsonString(FieldMask fieldMask) {
+    List<String> paths = new ArrayList<String>(fieldMask.getPathsCount());
+    for (String path : fieldMask.getPathsList()) {
+      if (path.isEmpty()) {
+        continue;
+      }
+      paths.add(CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, path));
+    }
+    return Joiner.on(FIELD_PATH_SEPARATOR).join(paths);
+  }
+
+  /**
+   * Converts a field mask from a Proto3 JSON string, that is splitting the paths along commas and
+   * converting from camel case to snake case.
+   */
+  public static FieldMask fromJsonString(String value) {
+    Iterable<String> paths = Splitter.on(FIELD_PATH_SEPARATOR).split(value);
+    FieldMask.Builder builder = FieldMask.newBuilder();
+    for (String path : paths) {
+      if (path.isEmpty()) {
+        continue;
+      }
+      builder.addPaths(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, path));
+    }
+    return builder.build();
+  }
+
   /**
    * Checks whether paths in a given fields mask are valid.
    */
   public static boolean isValid(Class<? extends Message> type, FieldMask fieldMask) {
-    Descriptor descriptor =
-        Internal.getDefaultInstance(type).getDescriptorForType();
-    
+    Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType();
+
     return isValid(descriptor, fieldMask);
   }
-  
+
   /**
    * Checks whether paths in a given fields mask are valid.
    */
@@ -171,9 +202,8 @@ public class FieldMaskUtil {
    * Checks whether a given field path is valid.
    */
   public static boolean isValid(Class<? extends Message> type, String path) {
-    Descriptor descriptor =
-        Internal.getDefaultInstance(type).getDescriptorForType();
-    
+    Descriptor descriptor = Internal.getDefaultInstance(type).getDescriptorForType();
+
     return isValid(descriptor, path);
   }
 
@@ -193,8 +223,7 @@ public class FieldMaskUtil {
       if (field == null) {
         return false;
       }
-      if (!field.isRepeated()
-          && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+      if (!field.isRepeated() && field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
         descriptor = field.getMessageType();
       } else {
         descriptor = null;
@@ -202,7 +231,7 @@ public class FieldMaskUtil {
     }
     return true;
   }
-  
+
   /**
    * Converts a FieldMask to its canonical form. In the canonical form of a
    * FieldMask, all field paths are sorted alphabetically and redundant field
@@ -251,7 +280,7 @@ public class FieldMaskUtil {
      * destination message fields) when merging.
      * Default behavior is to merge the source message field into the
      * destination message field.
-     */ 
+     */
     public boolean replaceMessageFields() {
       return replaceMessageFields;
     }
@@ -299,16 +328,15 @@ public class FieldMaskUtil {
    * Merges fields specified by a FieldMask from one message to another with the
    * specified merge options.
    */
-  public static void merge(FieldMask mask, Message source,
-      Message.Builder destination, MergeOptions options) {
+  public static void merge(
+      FieldMask mask, Message source, Message.Builder destination, MergeOptions options) {
     new FieldMaskTree(mask).merge(source, destination, options);
   }
 
   /**
    * Merges fields specified by a FieldMask from one message to another.
    */
-  public static void merge(FieldMask mask, Message source,
-      Message.Builder destination) {
+  public static void merge(FieldMask mask, Message source, Message.Builder destination) {
     merge(mask, source, destination, new MergeOptions());
   }
 }

文件差异内容过多而无法显示
+ 253 - 286
java/util/src/main/java/com/google/protobuf/util/JsonFormat.java


+ 116 - 265
java/util/src/main/java/com/google/protobuf/util/TimeUtil.java

@@ -35,15 +35,14 @@ import com.google.protobuf.Timestamp;
 
 import java.math.BigInteger;
 import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.TimeZone;
 
 /**
  * Utilities to help create/manipulate Timestamp/Duration
+ *
+ * @deprecated Use {@link Durations} and {@link Timestamps} instead.
  */
-public class TimeUtil {
+@Deprecated
+public final class TimeUtil {
   // Timestamp for "0001-01-01T00:00:00Z"
   public static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
 
@@ -53,28 +52,6 @@ public class TimeUtil {
   public static final long DURATION_SECONDS_MAX = 315576000000L;
 
   private static final long NANOS_PER_SECOND = 1000000000;
-  private static final long NANOS_PER_MILLISECOND = 1000000;
-  private static final long NANOS_PER_MICROSECOND = 1000;
-  private static final long MILLIS_PER_SECOND = 1000;
-  private static final long MICROS_PER_SECOND = 1000000;
-
-  private static final ThreadLocal<SimpleDateFormat> timestampFormat =
-      new ThreadLocal<SimpleDateFormat>() {
-        protected SimpleDateFormat initialValue() {
-          return createTimestampFormat();
-        }
-      };
-
-  private static SimpleDateFormat createTimestampFormat() {
-    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
-    GregorianCalendar calendar =
-      new GregorianCalendar(TimeZone.getTimeZone("UTC"));
-    // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends
-    // backwards to year one) for timestamp formating.
-    calendar.setGregorianChange(new Date(Long.MIN_VALUE));
-    sdf.setCalendar(calendar);
-    return sdf;
-  }
 
   private TimeUtil() {}
 
@@ -90,27 +67,11 @@ public class TimeUtil {
    * @return The string representation of the given timestamp.
    * @throws IllegalArgumentException if the given timestamp is not in the
    *         valid range.
+   * @deprecated Use {@link Timestamps#toString} instead.
    */
-  public static String toString(Timestamp timestamp)
-    throws IllegalArgumentException {
-    StringBuilder result = new StringBuilder();
-    // Format the seconds part.
-    if (timestamp.getSeconds() < TIMESTAMP_SECONDS_MIN
-        || timestamp.getSeconds() > TIMESTAMP_SECONDS_MAX) {
-      throw new IllegalArgumentException("Timestamp is out of range.");
-    }
-    Date date = new Date(timestamp.getSeconds() * MILLIS_PER_SECOND);
-    result.append(timestampFormat.get().format(date));
-    // Format the nanos part.
-    if (timestamp.getNanos() < 0 || timestamp.getNanos() >= NANOS_PER_SECOND) {
-      throw new IllegalArgumentException("Timestamp has invalid nanos value.");
-    }
-    if (timestamp.getNanos() != 0) {
-      result.append(".");
-      result.append(formatNanos(timestamp.getNanos()));
-    }
-    result.append("Z");
-    return result.toString();
+  @Deprecated
+  public static String toString(Timestamp timestamp) {
+    return Timestamps.toString(timestamp);
   }
 
   /**
@@ -123,59 +84,11 @@ public class TimeUtil {
    *
    * @return A Timestamp parsed from the string.
    * @throws ParseException if parsing fails.
+   * @deprecated Use {@link Timestamps#parse} instead.
    */
-
+  @Deprecated
   public static Timestamp parseTimestamp(String value) throws ParseException {
-    int dayOffset = value.indexOf('T');
-    if (dayOffset == -1) {
-      throw new ParseException(
-        "Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0);
-    }
-    int timezoneOffsetPosition = value.indexOf('Z', dayOffset);
-    if (timezoneOffsetPosition == -1) {
-      timezoneOffsetPosition = value.indexOf('+', dayOffset);
-    }
-    if (timezoneOffsetPosition == -1) {
-      timezoneOffsetPosition = value.indexOf('-', dayOffset);
-    }
-    if (timezoneOffsetPosition == -1) {
-      throw new ParseException(
-        "Failed to parse timestamp: missing valid timezone offset.", 0);
-    }
-    // Parse seconds and nanos.
-    String timeValue = value.substring(0, timezoneOffsetPosition);
-    String secondValue = timeValue;
-    String nanoValue = "";
-    int pointPosition = timeValue.indexOf('.');
-    if (pointPosition != -1) {
-      secondValue = timeValue.substring(0, pointPosition);
-      nanoValue = timeValue.substring(pointPosition + 1);
-    }
-    Date date = timestampFormat.get().parse(secondValue);
-    long seconds = date.getTime() / MILLIS_PER_SECOND;
-    int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
-    // Parse timezone offsets.
-    if (value.charAt(timezoneOffsetPosition) == 'Z') {
-      if (value.length() != timezoneOffsetPosition + 1) {
-        throw new ParseException(
-          "Failed to parse timestamp: invalid trailing data \""
-          + value.substring(timezoneOffsetPosition) + "\"", 0);
-      }
-    } else {
-      String offsetValue = value.substring(timezoneOffsetPosition + 1);
-      long offset = parseTimezoneOffset(offsetValue);
-      if (value.charAt(timezoneOffsetPosition) == '+') {
-        seconds -= offset;
-      } else {
-        seconds += offset;
-      }
-    }
-    try {
-      return normalizedTimestamp(seconds, nanos);
-    } catch (IllegalArgumentException e) {
-      throw new ParseException(
-        "Failed to parse timestmap: timestamp is out of range.", 0);
-    }
+    return Timestamps.parse(value);
   }
 
   /**
@@ -188,33 +101,11 @@ public class TimeUtil {
    * @return The string representation of the given duration.
    * @throws IllegalArgumentException if the given duration is not in the valid
    *         range.
+   * @deprecated Use {@link Durations#toString} instead.
    */
-  public static String toString(Duration duration)
-    throws IllegalArgumentException {
-    if (duration.getSeconds() < DURATION_SECONDS_MIN
-      || duration.getSeconds() > DURATION_SECONDS_MAX) {
-      throw new IllegalArgumentException("Duration is out of valid range.");
-    }
-    StringBuilder result = new StringBuilder();
-    long seconds = duration.getSeconds();
-    int nanos = duration.getNanos();
-    if (seconds < 0 || nanos < 0) {
-      if (seconds > 0 || nanos > 0) {
-        throw new IllegalArgumentException(
-            "Invalid duration: seconds value and nanos value must have the same"
-            + "sign.");
-      }
-      result.append("-");
-      seconds = -seconds;
-      nanos = -nanos;
-    }
-    result.append(seconds);
-    if (nanos != 0) {
-      result.append(".");
-      result.append(formatNanos(nanos));
-    }
-    result.append("s");
-    return result.toString();
+  @Deprecated
+  public static String toString(Duration duration) {
+    return Durations.toString(duration);
   }
 
   /**
@@ -222,54 +113,31 @@ public class TimeUtil {
    *
    * @return A Duration parsed from the string.
    * @throws ParseException if parsing fails.
+   * @deprecated Use {@link Durations#parse} instead.
    */
+  @Deprecated
   public static Duration parseDuration(String value) throws ParseException {
-    // Must ended with "s".
-    if (value.isEmpty() || value.charAt(value.length() - 1) != 's') {
-      throw new ParseException("Invalid duration string: " + value, 0);
-    }
-    boolean negative = false;
-    if (value.charAt(0) == '-') {
-      negative = true;
-      value = value.substring(1);
-    }
-    String secondValue = value.substring(0, value.length() - 1);
-    String nanoValue = "";
-    int pointPosition = secondValue.indexOf('.');
-    if (pointPosition != -1) {
-      nanoValue = secondValue.substring(pointPosition + 1);
-      secondValue = secondValue.substring(0, pointPosition);
-    }
-    long seconds = Long.parseLong(secondValue);
-    int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
-    if (seconds < 0) {
-      throw new ParseException("Invalid duration string: " + value, 0);
-    }
-    if (negative) {
-      seconds = -seconds;
-      nanos = -nanos;
-    }
-    try {
-      return normalizedDuration(seconds, nanos);
-    } catch (IllegalArgumentException e) {
-      throw new ParseException("Duration value is out of range.", 0);
-    }
+    return Durations.parse(value);
   }
 
   /**
    * Create a Timestamp from the number of milliseconds elapsed from the epoch.
+   *
+   * @deprecated Use {@link Timestamps#fromMillis} instead.
    */
+  @Deprecated
   public static Timestamp createTimestampFromMillis(long milliseconds) {
-    return normalizedTimestamp(milliseconds / MILLIS_PER_SECOND,
-      (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
+    return Timestamps.fromMillis(milliseconds);
   }
 
   /**
    * Create a Duration from the number of milliseconds.
+   *
+   * @deprecated Use {@link Durations#fromMillis} instead.
    */
+  @Deprecated
   public static Duration createDurationFromMillis(long milliseconds) {
-    return normalizedDuration(milliseconds / MILLIS_PER_SECOND,
-      (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
+    return Durations.fromMillis(milliseconds);
   }
 
   /**
@@ -278,36 +146,44 @@ public class TimeUtil {
    * <p>The result will be rounded down to the nearest millisecond. E.g., if the
    * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
    * to -1 millisecond.
+   *
+   * @deprecated Use {@link Timestamps#toMillis} instead.
    */
+  @Deprecated
   public static long toMillis(Timestamp timestamp) {
-    return timestamp.getSeconds() * MILLIS_PER_SECOND + timestamp.getNanos()
-      / NANOS_PER_MILLISECOND;
+    return Timestamps.toMillis(timestamp);
   }
 
   /**
    * Convert a Duration to the number of milliseconds.The result will be
    * rounded towards 0 to the nearest millisecond. E.g., if the duration
    * represents -1 nanosecond, it will be rounded to 0.
+   *
+   * @deprecated Use {@link Durations#toMillis} instead.
    */
+  @Deprecated
   public static long toMillis(Duration duration) {
-    return duration.getSeconds() * MILLIS_PER_SECOND + duration.getNanos()
-      / NANOS_PER_MILLISECOND;
+    return Durations.toMillis(duration);
   }
 
   /**
    * Create a Timestamp from the number of microseconds elapsed from the epoch.
+   *
+   * @deprecated Use {@link Timestamps#fromMicros} instead.
    */
+  @Deprecated
   public static Timestamp createTimestampFromMicros(long microseconds) {
-    return normalizedTimestamp(microseconds / MICROS_PER_SECOND,
-      (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
+    return Timestamps.fromMicros(microseconds);
   }
 
   /**
    * Create a Duration from the number of microseconds.
+   *
+   * @deprecated Use {@link Durations#fromMicros} instead.
    */
+  @Deprecated
   public static Duration createDurationFromMicros(long microseconds) {
-    return normalizedDuration(microseconds / MICROS_PER_SECOND,
-      (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
+    return Durations.fromMicros(microseconds);
   }
 
   /**
@@ -316,111 +192,141 @@ public class TimeUtil {
    * <p>The result will be rounded down to the nearest microsecond. E.g., if the
    * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
    * to -1 millisecond.
+   *
+   * @deprecated Use {@link Timestamps#toMicros} instead.
    */
+  @Deprecated
   public static long toMicros(Timestamp timestamp) {
-    return timestamp.getSeconds() * MICROS_PER_SECOND + timestamp.getNanos()
-      / NANOS_PER_MICROSECOND;
+    return Timestamps.toMicros(timestamp);
   }
 
   /**
    * Convert a Duration to the number of microseconds.The result will be
    * rounded towards 0 to the nearest microseconds. E.g., if the duration
    * represents -1 nanosecond, it will be rounded to 0.
+   *
+   * @deprecated Use {@link Durations#toMicros} instead.
    */
+  @Deprecated
   public static long toMicros(Duration duration) {
-    return duration.getSeconds() * MICROS_PER_SECOND + duration.getNanos()
-      / NANOS_PER_MICROSECOND;
+    return Durations.toMicros(duration);
   }
 
   /**
    * Create a Timestamp from the number of nanoseconds elapsed from the epoch.
+   *
+   * @deprecated Use {@link Timestamps#fromNanos} instead.
    */
+  @Deprecated
   public static Timestamp createTimestampFromNanos(long nanoseconds) {
-    return normalizedTimestamp(nanoseconds / NANOS_PER_SECOND,
-      (int) (nanoseconds % NANOS_PER_SECOND));
+    return Timestamps.fromNanos(nanoseconds);
   }
 
   /**
    * Create a Duration from the number of nanoseconds.
+   *
+   * @deprecated Use {@link Durations#fromNanos} instead.
    */
+  @Deprecated
   public static Duration createDurationFromNanos(long nanoseconds) {
-    return normalizedDuration(nanoseconds / NANOS_PER_SECOND,
-      (int) (nanoseconds % NANOS_PER_SECOND));
+    return Durations.fromNanos(nanoseconds);
   }
 
   /**
    * Convert a Timestamp to the number of nanoseconds elapsed from the epoch.
+   *
+   * @deprecated Use {@link Timestamps#toNanos} instead.
    */
+  @Deprecated
   public static long toNanos(Timestamp timestamp) {
-    return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos();
+    return Timestamps.toNanos(timestamp);
   }
 
   /**
    * Convert a Duration to the number of nanoseconds.
+   *
+   * @deprecated Use {@link Durations#toNanos} instead.
    */
+  @Deprecated
   public static long toNanos(Duration duration) {
-    return duration.getSeconds() * NANOS_PER_SECOND + duration.getNanos();
+    return Durations.toNanos(duration);
   }
 
   /**
    * Get the current time.
+   *
+   * @deprecated Use {@code Timestamps.fromMillis(System.currentTimeMillis())} instead.
    */
+  @Deprecated
   public static Timestamp getCurrentTime() {
-    return createTimestampFromMillis(System.currentTimeMillis());
+    return Timestamps.fromMillis(System.currentTimeMillis());
   }
 
   /**
    * Get the epoch.
+   *
+   * @deprecated Use {@code Timestamps.fromMillis(0)} instead.
    */
+  @Deprecated
   public static Timestamp getEpoch() {
     return Timestamp.getDefaultInstance();
   }
 
   /**
    * Calculate the difference between two timestamps.
+   *
+   * @deprecated Use {@link Timestamps#between} instead.
    */
+  @Deprecated
   public static Duration distance(Timestamp from, Timestamp to) {
-    return normalizedDuration(to.getSeconds() - from.getSeconds(),
-      to.getNanos() - from.getNanos());
+    return Timestamps.between(from, to);
   }
 
   /**
    * Add a duration to a timestamp.
+   *
+   * @deprecated Use {@link Timestamps#add} instead.
    */
+  @Deprecated
   public static Timestamp add(Timestamp start, Duration length) {
-    return normalizedTimestamp(start.getSeconds() + length.getSeconds(),
-      start.getNanos() + length.getNanos());
+    return Timestamps.add(start, length);
   }
 
   /**
    * Subtract a duration from a timestamp.
+   *
+   * @deprecated Use {@link Timestamps#subtract} instead.
    */
+  @Deprecated
   public static Timestamp subtract(Timestamp start, Duration length) {
-    return normalizedTimestamp(start.getSeconds() - length.getSeconds(),
-      start.getNanos() - length.getNanos());
+    return Timestamps.subtract(start, length);
   }
 
   /**
    * Add two durations.
+   *
+   * @deprecated Use {@link Durations#add} instead.
    */
+  @Deprecated
   public static Duration add(Duration d1, Duration d2) {
-    return normalizedDuration(d1.getSeconds() + d2.getSeconds(),
-      d1.getNanos() + d2.getNanos());
+    return Durations.add(d1, d2);
   }
 
   /**
    * Subtract a duration from another.
+   *
+   * @deprecated Use {@link Durations#subtract} instead.
    */
+  @Deprecated
   public static Duration subtract(Duration d1, Duration d2) {
-    return normalizedDuration(d1.getSeconds() - d2.getSeconds(),
-      d1.getNanos() - d2.getNanos());
+    return Durations.subtract(d1, d2);
   }
 
   // Multiplications and divisions.
 
+  // TODO(kak): Delete this.
   public static Duration multiply(Duration duration, double times) {
-    double result = duration.getSeconds() * times + duration.getNanos() * times
-      / 1000000000.0;
+    double result = duration.getSeconds() * times + duration.getNanos() * times / 1000000000.0;
     if (result < Long.MIN_VALUE || result > Long.MAX_VALUE) {
       throw new IllegalArgumentException("Result is out of valid range.");
     }
@@ -428,50 +334,49 @@ public class TimeUtil {
     int nanos = (int) ((result - seconds) * 1000000000);
     return normalizedDuration(seconds, nanos);
   }
-  
+
+  // TODO(kak): Delete this.
   public static Duration divide(Duration duration, double value) {
     return multiply(duration, 1.0 / value);
   }
-  
+
+  // TODO(kak): Delete this.
   public static Duration multiply(Duration duration, long times) {
-    return createDurationFromBigInteger(
-      toBigInteger(duration).multiply(toBigInteger(times)));
+    return createDurationFromBigInteger(toBigInteger(duration).multiply(toBigInteger(times)));
   }
-  
+
+  // TODO(kak): Delete this.
   public static Duration divide(Duration duration, long times) {
-    return createDurationFromBigInteger(
-      toBigInteger(duration).divide(toBigInteger(times)));
+    return createDurationFromBigInteger(toBigInteger(duration).divide(toBigInteger(times)));
   }
-  
+
+  // TODO(kak): Delete this.
   public static long divide(Duration d1, Duration d2) {
     return toBigInteger(d1).divide(toBigInteger(d2)).longValue();
   }
-  
+
+  // TODO(kak): Delete this.
   public static Duration remainder(Duration d1, Duration d2) {
-    return createDurationFromBigInteger(
-      toBigInteger(d1).remainder(toBigInteger(d2)));
+    return createDurationFromBigInteger(toBigInteger(d1).remainder(toBigInteger(d2)));
   }
-  
+
   private static final BigInteger NANOS_PER_SECOND_BIG_INTEGER =
       new BigInteger(String.valueOf(NANOS_PER_SECOND));
-  
+
   private static BigInteger toBigInteger(Duration duration) {
     return toBigInteger(duration.getSeconds())
-      .multiply(NANOS_PER_SECOND_BIG_INTEGER)
-      .add(toBigInteger(duration.getNanos()));
+        .multiply(NANOS_PER_SECOND_BIG_INTEGER)
+        .add(toBigInteger(duration.getNanos()));
   }
-  
+
   private static BigInteger toBigInteger(long value) {
     return new BigInteger(String.valueOf(value));
   }
-  
+
   private static Duration createDurationFromBigInteger(BigInteger value) {
-    long seconds = value.divide(
-      new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue();
-    int nanos = value.remainder(
-      new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue();
+    long seconds = value.divide(new BigInteger(String.valueOf(NANOS_PER_SECOND))).longValue();
+    int nanos = value.remainder(new BigInteger(String.valueOf(NANOS_PER_SECOND))).intValue();
     return normalizedDuration(seconds, nanos);
-    
   }
 
   private static Duration normalizedDuration(long seconds, int nanos) {
@@ -492,58 +397,4 @@ public class TimeUtil {
     }
     return Duration.newBuilder().setSeconds(seconds).setNanos(nanos).build();
   }
-
-  private static Timestamp normalizedTimestamp(long seconds, int nanos) {
-    if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
-      seconds += nanos / NANOS_PER_SECOND;
-      nanos %= NANOS_PER_SECOND;
-    }
-    if (nanos < 0) {
-      nanos += NANOS_PER_SECOND;
-      seconds -= 1;
-    }
-    if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) {
-      throw new IllegalArgumentException("Timestamp is out of valid range.");
-    }
-    return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
-  }
-
-  /**
-   * Format the nano part of a timestamp or a duration.
-   */
-  private static String formatNanos(int nanos) {
-    assert nanos >= 1 && nanos <= 999999999;
-    // Determine whether to use 3, 6, or 9 digits for the nano part.
-    if (nanos % NANOS_PER_MILLISECOND == 0) {
-      return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND);
-    } else if (nanos % NANOS_PER_MICROSECOND == 0) {
-      return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND);
-    } else {
-      return String.format("%1$09d", nanos);
-    }
-  }
-
-  private static int parseNanos(String value) throws ParseException {
-    int result = 0;
-    for (int i = 0; i < 9; ++i) {
-      result = result * 10;
-      if (i < value.length()) {
-        if (value.charAt(i) < '0' || value.charAt(i) > '9') {
-          throw new ParseException("Invalid nanosecnds.", 0);
-        }
-        result += value.charAt(i) - '0';
-      }
-    }
-    return result;
-  }
-
-  private static long parseTimezoneOffset(String value) throws ParseException {
-    int pos = value.indexOf(':');
-    if (pos == -1) {
-      throw new ParseException("Invalid offset value: " + value, 0);
-    }
-    String hours = value.substring(0, pos);
-    String minutes = value.substring(pos + 1);
-    return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60;
-  }
 }

+ 349 - 0
java/util/src/main/java/com/google/protobuf/util/Timestamps.java

@@ -0,0 +1,349 @@
+// 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.util;
+
+import com.google.protobuf.Duration;
+import com.google.protobuf.Timestamp;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+/**
+ * Utilities to help create/manipulate {@code protobuf/timestamp.proto}.
+ */
+public final class Timestamps {
+  // Timestamp for "0001-01-01T00:00:00Z"
+  static final long TIMESTAMP_SECONDS_MIN = -62135596800L;
+
+  // Timestamp for "9999-12-31T23:59:59Z"
+  static final long TIMESTAMP_SECONDS_MAX = 253402300799L;
+
+  static final long NANOS_PER_SECOND = 1000000000;
+  static final long NANOS_PER_MILLISECOND = 1000000;
+  static final long NANOS_PER_MICROSECOND = 1000;
+  static final long MILLIS_PER_SECOND = 1000;
+  static final long MICROS_PER_SECOND = 1000000;
+
+  // TODO(kak): Do we want to expose Timestamp constants for MAX/MIN?
+
+  private static final ThreadLocal<SimpleDateFormat> timestampFormat =
+      new ThreadLocal<SimpleDateFormat>() {
+        protected SimpleDateFormat initialValue() {
+          return createTimestampFormat();
+        }
+      };
+
+  private static SimpleDateFormat createTimestampFormat() {
+    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+    GregorianCalendar calendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
+    // We use Proleptic Gregorian Calendar (i.e., Gregorian calendar extends
+    // backwards to year one) for timestamp formating.
+    calendar.setGregorianChange(new Date(Long.MIN_VALUE));
+    sdf.setCalendar(calendar);
+    return sdf;
+  }
+
+  private Timestamps() {}
+
+  /**
+   * Returns true if the given {@link Timestamp} is valid. The {@code seconds} value must be in the
+   * range [-62,135,596,800, +253,402,300,799] (i.e., between 0001-01-01T00:00:00Z and
+   * 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range [0, +999,999,999].
+   *
+   * <p>Note: Negative second values with fractions must still have non-negative nanos value that
+   * counts forward in time.
+   */
+  public static boolean isValid(Timestamp timestamp) {
+    return isValid(timestamp.getSeconds(), timestamp.getNanos());
+  }
+
+  /**
+   * Returns true if the given number of seconds and nanos is a valid {@link Timestamp}. The
+   * {@code seconds} value must be in the range [-62,135,596,800, +253,402,300,799] (i.e., between
+   * 0001-01-01T00:00:00Z and 9999-12-31T23:59:59Z). The {@code nanos} value must be in the range
+   * [0, +999,999,999].
+   *
+   * <p>Note: Negative second values with fractions must still have non-negative nanos value that
+   * counts forward in time.
+   */
+  public static boolean isValid(long seconds, long nanos) {
+    if (seconds < TIMESTAMP_SECONDS_MIN || seconds > TIMESTAMP_SECONDS_MAX) {
+      return false;
+    }
+    if (nanos < 0 || nanos >= NANOS_PER_SECOND) {
+      return false;
+    }
+    return true;
+  }
+
+  /**
+   * Throws an {@link IllegalArgumentException} if the given seconds/nanos are not
+   * a valid {@link Timestamp}.
+   */
+  private static void checkValid(long seconds, int nanos) {
+    if (!isValid(seconds, nanos)) {
+      throw new IllegalArgumentException(String.format(
+          "Timestamp is not valid. See proto definition for valid values. "
+          + "Seconds (%s) must be in range [-62,135,596,800, +253,402,300,799]."
+          + "Nanos (%s) must be in range [0, +999,999,999].",
+          seconds, nanos));
+    }
+  }
+
+  /**
+   * Convert Timestamp to RFC 3339 date string format. The output will always
+   * be Z-normalized and uses 3, 6 or 9 fractional digits as required to
+   * represent the exact value. Note that Timestamp can only represent time
+   * from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. See
+   * https://www.ietf.org/rfc/rfc3339.txt
+   *
+   * <p>Example of generated format: "1972-01-01T10:00:20.021Z"
+   *
+   * @return The string representation of the given timestamp.
+   * @throws IllegalArgumentException if the given timestamp is not in the
+   *         valid range.
+   */
+  public static String toString(Timestamp timestamp) {
+    long seconds = timestamp.getSeconds();
+    int nanos = timestamp.getNanos();
+    checkValid(seconds, nanos);
+    StringBuilder result = new StringBuilder();
+    // Format the seconds part.
+    Date date = new Date(seconds * MILLIS_PER_SECOND);
+    result.append(timestampFormat.get().format(date));
+    // Format the nanos part.
+    if (nanos != 0) {
+      result.append(".");
+      result.append(formatNanos(nanos));
+    }
+    result.append("Z");
+    return result.toString();
+  }
+
+  /**
+   * Parse from RFC 3339 date string to Timestamp. This method accepts all
+   * outputs of {@link #toString(Timestamp)} and it also accepts any fractional
+   * digits (or none) and any offset as long as they fit into nano-seconds
+   * precision.
+   *
+   * <p>Example of accepted format: "1972-01-01T10:00:20.021-05:00"
+   *
+   * @return A Timestamp parsed from the string.
+   * @throws ParseException if parsing fails.
+   */
+  public static Timestamp parse(String value) throws ParseException {
+    int dayOffset = value.indexOf('T');
+    if (dayOffset == -1) {
+      throw new ParseException("Failed to parse timestamp: invalid timestamp \"" + value + "\"", 0);
+    }
+    int timezoneOffsetPosition = value.indexOf('Z', dayOffset);
+    if (timezoneOffsetPosition == -1) {
+      timezoneOffsetPosition = value.indexOf('+', dayOffset);
+    }
+    if (timezoneOffsetPosition == -1) {
+      timezoneOffsetPosition = value.indexOf('-', dayOffset);
+    }
+    if (timezoneOffsetPosition == -1) {
+      throw new ParseException("Failed to parse timestamp: missing valid timezone offset.", 0);
+    }
+    // Parse seconds and nanos.
+    String timeValue = value.substring(0, timezoneOffsetPosition);
+    String secondValue = timeValue;
+    String nanoValue = "";
+    int pointPosition = timeValue.indexOf('.');
+    if (pointPosition != -1) {
+      secondValue = timeValue.substring(0, pointPosition);
+      nanoValue = timeValue.substring(pointPosition + 1);
+    }
+    Date date = timestampFormat.get().parse(secondValue);
+    long seconds = date.getTime() / MILLIS_PER_SECOND;
+    int nanos = nanoValue.isEmpty() ? 0 : parseNanos(nanoValue);
+    // Parse timezone offsets.
+    if (value.charAt(timezoneOffsetPosition) == 'Z') {
+      if (value.length() != timezoneOffsetPosition + 1) {
+        throw new ParseException(
+            "Failed to parse timestamp: invalid trailing data \""
+                + value.substring(timezoneOffsetPosition)
+                + "\"",
+            0);
+      }
+    } else {
+      String offsetValue = value.substring(timezoneOffsetPosition + 1);
+      long offset = parseTimezoneOffset(offsetValue);
+      if (value.charAt(timezoneOffsetPosition) == '+') {
+        seconds -= offset;
+      } else {
+        seconds += offset;
+      }
+    }
+    try {
+      return normalizedTimestamp(seconds, nanos);
+    } catch (IllegalArgumentException e) {
+      throw new ParseException("Failed to parse timestmap: timestamp is out of range.", 0);
+    }
+  }
+
+  /**
+   * Create a Timestamp from the number of milliseconds elapsed from the epoch.
+   */
+  public static Timestamp fromMillis(long milliseconds) {
+    return normalizedTimestamp(
+        milliseconds / MILLIS_PER_SECOND,
+        (int) (milliseconds % MILLIS_PER_SECOND * NANOS_PER_MILLISECOND));
+  }
+
+  /**
+   * Convert a Timestamp to the number of milliseconds elapsed from the epoch.
+   *
+   * <p>The result will be rounded down to the nearest millisecond. E.g., if the
+   * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
+   * to -1 millisecond.
+   */
+  public static long toMillis(Timestamp timestamp) {
+    return timestamp.getSeconds() * MILLIS_PER_SECOND
+        + timestamp.getNanos() / NANOS_PER_MILLISECOND;
+  }
+
+  /**
+   * Create a Timestamp from the number of microseconds elapsed from the epoch.
+   */
+  public static Timestamp fromMicros(long microseconds) {
+    return normalizedTimestamp(
+        microseconds / MICROS_PER_SECOND,
+        (int) (microseconds % MICROS_PER_SECOND * NANOS_PER_MICROSECOND));
+  }
+
+  /**
+   * Convert a Timestamp to the number of microseconds elapsed from the epoch.
+   *
+   * <p>The result will be rounded down to the nearest microsecond. E.g., if the
+   * timestamp represents "1969-12-31T23:59:59.999999999Z", it will be rounded
+   * to -1 millisecond.
+   */
+  public static long toMicros(Timestamp timestamp) {
+    return timestamp.getSeconds() * MICROS_PER_SECOND
+        + timestamp.getNanos() / NANOS_PER_MICROSECOND;
+  }
+
+  /**
+   * Create a Timestamp from the number of nanoseconds elapsed from the epoch.
+   */
+  public static Timestamp fromNanos(long nanoseconds) {
+    return normalizedTimestamp(
+        nanoseconds / NANOS_PER_SECOND, (int) (nanoseconds % NANOS_PER_SECOND));
+  }
+
+  /**
+   * Convert a Timestamp to the number of nanoseconds elapsed from the epoch.
+   */
+  public static long toNanos(Timestamp timestamp) {
+    return timestamp.getSeconds() * NANOS_PER_SECOND + timestamp.getNanos();
+  }
+
+  /**
+   * Calculate the difference between two timestamps.
+   */
+  public static Duration between(Timestamp from, Timestamp to) {
+    return Durations.normalizedDuration(
+        to.getSeconds() - from.getSeconds(), to.getNanos() - from.getNanos());
+  }
+
+  /**
+   * Add a duration to a timestamp.
+   */
+  public static Timestamp add(Timestamp start, Duration length) {
+    return normalizedTimestamp(
+        start.getSeconds() + length.getSeconds(), start.getNanos() + length.getNanos());
+  }
+
+  /**
+   * Subtract a duration from a timestamp.
+   */
+  public static Timestamp subtract(Timestamp start, Duration length) {
+    return normalizedTimestamp(
+        start.getSeconds() - length.getSeconds(), start.getNanos() - length.getNanos());
+  }
+
+  private static Timestamp normalizedTimestamp(long seconds, int nanos) {
+    if (nanos <= -NANOS_PER_SECOND || nanos >= NANOS_PER_SECOND) {
+      seconds += nanos / NANOS_PER_SECOND;
+      nanos %= NANOS_PER_SECOND;
+    }
+    if (nanos < 0) {
+      nanos += NANOS_PER_SECOND;
+      seconds -= 1;
+    }
+    checkValid(seconds, nanos);
+    return Timestamp.newBuilder().setSeconds(seconds).setNanos(nanos).build();
+  }
+
+  private static long parseTimezoneOffset(String value) throws ParseException {
+    int pos = value.indexOf(':');
+    if (pos == -1) {
+      throw new ParseException("Invalid offset value: " + value, 0);
+    }
+    String hours = value.substring(0, pos);
+    String minutes = value.substring(pos + 1);
+    return (Long.parseLong(hours) * 60 + Long.parseLong(minutes)) * 60;
+  }
+
+  static int parseNanos(String value) throws ParseException {
+    int result = 0;
+    for (int i = 0; i < 9; ++i) {
+      result = result * 10;
+      if (i < value.length()) {
+        if (value.charAt(i) < '0' || value.charAt(i) > '9') {
+          throw new ParseException("Invalid nanosecnds.", 0);
+        }
+        result += value.charAt(i) - '0';
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Format the nano part of a timestamp or a duration.
+   */
+  static String formatNanos(int nanos) {
+    assert nanos >= 1 && nanos <= 999999999;
+    // Determine whether to use 3, 6, or 9 digits for the nano part.
+    if (nanos % NANOS_PER_MILLISECOND == 0) {
+      return String.format("%1$03d", nanos / NANOS_PER_MILLISECOND);
+    } else if (nanos % NANOS_PER_MICROSECOND == 0) {
+      return String.format("%1$06d", nanos / NANOS_PER_MICROSECOND);
+    } else {
+      return String.format("%1$09d", nanos);
+    }
+  }
+}

+ 71 - 42
java/util/src/test/java/com/google/protobuf/util/FieldMaskUtilTest.java

@@ -41,52 +41,55 @@ public class FieldMaskUtilTest extends TestCase {
   public void testIsValid() throws Exception {
     assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload"));
     assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "nonexist"));
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.optional_int32"));
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.repeated_int32"));
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.optional_nested_message"));
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.repeated_nested_message"));
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.nonexist"));
-    
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, FieldMaskUtil.fromString("payload")));
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist")));
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist")));
-    
+    assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_int32"));
+    assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_int32"));
+    assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_nested_message"));
+    assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message"));
+    assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.nonexist"));
+
+    assertTrue(
+        FieldMaskUtil.isValid(NestedTestAllTypes.class, FieldMaskUtil.fromString("payload")));
+    assertFalse(
+        FieldMaskUtil.isValid(NestedTestAllTypes.class, FieldMaskUtil.fromString("nonexist")));
+    assertFalse(
+        FieldMaskUtil.isValid(
+            NestedTestAllTypes.class, FieldMaskUtil.fromString("payload,nonexist")));
+
     assertTrue(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "payload"));
     assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.getDescriptor(), "nonexist"));
-    
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload")));
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist")));
-    
-    assertTrue(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.optional_nested_message.bb"));
+
+    assertTrue(
+        FieldMaskUtil.isValid(
+            NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("payload")));
+    assertFalse(
+        FieldMaskUtil.isValid(
+            NestedTestAllTypes.getDescriptor(), FieldMaskUtil.fromString("nonexist")));
+
+    assertTrue(
+        FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_nested_message.bb"));
     // Repeated fields cannot have sub-paths.
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.repeated_nested_message.bb"));
+    assertFalse(
+        FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.repeated_nested_message.bb"));
     // Non-message fields cannot have sub-paths.
-    assertFalse(FieldMaskUtil.isValid(
-        NestedTestAllTypes.class, "payload.optional_int32.bb"));
+    assertFalse(FieldMaskUtil.isValid(NestedTestAllTypes.class, "payload.optional_int32.bb"));
   }
-  
+
   public void testToString() throws Exception {
     assertEquals("", FieldMaskUtil.toString(FieldMask.getDefaultInstance()));
     FieldMask mask = FieldMask.newBuilder().addPaths("foo").build();
     assertEquals("foo", FieldMaskUtil.toString(mask));
     mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar").build();
     assertEquals("foo,bar", FieldMaskUtil.toString(mask));
-    
+
     // Empty field paths are ignored.
-    mask = FieldMask.newBuilder().addPaths("").addPaths("foo").addPaths("").
-      addPaths("bar").addPaths("").build();
+    mask =
+        FieldMask.newBuilder()
+            .addPaths("")
+            .addPaths("foo")
+            .addPaths("")
+            .addPaths("bar")
+            .addPaths("")
+            .build();
     assertEquals("foo,bar", FieldMaskUtil.toString(mask));
   }
 
@@ -111,8 +114,7 @@ public class FieldMaskUtilTest extends TestCase {
     mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, ",payload");
 
     try {
-      mask = FieldMaskUtil.fromString(
-          NestedTestAllTypes.class, "payload,nonexist");
+      mask = FieldMaskUtil.fromString(NestedTestAllTypes.class, "payload,nonexist");
       fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
       // Expected.
@@ -143,7 +145,33 @@ public class FieldMaskUtilTest extends TestCase {
     } catch (IllegalArgumentException expected) {
     }
   }
-  
+
+  public void testToJsonString() throws Exception {
+    FieldMask mask = FieldMask.getDefaultInstance();
+    assertEquals("", FieldMaskUtil.toJsonString(mask));
+    mask = FieldMask.newBuilder().addPaths("foo").build();
+    assertEquals("foo", FieldMaskUtil.toJsonString(mask));
+    mask = FieldMask.newBuilder().addPaths("foo.bar_baz").addPaths("").build();
+    assertEquals("foo.barBaz", FieldMaskUtil.toJsonString(mask));
+    mask = FieldMask.newBuilder().addPaths("foo").addPaths("bar_baz").build();
+    assertEquals("foo,barBaz", FieldMaskUtil.toJsonString(mask));
+  }
+
+  public void testFromJsonString() throws Exception {
+    FieldMask mask = FieldMaskUtil.fromJsonString("");
+    assertEquals(0, mask.getPathsCount());
+    mask = FieldMaskUtil.fromJsonString("foo");
+    assertEquals(1, mask.getPathsCount());
+    assertEquals("foo", mask.getPaths(0));
+    mask = FieldMaskUtil.fromJsonString("foo.barBaz");
+    assertEquals(1, mask.getPathsCount());
+    assertEquals("foo.bar_baz", mask.getPaths(0));
+    mask = FieldMaskUtil.fromJsonString("foo,barBaz");
+    assertEquals(2, mask.getPathsCount());
+    assertEquals("foo", mask.getPaths(0));
+    assertEquals("bar_baz", mask.getPaths(1));
+  }
+
   public void testUnion() throws Exception {
     // Only test a simple case here and expect
     // {@link FieldMaskTreeTest#testAddFieldPath} to cover all scenarios.
@@ -161,7 +189,7 @@ public class FieldMaskUtilTest extends TestCase {
     FieldMask result = FieldMaskUtil.union(mask1, mask2, mask3, mask4);
     assertEquals("bar,foo", FieldMaskUtil.toString(result));
   }
-  
+
   public void testIntersection() throws Exception {
     // Only test a simple case here and expect
     // {@link FieldMaskTreeTest#testIntersectFieldPath} to cover all scenarios.
@@ -170,13 +198,14 @@ public class FieldMaskUtilTest extends TestCase {
     FieldMask result = FieldMaskUtil.intersection(mask1, mask2);
     assertEquals("bar.baz,bar.quz,foo.bar", FieldMaskUtil.toString(result));
   }
-  
+
   public void testMerge() throws Exception {
     // Only test a simple case here and expect
     // {@link FieldMaskTreeTest#testMerge} to cover all scenarios.
-    NestedTestAllTypes source = NestedTestAllTypes.newBuilder()
-        .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234))
-        .build();
+    NestedTestAllTypes source =
+        NestedTestAllTypes.newBuilder()
+            .setPayload(TestAllTypes.newBuilder().setOptionalInt32(1234))
+            .build();
     NestedTestAllTypes.Builder builder = NestedTestAllTypes.newBuilder();
     FieldMaskUtil.merge(FieldMaskUtil.fromString("payload"), source, builder);
     assertEquals(1234, builder.getPayload().getOptionalInt32());

文件差异内容过多而无法显示
+ 358 - 358
java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java


+ 36 - 41
java/util/src/test/java/com/google/protobuf/util/TimeUtilTest.java

@@ -84,6 +84,7 @@ public class TimeUtilTest extends TestCase {
   private class ParseTimestampThread extends Thread {
     private final String[] strings;
     private final Timestamp[] values;
+
     public ParseTimestampThread(String[] strings, Timestamp[] values) {
       this.strings = strings;
       this.values = values;
@@ -102,8 +103,8 @@ public class TimeUtilTest extends TestCase {
         }
         if (result.getSeconds() != values[index].getSeconds()
             || result.getNanos() != values[index].getNanos()) {
-          errorMessage = "Actual result: " + result.toString() + ", expected: "
-              + values[index].toString();
+          errorMessage =
+              "Actual result: " + result.toString() + ", expected: " + values[index].toString();
           break;
         }
         index = (index + 1) % strings.length;
@@ -112,26 +113,26 @@ public class TimeUtilTest extends TestCase {
   }
 
   public void testTimestampConcurrentParsing() throws Exception {
-    String[] timestampStrings = new String[]{
-      "0001-01-01T00:00:00Z",
-      "9999-12-31T23:59:59.999999999Z",
-      "1970-01-01T00:00:00Z",
-      "1969-12-31T23:59:59.999Z",
-    };
+    String[] timestampStrings =
+        new String[] {
+          "0001-01-01T00:00:00Z",
+          "9999-12-31T23:59:59.999999999Z",
+          "1970-01-01T00:00:00Z",
+          "1969-12-31T23:59:59.999Z",
+        };
     Timestamp[] timestampValues = new Timestamp[timestampStrings.length];
     for (int i = 0; i < timestampStrings.length; i++) {
       timestampValues[i] = TimeUtil.parseTimestamp(timestampStrings[i]);
     }
 
     final int THREAD_COUNT = 16;
-    final int RUNNING_TIME = 5000;  // in milliseconds.
+    final int RUNNING_TIME = 5000; // in milliseconds.
     final List<Thread> threads = new ArrayList<Thread>();
 
     stopParsingThreads = false;
     errorMessage = "";
     for (int i = 0; i < THREAD_COUNT; i++) {
-      Thread thread = new ParseTimestampThread(
-          timestampStrings, timestampValues);
+      Thread thread = new ParseTimestampThread(timestampStrings, timestampValues);
       thread.start();
       threads.add(thread);
     }
@@ -146,8 +147,8 @@ public class TimeUtilTest extends TestCase {
   public void testTimetampInvalidFormat() throws Exception {
     try {
       // Value too small.
-      Timestamp value = Timestamp.newBuilder()
-        .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build();
+      Timestamp value =
+          Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MIN - 1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
@@ -156,14 +157,14 @@ public class TimeUtilTest extends TestCase {
 
     try {
       // Value too large.
-      Timestamp value = Timestamp.newBuilder()
-        .setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build();
+      Timestamp value =
+          Timestamp.newBuilder().setSeconds(TimeUtil.TIMESTAMP_SECONDS_MAX + 1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
       // Expected.
     }
-    
+
     try {
       // Invalid nanos value.
       Timestamp value = Timestamp.newBuilder().setNanos(-1).build();
@@ -279,8 +280,7 @@ public class TimeUtilTest extends TestCase {
   public void testDurationInvalidFormat() throws Exception {
     try {
       // Value too small.
-      Duration value = Duration.newBuilder()
-        .setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build();
+      Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MIN - 1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
@@ -289,18 +289,16 @@ public class TimeUtilTest extends TestCase {
 
     try {
       // Value too large.
-      Duration value = Duration.newBuilder()
-        .setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build();
+      Duration value = Duration.newBuilder().setSeconds(TimeUtil.DURATION_SECONDS_MAX + 1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
       // Expected.
     }
-    
+
     try {
       // Invalid nanos value.
-      Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1)
-          .build();
+      Duration value = Duration.newBuilder().setSeconds(1).setNanos(-1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
@@ -309,8 +307,7 @@ public class TimeUtilTest extends TestCase {
 
     try {
       // Invalid nanos value.
-      Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1)
-          .build();
+      Duration value = Duration.newBuilder().setSeconds(-1).setNanos(1).build();
       TimeUtil.toString(value);
       Assert.fail("Exception is expected.");
     } catch (IllegalArgumentException e) {
@@ -367,8 +364,7 @@ public class TimeUtilTest extends TestCase {
   }
 
   public void testTimestampConversion() throws Exception {
-    Timestamp timestamp =
-      TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z");
+    Timestamp timestamp = TimeUtil.parseTimestamp("1970-01-01T00:00:01.111111111Z");
     assertEquals(1111111111, TimeUtil.toNanos(timestamp));
     assertEquals(1111111, TimeUtil.toMicros(timestamp));
     assertEquals(1111, TimeUtil.toMillis(timestamp));
@@ -378,7 +374,7 @@ public class TimeUtilTest extends TestCase {
     assertEquals("1970-01-01T00:00:01.111111Z", TimeUtil.toString(timestamp));
     timestamp = TimeUtil.createTimestampFromMillis(1111);
     assertEquals("1970-01-01T00:00:01.111Z", TimeUtil.toString(timestamp));
-    
+
     timestamp = TimeUtil.parseTimestamp("1969-12-31T23:59:59.111111111Z");
     assertEquals(-888888889, TimeUtil.toNanos(timestamp));
     assertEquals(-888889, TimeUtil.toMicros(timestamp));
@@ -402,7 +398,7 @@ public class TimeUtilTest extends TestCase {
     assertEquals("1.111111s", TimeUtil.toString(duration));
     duration = TimeUtil.createDurationFromMillis(1111);
     assertEquals("1.111s", TimeUtil.toString(duration));
-    
+
     duration = TimeUtil.parseDuration("-1.111111111s");
     assertEquals(-1111111111, TimeUtil.toNanos(duration));
     assertEquals(-1111111, TimeUtil.toMicros(duration));
@@ -459,29 +455,28 @@ public class TimeUtilTest extends TestCase {
 
     duration = TimeUtil.add(duration, duration);
     assertEquals("-2.250s", TimeUtil.toString(duration));
-    
+
     duration = TimeUtil.subtract(duration, TimeUtil.parseDuration("-1s"));
     assertEquals("-1.250s", TimeUtil.toString(duration));
-    
+
     // Multiplications (with results larger than Long.MAX_VALUE in nanoseconds).
     duration = TimeUtil.parseDuration("0.999999999s");
-    assertEquals("315575999684.424s",
-      TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L)));
+    assertEquals(
+        "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L)));
     duration = TimeUtil.parseDuration("-0.999999999s");
-    assertEquals("-315575999684.424s",
-      TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L)));
-    assertEquals("315575999684.424s",
-      TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L)));
-    
+    assertEquals(
+        "-315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, 315576000000L)));
+    assertEquals(
+        "315575999684.424s", TimeUtil.toString(TimeUtil.multiply(duration, -315576000000L)));
+
     // Divisions (with values larger than Long.MAX_VALUE in nanoseconds).
     Duration d1 = TimeUtil.parseDuration("315576000000s");
     Duration d2 = TimeUtil.subtract(d1, TimeUtil.createDurationFromNanos(1));
     assertEquals(1, TimeUtil.divide(d1, d2));
     assertEquals(0, TimeUtil.divide(d2, d1));
     assertEquals("0.000000001s", TimeUtil.toString(TimeUtil.remainder(d1, d2)));
-    assertEquals("315575999999.999999999s",
-      TimeUtil.toString(TimeUtil.remainder(d2, d1)));
-    
+    assertEquals("315575999999.999999999s", TimeUtil.toString(TimeUtil.remainder(d2, d1)));
+
     // Divisions involving negative values.
     //
     // (-5) / 2 = -2, remainder = -1

+ 1 - 0
java/util/src/test/proto/com/google/protobuf/util/json_test.proto

@@ -124,6 +124,7 @@ message TestOneof {
   oneof oneof_field {
     int32 oneof_int32 = 1;
     TestAllTypes.NestedMessage oneof_nested_message = 2;
+    google.protobuf.NullValue oneof_null_value = 3;
   }
 }
 

+ 2 - 2
js/binary/constants.js

@@ -141,8 +141,8 @@ jspb.ReaderFunction;
 
 /**
  * A writer function serializes a message to a BinaryWriter.
- * @typedef {!function(!jspb.Message, !jspb.BinaryWriter):void |
- *           !function(!jspb.ConstBinaryMessage, !jspb.BinaryWriter):void}
+ * @typedef {function((!jspb.Message|!jspb.ConstBinaryMessage),
+ *                    !jspb.BinaryWriter):void}
  */
 jspb.WriterFunction;
 

+ 1 - 2
js/binary/decoder_test.js

@@ -147,9 +147,8 @@ function doTestSignedValue(readValue,
 describe('binaryDecoderTest', function() {
   /**
    * Tests the decoder instance cache.
-   * @suppress {visibility}
    */
-  it('testInstanceCache', function() {
+  it('testInstanceCache', /** @suppress {visibility} */ function() {
     // Empty the instance caches.
     jspb.BinaryDecoder.instanceCache_ = [];
 

+ 11 - 14
js/binary/reader_test.js

@@ -52,9 +52,8 @@ goog.require('jspb.BinaryWriter');
 describe('binaryReaderTest', function() {
   /**
    * Tests the reader instance cache.
-   * @suppress {visibility}
    */
-  it('testInstanceCaches', function() {
+  it('testInstanceCaches', /** @suppress {visibility} */ function() {
     var writer = new jspb.BinaryWriter();
     var dummyMessage = /** @type {!jspb.BinaryMessage} */({});
     writer.writeMessage(1, dummyMessage, goog.nullFunction);
@@ -131,9 +130,8 @@ describe('binaryReaderTest', function() {
 
   /**
    * Verifies that misuse of the reader class triggers assertions.
-   * @suppress {checkTypes|visibility}
    */
-  it('testReadErrors', function() {
+  it('testReadErrors', /** @suppress {checkTypes|visibility} */ function() {
     // Calling readMessage on a non-delimited field should trigger an
     // assertion.
     var reader = jspb.BinaryReader.alloc([8, 1]);
@@ -200,7 +198,7 @@ describe('binaryReaderTest', function() {
    * @private
    * @suppress {missingProperties}
    */
-  function doTestUnsignedField_(readField,
+  var doTestUnsignedField_ = function(readField,
       writeField, epsilon, upperLimit, filter) {
     assertNotNull(readField);
     assertNotNull(writeField);
@@ -252,7 +250,7 @@ describe('binaryReaderTest', function() {
    * @private
    * @suppress {missingProperties}
    */
-  function doTestSignedField_(readField,
+  var doTestSignedField_ = function(readField,
       writeField, epsilon, lowerLimit, upperLimit, filter) {
     var writer = new jspb.BinaryWriter();
 
@@ -321,12 +319,12 @@ describe('binaryReaderTest', function() {
    * Tests fields that use varint encoding.
    */
   it('testVarintFields', function() {
-    assertNotNull(jspb.BinaryReader.prototype.readUint32);
-    assertNotNull(jspb.BinaryReader.prototype.writeUint32);
-    assertNotNull(jspb.BinaryReader.prototype.readUint64);
-    assertNotNull(jspb.BinaryReader.prototype.writeUint64);
-    assertNotNull(jspb.BinaryReader.prototype.readBool);
-    assertNotNull(jspb.BinaryReader.prototype.writeBool);
+    assertNotUndefined(jspb.BinaryReader.prototype.readUint32);
+    assertNotUndefined(jspb.BinaryWriter.prototype.writeUint32);
+    assertNotUndefined(jspb.BinaryReader.prototype.readUint64);
+    assertNotUndefined(jspb.BinaryWriter.prototype.writeUint64);
+    assertNotUndefined(jspb.BinaryReader.prototype.readBool);
+    assertNotUndefined(jspb.BinaryWriter.prototype.writeBool);
     doTestUnsignedField_(
         jspb.BinaryReader.prototype.readUint32,
         jspb.BinaryWriter.prototype.writeUint32,
@@ -369,8 +367,7 @@ describe('binaryReaderTest', function() {
     var bytesCount = (hexString.length + 1) / 3;
     var bytes = new Uint8Array(bytesCount);
     for (var i = 0; i < bytesCount; i++) {
-      byte = parseInt(hexString.substring(i * 3, i * 3 + 2), 16);
-      bytes[i] = byte;
+      bytes[i] = parseInt(hexString.substring(i * 3, i * 3 + 2), 16);
     }
     var reader = jspb.BinaryReader.alloc(bytes);
     reader.nextField();

+ 26 - 10
js/binary/writer.js

@@ -717,11 +717,19 @@ jspb.BinaryWriter.prototype.writeBytes = function(field, value) {
 
 /**
  * Writes a message to the buffer.
- * @template MessageType
  * @param {number} field The field number.
  * @param {?MessageType} value The message to write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- *     to write and the writer to write it with.
+ * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback
+ *     Will be invoked with the value to write and the writer to write it with.
+ * @template MessageType
+ * Use go/closure-ttl to declare a non-nullable version of MessageType.  Replace
+ * the null in blah|null with none.  This is necessary because the compiler will
+ * infer MessageType to be nullable if the value parameter is nullable.
+ * @template MessageTypeNonNull :=
+ *     cond(isUnknown(MessageType), unknown(),
+ *       mapunion(MessageType, (X) =>
+ *         cond(eq(X, 'null'), none(), X)))
+ * =:
  */
 jspb.BinaryWriter.prototype.writeMessage = function(
     field, value, writerCallback) {
@@ -735,12 +743,20 @@ jspb.BinaryWriter.prototype.writeMessage = function(
 /**
  * Writes a group message to the buffer.
  *
- * @template MessageType
  * @param {number} field The field number.
  * @param {?MessageType} value The message to write, wrapped with START_GROUP /
  *     END_GROUP tags. Will be a no-op if 'value' is null.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- *     to write and the writer to write it with.
+ * @param {function(MessageTypeNonNull, !jspb.BinaryWriter)} writerCallback
+ *     Will be invoked with the value to write and the writer to write it with.
+ * @template MessageType
+ * Use go/closure-ttl to declare a non-nullable version of MessageType.  Replace
+ * the null in blah|null with none.  This is necessary because the compiler will
+ * infer MessageType to be nullable if the value parameter is nullable.
+ * @template MessageTypeNonNull :=
+ *     cond(isUnknown(MessageType), unknown(),
+ *       mapunion(MessageType, (X) =>
+ *         cond(eq(X, 'null'), none(), X)))
+ * =:
  */
 jspb.BinaryWriter.prototype.writeGroup = function(
     field, value, writerCallback) {
@@ -1122,8 +1138,8 @@ jspb.BinaryWriter.prototype.writeRepeatedBytes = function(field, value) {
  * @param {number} field The field number.
  * @param {?Array.<MessageType>} value The array of messages to
  *    write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- *     to write and the writer to write it with.
+ * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback
+ *     Will be invoked with the value to write and the writer to write it with.
  */
 jspb.BinaryWriter.prototype.writeRepeatedMessage = function(
     field, value, writerCallback) {
@@ -1142,8 +1158,8 @@ jspb.BinaryWriter.prototype.writeRepeatedMessage = function(
  * @param {number} field The field number.
  * @param {?Array.<MessageType>} value The array of messages to
  *    write.
- * @param {!jspb.WriterFunction} writerCallback Will be invoked with the value
- *     to write and the writer to write it with.
+ * @param {function(MessageType, !jspb.BinaryWriter)} writerCallback
+ *     Will be invoked with the value to write and the writer to write it with.
  */
 jspb.BinaryWriter.prototype.writeRepeatedGroup = function(
     field, value, writerCallback) {

+ 96 - 1
js/message.js

@@ -41,6 +41,7 @@ goog.require('goog.array');
 goog.require('goog.asserts');
 goog.require('goog.crypt.base64');
 goog.require('goog.json');
+goog.require('jspb.Map');
 
 // Not needed in compilation units that have no protos with xids.
 goog.forwardDeclare('xid.String');
@@ -371,7 +372,8 @@ jspb.Message.materializeExtensionObject_ = function(msg, suggestedPivot) {
     // the object is not an array, since arrays are valid field values.
     // NOTE(lukestebbing): We avoid looking at .length to avoid a JIT bug
     // in Safari on iOS 8. See the description of CL/86511464 for details.
-    if (obj && typeof obj == 'object' && !goog.isArray(obj)) {
+    if (obj && typeof obj == 'object' && !goog.isArray(obj) &&
+       !(jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array)) {
       msg.pivot_ = foundIndex - msg.arrayIndexOffset_;
       msg.extensionObject_ = obj;
       return;
@@ -737,6 +739,62 @@ jspb.Message.getFieldProto3 = function(msg, fieldNumber, defaultValue) {
 };
 
 
+/**
+ * Gets the value of a map field, lazily creating the map container if
+ * necessary.
+ *
+ * This should only be called from generated code, because it requires knowledge
+ * of serialization/parsing callbacks (which are required by the map at
+ * construction time, and the map may be constructed here).
+ *
+ * The below callbacks are used to allow the map to serialize and parse its
+ * binary wire format data. Their purposes are described in more detail in
+ * `jspb.Map`'s constructor documentation.
+ *
+ * @template K, V
+ * @param {!jspb.Message} msg
+ * @param {number} fieldNumber
+ * @param {boolean|undefined} noLazyCreate
+ * @param {?=} opt_valueCtor
+ * @param {function(number,K)=} opt_keyWriterFn
+ * @param {function():K=} opt_keyReaderFn
+ * @param {function(number,V)|function(number,V,?)|
+ *         function(number,V,?,?,?,?)=} opt_valueWriterFn
+ * @param {function():V|
+ *         function(V,function(?,?))=} opt_valueReaderFn
+ * @param {function(?,?)|function(?,?,?,?,?)=} opt_valueWriterCallback
+ * @param {function(?,?)=} opt_valueReaderCallback
+ * @return {!jspb.Map<K, V>|undefined}
+ * @protected
+ */
+jspb.Message.getMapField = function(msg, fieldNumber, noLazyCreate,
+    opt_valueCtor, opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn,
+    opt_valueReaderFn, opt_valueWriterCallback, opt_valueReaderCallback) {
+  if (!msg.wrappers_) {
+    msg.wrappers_ = {};
+  }
+  // If we already have a map in the map wrappers, return that.
+  if (fieldNumber in msg.wrappers_) {
+    return msg.wrappers_[fieldNumber];
+  } else if (noLazyCreate) {
+    return undefined;
+  } else {
+    // Wrap the underlying elements array with a Map.
+    var arr = jspb.Message.getField(msg, fieldNumber);
+    if (!arr) {
+      arr = [];
+      jspb.Message.setField(msg, fieldNumber, arr);
+    }
+    return msg.wrappers_[fieldNumber] =
+        new jspb.Map(
+            /** @type {!Array<!Array<!Object>>} */ (arr),
+            opt_keyWriterFn, opt_keyReaderFn, opt_valueWriterFn,
+            opt_valueReaderFn, opt_valueCtor, opt_valueWriterCallback,
+            opt_valueReaderCallback);
+  }
+};
+
+
 /**
  * Sets the value of a non-extension field.
  * @param {!jspb.Message} msg A jspb proto.
@@ -952,6 +1010,38 @@ jspb.Message.toMap = function(
 };
 
 
+/**
+ * Syncs all map fields' contents back to their underlying arrays.
+ * @private
+ */
+jspb.Message.prototype.syncMapFields_ = function() {
+  // This iterates over submessage, map, and repeated fields, which is intended.
+  // Submessages can contain maps which also need to be synced.
+  //
+  // There is a lot of opportunity for optimization here.  For example we could
+  // statically determine that some messages have no submessages with maps and
+  // optimize this method away for those just by generating one extra static
+  // boolean per message type.
+  if (this.wrappers_) {
+    for (var fieldNumber in this.wrappers_) {
+      var val = this.wrappers_[fieldNumber];
+      if (goog.isArray(val)) {
+        for (var i = 0; i < val.length; i++) {
+          if (val[i]) {
+            val[i].toArray();
+          }
+        }
+      } else {
+        // Works for submessages and maps.
+        if (val) {
+          val.toArray();
+        }
+      }
+    }
+  }
+};
+
+
 /**
  * Returns the internal array of this proto.
  * <p>Note: If you use this array to construct a second proto, the content
@@ -959,6 +1049,7 @@ jspb.Message.toMap = function(
  * @return {!Array} The proto represented as an array.
  */
 jspb.Message.prototype.toArray = function() {
+  this.syncMapFields_();
   return this.array;
 };
 
@@ -972,6 +1063,7 @@ jspb.Message.prototype.toArray = function() {
  * @override
  */
 jspb.Message.prototype.toString = function() {
+  this.syncMapFields_();
   return this.array.toString();
 };
 
@@ -1293,6 +1385,9 @@ jspb.Message.clone_ = function(obj) {
     }
     return clonedArray;
   }
+  if (jspb.Message.SUPPORTS_UINT8ARRAY_ && obj instanceof Uint8Array) {
+    return new Uint8Array(obj);
+  }
   var clone = {};
   for (var key in obj) {
     if ((o = obj[key]) != null) {

+ 30 - 3
js/message_test.js

@@ -34,6 +34,7 @@ goog.setTestOnly();
 
 goog.require('goog.json');
 goog.require('goog.testing.asserts');
+goog.require('goog.userAgent');
 
 // CommonJS-LoadFromFile: google-protobuf jspb
 goog.require('jspb.Message');
@@ -66,6 +67,7 @@ goog.require('proto.jspb.test.Simple1');
 goog.require('proto.jspb.test.Simple2');
 goog.require('proto.jspb.test.SpecialCases');
 goog.require('proto.jspb.test.TestClone');
+goog.require('proto.jspb.test.TestEndsWithBytes');
 goog.require('proto.jspb.test.TestGroup');
 goog.require('proto.jspb.test.TestGroup1');
 goog.require('proto.jspb.test.TestMessageWithOneof');
@@ -438,6 +440,8 @@ describe('Message test suite', function() {
   });
 
   it('testClone', function() {
+    var supportsUint8Array =
+        !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10');
     var original = new proto.jspb.test.TestClone();
     original.setStr('v1');
     var simple1 = new proto.jspb.test.Simple1(['x1', ['y1', 'z1']]);
@@ -445,12 +449,14 @@ describe('Message test suite', function() {
     var simple3 = new proto.jspb.test.Simple1(['x3', ['y3', 'z3']]);
     original.setSimple1(simple1);
     original.setSimple2List([simple2, simple3]);
+    var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123';
+    original.setBytesField(bytes1);
     var extension = new proto.jspb.test.CloneExtension();
     extension.setExt('e1');
     original.setExtension(proto.jspb.test.IsExtension.extField, extension);
     var clone = original.cloneMessage();
     assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],,
-      [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }],
+      [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }],
         clone.toArray());
     clone.setStr('v2');
     var simple4 = new proto.jspb.test.Simple1(['a1', ['b1', 'c1']]);
@@ -458,18 +464,26 @@ describe('Message test suite', function() {
     var simple6 = new proto.jspb.test.Simple1(['a3', ['b3', 'c3']]);
     clone.setSimple1(simple4);
     clone.setSimple2List([simple5, simple6]);
+    if (supportsUint8Array) {
+      clone.getBytesField()[0] = 4;
+      assertObjectEquals(bytes1, original.getBytesField());
+    }
+    var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456';
+    clone.setBytesField(bytes2);
     var newExtension = new proto.jspb.test.CloneExtension();
     newExtension.setExt('e2');
     clone.setExtension(proto.jspb.test.CloneExtension.extField, newExtension);
     assertArrayEquals(['v2',, ['a1', ['b1', 'c1']],,
-      [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]],,, { 100: [, 'e2'] }],
+      [['a2', ['b2', 'c2']], ['a3', ['b3', 'c3']]], bytes2,, { 100: [, 'e2'] }],
         clone.toArray());
     assertArrayEquals(['v1',, ['x1', ['y1', 'z1']],,
-      [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]],,, { 100: [, 'e1'] }],
+      [['x2', ['y2', 'z2']], ['x3', ['y3', 'z3']]], bytes1,, { 100: [, 'e1'] }],
         original.toArray());
   });
 
   it('testCopyInto', function() {
+    var supportsUint8Array =
+        !goog.userAgent.IE || goog.userAgent.isVersionOrHigher('10');
     var original = new proto.jspb.test.TestClone();
     original.setStr('v1');
     var dest = new proto.jspb.test.TestClone();
@@ -484,6 +498,10 @@ describe('Message test suite', function() {
     original.setSimple2List([simple2, simple3]);
     dest.setSimple1(destSimple1);
     dest.setSimple2List([destSimple2, destSimple3]);
+    var bytes1 = supportsUint8Array ? new Uint8Array([1, 2, 3]) : '123';
+    var bytes2 = supportsUint8Array ? new Uint8Array([4, 5, 6]) : '456';
+    original.setBytesField(bytes1);
+    dest.setBytesField(bytes2);
     var extension = new proto.jspb.test.CloneExtension();
     extension.setExt('e1');
     original.setExtension(proto.jspb.test.CloneExtension.extField, extension);
@@ -496,6 +514,15 @@ describe('Message test suite', function() {
     dest.getSimple1().setAString('new value');
     assertNotEquals(dest.getSimple1().getAString(),
         original.getSimple1().getAString());
+    if (supportsUint8Array) {
+      dest.getBytesField()[0] = 7;
+      assertObjectEquals(bytes1, original.getBytesField());
+      assertObjectEquals(new Uint8Array([7, 2, 3]), dest.getBytesField());
+    } else {
+      dest.setBytesField('789');
+      assertObjectEquals(bytes1, original.getBytesField());
+      assertObjectEquals('789', dest.getBytesField());
+    }
     dest.getExtension(proto.jspb.test.CloneExtension.extField).
         setExt('new value');
     assertNotEquals(

+ 1 - 0
js/test.proto

@@ -160,6 +160,7 @@ message TestClone {
   optional string str = 1;
   optional Simple1 simple1 = 3;
   repeated Simple1 simple2 = 5;
+  optional bytes bytes_field = 6;
   optional string unused = 7;
   extensions 10 to max;
 }

+ 29 - 0
js/testbinary.proto

@@ -183,3 +183,32 @@ extend TestExtendable {
       [packed=true];
 
 }
+
+message TestMapFields {
+  option (jspb.generate_from_object) = true;
+
+  map<string, string> map_string_string = 1;
+  map<string, int32> map_string_int32 = 2;
+  map<string, int64> map_string_int64 = 3;
+  map<string, bool> map_string_bool = 4;
+  map<string, double> map_string_double = 5;
+  map<string, MapValueEnum> map_string_enum = 6;
+  map<string, MapValueMessage> map_string_msg = 7;
+
+  map<int32, string> map_int32_string = 8;
+  map<int64, string> map_int64_string = 9;
+  map<bool, string> map_bool_string = 10;
+
+  optional TestMapFields test_map_fields = 11;
+  map<string, TestMapFields> map_string_testmapfields = 12;
+}
+
+enum MapValueEnum {
+  MAP_VALUE_FOO = 0;
+  MAP_VALUE_BAR = 1;
+  MAP_VALUE_BAZ = 2;
+}
+
+message MapValueMessage {
+  optional int32 foo = 1;
+}

+ 32 - 10
python/google/protobuf/descriptor.py

@@ -258,7 +258,7 @@ class Descriptor(_NestedDescriptorBase):
     def __new__(cls, name, full_name, filename, containing_type, fields,
                 nested_types, enum_types, extensions, options=None,
                 is_extendable=True, extension_ranges=None, oneofs=None,
-                file=None, serialized_start=None, serialized_end=None,
+                file=None, serialized_start=None, serialized_end=None,  # pylint: disable=redefined-builtin
                 syntax=None):
       _message.Message._CheckCalledFromGeneratedFile()
       return _message.default_pool.FindMessageTypeByName(full_name)
@@ -269,8 +269,8 @@ class Descriptor(_NestedDescriptorBase):
   def __init__(self, name, full_name, filename, containing_type, fields,
                nested_types, enum_types, extensions, options=None,
                is_extendable=True, extension_ranges=None, oneofs=None,
-               file=None, serialized_start=None, serialized_end=None,
-               syntax=None):  # pylint:disable=redefined-builtin
+               file=None, serialized_start=None, serialized_end=None,  # pylint: disable=redefined-builtin
+               syntax=None):
     """Arguments to __init__() are as described in the description
     of Descriptor fields above.
 
@@ -665,7 +665,7 @@ class EnumValueDescriptor(DescriptorBase):
     self.type = type
 
 
-class OneofDescriptor(object):
+class OneofDescriptor(DescriptorBase):
   """Descriptor for a oneof field.
 
     name: (str) Name of the oneof field.
@@ -682,12 +682,15 @@ class OneofDescriptor(object):
   if _USE_C_DESCRIPTORS:
     _C_DESCRIPTOR_CLASS = _message.OneofDescriptor
 
-    def __new__(cls, name, full_name, index, containing_type, fields):
+    def __new__(
+        cls, name, full_name, index, containing_type, fields, options=None):
       _message.Message._CheckCalledFromGeneratedFile()
       return _message.default_pool.FindOneofByName(full_name)
 
-  def __init__(self, name, full_name, index, containing_type, fields):
+  def __init__(
+      self, name, full_name, index, containing_type, fields, options=None):
     """Arguments are as described in the attribute description above."""
+    super(OneofDescriptor, self).__init__(options, 'OneofOptions')
     self.name = name
     self.full_name = full_name
     self.index = index
@@ -705,11 +708,22 @@ class ServiceDescriptor(_NestedDescriptorBase):
       definition appears withing the .proto file.
     methods: (list of MethodDescriptor) List of methods provided by this
       service.
+    methods_by_name: (dict str -> MethodDescriptor) Same MethodDescriptor
+      objects as in |methods_by_name|, but indexed by "name" attribute in each
+      MethodDescriptor.
     options: (descriptor_pb2.ServiceOptions) Service options message or
       None to use default service options.
     file: (FileDescriptor) Reference to file info.
   """
 
+  if _USE_C_DESCRIPTORS:
+    _C_DESCRIPTOR_CLASS = _message.ServiceDescriptor
+
+    def __new__(cls, name, full_name, index, methods, options=None, file=None,  # pylint: disable=redefined-builtin
+                serialized_start=None, serialized_end=None):
+      _message.Message._CheckCalledFromGeneratedFile()  # pylint: disable=protected-access
+      return _message.default_pool.FindServiceByName(full_name)
+
   def __init__(self, name, full_name, index, methods, options=None, file=None,
                serialized_start=None, serialized_end=None):
     super(ServiceDescriptor, self).__init__(
@@ -718,16 +732,14 @@ class ServiceDescriptor(_NestedDescriptorBase):
         serialized_end=serialized_end)
     self.index = index
     self.methods = methods
+    self.methods_by_name = dict((m.name, m) for m in methods)
     # Set the containing service for each method in this service.
     for method in self.methods:
       method.containing_service = self
 
   def FindMethodByName(self, name):
     """Searches for the specified method, and returns its descriptor."""
-    for method in self.methods:
-      if name == method.name:
-        return method
-    return None
+    return self.methods_by_name.get(name, None)
 
   def CopyToProto(self, proto):
     """Copies this to a descriptor_pb2.ServiceDescriptorProto.
@@ -754,6 +766,14 @@ class MethodDescriptor(DescriptorBase):
     None to use default method options.
   """
 
+  if _USE_C_DESCRIPTORS:
+    _C_DESCRIPTOR_CLASS = _message.MethodDescriptor
+
+    def __new__(cls, name, full_name, index, containing_service,
+                input_type, output_type, options=None):
+      _message.Message._CheckCalledFromGeneratedFile()  # pylint: disable=protected-access
+      return _message.default_pool.FindMethodByName(full_name)
+
   def __init__(self, name, full_name, index, containing_service,
                input_type, output_type, options=None):
     """The arguments are as described in the description of MethodDescriptor
@@ -788,6 +808,7 @@ class FileDescriptor(DescriptorBase):
   message_types_by_name: Dict of message names of their descriptors.
   enum_types_by_name: Dict of enum names and their descriptors.
   extensions_by_name: Dict of extension names and their descriptors.
+  services_by_name: Dict of services names and their descriptors.
   pool: the DescriptorPool this descriptor belongs to.  When not passed to the
     constructor, the global default pool is used.
   """
@@ -825,6 +846,7 @@ class FileDescriptor(DescriptorBase):
 
     self.enum_types_by_name = {}
     self.extensions_by_name = {}
+    self.services_by_name = {}
     self.dependencies = (dependencies or [])
     self.public_dependencies = (public_dependencies or [])
 

+ 64 - 1
python/google/protobuf/descriptor_pool.py

@@ -394,6 +394,11 @@ class DescriptorPool(object):
               desc_proto_prefix, desc_proto.name, scope)
           file_descriptor.message_types_by_name[desc_proto.name] = desc
 
+        for index, service_proto in enumerate(file_proto.service):
+          file_descriptor.services_by_name[service_proto.name] = (
+              self._MakeServiceDescriptor(service_proto, index, scope,
+                                          file_proto.package, file_descriptor))
+
       self.Add(file_proto)
       self._file_descriptors[file_proto.name] = file_descriptor
 
@@ -441,7 +446,7 @@ class DescriptorPool(object):
         for index, extension in enumerate(desc_proto.extension)]
     oneofs = [
         descriptor.OneofDescriptor(desc.name, '.'.join((desc_name, desc.name)),
-                                   index, None, [])
+                                   index, None, [], desc.options)
         for index, desc in enumerate(desc_proto.oneof_decl)]
     extension_ranges = [(r.start, r.end) for r in desc_proto.extension_range]
     if extension_ranges:
@@ -679,6 +684,64 @@ class DescriptorPool(object):
         options=value_proto.options,
         type=None)
 
+  def _MakeServiceDescriptor(self, service_proto, service_index, scope,
+                             package, file_desc):
+    """Make a protobuf ServiceDescriptor given a ServiceDescriptorProto.
+
+    Args:
+      service_proto: The descriptor_pb2.ServiceDescriptorProto protobuf message.
+      service_index: The index of the service in the File.
+      scope: Dict mapping short and full symbols to message and enum types.
+      package: Optional package name for the new message EnumDescriptor.
+      file_desc: The file containing the service descriptor.
+
+    Returns:
+      The added descriptor.
+    """
+
+    if package:
+      service_name = '.'.join((package, service_proto.name))
+    else:
+      service_name = service_proto.name
+
+    methods = [self._MakeMethodDescriptor(method_proto, service_name, package,
+                                          scope, index)
+               for index, method_proto in enumerate(service_proto.method)]
+    desc = descriptor.ServiceDescriptor(name=service_proto.name,
+                                        full_name=service_name,
+                                        index=service_index,
+                                        methods=methods,
+                                        options=service_proto.options,
+                                        file=file_desc)
+    return desc
+
+  def _MakeMethodDescriptor(self, method_proto, service_name, package, scope,
+                            index):
+    """Creates a method descriptor from a MethodDescriptorProto.
+
+    Args:
+      method_proto: The proto describing the method.
+      service_name: The name of the containing service.
+      package: Optional package name to look up for types.
+      scope: Scope containing available types.
+      index: Index of the method in the service.
+
+    Returns:
+      An initialized MethodDescriptor object.
+    """
+    full_name = '.'.join((service_name, method_proto.name))
+    input_type = self._GetTypeFromScope(
+        package, method_proto.input_type, scope)
+    output_type = self._GetTypeFromScope(
+        package, method_proto.output_type, scope)
+    return descriptor.MethodDescriptor(name=method_proto.name,
+                                       full_name=full_name,
+                                       index=index,
+                                       containing_service=None,
+                                       input_type=input_type,
+                                       output_type=output_type,
+                                       options=method_proto.options)
+
   def _ExtractSymbols(self, descriptors):
     """Pulls out all the symbols from descriptor protos.
 

+ 5 - 1
python/google/protobuf/internal/containers.py

@@ -594,7 +594,11 @@ class MessageMap(MutableMapping):
 
   def MergeFrom(self, other):
     for key in other:
-      self[key].MergeFrom(other[key])
+      # According to documentation: "When parsing from the wire or when merging,
+      # if there are duplicate map keys the last key seen is used".
+      if key in self:
+        del self[key]
+      self[key].CopyFrom(other[key])
     # self._message_listener.Modified() not required here, because
     # mutations to submessages already propagate.
 

+ 18 - 0
python/google/protobuf/internal/descriptor_pool_test.py

@@ -51,6 +51,7 @@ from google.protobuf.internal import descriptor_pool_test1_pb2
 from google.protobuf.internal import descriptor_pool_test2_pb2
 from google.protobuf.internal import factory_test1_pb2
 from google.protobuf.internal import factory_test2_pb2
+from google.protobuf.internal import file_options_test_pb2
 from google.protobuf.internal import more_messages_pb2
 from google.protobuf import descriptor
 from google.protobuf import descriptor_database
@@ -630,6 +631,23 @@ class AddDescriptorTest(unittest.TestCase):
     self.assertEqual(pool.FindMessageTypeByName('package.Message').name,
                      'Message')
 
+  def testFileDescriptorOptionsWithCustomDescriptorPool(self):
+    # Create a descriptor pool, and add a new FileDescriptorProto to it.
+    pool = descriptor_pool.DescriptorPool()
+    file_name = 'file_descriptor_options_with_custom_descriptor_pool.proto'
+    file_descriptor_proto = descriptor_pb2.FileDescriptorProto(name=file_name)
+    extension_id = file_options_test_pb2.foo_options
+    file_descriptor_proto.options.Extensions[extension_id].foo_name = 'foo'
+    pool.Add(file_descriptor_proto)
+    # The options set on the FileDescriptorProto should be available in the
+    # descriptor even if they contain extensions that cannot be deserialized
+    # using the pool.
+    file_descriptor = pool.FindFileByName(file_name)
+    options = file_descriptor.GetOptions()
+    self.assertEqual('foo', options.Extensions[extension_id].foo_name)
+    # The object returned by GetOptions() is cached.
+    self.assertIs(options, file_descriptor.GetOptions())
+
 
 @unittest.skipIf(
     api_implementation.Type() != 'cpp',

+ 21 - 20
python/google/protobuf/internal/descriptor_test.py

@@ -77,27 +77,24 @@ class DescriptorTest(unittest.TestCase):
     enum_proto.value.add(name='FOREIGN_BAR', number=5)
     enum_proto.value.add(name='FOREIGN_BAZ', number=6)
 
+    file_proto.message_type.add(name='ResponseMessage')
+    service_proto = file_proto.service.add(
+        name='Service')
+    method_proto = service_proto.method.add(
+        name='CallMethod',
+        input_type='.protobuf_unittest.NestedMessage',
+        output_type='.protobuf_unittest.ResponseMessage')
+
+    # Note: Calling DescriptorPool.Add() multiple times with the same file only
+    # works if the input is canonical; in particular, all type names must be
+    # fully qualified.
     self.pool = self.GetDescriptorPool()
     self.pool.Add(file_proto)
     self.my_file = self.pool.FindFileByName(file_proto.name)
     self.my_message = self.my_file.message_types_by_name[message_proto.name]
     self.my_enum = self.my_message.enum_types_by_name[enum_proto.name]
-
-    self.my_method = descriptor.MethodDescriptor(
-        name='Bar',
-        full_name='protobuf_unittest.TestService.Bar',
-        index=0,
-        containing_service=None,
-        input_type=None,
-        output_type=None)
-    self.my_service = descriptor.ServiceDescriptor(
-        name='TestServiceWithOptions',
-        full_name='protobuf_unittest.TestServiceWithOptions',
-        file=self.my_file,
-        index=0,
-        methods=[
-            self.my_method
-        ])
+    self.my_service = self.my_file.services_by_name[service_proto.name]
+    self.my_method = self.my_service.methods_by_name[method_proto.name]
 
   def GetDescriptorPool(self):
     return symbol_database.Default().pool
@@ -139,13 +136,14 @@ class DescriptorTest(unittest.TestCase):
     file_descriptor = unittest_custom_options_pb2.DESCRIPTOR
     message_descriptor =\
         unittest_custom_options_pb2.TestMessageWithCustomOptions.DESCRIPTOR
-    field_descriptor = message_descriptor.fields_by_name["field1"]
-    enum_descriptor = message_descriptor.enum_types_by_name["AnEnum"]
+    field_descriptor = message_descriptor.fields_by_name['field1']
+    oneof_descriptor = message_descriptor.oneofs_by_name['AnOneof']
+    enum_descriptor = message_descriptor.enum_types_by_name['AnEnum']
     enum_value_descriptor =\
-        message_descriptor.enum_values_by_name["ANENUM_VAL2"]
+        message_descriptor.enum_values_by_name['ANENUM_VAL2']
     service_descriptor =\
         unittest_custom_options_pb2.TestServiceWithCustomOptions.DESCRIPTOR
-    method_descriptor = service_descriptor.FindMethodByName("Foo")
+    method_descriptor = service_descriptor.FindMethodByName('Foo')
 
     file_options = file_descriptor.GetOptions()
     file_opt1 = unittest_custom_options_pb2.file_opt1
@@ -158,6 +156,9 @@ class DescriptorTest(unittest.TestCase):
     self.assertEqual(8765432109, field_options.Extensions[field_opt1])
     field_opt2 = unittest_custom_options_pb2.field_opt2
     self.assertEqual(42, field_options.Extensions[field_opt2])
+    oneof_options = oneof_descriptor.GetOptions()
+    oneof_opt1 = unittest_custom_options_pb2.oneof_opt1
+    self.assertEqual(-99, oneof_options.Extensions[oneof_opt1])
     enum_options = enum_descriptor.GetOptions()
     enum_opt1 = unittest_custom_options_pb2.enum_opt1
     self.assertEqual(-789, enum_options.Extensions[enum_opt1])

+ 43 - 0
python/google/protobuf/internal/file_options_test.proto

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

+ 13 - 0
python/google/protobuf/internal/json_format_test.py

@@ -643,6 +643,19 @@ class JsonFormatTest(JsonFormatBase):
                     'Message type "proto3.TestMessage" has no field named '
                     '"unknownName".')
 
+  def testIgnoreUnknownField(self):
+    text = '{"unknownName": 1}'
+    parsed_message = json_format_proto3_pb2.TestMessage()
+    json_format.Parse(text, parsed_message, ignore_unknown_fields=True)
+    text = ('{\n'
+            '  "repeatedValue": [ {\n'
+            '    "@type": "type.googleapis.com/proto3.MessageType",\n'
+            '    "unknownName": 1\n'
+            '  }]\n'
+            '}\n')
+    parsed_message = json_format_proto3_pb2.TestAny()
+    json_format.Parse(text, parsed_message, ignore_unknown_fields=True)
+
   def testDuplicateField(self):
     # Duplicate key check is not supported for python2.6
     if sys.version_info < (2, 7):

+ 4 - 0
python/google/protobuf/internal/message_test.py

@@ -1435,6 +1435,8 @@ class Proto3Test(unittest.TestCase):
     msg2.map_int32_int32[12] = 55
     msg2.map_int64_int64[88] = 99
     msg2.map_int32_foreign_message[222].c = 15
+    msg2.map_int32_foreign_message[222].d = 20
+    old_map_value = msg2.map_int32_foreign_message[222]
 
     msg2.MergeFrom(msg)
 
@@ -1444,6 +1446,8 @@ class Proto3Test(unittest.TestCase):
     self.assertEqual(99, msg2.map_int64_int64[88])
     self.assertEqual(5, msg2.map_int32_foreign_message[111].c)
     self.assertEqual(10, msg2.map_int32_foreign_message[222].c)
+    self.assertFalse(msg2.map_int32_foreign_message[222].HasField('d'))
+    self.assertEqual(15, old_map_value.c)
 
     # Verify that there is only one entry per key, even though the MergeFrom
     # may have internally created multiple entries for a single key in the

+ 412 - 212
python/google/protobuf/internal/text_format_test.py

@@ -40,12 +40,13 @@ import six
 import string
 
 try:
-  import unittest2 as unittest  #PY26
+  import unittest2 as unittest  # PY26, pylint: disable=g-import-not-at-top
 except ImportError:
-  import unittest
+  import unittest  # pylint: disable=g-import-not-at-top
 
 from google.protobuf.internal import _parameterized
 
+from google.protobuf import any_test_pb2
 from google.protobuf import map_unittest_pb2
 from google.protobuf import unittest_mset_pb2
 from google.protobuf import unittest_pb2
@@ -53,6 +54,7 @@ from google.protobuf import unittest_proto3_arena_pb2
 from google.protobuf.internal import api_implementation
 from google.protobuf.internal import test_util
 from google.protobuf.internal import message_set_extensions_pb2
+from google.protobuf import descriptor_pool
 from google.protobuf import text_format
 
 
@@ -90,13 +92,11 @@ class TextFormatBase(unittest.TestCase):
                .replace('e-0','e-').replace('e-0','e-')
     # Floating point fields are printed with .0 suffix even if they are
     # actualy integer numbers.
-    text = re.compile('\.0$', re.MULTILINE).sub('', text)
+    text = re.compile(r'\.0$', re.MULTILINE).sub('', text)
     return text
 
 
-@_parameterized.Parameters(
-    (unittest_pb2),
-    (unittest_proto3_arena_pb2))
+@_parameterized.Parameters((unittest_pb2), (unittest_proto3_arena_pb2))
 class TextFormatTest(TextFormatBase):
 
   def testPrintExotic(self, message_module):
@@ -120,8 +120,10 @@ class TextFormatTest(TextFormatBase):
         'repeated_string: "\\303\\274\\352\\234\\237"\n')
 
   def testPrintExoticUnicodeSubclass(self, message_module):
+
     class UnicodeSub(six.text_type):
       pass
+
     message = message_module.TestAllTypes()
     message.repeated_string.append(UnicodeSub(u'\u00fc\ua71f'))
     self.CompareToGoldenText(
@@ -165,8 +167,8 @@ class TextFormatTest(TextFormatBase):
     message.repeated_string.append('\000\001\a\b\f\n\r\t\v\\\'"')
     message.repeated_string.append(u'\u00fc\ua71f')
     self.CompareToGoldenText(
-        self.RemoveRedundantZeros(
-            text_format.MessageToString(message, as_one_line=True)),
+        self.RemoveRedundantZeros(text_format.MessageToString(
+            message, as_one_line=True)),
         'repeated_int64: -9223372036854775808'
         ' repeated_uint64: 18446744073709551615'
         ' repeated_double: 123.456'
@@ -187,21 +189,23 @@ class TextFormatTest(TextFormatBase):
     message.repeated_string.append(u'\u00fc\ua71f')
 
     # Test as_utf8 = False.
-    wire_text = text_format.MessageToString(
-        message, as_one_line=True, as_utf8=False)
+    wire_text = text_format.MessageToString(message,
+                                            as_one_line=True,
+                                            as_utf8=False)
     parsed_message = message_module.TestAllTypes()
     r = text_format.Parse(wire_text, parsed_message)
     self.assertIs(r, parsed_message)
     self.assertEqual(message, parsed_message)
 
     # Test as_utf8 = True.
-    wire_text = text_format.MessageToString(
-        message, as_one_line=True, as_utf8=True)
+    wire_text = text_format.MessageToString(message,
+                                            as_one_line=True,
+                                            as_utf8=True)
     parsed_message = message_module.TestAllTypes()
     r = text_format.Parse(wire_text, parsed_message)
     self.assertIs(r, parsed_message)
     self.assertEqual(message, parsed_message,
-                      '\n%s != %s' % (message, parsed_message))
+                     '\n%s != %s' % (message, parsed_message))
 
   def testPrintRawUtf8String(self, message_module):
     message = message_module.TestAllTypes()
@@ -211,7 +215,7 @@ class TextFormatTest(TextFormatBase):
     parsed_message = message_module.TestAllTypes()
     text_format.Parse(text, parsed_message)
     self.assertEqual(message, parsed_message,
-                      '\n%s != %s' % (message, parsed_message))
+                     '\n%s != %s' % (message, parsed_message))
 
   def testPrintFloatFormat(self, message_module):
     # Check that float_format argument is passed to sub-message formatting.
@@ -232,14 +236,15 @@ class TextFormatTest(TextFormatBase):
     message.payload.repeated_double.append(.000078900)
     formatted_fields = ['optional_float: 1.25',
                         'optional_double: -3.45678901234568e-6',
-                        'repeated_float: -5642',
-                        'repeated_double: 7.89e-5']
+                        'repeated_float: -5642', 'repeated_double: 7.89e-5']
     text_message = text_format.MessageToString(message, float_format='.15g')
     self.CompareToGoldenText(
         self.RemoveRedundantZeros(text_message),
-        'payload {{\n  {0}\n  {1}\n  {2}\n  {3}\n}}\n'.format(*formatted_fields))
+        'payload {{\n  {0}\n  {1}\n  {2}\n  {3}\n}}\n'.format(
+            *formatted_fields))
     # as_one_line=True is a separate code branch where float_format is passed.
-    text_message = text_format.MessageToString(message, as_one_line=True,
+    text_message = text_format.MessageToString(message,
+                                               as_one_line=True,
                                                float_format='.15g')
     self.CompareToGoldenText(
         self.RemoveRedundantZeros(text_message),
@@ -311,8 +316,7 @@ class TextFormatTest(TextFormatBase):
     self.assertEqual(123.456, message.repeated_double[0])
     self.assertEqual(1.23e22, message.repeated_double[1])
     self.assertEqual(1.23e-18, message.repeated_double[2])
-    self.assertEqual(
-        '\000\001\a\b\f\n\r\t\v\\\'"', message.repeated_string[0])
+    self.assertEqual('\000\001\a\b\f\n\r\t\v\\\'"', message.repeated_string[0])
     self.assertEqual('foocorgegrault', message.repeated_string[1])
     self.assertEqual(u'\u00fc\ua71f', message.repeated_string[2])
     self.assertEqual(u'\u00fc', message.repeated_string[3])
@@ -371,45 +375,38 @@ class TextFormatTest(TextFormatBase):
   def testParseSingleWord(self, message_module):
     message = message_module.TestAllTypes()
     text = 'foo'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
-         r'"foo".'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        r'1:1 : Message type "\w+.TestAllTypes" has no field named '
+        r'"foo".'), text_format.Parse, text, message)
 
   def testParseUnknownField(self, message_module):
     message = message_module.TestAllTypes()
     text = 'unknown_field: 8\n'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
-         r'"unknown_field".'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        r'1:1 : Message type "\w+.TestAllTypes" has no field named '
+        r'"unknown_field".'), text_format.Parse, text, message)
 
   def testParseBadEnumValue(self, message_module):
     message = message_module.TestAllTypes()
     text = 'optional_nested_enum: BARR'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
-         r'has no value named BARR.'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError,
+                          (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
+                           r'has no value named BARR.'), text_format.Parse,
+                          text, message)
 
     message = message_module.TestAllTypes()
     text = 'optional_nested_enum: 100'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
-         r'has no value with number 100.'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError,
+                          (r'1:23 : Enum type "\w+.TestAllTypes.NestedEnum" '
+                           r'has no value with number 100.'), text_format.Parse,
+                          text, message)
 
   def testParseBadIntValue(self, message_module):
     message = message_module.TestAllTypes()
     text = 'optional_int32: bork'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        ('1:17 : Couldn\'t parse integer: bork'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError,
+                          ('1:17 : Couldn\'t parse integer: bork'),
+                          text_format.Parse, text, message)
 
   def testParseStringFieldUnescape(self, message_module):
     message = message_module.TestAllTypes()
@@ -419,6 +416,7 @@ class TextFormatTest(TextFormatBase):
                repeated_string: "\\\\xf\\\\x62"
                repeated_string: "\\\\\xf\\\\\x62"
                repeated_string: "\x5cx20"'''
+
     text_format.Parse(text, message)
 
     SLASH = '\\'
@@ -433,8 +431,7 @@ class TextFormatTest(TextFormatBase):
 
   def testMergeDuplicateScalars(self, message_module):
     message = message_module.TestAllTypes()
-    text = ('optional_int32: 42 '
-            'optional_int32: 67')
+    text = ('optional_int32: 42 ' 'optional_int32: 67')
     r = text_format.Merge(text, message)
     self.assertIs(r, message)
     self.assertEqual(67, message.optional_int32)
@@ -455,13 +452,11 @@ class TextFormatTest(TextFormatBase):
     self.assertEqual('oneof_uint32', m2.WhichOneof('oneof_field'))
 
   def testParseMultipleOneof(self, message_module):
-    m_string = '\n'.join([
-        'oneof_uint32: 11',
-        'oneof_string: "foo"'])
+    m_string = '\n'.join(['oneof_uint32: 11', 'oneof_string: "foo"'])
     m2 = message_module.TestAllTypes()
     if message_module is unittest_pb2:
-      with self.assertRaisesRegexp(
-          text_format.ParseError, ' is specified along with field '):
+      with self.assertRaisesRegexp(text_format.ParseError,
+                                   ' is specified along with field '):
         text_format.Parse(m_string, m2)
     else:
       text_format.Parse(m_string, m2)
@@ -477,8 +472,8 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
     message = unittest_pb2.TestAllTypes()
     test_util.SetAllFields(message)
     self.CompareToGoldenFile(
-        self.RemoveRedundantZeros(
-            text_format.MessageToString(message, pointy_brackets=True)),
+        self.RemoveRedundantZeros(text_format.MessageToString(
+            message, pointy_brackets=True)),
         'text_format_unittest_data_pointy_oneof.txt')
 
   def testParseGolden(self):
@@ -499,14 +494,6 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
         self.RemoveRedundantZeros(text_format.MessageToString(message)),
         'text_format_unittest_data_oneof_implemented.txt')
 
-  def testPrintAllFieldsPointy(self):
-    message = unittest_pb2.TestAllTypes()
-    test_util.SetAllFields(message)
-    self.CompareToGoldenFile(
-        self.RemoveRedundantZeros(
-            text_format.MessageToString(message, pointy_brackets=True)),
-        'text_format_unittest_data_pointy_oneof.txt')
-
   def testPrintInIndexOrder(self):
     message = unittest_pb2.TestFieldOrderings()
     message.my_string = '115'
@@ -520,8 +507,7 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
         'my_string: \"115\"\nmy_int: 101\nmy_float: 111\n'
         'optional_nested_message {\n  oo: 0\n  bb: 1\n}\n')
     self.CompareToGoldenText(
-        self.RemoveRedundantZeros(text_format.MessageToString(
-            message)),
+        self.RemoveRedundantZeros(text_format.MessageToString(message)),
         'my_int: 101\nmy_string: \"115\"\nmy_float: 111\n'
         'optional_nested_message {\n  bb: 1\n  oo: 0\n}\n')
 
@@ -552,14 +538,13 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
     message.map_int64_int64[-2**33] = -2**34
     message.map_uint32_uint32[123] = 456
     message.map_uint64_uint64[2**33] = 2**34
-    message.map_string_string["abc"] = "123"
+    message.map_string_string['abc'] = '123'
     message.map_int32_foreign_message[111].c = 5
 
     # Maps are serialized to text format using their underlying repeated
     # representation.
     self.CompareToGoldenText(
-        text_format.MessageToString(message),
-        'map_int32_int32 {\n'
+        text_format.MessageToString(message), 'map_int32_int32 {\n'
         '  key: -123\n'
         '  value: -456\n'
         '}\n'
@@ -592,9 +577,8 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
       message.map_string_string[letter] = 'dummy'
     for letter in reversed(string.ascii_uppercase[0:13]):
       message.map_string_string[letter] = 'dummy'
-    golden = ''.join((
-        'map_string_string {\n  key: "%c"\n  value: "dummy"\n}\n' % (letter,)
-        for letter in string.ascii_uppercase))
+    golden = ''.join(('map_string_string {\n  key: "%c"\n  value: "dummy"\n}\n'
+                      % (letter,) for letter in string.ascii_uppercase))
     self.CompareToGoldenText(text_format.MessageToString(message), golden)
 
   def testMapOrderSemantics(self):
@@ -602,9 +586,7 @@ class OnlyWorksWithProto2RightNowTests(TextFormatBase):
     # The C++ implementation emits defaulted-value fields, while the Python
     # implementation does not.  Adjusting for this is awkward, but it is
     # valuable to test against a common golden file.
-    line_blacklist = ('  key: 0\n',
-                      '  value: 0\n',
-                      '  key: false\n',
+    line_blacklist = ('  key: 0\n', '  value: 0\n', '  key: false\n',
                       '  value: false\n')
     golden_lines = [line for line in golden_lines if line not in line_blacklist]
 
@@ -627,8 +609,7 @@ class Proto2Tests(TextFormatBase):
     message.message_set.Extensions[ext1].i = 23
     message.message_set.Extensions[ext2].str = 'foo'
     self.CompareToGoldenText(
-        text_format.MessageToString(message),
-        'message_set {\n'
+        text_format.MessageToString(message), 'message_set {\n'
         '  [protobuf_unittest.TestMessageSetExtension1] {\n'
         '    i: 23\n'
         '  }\n'
@@ -654,16 +635,14 @@ class Proto2Tests(TextFormatBase):
     message.message_set.Extensions[ext1].i = 23
     message.message_set.Extensions[ext2].str = 'foo'
     text_format.PrintMessage(message, out, use_field_number=True)
-    self.CompareToGoldenText(
-        out.getvalue(),
-        '1 {\n'
-        '  1545008 {\n'
-        '    15: 23\n'
-        '  }\n'
-        '  1547769 {\n'
-        '    25: \"foo\"\n'
-        '  }\n'
-        '}\n')
+    self.CompareToGoldenText(out.getvalue(), '1 {\n'
+                             '  1545008 {\n'
+                             '    15: 23\n'
+                             '  }\n'
+                             '  1547769 {\n'
+                             '    25: \"foo\"\n'
+                             '  }\n'
+                             '}\n')
     out.close()
 
   def testPrintMessageSetAsOneLine(self):
@@ -685,8 +664,7 @@ class Proto2Tests(TextFormatBase):
 
   def testParseMessageSet(self):
     message = unittest_pb2.TestAllTypes()
-    text = ('repeated_uint64: 1\n'
-            'repeated_uint64: 2\n')
+    text = ('repeated_uint64: 1\n' 'repeated_uint64: 2\n')
     text_format.Parse(text, message)
     self.assertEqual(1, message.repeated_uint64[0])
     self.assertEqual(2, message.repeated_uint64[1])
@@ -708,8 +686,7 @@ class Proto2Tests(TextFormatBase):
 
   def testParseMessageByFieldNumber(self):
     message = unittest_pb2.TestAllTypes()
-    text = ('34: 1\n'
-            'repeated_uint64: 2\n')
+    text = ('34: 1\n' 'repeated_uint64: 2\n')
     text_format.Parse(text, message, allow_field_number=True)
     self.assertEqual(1, message.repeated_uint64[0])
     self.assertEqual(2, message.repeated_uint64[1])
@@ -732,12 +709,9 @@ class Proto2Tests(TextFormatBase):
     # Can't parse field number without set allow_field_number=True.
     message = unittest_pb2.TestAllTypes()
     text = '34:1\n'
-    six.assertRaisesRegex(
-        self,
-        text_format.ParseError,
-        (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
-         r'"34".'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        r'1:1 : Message type "\w+.TestAllTypes" has no field named '
+        r'"34".'), text_format.Parse, text, message)
 
     # Can't parse if field number is not found.
     text = '1234:1\n'
@@ -746,7 +720,10 @@ class Proto2Tests(TextFormatBase):
         text_format.ParseError,
         (r'1:1 : Message type "\w+.TestAllTypes" has no field named '
          r'"1234".'),
-        text_format.Parse, text, message, allow_field_number=True)
+        text_format.Parse,
+        text,
+        message,
+        allow_field_number=True)
 
   def testPrintAllExtensions(self):
     message = unittest_pb2.TestAllExtensions()
@@ -824,7 +801,9 @@ class Proto2Tests(TextFormatBase):
     six.assertRaisesRegex(self,
                           text_format.ParseError,
                           'Invalid field value: }',
-                          text_format.Parse, malformed, message,
+                          text_format.Parse,
+                          malformed,
+                          message,
                           allow_unknown_extension=True)
 
     message = unittest_mset_pb2.TestMessageSetContainer()
@@ -836,7 +815,9 @@ class Proto2Tests(TextFormatBase):
     six.assertRaisesRegex(self,
                           text_format.ParseError,
                           'Invalid field value: "',
-                          text_format.Parse, malformed, message,
+                          text_format.Parse,
+                          malformed,
+                          message,
                           allow_unknown_extension=True)
 
     message = unittest_mset_pb2.TestMessageSetContainer()
@@ -848,7 +829,9 @@ class Proto2Tests(TextFormatBase):
     six.assertRaisesRegex(self,
                           text_format.ParseError,
                           'Invalid field value: "',
-                          text_format.Parse, malformed, message,
+                          text_format.Parse,
+                          malformed,
+                          message,
                           allow_unknown_extension=True)
 
     message = unittest_mset_pb2.TestMessageSetContainer()
@@ -860,7 +843,9 @@ class Proto2Tests(TextFormatBase):
     six.assertRaisesRegex(self,
                           text_format.ParseError,
                           '5:1 : Expected ">".',
-                          text_format.Parse, malformed, message,
+                          text_format.Parse,
+                          malformed,
+                          message,
                           allow_unknown_extension=True)
 
     # Don't allow unknown fields with allow_unknown_extension=True.
@@ -874,7 +859,9 @@ class Proto2Tests(TextFormatBase):
                           ('2:3 : Message type '
                            '"proto2_wireformat_unittest.TestMessageSet" has no'
                            ' field named "unknown_field".'),
-                          text_format.Parse, malformed, message,
+                          text_format.Parse,
+                          malformed,
+                          message,
                           allow_unknown_extension=True)
 
     # Parse known extension correcty.
@@ -896,67 +883,57 @@ class Proto2Tests(TextFormatBase):
   def testParseBadExtension(self):
     message = unittest_pb2.TestAllExtensions()
     text = '[unknown_extension]: 8\n'
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        '1:2 : Extension "unknown_extension" not registered.',
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError,
+                          '1:2 : Extension "unknown_extension" not registered.',
+                          text_format.Parse, text, message)
     message = unittest_pb2.TestAllTypes()
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        ('1:2 : Message type "protobuf_unittest.TestAllTypes" does not have '
-         'extensions.'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        '1:2 : Message type "protobuf_unittest.TestAllTypes" does not have '
+        'extensions.'), text_format.Parse, text, message)
 
   def testMergeDuplicateExtensionScalars(self):
     message = unittest_pb2.TestAllExtensions()
     text = ('[protobuf_unittest.optional_int32_extension]: 42 '
             '[protobuf_unittest.optional_int32_extension]: 67')
     text_format.Merge(text, message)
-    self.assertEqual(
-        67,
-        message.Extensions[unittest_pb2.optional_int32_extension])
+    self.assertEqual(67,
+                     message.Extensions[unittest_pb2.optional_int32_extension])
 
   def testParseDuplicateExtensionScalars(self):
     message = unittest_pb2.TestAllExtensions()
     text = ('[protobuf_unittest.optional_int32_extension]: 42 '
             '[protobuf_unittest.optional_int32_extension]: 67')
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        ('1:96 : Message type "protobuf_unittest.TestAllExtensions" '
-         'should not have multiple '
-         '"protobuf_unittest.optional_int32_extension" extensions.'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        '1:96 : Message type "protobuf_unittest.TestAllExtensions" '
+        'should not have multiple '
+        '"protobuf_unittest.optional_int32_extension" extensions.'),
+                          text_format.Parse, text, message)
 
   def testParseDuplicateNestedMessageScalars(self):
     message = unittest_pb2.TestAllTypes()
     text = ('optional_nested_message { bb: 1 } '
             'optional_nested_message { bb: 2 }')
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        ('1:65 : Message type "protobuf_unittest.TestAllTypes.NestedMessage" '
-         'should not have multiple "bb" fields.'),
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        '1:65 : Message type "protobuf_unittest.TestAllTypes.NestedMessage" '
+        'should not have multiple "bb" fields.'), text_format.Parse, text,
+                          message)
 
   def testParseDuplicateScalars(self):
     message = unittest_pb2.TestAllTypes()
-    text = ('optional_int32: 42 '
-            'optional_int32: 67')
-    six.assertRaisesRegex(self,
-        text_format.ParseError,
-        ('1:36 : Message type "protobuf_unittest.TestAllTypes" should not '
-         'have multiple "optional_int32" fields.'),
-        text_format.Parse, text, message)
+    text = ('optional_int32: 42 ' 'optional_int32: 67')
+    six.assertRaisesRegex(self, text_format.ParseError, (
+        '1:36 : Message type "protobuf_unittest.TestAllTypes" should not '
+        'have multiple "optional_int32" fields.'), text_format.Parse, text,
+                          message)
 
   def testParseGroupNotClosed(self):
     message = unittest_pb2.TestAllTypes()
     text = 'RepeatedGroup: <'
-    six.assertRaisesRegex(self,
-        text_format.ParseError, '1:16 : Expected ">".',
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, '1:16 : Expected ">".',
+                          text_format.Parse, text, message)
     text = 'RepeatedGroup: {'
-    six.assertRaisesRegex(self,
-        text_format.ParseError, '1:16 : Expected "}".',
-        text_format.Parse, text, message)
+    six.assertRaisesRegex(self, text_format.ParseError, '1:16 : Expected "}".',
+                          text_format.Parse, text, message)
 
   def testParseEmptyGroup(self):
     message = unittest_pb2.TestAllTypes()
@@ -1007,10 +984,197 @@ class Proto2Tests(TextFormatBase):
     self.assertEqual(-2**34, message.map_int64_int64[-2**33])
     self.assertEqual(456, message.map_uint32_uint32[123])
     self.assertEqual(2**34, message.map_uint64_uint64[2**33])
-    self.assertEqual("123", message.map_string_string["abc"])
+    self.assertEqual('123', message.map_string_string['abc'])
     self.assertEqual(5, message.map_int32_foreign_message[111].c)
 
 
+class Proto3Tests(unittest.TestCase):
+
+  def testPrintMessageExpandAny(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message,
+                                    descriptor_pool=descriptor_pool.Default()),
+        'any_value {\n'
+        '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+        '    data: "string"\n'
+        '  }\n'
+        '}\n')
+
+  def testPrintMessageExpandAnyRepeated(self):
+    packed_message = unittest_pb2.OneString()
+    message = any_test_pb2.TestAny()
+    packed_message.data = 'string0'
+    message.repeated_any_value.add().Pack(packed_message)
+    packed_message.data = 'string1'
+    message.repeated_any_value.add().Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message,
+                                    descriptor_pool=descriptor_pool.Default()),
+        'repeated_any_value {\n'
+        '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+        '    data: "string0"\n'
+        '  }\n'
+        '}\n'
+        'repeated_any_value {\n'
+        '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+        '    data: "string1"\n'
+        '  }\n'
+        '}\n')
+
+  def testPrintMessageExpandAnyNoDescriptorPool(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message, descriptor_pool=None),
+        'any_value {\n'
+        '  type_url: "type.googleapis.com/protobuf_unittest.OneString"\n'
+        '  value: "\\n\\006string"\n'
+        '}\n')
+
+  def testPrintMessageExpandAnyDescriptorPoolMissingType(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    empty_pool = descriptor_pool.DescriptorPool()
+    self.assertEqual(
+        text_format.MessageToString(message, descriptor_pool=empty_pool),
+        'any_value {\n'
+        '  type_url: "type.googleapis.com/protobuf_unittest.OneString"\n'
+        '  value: "\\n\\006string"\n'
+        '}\n')
+
+  def testPrintMessageExpandAnyPointyBrackets(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message,
+                                    pointy_brackets=True,
+                                    descriptor_pool=descriptor_pool.Default()),
+        'any_value <\n'
+        '  [type.googleapis.com/protobuf_unittest.OneString] <\n'
+        '    data: "string"\n'
+        '  >\n'
+        '>\n')
+
+  def testPrintMessageExpandAnyAsOneLine(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message,
+                                    as_one_line=True,
+                                    descriptor_pool=descriptor_pool.Default()),
+        'any_value {'
+        ' [type.googleapis.com/protobuf_unittest.OneString]'
+        ' { data: "string" } '
+        '}')
+
+  def testPrintMessageExpandAnyAsOneLinePointyBrackets(self):
+    packed_message = unittest_pb2.OneString()
+    packed_message.data = 'string'
+    message = any_test_pb2.TestAny()
+    message.any_value.Pack(packed_message)
+    self.assertEqual(
+        text_format.MessageToString(message,
+                                    as_one_line=True,
+                                    pointy_brackets=True,
+                                    descriptor_pool=descriptor_pool.Default()),
+        'any_value <'
+        ' [type.googleapis.com/protobuf_unittest.OneString]'
+        ' < data: "string" > '
+        '>')
+
+  def testMergeExpandedAny(self):
+    message = any_test_pb2.TestAny()
+    text = ('any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+            '    data: "string"\n'
+            '  }\n'
+            '}\n')
+    text_format.Merge(text, message, descriptor_pool=descriptor_pool.Default())
+    packed_message = unittest_pb2.OneString()
+    message.any_value.Unpack(packed_message)
+    self.assertEqual('string', packed_message.data)
+
+  def testMergeExpandedAnyRepeated(self):
+    message = any_test_pb2.TestAny()
+    text = ('repeated_any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+            '    data: "string0"\n'
+            '  }\n'
+            '}\n'
+            'repeated_any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+            '    data: "string1"\n'
+            '  }\n'
+            '}\n')
+    text_format.Merge(text, message, descriptor_pool=descriptor_pool.Default())
+    packed_message = unittest_pb2.OneString()
+    message.repeated_any_value[0].Unpack(packed_message)
+    self.assertEqual('string0', packed_message.data)
+    message.repeated_any_value[1].Unpack(packed_message)
+    self.assertEqual('string1', packed_message.data)
+
+  def testMergeExpandedAnyPointyBrackets(self):
+    message = any_test_pb2.TestAny()
+    text = ('any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] <\n'
+            '    data: "string"\n'
+            '  >\n'
+            '}\n')
+    text_format.Merge(text, message, descriptor_pool=descriptor_pool.Default())
+    packed_message = unittest_pb2.OneString()
+    message.any_value.Unpack(packed_message)
+    self.assertEqual('string', packed_message.data)
+
+  def testMergeExpandedAnyNoDescriptorPool(self):
+    message = any_test_pb2.TestAny()
+    text = ('any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+            '    data: "string"\n'
+            '  }\n'
+            '}\n')
+    with self.assertRaises(text_format.ParseError) as e:
+      text_format.Merge(text, message, descriptor_pool=None)
+    self.assertEqual(str(e.exception),
+                     'Descriptor pool required to parse expanded Any field')
+
+  def testMergeExpandedAnyDescriptorPoolMissingType(self):
+    message = any_test_pb2.TestAny()
+    text = ('any_value {\n'
+            '  [type.googleapis.com/protobuf_unittest.OneString] {\n'
+            '    data: "string"\n'
+            '  }\n'
+            '}\n')
+    with self.assertRaises(text_format.ParseError) as e:
+      empty_pool = descriptor_pool.DescriptorPool()
+      text_format.Merge(text, message, descriptor_pool=empty_pool)
+    self.assertEqual(
+        str(e.exception),
+        'Type protobuf_unittest.OneString not found in descriptor pool')
+
+  def testMergeUnexpandedAny(self):
+    text = ('any_value {\n'
+            '  type_url: "type.googleapis.com/protobuf_unittest.OneString"\n'
+            '  value: "\\n\\006string"\n'
+            '}\n')
+    message = any_test_pb2.TestAny()
+    text_format.Merge(text, message)
+    packed_message = unittest_pb2.OneString()
+    message.any_value.Unpack(packed_message)
+    self.assertEqual('string', packed_message.data)
+
+
 class TokenizerTest(unittest.TestCase):
 
   def testSimpleTokenCases(self):
@@ -1021,79 +1185,55 @@ class TokenizerTest(unittest.TestCase):
             'ID9: 22 ID10: -111111111111111111 ID11: -22\n'
             'ID12: 2222222222222222222 ID13: 1.23456f ID14: 1.2e+2f '
             'false_bool:  0 true_BOOL:t \n true_bool1:  1 false_BOOL1:f ')
-    tokenizer = text_format._Tokenizer(text.splitlines())
-    methods = [(tokenizer.ConsumeIdentifier, 'identifier1'),
-               ':',
+    tokenizer = text_format.Tokenizer(text.splitlines())
+    methods = [(tokenizer.ConsumeIdentifier, 'identifier1'), ':',
                (tokenizer.ConsumeString, 'string1'),
-               (tokenizer.ConsumeIdentifier, 'identifier2'),
-               ':',
-               (tokenizer.ConsumeInt32, 123),
-               (tokenizer.ConsumeIdentifier, 'identifier3'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'identifier2'), ':',
+               (tokenizer.ConsumeInteger, 123),
+               (tokenizer.ConsumeIdentifier, 'identifier3'), ':',
                (tokenizer.ConsumeString, 'string'),
-               (tokenizer.ConsumeIdentifier, 'identifiER_4'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'identifiER_4'), ':',
                (tokenizer.ConsumeFloat, 1.1e+2),
-               (tokenizer.ConsumeIdentifier, 'ID5'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'ID5'), ':',
                (tokenizer.ConsumeFloat, -0.23),
-               (tokenizer.ConsumeIdentifier, 'ID6'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'ID6'), ':',
                (tokenizer.ConsumeString, 'aaaa\'bbbb'),
-               (tokenizer.ConsumeIdentifier, 'ID7'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'ID7'), ':',
                (tokenizer.ConsumeString, 'aa\"bb'),
-               (tokenizer.ConsumeIdentifier, 'ID8'),
-               ':',
-               '{',
-               (tokenizer.ConsumeIdentifier, 'A'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'ID8'), ':', '{',
+               (tokenizer.ConsumeIdentifier, 'A'), ':',
                (tokenizer.ConsumeFloat, float('inf')),
-               (tokenizer.ConsumeIdentifier, 'B'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'B'), ':',
                (tokenizer.ConsumeFloat, -float('inf')),
-               (tokenizer.ConsumeIdentifier, 'C'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'C'), ':',
                (tokenizer.ConsumeBool, True),
-               (tokenizer.ConsumeIdentifier, 'D'),
-               ':',
-               (tokenizer.ConsumeBool, False),
-               '}',
-               (tokenizer.ConsumeIdentifier, 'ID9'),
-               ':',
-               (tokenizer.ConsumeUint32, 22),
-               (tokenizer.ConsumeIdentifier, 'ID10'),
-               ':',
-               (tokenizer.ConsumeInt64, -111111111111111111),
-               (tokenizer.ConsumeIdentifier, 'ID11'),
-               ':',
-               (tokenizer.ConsumeInt32, -22),
-               (tokenizer.ConsumeIdentifier, 'ID12'),
-               ':',
-               (tokenizer.ConsumeUint64, 2222222222222222222),
-               (tokenizer.ConsumeIdentifier, 'ID13'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'D'), ':',
+               (tokenizer.ConsumeBool, False), '}',
+               (tokenizer.ConsumeIdentifier, 'ID9'), ':',
+               (tokenizer.ConsumeInteger, 22),
+               (tokenizer.ConsumeIdentifier, 'ID10'), ':',
+               (tokenizer.ConsumeInteger, -111111111111111111),
+               (tokenizer.ConsumeIdentifier, 'ID11'), ':',
+               (tokenizer.ConsumeInteger, -22),
+               (tokenizer.ConsumeIdentifier, 'ID12'), ':',
+               (tokenizer.ConsumeInteger, 2222222222222222222),
+               (tokenizer.ConsumeIdentifier, 'ID13'), ':',
                (tokenizer.ConsumeFloat, 1.23456),
-               (tokenizer.ConsumeIdentifier, 'ID14'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'ID14'), ':',
                (tokenizer.ConsumeFloat, 1.2e+2),
-               (tokenizer.ConsumeIdentifier, 'false_bool'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'false_bool'), ':',
                (tokenizer.ConsumeBool, False),
-               (tokenizer.ConsumeIdentifier, 'true_BOOL'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'true_BOOL'), ':',
                (tokenizer.ConsumeBool, True),
-               (tokenizer.ConsumeIdentifier, 'true_bool1'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'true_bool1'), ':',
                (tokenizer.ConsumeBool, True),
-               (tokenizer.ConsumeIdentifier, 'false_BOOL1'),
-               ':',
+               (tokenizer.ConsumeIdentifier, 'false_BOOL1'), ':',
                (tokenizer.ConsumeBool, False)]
 
     i = 0
     while not tokenizer.AtEnd():
       m = methods[i]
-      if type(m) == str:
+      if isinstance(m, str):
         token = tokenizer.token
         self.assertEqual(token, m)
         tokenizer.NextToken()
@@ -1101,59 +1241,119 @@ class TokenizerTest(unittest.TestCase):
         self.assertEqual(m[1], m[0]())
       i += 1
 
-  def testConsumeIntegers(self):
+  def testConsumeAbstractIntegers(self):
     # This test only tests the failures in the integer parsing methods as well
     # as the '0' special cases.
     int64_max = (1 << 63) - 1
     uint32_max = (1 << 32) - 1
     text = '-1 %d %d' % (uint32_max + 1, int64_max + 1)
-    tokenizer = text_format._Tokenizer(text.splitlines())
-    self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint32)
-    self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint64)
-    self.assertEqual(-1, tokenizer.ConsumeInt32())
+    tokenizer = text_format.Tokenizer(text.splitlines())
+    self.assertEqual(-1, tokenizer.ConsumeInteger())
 
-    self.assertRaises(text_format.ParseError, tokenizer.ConsumeUint32)
-    self.assertRaises(text_format.ParseError, tokenizer.ConsumeInt32)
-    self.assertEqual(uint32_max + 1, tokenizer.ConsumeInt64())
+    self.assertEqual(uint32_max + 1, tokenizer.ConsumeInteger())
 
-    self.assertRaises(text_format.ParseError, tokenizer.ConsumeInt64)
-    self.assertEqual(int64_max + 1, tokenizer.ConsumeUint64())
+    self.assertEqual(int64_max + 1, tokenizer.ConsumeInteger())
+    self.assertTrue(tokenizer.AtEnd())
+
+    text = '-0 0'
+    tokenizer = text_format.Tokenizer(text.splitlines())
+    self.assertEqual(0, tokenizer.ConsumeInteger())
+    self.assertEqual(0, tokenizer.ConsumeInteger())
+    self.assertTrue(tokenizer.AtEnd())
+
+  def testConsumeIntegers(self):
+    # This test only tests the failures in the integer parsing methods as well
+    # as the '0' special cases.
+    int64_max = (1 << 63) - 1
+    uint32_max = (1 << 32) - 1
+    text = '-1 %d %d' % (uint32_max + 1, int64_max + 1)
+    tokenizer = text_format.Tokenizer(text.splitlines())
+    self.assertRaises(text_format.ParseError,
+                      text_format._ConsumeUint32, tokenizer)
+    self.assertRaises(text_format.ParseError,
+                      text_format._ConsumeUint64, tokenizer)
+    self.assertEqual(-1, text_format._ConsumeInt32(tokenizer))
+
+    self.assertRaises(text_format.ParseError,
+                      text_format._ConsumeUint32, tokenizer)
+    self.assertRaises(text_format.ParseError,
+                      text_format._ConsumeInt32, tokenizer)
+    self.assertEqual(uint32_max + 1, text_format._ConsumeInt64(tokenizer))
+
+    self.assertRaises(text_format.ParseError,
+                      text_format._ConsumeInt64, tokenizer)
+    self.assertEqual(int64_max + 1, text_format._ConsumeUint64(tokenizer))
     self.assertTrue(tokenizer.AtEnd())
 
     text = '-0 -0 0 0'
-    tokenizer = text_format._Tokenizer(text.splitlines())
-    self.assertEqual(0, tokenizer.ConsumeUint32())
-    self.assertEqual(0, tokenizer.ConsumeUint64())
-    self.assertEqual(0, tokenizer.ConsumeUint32())
-    self.assertEqual(0, tokenizer.ConsumeUint64())
+    tokenizer = text_format.Tokenizer(text.splitlines())
+    self.assertEqual(0, text_format._ConsumeUint32(tokenizer))
+    self.assertEqual(0, text_format._ConsumeUint64(tokenizer))
+    self.assertEqual(0, text_format._ConsumeUint32(tokenizer))
+    self.assertEqual(0, text_format._ConsumeUint64(tokenizer))
     self.assertTrue(tokenizer.AtEnd())
 
   def testConsumeByteString(self):
     text = '"string1\''
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
 
     text = 'string1"'
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
 
     text = '\n"\\xt"'
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
 
     text = '\n"\\"'
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
 
     text = '\n"\\x"'
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeByteString)
 
   def testConsumeBool(self):
     text = 'not-a-bool'
-    tokenizer = text_format._Tokenizer(text.splitlines())
+    tokenizer = text_format.Tokenizer(text.splitlines())
     self.assertRaises(text_format.ParseError, tokenizer.ConsumeBool)
 
+  def testSkipComment(self):
+    tokenizer = text_format.Tokenizer('# some comment'.splitlines())
+    self.assertTrue(tokenizer.AtEnd())
+    self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment)
+
+  def testConsumeComment(self):
+    tokenizer = text_format.Tokenizer('# some comment'.splitlines(),
+                                      skip_comments=False)
+    self.assertFalse(tokenizer.AtEnd())
+    self.assertEqual('# some comment', tokenizer.ConsumeComment())
+    self.assertTrue(tokenizer.AtEnd())
+
+  def testConsumeTwoComments(self):
+    text = '# some comment\n# another comment'
+    tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False)
+    self.assertEqual('# some comment', tokenizer.ConsumeComment())
+    self.assertFalse(tokenizer.AtEnd())
+    self.assertEqual('# another comment', tokenizer.ConsumeComment())
+    self.assertTrue(tokenizer.AtEnd())
+
+  def testConsumeTrailingComment(self):
+    text = 'some_number: 4\n# some comment'
+    tokenizer = text_format.Tokenizer(text.splitlines(), skip_comments=False)
+    self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment)
+
+    self.assertEqual('some_number', tokenizer.ConsumeIdentifier())
+    self.assertEqual(tokenizer.token, ':')
+    tokenizer.NextToken()
+    self.assertRaises(text_format.ParseError, tokenizer.ConsumeComment)
+    self.assertEqual(4, tokenizer.ConsumeInteger())
+    self.assertFalse(tokenizer.AtEnd())
+
+    self.assertEqual('# some comment', tokenizer.ConsumeComment())
+    self.assertTrue(tokenizer.AtEnd())
+
 
 if __name__ == '__main__':
   unittest.main()

+ 384 - 378
python/google/protobuf/json_format.py

@@ -53,6 +53,7 @@ import re
 import six
 import sys
 
+from operator import methodcaller
 from google.protobuf import descriptor
 from google.protobuf import symbol_database
 
@@ -98,22 +99,8 @@ def MessageToJson(message, including_default_value_fields=False):
   Returns:
     A string containing the JSON formatted protocol buffer message.
   """
-  js = _MessageToJsonObject(message, including_default_value_fields)
-  return json.dumps(js, indent=2)
-
-
-def _MessageToJsonObject(message, including_default_value_fields):
-  """Converts message to an object according to Proto3 JSON Specification."""
-  message_descriptor = message.DESCRIPTOR
-  full_name = message_descriptor.full_name
-  if _IsWrapperMessage(message_descriptor):
-    return _WrapperMessageToJsonObject(message)
-  if full_name in _WKTJSONMETHODS:
-    return _WKTJSONMETHODS[full_name][0](
-        message, including_default_value_fields)
-  js = {}
-  return _RegularMessageToJsonObject(
-      message, js, including_default_value_fields)
+  printer = _Printer(including_default_value_fields)
+  return printer.ToJsonString(message)
 
 
 def _IsMapEntry(field):
@@ -122,179 +109,179 @@ def _IsMapEntry(field):
           field.message_type.GetOptions().map_entry)
 
 
-def _RegularMessageToJsonObject(message, js, including_default_value_fields):
-  """Converts normal message according to Proto3 JSON Specification."""
-  fields = message.ListFields()
-  include_default = including_default_value_fields
+class _Printer(object):
+  """JSON format printer for protocol message."""
 
-  try:
-    for field, value in fields:
-      name = field.camelcase_name
-      if _IsMapEntry(field):
-        # Convert a map field.
-        v_field = field.message_type.fields_by_name['value']
-        js_map = {}
-        for key in value:
-          if isinstance(key, bool):
-            if key:
-              recorded_key = 'true'
-            else:
-              recorded_key = 'false'
-          else:
-            recorded_key = key
-          js_map[recorded_key] = _FieldToJsonObject(
-              v_field, value[key], including_default_value_fields)
-        js[name] = js_map
-      elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
-        # Convert a repeated field.
-        js[name] = [_FieldToJsonObject(field, k, include_default)
-                    for k in value]
-      else:
-        js[name] = _FieldToJsonObject(field, value, include_default)
-
-    # Serialize default value if including_default_value_fields is True.
-    if including_default_value_fields:
-      message_descriptor = message.DESCRIPTOR
-      for field in message_descriptor.fields:
-        # Singular message fields and oneof fields will not be affected.
-        if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
-             field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
-            field.containing_oneof):
-          continue
-        name = field.camelcase_name
-        if name in js:
-          # Skip the field which has been serailized already.
-          continue
-        if _IsMapEntry(field):
-          js[name] = {}
-        elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
-          js[name] = []
-        else:
-          js[name] = _FieldToJsonObject(field, field.default_value)
+  def __init__(self,
+               including_default_value_fields=False):
+    self.including_default_value_fields = including_default_value_fields
 
-  except ValueError as e:
-    raise SerializeToJsonError(
-        'Failed to serialize {0} field: {1}.'.format(field.name, e))
+  def ToJsonString(self, message):
+    js = self._MessageToJsonObject(message)
+    return json.dumps(js, indent=2)
 
-  return js
+  def _MessageToJsonObject(self, message):
+    """Converts message to an object according to Proto3 JSON Specification."""
+    message_descriptor = message.DESCRIPTOR
+    full_name = message_descriptor.full_name
+    if _IsWrapperMessage(message_descriptor):
+      return self._WrapperMessageToJsonObject(message)
+    if full_name in _WKTJSONMETHODS:
+      return methodcaller(_WKTJSONMETHODS[full_name][0], message)(self)
+    js = {}
+    return self._RegularMessageToJsonObject(message, js)
 
+  def _RegularMessageToJsonObject(self, message, js):
+    """Converts normal message according to Proto3 JSON Specification."""
+    fields = message.ListFields()
 
-def _FieldToJsonObject(
-    field, value, including_default_value_fields=False):
-  """Converts field value according to Proto3 JSON Specification."""
-  if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
-    return _MessageToJsonObject(value, including_default_value_fields)
-  elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
-    enum_value = field.enum_type.values_by_number.get(value, None)
-    if enum_value is not None:
-      return enum_value.name
-    else:
-      raise SerializeToJsonError('Enum field contains an integer value '
-                                 'which can not mapped to an enum value.')
-  elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
-    if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
-      # Use base64 Data encoding for bytes
-      return base64.b64encode(value).decode('utf-8')
-    else:
-      return value
-  elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
-    return bool(value)
-  elif field.cpp_type in _INT64_TYPES:
-    return str(value)
-  elif field.cpp_type in _FLOAT_TYPES:
-    if math.isinf(value):
-      if value < 0.0:
-        return _NEG_INFINITY
-      else:
-        return _INFINITY
-    if math.isnan(value):
-      return _NAN
-  return value
+    try:
+      for field, value in fields:
+        name = field.camelcase_name
+        if _IsMapEntry(field):
+          # Convert a map field.
+          v_field = field.message_type.fields_by_name['value']
+          js_map = {}
+          for key in value:
+            if isinstance(key, bool):
+              if key:
+                recorded_key = 'true'
+              else:
+                recorded_key = 'false'
+            else:
+              recorded_key = key
+            js_map[recorded_key] = self._FieldToJsonObject(
+                v_field, value[key])
+          js[name] = js_map
+        elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
+          # Convert a repeated field.
+          js[name] = [self._FieldToJsonObject(field, k)
+                      for k in value]
+        else:
+          js[name] = self._FieldToJsonObject(field, value)
+
+      # Serialize default value if including_default_value_fields is True.
+      if self.including_default_value_fields:
+        message_descriptor = message.DESCRIPTOR
+        for field in message_descriptor.fields:
+          # Singular message fields and oneof fields will not be affected.
+          if ((field.label != descriptor.FieldDescriptor.LABEL_REPEATED and
+               field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE) or
+              field.containing_oneof):
+            continue
+          name = field.camelcase_name
+          if name in js:
+            # Skip the field which has been serailized already.
+            continue
+          if _IsMapEntry(field):
+            js[name] = {}
+          elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
+            js[name] = []
+          else:
+            js[name] = self._FieldToJsonObject(field, field.default_value)
 
+    except ValueError as e:
+      raise SerializeToJsonError(
+          'Failed to serialize {0} field: {1}.'.format(field.name, e))
 
-def _AnyMessageToJsonObject(message, including_default):
-  """Converts Any message according to Proto3 JSON Specification."""
-  if not message.ListFields():
-    return {}
-  # Must print @type first, use OrderedDict instead of {}
-  js = OrderedDict()
-  type_url = message.type_url
-  js['@type'] = type_url
-  sub_message = _CreateMessageFromTypeUrl(type_url)
-  sub_message.ParseFromString(message.value)
-  message_descriptor = sub_message.DESCRIPTOR
-  full_name = message_descriptor.full_name
-  if _IsWrapperMessage(message_descriptor):
-    js['value'] = _WrapperMessageToJsonObject(sub_message)
     return js
-  if full_name in _WKTJSONMETHODS:
-    js['value'] = _WKTJSONMETHODS[full_name][0](sub_message, including_default)
-    return js
-  return _RegularMessageToJsonObject(sub_message, js, including_default)
-
-
-def _CreateMessageFromTypeUrl(type_url):
-  # TODO(jieluo): Should add a way that users can register the type resolver
-  # instead of the default one.
-  db = symbol_database.Default()
-  type_name = type_url.split('/')[-1]
-  try:
-    message_descriptor = db.pool.FindMessageTypeByName(type_name)
-  except KeyError:
-    raise TypeError(
-        'Can not find message descriptor by type_url: {0}.'.format(type_url))
-  message_class = db.GetPrototype(message_descriptor)
-  return message_class()
-
-
-def _GenericMessageToJsonObject(message, unused_including_default):
-  """Converts message by ToJsonString according to Proto3 JSON Specification."""
-  # Duration, Timestamp and FieldMask have ToJsonString method to do the
-  # convert. Users can also call the method directly.
-  return message.ToJsonString()
-
-
-def _ValueMessageToJsonObject(message, unused_including_default=False):
-  """Converts Value message according to Proto3 JSON Specification."""
-  which = message.WhichOneof('kind')
-  # If the Value message is not set treat as null_value when serialize
-  # to JSON. The parse back result will be different from original message.
-  if which is None or which == 'null_value':
-    return None
-  if which == 'list_value':
-    return _ListValueMessageToJsonObject(message.list_value)
-  if which == 'struct_value':
-    value = message.struct_value
-  else:
-    value = getattr(message, which)
-  oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
-  return _FieldToJsonObject(oneof_descriptor, value)
 
+  def _FieldToJsonObject(self, field, value):
+    """Converts field value according to Proto3 JSON Specification."""
+    if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
+      return self._MessageToJsonObject(value)
+    elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_ENUM:
+      enum_value = field.enum_type.values_by_number.get(value, None)
+      if enum_value is not None:
+        return enum_value.name
+      else:
+        raise SerializeToJsonError('Enum field contains an integer value '
+                                   'which can not mapped to an enum value.')
+    elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_STRING:
+      if field.type == descriptor.FieldDescriptor.TYPE_BYTES:
+        # Use base64 Data encoding for bytes
+        return base64.b64encode(value).decode('utf-8')
+      else:
+        return value
+    elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_BOOL:
+      return bool(value)
+    elif field.cpp_type in _INT64_TYPES:
+      return str(value)
+    elif field.cpp_type in _FLOAT_TYPES:
+      if math.isinf(value):
+        if value < 0.0:
+          return _NEG_INFINITY
+        else:
+          return _INFINITY
+      if math.isnan(value):
+        return _NAN
+    return value
+
+  def _AnyMessageToJsonObject(self, message):
+    """Converts Any message according to Proto3 JSON Specification."""
+    if not message.ListFields():
+      return {}
+    # Must print @type first, use OrderedDict instead of {}
+    js = OrderedDict()
+    type_url = message.type_url
+    js['@type'] = type_url
+    sub_message = _CreateMessageFromTypeUrl(type_url)
+    sub_message.ParseFromString(message.value)
+    message_descriptor = sub_message.DESCRIPTOR
+    full_name = message_descriptor.full_name
+    if _IsWrapperMessage(message_descriptor):
+      js['value'] = self._WrapperMessageToJsonObject(sub_message)
+      return js
+    if full_name in _WKTJSONMETHODS:
+      js['value'] = methodcaller(_WKTJSONMETHODS[full_name][0],
+                                 sub_message)(self)
+      return js
+    return self._RegularMessageToJsonObject(sub_message, js)
+
+  def _GenericMessageToJsonObject(self, message):
+    """Converts message according to Proto3 JSON Specification."""
+    # Duration, Timestamp and FieldMask have ToJsonString method to do the
+    # convert. Users can also call the method directly.
+    return message.ToJsonString()
+
+  def _ValueMessageToJsonObject(self, message):
+    """Converts Value message according to Proto3 JSON Specification."""
+    which = message.WhichOneof('kind')
+    # If the Value message is not set treat as null_value when serialize
+    # to JSON. The parse back result will be different from original message.
+    if which is None or which == 'null_value':
+      return None
+    if which == 'list_value':
+      return self._ListValueMessageToJsonObject(message.list_value)
+    if which == 'struct_value':
+      value = message.struct_value
+    else:
+      value = getattr(message, which)
+    oneof_descriptor = message.DESCRIPTOR.fields_by_name[which]
+    return self._FieldToJsonObject(oneof_descriptor, value)
 
-def _ListValueMessageToJsonObject(message, unused_including_default=False):
-  """Converts ListValue message according to Proto3 JSON Specification."""
-  return [_ValueMessageToJsonObject(value)
-          for value in message.values]
+  def _ListValueMessageToJsonObject(self, message):
+    """Converts ListValue message according to Proto3 JSON Specification."""
+    return [self._ValueMessageToJsonObject(value)
+            for value in message.values]
 
+  def _StructMessageToJsonObject(self, message):
+    """Converts Struct message according to Proto3 JSON Specification."""
+    fields = message.fields
+    ret = {}
+    for key in fields:
+      ret[key] = self._ValueMessageToJsonObject(fields[key])
+    return ret
 
-def _StructMessageToJsonObject(message, unused_including_default=False):
-  """Converts Struct message according to Proto3 JSON Specification."""
-  fields = message.fields
-  ret = {}
-  for key in fields:
-    ret[key] = _ValueMessageToJsonObject(fields[key])
-  return ret
+  def _WrapperMessageToJsonObject(self, message):
+    return self._FieldToJsonObject(
+        message.DESCRIPTOR.fields_by_name['value'], message.value)
 
 
 def _IsWrapperMessage(message_descriptor):
   return message_descriptor.file.name == 'google/protobuf/wrappers.proto'
 
 
-def _WrapperMessageToJsonObject(message):
-  return _FieldToJsonObject(
-      message.DESCRIPTOR.fields_by_name['value'], message.value)
-
-
 def _DuplicateChecker(js):
   result = {}
   for name, value in js:
@@ -304,12 +291,27 @@ def _DuplicateChecker(js):
   return result
 
 
-def Parse(text, message):
+def _CreateMessageFromTypeUrl(type_url):
+  # TODO(jieluo): Should add a way that users can register the type resolver
+  # instead of the default one.
+  db = symbol_database.Default()
+  type_name = type_url.split('/')[-1]
+  try:
+    message_descriptor = db.pool.FindMessageTypeByName(type_name)
+  except KeyError:
+    raise TypeError(
+        'Can not find message descriptor by type_url: {0}.'.format(type_url))
+  message_class = db.GetPrototype(message_descriptor)
+  return message_class()
+
+
+def Parse(text, message, ignore_unknown_fields=False):
   """Parses a JSON representation of a protocol message into a message.
 
   Args:
     text: Message JSON representation.
     message: A protocol beffer message to merge into.
+    ignore_unknown_fields: If True, do not raise errors for unknown fields.
 
   Returns:
     The same message passed as argument.
@@ -326,213 +328,217 @@ def Parse(text, message):
       js = json.loads(text, object_pairs_hook=_DuplicateChecker)
   except ValueError as e:
     raise ParseError('Failed to load JSON: {0}.'.format(str(e)))
-  _ConvertMessage(js, message)
+  parser = _Parser(ignore_unknown_fields)
+  parser.ConvertMessage(js, message)
   return message
 
 
-def _ConvertFieldValuePair(js, message):
-  """Convert field value pairs into regular message.
+_INT_OR_FLOAT = six.integer_types + (float,)
 
-  Args:
-    js: A JSON object to convert the field value pairs.
-    message: A regular protocol message to record the data.
 
-  Raises:
-    ParseError: In case of problems converting.
-  """
-  names = []
-  message_descriptor = message.DESCRIPTOR
-  for name in js:
-    try:
-      field = message_descriptor.fields_by_camelcase_name.get(name, None)
-      if not field:
-        raise ParseError(
-            'Message type "{0}" has no field named "{1}".'.format(
-                message_descriptor.full_name, name))
-      if name in names:
-        raise ParseError(
-            'Message type "{0}" should not have multiple "{1}" fields.'.format(
-                message.DESCRIPTOR.full_name, name))
-      names.append(name)
-      # Check no other oneof field is parsed.
-      if field.containing_oneof is not None:
-        oneof_name = field.containing_oneof.name
-        if oneof_name in names:
-          raise ParseError('Message type "{0}" should not have multiple "{1}" '
-                           'oneof fields.'.format(
-                               message.DESCRIPTOR.full_name, oneof_name))
-        names.append(oneof_name)
-
-      value = js[name]
-      if value is None:
-        message.ClearField(field.name)
-        continue
-
-      # Parse field value.
-      if _IsMapEntry(field):
-        message.ClearField(field.name)
-        _ConvertMapFieldValue(value, message, field)
-      elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
-        message.ClearField(field.name)
-        if not isinstance(value, list):
-          raise ParseError('repeated field {0} must be in [] which is '
-                           '{1}.'.format(name, value))
-        if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
-          # Repeated message field.
-          for item in value:
-            sub_message = getattr(message, field.name).add()
-            # None is a null_value in Value.
-            if (item is None and
-                sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'):
-              raise ParseError('null is not allowed to be used as an element'
-                               ' in a repeated field.')
-            _ConvertMessage(item, sub_message)
-        else:
-          # Repeated scalar field.
-          for item in value:
-            if item is None:
-              raise ParseError('null is not allowed to be used as an element'
-                               ' in a repeated field.')
-            getattr(message, field.name).append(
-                _ConvertScalarFieldValue(item, field))
-      elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
-        sub_message = getattr(message, field.name)
-        _ConvertMessage(value, sub_message)
-      else:
-        setattr(message, field.name, _ConvertScalarFieldValue(value, field))
-    except ParseError as e:
-      if field and field.containing_oneof is None:
-        raise ParseError('Failed to parse {0} field: {1}'.format(name, e))
-      else:
-        raise ParseError(str(e))
-    except ValueError as e:
-      raise ParseError('Failed to parse {0} field: {1}.'.format(name, e))
-    except TypeError as e:
-      raise ParseError('Failed to parse {0} field: {1}.'.format(name, e))
+class _Parser(object):
+  """JSON format parser for protocol message."""
 
+  def __init__(self,
+               ignore_unknown_fields):
+    self.ignore_unknown_fields = ignore_unknown_fields
 
-def _ConvertMessage(value, message):
-  """Convert a JSON object into a message.
+  def ConvertMessage(self, value, message):
+    """Convert a JSON object into a message.
 
-  Args:
-    value: A JSON object.
-    message: A WKT or regular protocol message to record the data.
+    Args:
+      value: A JSON object.
+      message: A WKT or regular protocol message to record the data.
 
-  Raises:
-    ParseError: In case of convert problems.
-  """
-  message_descriptor = message.DESCRIPTOR
-  full_name = message_descriptor.full_name
-  if _IsWrapperMessage(message_descriptor):
-    _ConvertWrapperMessage(value, message)
-  elif full_name in _WKTJSONMETHODS:
-    _WKTJSONMETHODS[full_name][1](value, message)
-  else:
-    _ConvertFieldValuePair(value, message)
-
-
-def _ConvertAnyMessage(value, message):
-  """Convert a JSON representation into Any message."""
-  if isinstance(value, dict) and not value:
-    return
-  try:
-    type_url = value['@type']
-  except KeyError:
-    raise ParseError('@type is missing when parsing any message.')
-
-  sub_message = _CreateMessageFromTypeUrl(type_url)
-  message_descriptor = sub_message.DESCRIPTOR
-  full_name = message_descriptor.full_name
-  if _IsWrapperMessage(message_descriptor):
-    _ConvertWrapperMessage(value['value'], sub_message)
-  elif full_name in _WKTJSONMETHODS:
-    _WKTJSONMETHODS[full_name][1](value['value'], sub_message)
-  else:
-    del value['@type']
-    _ConvertFieldValuePair(value, sub_message)
-  # Sets Any message
-  message.value = sub_message.SerializeToString()
-  message.type_url = type_url
-
-
-def _ConvertGenericMessage(value, message):
-  """Convert a JSON representation into message with FromJsonString."""
-  # Durantion, Timestamp, FieldMask have FromJsonString method to do the
-  # convert. Users can also call the method directly.
-  message.FromJsonString(value)
+    Raises:
+      ParseError: In case of convert problems.
+    """
+    message_descriptor = message.DESCRIPTOR
+    full_name = message_descriptor.full_name
+    if _IsWrapperMessage(message_descriptor):
+      self._ConvertWrapperMessage(value, message)
+    elif full_name in _WKTJSONMETHODS:
+      methodcaller(_WKTJSONMETHODS[full_name][1], value, message)(self)
+    else:
+      self._ConvertFieldValuePair(value, message)
+
+  def _ConvertFieldValuePair(self, js, message):
+    """Convert field value pairs into regular message.
+
+    Args:
+      js: A JSON object to convert the field value pairs.
+      message: A regular protocol message to record the data.
+
+    Raises:
+      ParseError: In case of problems converting.
+    """
+    names = []
+    message_descriptor = message.DESCRIPTOR
+    for name in js:
+      try:
+        field = message_descriptor.fields_by_camelcase_name.get(name, None)
+        if not field:
+          if self.ignore_unknown_fields:
+            continue
+          raise ParseError(
+              'Message type "{0}" has no field named "{1}".'.format(
+                  message_descriptor.full_name, name))
+        if name in names:
+          raise ParseError('Message type "{0}" should not have multiple '
+                           '"{1}" fields.'.format(
+                               message.DESCRIPTOR.full_name, name))
+        names.append(name)
+        # Check no other oneof field is parsed.
+        if field.containing_oneof is not None:
+          oneof_name = field.containing_oneof.name
+          if oneof_name in names:
+            raise ParseError('Message type "{0}" should not have multiple '
+                             '"{1}" oneof fields.'.format(
+                                 message.DESCRIPTOR.full_name, oneof_name))
+          names.append(oneof_name)
+
+        value = js[name]
+        if value is None:
+          message.ClearField(field.name)
+          continue
 
+        # Parse field value.
+        if _IsMapEntry(field):
+          message.ClearField(field.name)
+          self._ConvertMapFieldValue(value, message, field)
+        elif field.label == descriptor.FieldDescriptor.LABEL_REPEATED:
+          message.ClearField(field.name)
+          if not isinstance(value, list):
+            raise ParseError('repeated field {0} must be in [] which is '
+                             '{1}.'.format(name, value))
+          if field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
+            # Repeated message field.
+            for item in value:
+              sub_message = getattr(message, field.name).add()
+              # None is a null_value in Value.
+              if (item is None and
+                  sub_message.DESCRIPTOR.full_name != 'google.protobuf.Value'):
+                raise ParseError('null is not allowed to be used as an element'
+                                 ' in a repeated field.')
+              self.ConvertMessage(item, sub_message)
+          else:
+            # Repeated scalar field.
+            for item in value:
+              if item is None:
+                raise ParseError('null is not allowed to be used as an element'
+                                 ' in a repeated field.')
+              getattr(message, field.name).append(
+                  _ConvertScalarFieldValue(item, field))
+        elif field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
+          sub_message = getattr(message, field.name)
+          self.ConvertMessage(value, sub_message)
+        else:
+          setattr(message, field.name, _ConvertScalarFieldValue(value, field))
+      except ParseError as e:
+        if field and field.containing_oneof is None:
+          raise ParseError('Failed to parse {0} field: {1}'.format(name, e))
+        else:
+          raise ParseError(str(e))
+      except ValueError as e:
+        raise ParseError('Failed to parse {0} field: {1}.'.format(name, e))
+      except TypeError as e:
+        raise ParseError('Failed to parse {0} field: {1}.'.format(name, e))
+
+  def _ConvertAnyMessage(self, value, message):
+    """Convert a JSON representation into Any message."""
+    if isinstance(value, dict) and not value:
+      return
+    try:
+      type_url = value['@type']
+    except KeyError:
+      raise ParseError('@type is missing when parsing any message.')
+
+    sub_message = _CreateMessageFromTypeUrl(type_url)
+    message_descriptor = sub_message.DESCRIPTOR
+    full_name = message_descriptor.full_name
+    if _IsWrapperMessage(message_descriptor):
+      self._ConvertWrapperMessage(value['value'], sub_message)
+    elif full_name in _WKTJSONMETHODS:
+      methodcaller(
+          _WKTJSONMETHODS[full_name][1], value['value'], sub_message)(self)
+    else:
+      del value['@type']
+      self._ConvertFieldValuePair(value, sub_message)
+    # Sets Any message
+    message.value = sub_message.SerializeToString()
+    message.type_url = type_url
+
+  def _ConvertGenericMessage(self, value, message):
+    """Convert a JSON representation into message with FromJsonString."""
+    # Durantion, Timestamp, FieldMask have FromJsonString method to do the
+    # convert. Users can also call the method directly.
+    message.FromJsonString(value)
+
+  def _ConvertValueMessage(self, value, message):
+    """Convert a JSON representation into Value message."""
+    if isinstance(value, dict):
+      self._ConvertStructMessage(value, message.struct_value)
+    elif isinstance(value, list):
+      self. _ConvertListValueMessage(value, message.list_value)
+    elif value is None:
+      message.null_value = 0
+    elif isinstance(value, bool):
+      message.bool_value = value
+    elif isinstance(value, six.string_types):
+      message.string_value = value
+    elif isinstance(value, _INT_OR_FLOAT):
+      message.number_value = value
+    else:
+      raise ParseError('Unexpected type for Value message.')
 
-_INT_OR_FLOAT = six.integer_types + (float,)
+  def _ConvertListValueMessage(self, value, message):
+    """Convert a JSON representation into ListValue message."""
+    if not isinstance(value, list):
+      raise ParseError(
+          'ListValue must be in [] which is {0}.'.format(value))
+    message.ClearField('values')
+    for item in value:
+      self._ConvertValueMessage(item, message.values.add())
+
+  def _ConvertStructMessage(self, value, message):
+    """Convert a JSON representation into Struct message."""
+    if not isinstance(value, dict):
+      raise ParseError(
+          'Struct must be in a dict which is {0}.'.format(value))
+    for key in value:
+      self._ConvertValueMessage(value[key], message.fields[key])
+    return
 
+  def _ConvertWrapperMessage(self, value, message):
+    """Convert a JSON representation into Wrapper message."""
+    field = message.DESCRIPTOR.fields_by_name['value']
+    setattr(message, 'value', _ConvertScalarFieldValue(value, field))
 
-def _ConvertValueMessage(value, message):
-  """Convert a JSON representation into Value message."""
-  if isinstance(value, dict):
-    _ConvertStructMessage(value, message.struct_value)
-  elif isinstance(value, list):
-    _ConvertListValueMessage(value, message.list_value)
-  elif value is None:
-    message.null_value = 0
-  elif isinstance(value, bool):
-    message.bool_value = value
-  elif isinstance(value, six.string_types):
-    message.string_value = value
-  elif isinstance(value, _INT_OR_FLOAT):
-    message.number_value = value
-  else:
-    raise ParseError('Unexpected type for Value message.')
-
-
-def _ConvertListValueMessage(value, message):
-  """Convert a JSON representation into ListValue message."""
-  if not isinstance(value, list):
-    raise ParseError(
-        'ListValue must be in [] which is {0}.'.format(value))
-  message.ClearField('values')
-  for item in value:
-    _ConvertValueMessage(item, message.values.add())
-
-
-def _ConvertStructMessage(value, message):
-  """Convert a JSON representation into Struct message."""
-  if not isinstance(value, dict):
-    raise ParseError(
-        'Struct must be in a dict which is {0}.'.format(value))
-  for key in value:
-    _ConvertValueMessage(value[key], message.fields[key])
-  return
-
-
-def _ConvertWrapperMessage(value, message):
-  """Convert a JSON representation into Wrapper message."""
-  field = message.DESCRIPTOR.fields_by_name['value']
-  setattr(message, 'value', _ConvertScalarFieldValue(value, field))
-
-
-def _ConvertMapFieldValue(value, message, field):
-  """Convert map field value for a message map field.
+  def _ConvertMapFieldValue(self, value, message, field):
+    """Convert map field value for a message map field.
 
-  Args:
-    value: A JSON object to convert the map field value.
-    message: A protocol message to record the converted data.
-    field: The descriptor of the map field to be converted.
+    Args:
+      value: A JSON object to convert the map field value.
+      message: A protocol message to record the converted data.
+      field: The descriptor of the map field to be converted.
 
-  Raises:
-    ParseError: In case of convert problems.
-  """
-  if not isinstance(value, dict):
-    raise ParseError(
-        'Map field {0} must be in a dict which is {1}.'.format(
-            field.name, value))
-  key_field = field.message_type.fields_by_name['key']
-  value_field = field.message_type.fields_by_name['value']
-  for key in value:
-    key_value = _ConvertScalarFieldValue(key, key_field, True)
-    if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
-      _ConvertMessage(value[key], getattr(message, field.name)[key_value])
-    else:
-      getattr(message, field.name)[key_value] = _ConvertScalarFieldValue(
-          value[key], value_field)
+    Raises:
+      ParseError: In case of convert problems.
+    """
+    if not isinstance(value, dict):
+      raise ParseError(
+          'Map field {0} must be in a dict which is {1}.'.format(
+              field.name, value))
+    key_field = field.message_type.fields_by_name['key']
+    value_field = field.message_type.fields_by_name['value']
+    for key in value:
+      key_value = _ConvertScalarFieldValue(key, key_field, True)
+      if value_field.cpp_type == descriptor.FieldDescriptor.CPPTYPE_MESSAGE:
+        self.ConvertMessage(value[key], getattr(
+            message, field.name)[key_value])
+      else:
+        getattr(message, field.name)[key_value] = _ConvertScalarFieldValue(
+            value[key], value_field)
 
 
 def _ConvertScalarFieldValue(value, field, require_str=False):
@@ -641,18 +647,18 @@ def _ConvertBool(value, require_str):
   return value
 
 _WKTJSONMETHODS = {
-    'google.protobuf.Any': [_AnyMessageToJsonObject,
-                            _ConvertAnyMessage],
-    'google.protobuf.Duration': [_GenericMessageToJsonObject,
-                                 _ConvertGenericMessage],
-    'google.protobuf.FieldMask': [_GenericMessageToJsonObject,
-                                  _ConvertGenericMessage],
-    'google.protobuf.ListValue': [_ListValueMessageToJsonObject,
-                                  _ConvertListValueMessage],
-    'google.protobuf.Struct': [_StructMessageToJsonObject,
-                               _ConvertStructMessage],
-    'google.protobuf.Timestamp': [_GenericMessageToJsonObject,
-                                  _ConvertGenericMessage],
-    'google.protobuf.Value': [_ValueMessageToJsonObject,
-                              _ConvertValueMessage]
+    'google.protobuf.Any': ['_AnyMessageToJsonObject',
+                            '_ConvertAnyMessage'],
+    'google.protobuf.Duration': ['_GenericMessageToJsonObject',
+                                 '_ConvertGenericMessage'],
+    'google.protobuf.FieldMask': ['_GenericMessageToJsonObject',
+                                  '_ConvertGenericMessage'],
+    'google.protobuf.ListValue': ['_ListValueMessageToJsonObject',
+                                  '_ConvertListValueMessage'],
+    'google.protobuf.Struct': ['_StructMessageToJsonObject',
+                               '_ConvertStructMessage'],
+    'google.protobuf.Timestamp': ['_GenericMessageToJsonObject',
+                                  '_ConvertGenericMessage'],
+    'google.protobuf.Value': ['_ValueMessageToJsonObject',
+                              '_ConvertValueMessage']
 }

+ 269 - 8
python/google/protobuf/pyext/descriptor.cc

@@ -172,12 +172,16 @@ template<>
 const FileDescriptor* GetFileDescriptor(const OneofDescriptor* descriptor) {
   return descriptor->containing_type()->file();
 }
+template<>
+const FileDescriptor* GetFileDescriptor(const MethodDescriptor* descriptor) {
+  return descriptor->service()->file();
+}
 
 // Converts options into a Python protobuf, and cache the result.
 //
 // This is a bit tricky because options can contain extension fields defined in
 // the same proto file. In this case the options parsed from the serialized_pb
-// have unkown fields, and we need to parse them again.
+// have unknown fields, and we need to parse them again.
 //
 // Always returns a new reference.
 template<class DescriptorClass>
@@ -204,11 +208,12 @@ static PyObject* GetOrBuildOptions(const DescriptorClass *descriptor) {
       cdescriptor_pool::GetMessageClass(pool, message_type));
   if (message_class == NULL) {
     // The Options message was not found in the current DescriptorPool.
-    // In this case, there cannot be extensions to these options, and we can
-    // try to use the basic pool instead.
+    // This means that the pool cannot contain any extensions to the Options
+    // message either, so falling back to the basic pool we can only increase
+    // the chances of successfully parsing the options.
     PyErr_Clear();
-    message_class = cdescriptor_pool::GetMessageClass(
-      GetDefaultDescriptorPool(), message_type);
+    pool = GetDefaultDescriptorPool();
+    message_class = cdescriptor_pool::GetMessageClass(pool, message_type);
   }
   if (message_class == NULL) {
     PyErr_Format(PyExc_TypeError, "Could not retrieve class for Options: %s",
@@ -248,7 +253,7 @@ static PyObject* GetOrBuildOptions(const DescriptorClass *descriptor) {
 
   // Cache the result.
   Py_INCREF(value.get());
-  (*pool->descriptor_options)[descriptor] = value.get();
+  (*descriptor_options)[descriptor] = value.get();
 
   return value.release();
 }
@@ -1091,7 +1096,7 @@ PyTypeObject PyEnumDescriptor_Type = {
   0,                                    // tp_weaklistoffset
   0,                                    // tp_iter
   0,                                    // tp_iternext
-  enum_descriptor::Methods,             // tp_getset
+  enum_descriptor::Methods,             // tp_methods
   0,                                    // tp_members
   enum_descriptor::Getters,             // tp_getset
   &descriptor::PyBaseDescriptor_Type,   // tp_base
@@ -1275,6 +1280,10 @@ static PyObject* GetExtensionsByName(PyFileDescriptor* self, void *closure) {
   return NewFileExtensionsByName(_GetDescriptor(self));
 }
 
+static PyObject* GetServicesByName(PyFileDescriptor* self, void *closure) {
+  return NewFileServicesByName(_GetDescriptor(self));
+}
+
 static PyObject* GetDependencies(PyFileDescriptor* self, void *closure) {
   return NewFileDependencies(_GetDescriptor(self));
 }
@@ -1324,6 +1333,7 @@ static PyGetSetDef Getters[] = {
   { "enum_types_by_name", (getter)GetEnumTypesByName, NULL, "Enums by name"},
   { "extensions_by_name", (getter)GetExtensionsByName, NULL,
     "Extensions by name"},
+  { "services_by_name", (getter)GetServicesByName, NULL, "Services by name"},
   { "dependencies", (getter)GetDependencies, NULL, "Dependencies"},
   { "public_dependencies", (getter)GetPublicDependencies, NULL, "Dependencies"},
 
@@ -1452,16 +1462,45 @@ static PyObject* GetContainingType(PyBaseDescriptor *self, void *closure) {
   }
 }
 
+static PyObject* GetHasOptions(PyBaseDescriptor *self, void *closure) {
+  const OneofOptions& options(_GetDescriptor(self)->options());
+  if (&options != &OneofOptions::default_instance()) {
+    Py_RETURN_TRUE;
+  } else {
+    Py_RETURN_FALSE;
+  }
+}
+static int SetHasOptions(PyBaseDescriptor *self, PyObject *value,
+                         void *closure) {
+  return CheckCalledFromGeneratedFile("has_options");
+}
+
+static PyObject* GetOptions(PyBaseDescriptor *self) {
+  return GetOrBuildOptions(_GetDescriptor(self));
+}
+
+static int SetOptions(PyBaseDescriptor *self, PyObject *value,
+                      void *closure) {
+  return CheckCalledFromGeneratedFile("_options");
+}
+
 static PyGetSetDef Getters[] = {
   { "name", (getter)GetName, NULL, "Name"},
   { "full_name", (getter)GetFullName, NULL, "Full name"},
   { "index", (getter)GetIndex, NULL, "Index"},
 
   { "containing_type", (getter)GetContainingType, NULL, "Containing type"},
+  { "has_options", (getter)GetHasOptions, (setter)SetHasOptions, "Has Options"},
+  { "_options", (getter)NULL, (setter)SetOptions, "Options"},
   { "fields", (getter)GetFields, NULL, "Fields"},
   {NULL}
 };
 
+static PyMethodDef Methods[] = {
+  { "GetOptions", (PyCFunction)GetOptions, METH_NOARGS },
+  {NULL}
+};
+
 }  // namespace oneof_descriptor
 
 PyTypeObject PyOneofDescriptor_Type = {
@@ -1492,7 +1531,7 @@ PyTypeObject PyOneofDescriptor_Type = {
   0,                                    // tp_weaklistoffset
   0,                                    // tp_iter
   0,                                    // tp_iternext
-  0,                                    // tp_methods
+  oneof_descriptor::Methods,            // tp_methods
   0,                                    // tp_members
   oneof_descriptor::Getters,            // tp_getset
   &descriptor::PyBaseDescriptor_Type,   // tp_base
@@ -1504,6 +1543,222 @@ PyObject* PyOneofDescriptor_FromDescriptor(
       &PyOneofDescriptor_Type, oneof_descriptor, NULL);
 }
 
+namespace service_descriptor {
+
+// Unchecked accessor to the C++ pointer.
+static const ServiceDescriptor* _GetDescriptor(
+    PyBaseDescriptor *self) {
+  return reinterpret_cast<const ServiceDescriptor*>(self->descriptor);
+}
+
+static PyObject* GetName(PyBaseDescriptor* self, void *closure) {
+  return PyString_FromCppString(_GetDescriptor(self)->name());
+}
+
+static PyObject* GetFullName(PyBaseDescriptor* self, void *closure) {
+  return PyString_FromCppString(_GetDescriptor(self)->full_name());
+}
+
+static PyObject* GetIndex(PyBaseDescriptor *self, void *closure) {
+  return PyInt_FromLong(_GetDescriptor(self)->index());
+}
+
+static PyObject* GetMethods(PyBaseDescriptor* self, void *closure) {
+  return NewServiceMethodsSeq(_GetDescriptor(self));
+}
+
+static PyObject* GetMethodsByName(PyBaseDescriptor* self, void *closure) {
+  return NewServiceMethodsByName(_GetDescriptor(self));
+}
+
+static PyObject* FindMethodByName(PyBaseDescriptor *self, PyObject* arg) {
+  Py_ssize_t name_size;
+  char* name;
+  if (PyString_AsStringAndSize(arg, &name, &name_size) < 0) {
+    return NULL;
+  }
+
+  const MethodDescriptor* method_descriptor =
+      _GetDescriptor(self)->FindMethodByName(string(name, name_size));
+  if (method_descriptor == NULL) {
+    PyErr_Format(PyExc_KeyError, "Couldn't find method %.200s", name);
+    return NULL;
+  }
+
+  return PyMethodDescriptor_FromDescriptor(method_descriptor);
+}
+
+static PyObject* GetOptions(PyBaseDescriptor *self) {
+  return GetOrBuildOptions(_GetDescriptor(self));
+}
+
+static PyObject* CopyToProto(PyBaseDescriptor *self, PyObject *target) {
+  return CopyToPythonProto<ServiceDescriptorProto>(_GetDescriptor(self),
+                                                   target);
+}
+
+static PyGetSetDef Getters[] = {
+  { "name", (getter)GetName, NULL, "Name", NULL},
+  { "full_name", (getter)GetFullName, NULL, "Full name", NULL},
+  { "index", (getter)GetIndex, NULL, "Index", NULL},
+
+  { "methods", (getter)GetMethods, NULL, "Methods", NULL},
+  { "methods_by_name", (getter)GetMethodsByName, NULL, "Methods by name", NULL},
+  {NULL}
+};
+
+static PyMethodDef Methods[] = {
+  { "GetOptions", (PyCFunction)GetOptions, METH_NOARGS },
+  { "CopyToProto", (PyCFunction)CopyToProto, METH_O, },
+  { "FindMethodByName", (PyCFunction)FindMethodByName, METH_O },
+  {NULL}
+};
+
+}  // namespace service_descriptor
+
+PyTypeObject PyServiceDescriptor_Type = {
+  PyVarObject_HEAD_INIT(&PyType_Type, 0)
+  FULL_MODULE_NAME ".ServiceDescriptor",  // tp_name
+  sizeof(PyBaseDescriptor),             // tp_basicsize
+  0,                                    // tp_itemsize
+  0,                                    // tp_dealloc
+  0,                                    // tp_print
+  0,                                    // tp_getattr
+  0,                                    // tp_setattr
+  0,                                    // tp_compare
+  0,                                    // tp_repr
+  0,                                    // tp_as_number
+  0,                                    // tp_as_sequence
+  0,                                    // tp_as_mapping
+  0,                                    // tp_hash
+  0,                                    // tp_call
+  0,                                    // tp_str
+  0,                                    // tp_getattro
+  0,                                    // tp_setattro
+  0,                                    // tp_as_buffer
+  Py_TPFLAGS_DEFAULT,                   // tp_flags
+  "A Service Descriptor",               // tp_doc
+  0,                                    // tp_traverse
+  0,                                    // tp_clear
+  0,                                    // tp_richcompare
+  0,                                    // tp_weaklistoffset
+  0,                                    // tp_iter
+  0,                                    // tp_iternext
+  service_descriptor::Methods,          // tp_methods
+  0,                                    // tp_members
+  service_descriptor::Getters,          // tp_getset
+  &descriptor::PyBaseDescriptor_Type,   // tp_base
+};
+
+PyObject* PyServiceDescriptor_FromDescriptor(
+    const ServiceDescriptor* service_descriptor) {
+  return descriptor::NewInternedDescriptor(
+      &PyServiceDescriptor_Type, service_descriptor, NULL);
+}
+
+namespace method_descriptor {
+
+// Unchecked accessor to the C++ pointer.
+static const MethodDescriptor* _GetDescriptor(
+    PyBaseDescriptor *self) {
+  return reinterpret_cast<const MethodDescriptor*>(self->descriptor);
+}
+
+static PyObject* GetName(PyBaseDescriptor* self, void *closure) {
+  return PyString_FromCppString(_GetDescriptor(self)->name());
+}
+
+static PyObject* GetFullName(PyBaseDescriptor* self, void *closure) {
+  return PyString_FromCppString(_GetDescriptor(self)->full_name());
+}
+
+static PyObject* GetIndex(PyBaseDescriptor *self, void *closure) {
+  return PyInt_FromLong(_GetDescriptor(self)->index());
+}
+
+static PyObject* GetContainingService(PyBaseDescriptor *self, void *closure) {
+  const ServiceDescriptor* containing_service =
+      _GetDescriptor(self)->service();
+  return PyServiceDescriptor_FromDescriptor(containing_service);
+}
+
+static PyObject* GetInputType(PyBaseDescriptor *self, void *closure) {
+  const Descriptor* input_type = _GetDescriptor(self)->input_type();
+  return PyMessageDescriptor_FromDescriptor(input_type);
+}
+
+static PyObject* GetOutputType(PyBaseDescriptor *self, void *closure) {
+  const Descriptor* output_type = _GetDescriptor(self)->output_type();
+  return PyMessageDescriptor_FromDescriptor(output_type);
+}
+
+static PyObject* GetOptions(PyBaseDescriptor *self) {
+  return GetOrBuildOptions(_GetDescriptor(self));
+}
+
+static PyObject* CopyToProto(PyBaseDescriptor *self, PyObject *target) {
+  return CopyToPythonProto<MethodDescriptorProto>(_GetDescriptor(self), target);
+}
+
+static PyGetSetDef Getters[] = {
+  { "name", (getter)GetName, NULL, "Name", NULL},
+  { "full_name", (getter)GetFullName, NULL, "Full name", NULL},
+  { "index", (getter)GetIndex, NULL, "Index", NULL},
+  { "containing_service", (getter)GetContainingService, NULL,
+    "Containing service", NULL},
+  { "input_type", (getter)GetInputType, NULL, "Input type", NULL},
+  { "output_type", (getter)GetOutputType, NULL, "Output type", NULL},
+  {NULL}
+};
+
+static PyMethodDef Methods[] = {
+  { "GetOptions", (PyCFunction)GetOptions, METH_NOARGS, },
+  { "CopyToProto", (PyCFunction)CopyToProto, METH_O, },
+  {NULL}
+};
+
+}  // namespace method_descriptor
+
+PyTypeObject PyMethodDescriptor_Type = {
+  PyVarObject_HEAD_INIT(&PyType_Type, 0)
+  FULL_MODULE_NAME ".MethodDescriptor",  // tp_name
+  sizeof(PyBaseDescriptor),             // tp_basicsize
+  0,                                    // tp_itemsize
+  0,                                    // tp_dealloc
+  0,                                    // tp_print
+  0,                                    // tp_getattr
+  0,                                    // tp_setattr
+  0,                                    // tp_compare
+  0,                                    // tp_repr
+  0,                                    // tp_as_number
+  0,                                    // tp_as_sequence
+  0,                                    // tp_as_mapping
+  0,                                    // tp_hash
+  0,                                    // tp_call
+  0,                                    // tp_str
+  0,                                    // tp_getattro
+  0,                                    // tp_setattro
+  0,                                    // tp_as_buffer
+  Py_TPFLAGS_DEFAULT,                   // tp_flags
+  "A Method Descriptor",                // tp_doc
+  0,                                    // tp_traverse
+  0,                                    // tp_clear
+  0,                                    // tp_richcompare
+  0,                                    // tp_weaklistoffset
+  0,                                    // tp_iter
+  0,                                    // tp_iternext
+  method_descriptor::Methods,           // tp_methods
+  0,                                    // tp_members
+  method_descriptor::Getters,           // tp_getset
+  &descriptor::PyBaseDescriptor_Type,   // tp_base
+};
+
+PyObject* PyMethodDescriptor_FromDescriptor(
+    const MethodDescriptor* method_descriptor) {
+  return descriptor::NewInternedDescriptor(
+      &PyMethodDescriptor_Type, method_descriptor, NULL);
+}
+
 // Add a enum values to a type dictionary.
 static bool AddEnumValues(PyTypeObject *type,
                           const EnumDescriptor* enum_descriptor) {
@@ -1573,6 +1828,12 @@ bool InitDescriptor() {
   if (PyType_Ready(&PyOneofDescriptor_Type) < 0)
     return false;
 
+  if (PyType_Ready(&PyServiceDescriptor_Type) < 0)
+    return false;
+
+  if (PyType_Ready(&PyMethodDescriptor_Type) < 0)
+    return false;
+
   if (!InitDescriptorMappingTypes())
     return false;
 

+ 6 - 0
python/google/protobuf/pyext/descriptor.h

@@ -47,6 +47,8 @@ extern PyTypeObject PyEnumDescriptor_Type;
 extern PyTypeObject PyEnumValueDescriptor_Type;
 extern PyTypeObject PyFileDescriptor_Type;
 extern PyTypeObject PyOneofDescriptor_Type;
+extern PyTypeObject PyServiceDescriptor_Type;
+extern PyTypeObject PyMethodDescriptor_Type;
 
 // Wraps a Descriptor in a Python object.
 // The C++ pointer is usually borrowed from the global DescriptorPool.
@@ -60,6 +62,10 @@ PyObject* PyEnumValueDescriptor_FromDescriptor(
 PyObject* PyOneofDescriptor_FromDescriptor(const OneofDescriptor* descriptor);
 PyObject* PyFileDescriptor_FromDescriptor(
     const FileDescriptor* file_descriptor);
+PyObject* PyServiceDescriptor_FromDescriptor(
+    const ServiceDescriptor* descriptor);
+PyObject* PyMethodDescriptor_FromDescriptor(
+    const MethodDescriptor* descriptor);
 
 // Alternate constructor of PyFileDescriptor, used when we already have a
 // serialized FileDescriptorProto that can be cached.

+ 146 - 12
python/google/protobuf/pyext/descriptor_containers.cc

@@ -608,6 +608,24 @@ static PyObject* GetItem(PyContainer* self, Py_ssize_t index) {
   return _NewObj_ByIndex(self, index);
 }
 
+static PyObject *
+SeqSubscript(PyContainer* self, PyObject* item) {
+  if (PyIndex_Check(item)) {
+      Py_ssize_t index;
+      index = PyNumber_AsSsize_t(item, PyExc_IndexError);
+      if (index == -1 && PyErr_Occurred())
+          return NULL;
+      return GetItem(self, index);
+  }
+  // Materialize the list and delegate the operation to it.
+  ScopedPyObjectPtr list(PyObject_CallFunctionObjArgs(
+      reinterpret_cast<PyObject*>(&PyList_Type), self, NULL));
+  if (list == NULL) {
+    return NULL;
+  }
+  return Py_TYPE(list.get())->tp_as_mapping->mp_subscript(list.get(), item);
+}
+
 // Returns the position of the item in the sequence, of -1 if not found.
 // This function never fails.
 int Find(PyContainer* self, PyObject* item) {
@@ -703,14 +721,20 @@ static PyMethodDef SeqMethods[] = {
 };
 
 static PySequenceMethods SeqSequenceMethods = {
-    (lenfunc)Length,          // sq_length
-    0,                        // sq_concat
-    0,                        // sq_repeat
-    (ssizeargfunc)GetItem,    // sq_item
-    0,                        // sq_slice
-    0,                        // sq_ass_item
-    0,                        // sq_ass_slice
-    (objobjproc)SeqContains,  // sq_contains
+  (lenfunc)Length,          // sq_length
+  0,                        // sq_concat
+  0,                        // sq_repeat
+  (ssizeargfunc)GetItem,    // sq_item
+  0,                        // sq_slice
+  0,                        // sq_ass_item
+  0,                        // sq_ass_slice
+  (objobjproc)SeqContains,  // sq_contains
+};
+
+static PyMappingMethods SeqMappingMethods = {
+  (lenfunc)Length,           // mp_length
+  (binaryfunc)SeqSubscript,  // mp_subscript
+  0,                         // mp_ass_subscript
 };
 
 PyTypeObject DescriptorSequence_Type = {
@@ -726,7 +750,7 @@ PyTypeObject DescriptorSequence_Type = {
   (reprfunc)ContainerRepr,              // tp_repr
   0,                                    // tp_as_number
   &SeqSequenceMethods,                  // tp_as_sequence
-  0,                                    // tp_as_mapping
+  &SeqMappingMethods,                   // tp_as_mapping
   0,                                    // tp_hash
   0,                                    // tp_call
   0,                                    // tp_str
@@ -1407,6 +1431,68 @@ PyObject* NewOneofFieldsSeq(ParentDescriptor descriptor) {
 
 }  // namespace oneof_descriptor
 
+namespace service_descriptor {
+
+typedef const ServiceDescriptor* ParentDescriptor;
+
+static ParentDescriptor GetDescriptor(PyContainer* self) {
+  return reinterpret_cast<ParentDescriptor>(self->descriptor);
+}
+
+namespace methods {
+
+typedef const MethodDescriptor* ItemDescriptor;
+
+static int Count(PyContainer* self) {
+  return GetDescriptor(self)->method_count();
+}
+
+static ItemDescriptor GetByName(PyContainer* self, const string& name) {
+  return GetDescriptor(self)->FindMethodByName(name);
+}
+
+static ItemDescriptor GetByIndex(PyContainer* self, int index) {
+  return GetDescriptor(self)->method(index);
+}
+
+static PyObject* NewObjectFromItem(ItemDescriptor item) {
+  return PyMethodDescriptor_FromDescriptor(item);
+}
+
+static const string& GetItemName(ItemDescriptor item) {
+  return item->name();
+}
+
+static int GetItemIndex(ItemDescriptor item) {
+  return item->index();
+}
+
+static DescriptorContainerDef ContainerDef = {
+  "ServiceMethods",
+  (CountMethod)Count,
+  (GetByIndexMethod)GetByIndex,
+  (GetByNameMethod)GetByName,
+  (GetByCamelcaseNameMethod)NULL,
+  (GetByNumberMethod)NULL,
+  (NewObjectFromItemMethod)NewObjectFromItem,
+  (GetItemNameMethod)GetItemName,
+  (GetItemCamelcaseNameMethod)NULL,
+  (GetItemNumberMethod)NULL,
+  (GetItemIndexMethod)GetItemIndex,
+};
+
+}  // namespace methods
+
+PyObject* NewServiceMethodsSeq(ParentDescriptor descriptor) {
+  return descriptor::NewSequence(&methods::ContainerDef, descriptor);
+}
+
+PyObject* NewServiceMethodsByName(ParentDescriptor descriptor) {
+  return descriptor::NewMappingByName(&methods::ContainerDef, descriptor);
+}
+
+}  // namespace service_descriptor
+
 namespace file_descriptor {
 
 typedef const FileDescriptor* ParentDescriptor;
@@ -1459,7 +1545,7 @@ static DescriptorContainerDef ContainerDef = {
 
 }  // namespace messages
 
-PyObject* NewFileMessageTypesByName(const FileDescriptor* descriptor) {
+PyObject* NewFileMessageTypesByName(ParentDescriptor descriptor) {
   return descriptor::NewMappingByName(&messages::ContainerDef, descriptor);
 }
 
@@ -1507,7 +1593,7 @@ static DescriptorContainerDef ContainerDef = {
 
 }  // namespace enums
 
-PyObject* NewFileEnumTypesByName(const FileDescriptor* descriptor) {
+PyObject* NewFileEnumTypesByName(ParentDescriptor descriptor) {
   return descriptor::NewMappingByName(&enums::ContainerDef, descriptor);
 }
 
@@ -1555,10 +1641,58 @@ static DescriptorContainerDef ContainerDef = {
 
 }  // namespace extensions
 
-PyObject* NewFileExtensionsByName(const FileDescriptor* descriptor) {
+PyObject* NewFileExtensionsByName(ParentDescriptor descriptor) {
   return descriptor::NewMappingByName(&extensions::ContainerDef, descriptor);
 }
 
+namespace services {
+
+typedef const ServiceDescriptor* ItemDescriptor;
+
+static int Count(PyContainer* self) {
+  return GetDescriptor(self)->service_count();
+}
+
+static ItemDescriptor GetByName(PyContainer* self, const string& name) {
+  return GetDescriptor(self)->FindServiceByName(name);
+}
+
+static ItemDescriptor GetByIndex(PyContainer* self, int index) {
+  return GetDescriptor(self)->service(index);
+}
+
+static PyObject* NewObjectFromItem(ItemDescriptor item) {
+  return PyServiceDescriptor_FromDescriptor(item);
+}
+
+static const string& GetItemName(ItemDescriptor item) {
+  return item->name();
+}
+
+static int GetItemIndex(ItemDescriptor item) {
+  return item->index();
+}
+
+static DescriptorContainerDef ContainerDef = {
+  "FileServices",
+  (CountMethod)Count,
+  (GetByIndexMethod)GetByIndex,
+  (GetByNameMethod)GetByName,
+  (GetByCamelcaseNameMethod)NULL,
+  (GetByNumberMethod)NULL,
+  (NewObjectFromItemMethod)NewObjectFromItem,
+  (GetItemNameMethod)GetItemName,
+  (GetItemCamelcaseNameMethod)NULL,
+  (GetItemNumberMethod)NULL,
+  (GetItemIndexMethod)GetItemIndex,
+};
+
+}  // namespace services
+
+PyObject* NewFileServicesByName(const FileDescriptor* descriptor) {
+  return descriptor::NewMappingByName(&services::ContainerDef, descriptor);
+}
+
 namespace dependencies {
 
 typedef const FileDescriptor* ItemDescriptor;

+ 8 - 0
python/google/protobuf/pyext/descriptor_containers.h

@@ -43,6 +43,7 @@ class Descriptor;
 class FileDescriptor;
 class EnumDescriptor;
 class OneofDescriptor;
+class ServiceDescriptor;
 
 namespace python {
 
@@ -89,10 +90,17 @@ PyObject* NewFileEnumTypesByName(const FileDescriptor* descriptor);
 
 PyObject* NewFileExtensionsByName(const FileDescriptor* descriptor);
 
+PyObject* NewFileServicesByName(const FileDescriptor* descriptor);
+
 PyObject* NewFileDependencies(const FileDescriptor* descriptor);
 PyObject* NewFilePublicDependencies(const FileDescriptor* descriptor);
 }  // namespace file_descriptor
 
+namespace service_descriptor {
+PyObject* NewServiceMethodsSeq(const ServiceDescriptor* descriptor);
+PyObject* NewServiceMethodsByName(const ServiceDescriptor* descriptor);
+}  // namespace service_descriptor
+
 
 }  // namespace python
 }  // namespace protobuf

+ 38 - 0
python/google/protobuf/pyext/descriptor_pool.cc

@@ -305,6 +305,40 @@ PyObject* FindOneofByName(PyDescriptorPool* self, PyObject* arg) {
   return PyOneofDescriptor_FromDescriptor(oneof_descriptor);
 }
 
+PyObject* FindServiceByName(PyDescriptorPool* self, PyObject* arg) {
+  Py_ssize_t name_size;
+  char* name;
+  if (PyString_AsStringAndSize(arg, &name, &name_size) < 0) {
+    return NULL;
+  }
+
+  const ServiceDescriptor* service_descriptor =
+      self->pool->FindServiceByName(string(name, name_size));
+  if (service_descriptor == NULL) {
+    PyErr_Format(PyExc_KeyError, "Couldn't find service %.200s", name);
+    return NULL;
+  }
+
+  return PyServiceDescriptor_FromDescriptor(service_descriptor);
+}
+
+PyObject* FindMethodByName(PyDescriptorPool* self, PyObject* arg) {
+  Py_ssize_t name_size;
+  char* name;
+  if (PyString_AsStringAndSize(arg, &name, &name_size) < 0) {
+    return NULL;
+  }
+
+  const MethodDescriptor* method_descriptor =
+      self->pool->FindMethodByName(string(name, name_size));
+  if (method_descriptor == NULL) {
+    PyErr_Format(PyExc_KeyError, "Couldn't find method %.200s", name);
+    return NULL;
+  }
+
+  return PyMethodDescriptor_FromDescriptor(method_descriptor);
+}
+
 PyObject* FindFileContainingSymbol(PyDescriptorPool* self, PyObject* arg) {
   Py_ssize_t name_size;
   char* name;
@@ -491,6 +525,10 @@ static PyMethodDef Methods[] = {
     "Searches for enum type descriptor by full name." },
   { "FindOneofByName", (PyCFunction)FindOneofByName, METH_O,
     "Searches for oneof descriptor by full name." },
+  { "FindServiceByName", (PyCFunction)FindServiceByName, METH_O,
+    "Searches for service descriptor by full name." },
+  { "FindMethodByName", (PyCFunction)FindMethodByName, METH_O,
+    "Searches for method descriptor by full name." },
 
   { "FindFileContainingSymbol", (PyCFunction)FindFileContainingSymbol, METH_O,
     "Gets the FileDescriptor containing the specified symbol." },

+ 0 - 1
python/google/protobuf/pyext/map_container.cc

@@ -39,7 +39,6 @@
 
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/common.h>
-#include <google/protobuf/stubs/scoped_ptr.h>
 #include <google/protobuf/map_field.h>
 #include <google/protobuf/map.h>
 #include <google/protobuf/message.h>

+ 9 - 57
python/google/protobuf/pyext/message.cc

@@ -1593,23 +1593,20 @@ struct ReleaseChild : public ChildVisitor {
       parent_(parent) {}
 
   int VisitRepeatedCompositeContainer(RepeatedCompositeContainer* container) {
-    return repeated_composite_container::Release(
-        reinterpret_cast<RepeatedCompositeContainer*>(container));
+    return repeated_composite_container::Release(container);
   }
 
   int VisitRepeatedScalarContainer(RepeatedScalarContainer* container) {
-    return repeated_scalar_container::Release(
-        reinterpret_cast<RepeatedScalarContainer*>(container));
+    return repeated_scalar_container::Release(container);
   }
 
   int VisitMapContainer(MapContainer* container) {
-    return reinterpret_cast<MapContainer*>(container)->Release();
+    return container->Release();
   }
 
   int VisitCMessage(CMessage* cmessage,
                     const FieldDescriptor* field_descriptor) {
-    return ReleaseSubMessage(parent_, field_descriptor,
-        reinterpret_cast<CMessage*>(cmessage));
+    return ReleaseSubMessage(parent_, field_descriptor, cmessage);
   }
 
   CMessage* parent_;
@@ -1903,7 +1900,7 @@ static bool allow_oversize_protos = false;
 
 // Provide a method in the module to set allow_oversize_protos to a boolean
 // value. This method returns the newly value of allow_oversize_protos.
-static PyObject* SetAllowOversizeProtos(PyObject* m, PyObject* arg) {
+PyObject* SetAllowOversizeProtos(PyObject* m, PyObject* arg) {
   if (!arg || !PyBool_Check(arg)) {
     PyErr_SetString(PyExc_TypeError,
                     "Argument to SetAllowOversizeProtos must be boolean");
@@ -3044,6 +3041,10 @@ bool InitProto2MessageModule(PyObject *m) {
       &PyFileDescriptor_Type));
   PyModule_AddObject(m, "OneofDescriptor", reinterpret_cast<PyObject*>(
       &PyOneofDescriptor_Type));
+  PyModule_AddObject(m, "ServiceDescriptor", reinterpret_cast<PyObject*>(
+      &PyServiceDescriptor_Type));
+  PyModule_AddObject(m, "MethodDescriptor", reinterpret_cast<PyObject*>(
+      &PyMethodDescriptor_Type));
 
   PyObject* enum_type_wrapper = PyImport_ImportModule(
       "google.protobuf.internal.enum_type_wrapper");
@@ -3081,53 +3082,4 @@ bool InitProto2MessageModule(PyObject *m) {
 }  // namespace python
 }  // namespace protobuf
 
-static PyMethodDef ModuleMethods[] = {
-  {"SetAllowOversizeProtos",
-    (PyCFunction)google::protobuf::python::cmessage::SetAllowOversizeProtos,
-    METH_O, "Enable/disable oversize proto parsing."},
-  { NULL, NULL}
-};
-
-#if PY_MAJOR_VERSION >= 3
-static struct PyModuleDef _module = {
-  PyModuleDef_HEAD_INIT,
-  "_message",
-  google::protobuf::python::module_docstring,
-  -1,
-  ModuleMethods,  /* m_methods */
-  NULL,
-  NULL,
-  NULL,
-  NULL
-};
-#define INITFUNC PyInit__message
-#define INITFUNC_ERRORVAL NULL
-#else  // Python 2
-#define INITFUNC init_message
-#define INITFUNC_ERRORVAL
-#endif
-
-extern "C" {
-  PyMODINIT_FUNC INITFUNC(void) {
-    PyObject* m;
-#if PY_MAJOR_VERSION >= 3
-    m = PyModule_Create(&_module);
-#else
-    m = Py_InitModule3("_message", ModuleMethods,
-                       google::protobuf::python::module_docstring);
-#endif
-    if (m == NULL) {
-      return INITFUNC_ERRORVAL;
-    }
-
-    if (!google::protobuf::python::InitProto2MessageModule(m)) {
-      Py_DECREF(m);
-      return INITFUNC_ERRORVAL;
-    }
-
-#if PY_MAJOR_VERSION >= 3
-    return m;
-#endif
-  }
-}
 }  // namespace google

+ 5 - 1
python/google/protobuf/pyext/message.h

@@ -54,7 +54,7 @@ class MessageFactory;
 
 #ifdef _SHARED_PTR_H
 using std::shared_ptr;
-using ::std::string;
+using std::string;
 #else
 using internal::shared_ptr;
 #endif
@@ -269,6 +269,8 @@ int AssureWritable(CMessage* self);
 // even in the case of extensions.
 PyDescriptorPool* GetDescriptorPoolForMessage(CMessage* message);
 
+PyObject* SetAllowOversizeProtos(PyObject* m, PyObject* arg);
+
 }  // namespace cmessage
 
 
@@ -354,6 +356,8 @@ bool CheckFieldBelongsToMessage(const FieldDescriptor* field_descriptor,
 
 extern PyObject* PickleError_class;
 
+bool InitProto2MessageModule(PyObject *m);
+
 }  // namespace python
 }  // namespace protobuf
 

+ 88 - 0
python/google/protobuf/pyext/message_module.cc

@@ -0,0 +1,88 @@
+// 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.
+
+#include <google/protobuf/pyext/message.h>
+
+static const char module_docstring[] =
+"python-proto2 is a module that can be used to enhance proto2 Python API\n"
+"performance.\n"
+"\n"
+"It provides access to the protocol buffers C++ reflection API that\n"
+"implements the basic protocol buffer functions.";
+
+static PyMethodDef ModuleMethods[] = {
+  {"SetAllowOversizeProtos",
+    (PyCFunction)google::protobuf::python::cmessage::SetAllowOversizeProtos,
+    METH_O, "Enable/disable oversize proto parsing."},
+  { NULL, NULL}
+};
+
+#if PY_MAJOR_VERSION >= 3
+static struct PyModuleDef _module = {
+  PyModuleDef_HEAD_INIT,
+  "_message",
+  module_docstring,
+  -1,
+  ModuleMethods,  /* m_methods */
+  NULL,
+  NULL,
+  NULL,
+  NULL
+};
+#define INITFUNC PyInit__message
+#define INITFUNC_ERRORVAL NULL
+#else  // Python 2
+#define INITFUNC init_message
+#define INITFUNC_ERRORVAL
+#endif
+
+extern "C" {
+  PyMODINIT_FUNC INITFUNC(void) {
+    PyObject* m;
+#if PY_MAJOR_VERSION >= 3
+    m = PyModule_Create(&_module);
+#else
+    m = Py_InitModule3("_message", ModuleMethods,
+                       module_docstring);
+#endif
+    if (m == NULL) {
+      return INITFUNC_ERRORVAL;
+    }
+
+    if (!google::protobuf::python::InitProto2MessageModule(m)) {
+      Py_DECREF(m);
+      return INITFUNC_ERRORVAL;
+    }
+
+#if PY_MAJOR_VERSION >= 3
+    return m;
+#endif
+  }
+}

文件差异内容过多而无法显示
+ 436 - 158
python/google/protobuf/text_format.py


+ 2 - 0
python/setup.py

@@ -76,6 +76,7 @@ def generate_proto(source, require = True):
       sys.exit(-1)
 
 def GenerateUnittestProtos():
+  generate_proto("../src/google/protobuf/any_test.proto", False)
   generate_proto("../src/google/protobuf/map_unittest.proto", False)
   generate_proto("../src/google/protobuf/unittest_arena.proto", False)
   generate_proto("../src/google/protobuf/unittest_no_arena.proto", False)
@@ -94,6 +95,7 @@ def GenerateUnittestProtos():
   generate_proto("google/protobuf/internal/descriptor_pool_test2.proto", False)
   generate_proto("google/protobuf/internal/factory_test1.proto", False)
   generate_proto("google/protobuf/internal/factory_test2.proto", False)
+  generate_proto("google/protobuf/internal/file_options_test.proto", False)
   generate_proto("google/protobuf/internal/import_test_package/inner.proto", False)
   generate_proto("google/protobuf/internal/import_test_package/outer.proto", False)
   generate_proto("google/protobuf/internal/missing_enum_values.proto", False)

+ 2 - 2
src/google/protobuf/any.pb.cc

@@ -283,8 +283,8 @@ void Any::SerializeWithCachedSizes(
   // @@protoc_insertion_point(serialize_end:google.protobuf.Any)
 }
 
-::google::protobuf::uint8* Any::SerializeWithCachedSizesToArray(
-    ::google::protobuf::uint8* target) const {
+::google::protobuf::uint8* Any::InternalSerializeWithCachedSizesToArray(
+    bool deterministic, ::google::protobuf::uint8* target) const {
   // @@protoc_insertion_point(serialize_to_array_start:google.protobuf.Any)
   // optional string type_url = 1;
   if (this->type_url().size() > 0) {

+ 6 - 2
src/google/protobuf/any.pb.h

@@ -42,7 +42,7 @@ class Any;
 
 // ===================================================================
 
-class LIBPROTOBUF_EXPORT Any : public ::google::protobuf::Message {
+class LIBPROTOBUF_EXPORT Any : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:google.protobuf.Any) */ {
  public:
   Any();
   virtual ~Any();
@@ -86,7 +86,11 @@ class LIBPROTOBUF_EXPORT Any : public ::google::protobuf::Message {
       ::google::protobuf::io::CodedInputStream* input);
   void SerializeWithCachedSizes(
       ::google::protobuf::io::CodedOutputStream* output) const;
-  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
+      bool deterministic, ::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const {
+    return InternalSerializeWithCachedSizesToArray(false, output);
+  }
   int GetCachedSize() const { return _cached_size_; }
   private:
   void SharedCtor();

+ 13 - 3
src/google/protobuf/any.proto

@@ -65,6 +65,16 @@ option objc_class_prefix = "GPB";
 //       foo = any.unpack(Foo.class);
 //     }
 //
+//  Example 3: Pack and unpack a message in Python.
+//
+//     foo = Foo(...)
+//     any = Any()
+//     any.Pack(foo)
+//     ...
+//     if any.Is(Foo.DESCRIPTOR):
+//       any.Unpack(foo)
+//       ...
+//
 // The pack methods provided by protobuf library will by default use
 // 'type.googleapis.com/full.type.name' as the type URL and the unpack
 // methods only use the fully qualified type name after the last '/'
@@ -104,10 +114,10 @@ message Any {
   // A URL/resource name whose content describes the type of the
   // serialized protocol buffer message.
   //
-  // For URLs which use the schema `http`, `https`, or no schema, the
+  // For URLs which use the scheme `http`, `https`, or no scheme, the
   // following restrictions and interpretations apply:
   //
-  // * If no schema is provided, `https` is assumed.
+  // * If no scheme is provided, `https` is assumed.
   // * The last segment of the URL's path must represent the fully
   //   qualified name of the type (as in `path/google.protobuf.Duration`).
   //   The name should be in a canonical form (e.g., leading "." is
@@ -120,7 +130,7 @@ message Any {
   //   on changes to types. (Use versioned type names to manage
   //   breaking changes.)
   //
-  // Schemas other than `http`, `https` (or the empty schema) might be
+  // Schemes other than `http`, `https` (or the empty scheme) might be
   // used with implementation specific semantics.
   //
   string type_url = 1;

+ 16 - 16
src/google/protobuf/api.pb.cc

@@ -475,8 +475,8 @@ void Api::SerializeWithCachedSizes(
   // @@protoc_insertion_point(serialize_end:google.protobuf.Api)
 }
 
-::google::protobuf::uint8* Api::SerializeWithCachedSizesToArray(
-    ::google::protobuf::uint8* target) const {
+::google::protobuf::uint8* Api::InternalSerializeWithCachedSizesToArray(
+    bool deterministic, ::google::protobuf::uint8* target) const {
   // @@protoc_insertion_point(serialize_to_array_start:google.protobuf.Api)
   // optional string name = 1;
   if (this->name().size() > 0) {
@@ -492,15 +492,15 @@ void Api::SerializeWithCachedSizes(
   // repeated .google.protobuf.Method methods = 2;
   for (unsigned int i = 0, n = this->methods_size(); i < n; i++) {
     target = ::google::protobuf::internal::WireFormatLite::
-      WriteMessageNoVirtualToArray(
-        2, this->methods(i), target);
+      InternalWriteMessageNoVirtualToArray(
+        2, this->methods(i), false, target);
   }
 
   // repeated .google.protobuf.Option options = 3;
   for (unsigned int i = 0, n = this->options_size(); i < n; i++) {
     target = ::google::protobuf::internal::WireFormatLite::
-      WriteMessageNoVirtualToArray(
-        3, this->options(i), target);
+      InternalWriteMessageNoVirtualToArray(
+        3, this->options(i), false, target);
   }
 
   // optional string version = 4;
@@ -517,15 +517,15 @@ void Api::SerializeWithCachedSizes(
   // optional .google.protobuf.SourceContext source_context = 5;
   if (this->has_source_context()) {
     target = ::google::protobuf::internal::WireFormatLite::
-      WriteMessageNoVirtualToArray(
-        5, *this->source_context_, target);
+      InternalWriteMessageNoVirtualToArray(
+        5, *this->source_context_, false, target);
   }
 
   // repeated .google.protobuf.Mixin mixins = 6;
   for (unsigned int i = 0, n = this->mixins_size(); i < n; i++) {
     target = ::google::protobuf::internal::WireFormatLite::
-      WriteMessageNoVirtualToArray(
-        6, this->mixins(i), target);
+      InternalWriteMessageNoVirtualToArray(
+        6, this->mixins(i), false, target);
   }
 
   // optional .google.protobuf.Syntax syntax = 7;
@@ -1225,8 +1225,8 @@ void Method::SerializeWithCachedSizes(
   // @@protoc_insertion_point(serialize_end:google.protobuf.Method)
 }
 
-::google::protobuf::uint8* Method::SerializeWithCachedSizesToArray(
-    ::google::protobuf::uint8* target) const {
+::google::protobuf::uint8* Method::InternalSerializeWithCachedSizesToArray(
+    bool deterministic, ::google::protobuf::uint8* target) const {
   // @@protoc_insertion_point(serialize_to_array_start:google.protobuf.Method)
   // optional string name = 1;
   if (this->name().size() > 0) {
@@ -1274,8 +1274,8 @@ void Method::SerializeWithCachedSizes(
   // repeated .google.protobuf.Option options = 6;
   for (unsigned int i = 0, n = this->options_size(); i < n; i++) {
     target = ::google::protobuf::internal::WireFormatLite::
-      WriteMessageNoVirtualToArray(
-        6, this->options(i), target);
+      InternalWriteMessageNoVirtualToArray(
+        6, this->options(i), false, target);
   }
 
   // optional .google.protobuf.Syntax syntax = 7;
@@ -1803,8 +1803,8 @@ void Mixin::SerializeWithCachedSizes(
   // @@protoc_insertion_point(serialize_end:google.protobuf.Mixin)
 }
 
-::google::protobuf::uint8* Mixin::SerializeWithCachedSizesToArray(
-    ::google::protobuf::uint8* target) const {
+::google::protobuf::uint8* Mixin::InternalSerializeWithCachedSizesToArray(
+    bool deterministic, ::google::protobuf::uint8* target) const {
   // @@protoc_insertion_point(serialize_to_array_start:google.protobuf.Mixin)
   // optional string name = 1;
   if (this->name().size() > 0) {

+ 18 - 6
src/google/protobuf/api.pb.h

@@ -45,7 +45,7 @@ class Mixin;
 
 // ===================================================================
 
-class LIBPROTOBUF_EXPORT Api : public ::google::protobuf::Message {
+class LIBPROTOBUF_EXPORT Api : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:google.protobuf.Api) */ {
  public:
   Api();
   virtual ~Api();
@@ -79,7 +79,11 @@ class LIBPROTOBUF_EXPORT Api : public ::google::protobuf::Message {
       ::google::protobuf::io::CodedInputStream* input);
   void SerializeWithCachedSizes(
       ::google::protobuf::io::CodedOutputStream* output) const;
-  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
+      bool deterministic, ::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const {
+    return InternalSerializeWithCachedSizesToArray(false, output);
+  }
   int GetCachedSize() const { return _cached_size_; }
   private:
   void SharedCtor();
@@ -196,7 +200,7 @@ class LIBPROTOBUF_EXPORT Api : public ::google::protobuf::Message {
 };
 // -------------------------------------------------------------------
 
-class LIBPROTOBUF_EXPORT Method : public ::google::protobuf::Message {
+class LIBPROTOBUF_EXPORT Method : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:google.protobuf.Method) */ {
  public:
   Method();
   virtual ~Method();
@@ -230,7 +234,11 @@ class LIBPROTOBUF_EXPORT Method : public ::google::protobuf::Message {
       ::google::protobuf::io::CodedInputStream* input);
   void SerializeWithCachedSizes(
       ::google::protobuf::io::CodedOutputStream* output) const;
-  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
+      bool deterministic, ::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const {
+    return InternalSerializeWithCachedSizesToArray(false, output);
+  }
   int GetCachedSize() const { return _cached_size_; }
   private:
   void SharedCtor();
@@ -337,7 +345,7 @@ class LIBPROTOBUF_EXPORT Method : public ::google::protobuf::Message {
 };
 // -------------------------------------------------------------------
 
-class LIBPROTOBUF_EXPORT Mixin : public ::google::protobuf::Message {
+class LIBPROTOBUF_EXPORT Mixin : public ::google::protobuf::Message /* @@protoc_insertion_point(class_definition:google.protobuf.Mixin) */ {
  public:
   Mixin();
   virtual ~Mixin();
@@ -371,7 +379,11 @@ class LIBPROTOBUF_EXPORT Mixin : public ::google::protobuf::Message {
       ::google::protobuf::io::CodedInputStream* input);
   void SerializeWithCachedSizes(
       ::google::protobuf::io::CodedOutputStream* output) const;
-  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(
+      bool deterministic, ::google::protobuf::uint8* output) const;
+  ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const {
+    return InternalSerializeWithCachedSizesToArray(false, output);
+  }
   int GetCachedSize() const { return _cached_size_; }
   private:
   void SharedCtor();

+ 2 - 9
src/google/protobuf/arena.cc

@@ -133,12 +133,7 @@ Arena::Block* Arena::NewBlock(void* me, Block* my_last_block, size_t n,
   Block* b = reinterpret_cast<Block*>(options_.block_alloc(size));
   b->pos = kHeaderSize + n;
   b->size = size;
-  if (b->avail() == 0) {
-    // Do not attempt to reuse this block.
-    b->owner = NULL;
-  } else {
-    b->owner = me;
-  }
+  b->owner = me;
 #ifdef ADDRESS_SANITIZER
   // Poison the rest of the block for ASAN. It was unpoisoned by the underlying
   // malloc but it's not yet usable until we return it as part of an allocation.
@@ -223,9 +218,7 @@ void* Arena::SlowAlloc(size_t n) {
   }
   b = NewBlock(me, b, n, options_.start_block_size, options_.max_block_size);
   AddBlock(b);
-  if (b->owner == me) {  // If this block can be reused (see NewBlock()).
-    SetThreadCacheBlock(b);
-  }
+  SetThreadCacheBlock(b);
   return reinterpret_cast<char*>(b) + kHeaderSize;
 }
 

+ 1 - 1
src/google/protobuf/arena.h

@@ -801,7 +801,7 @@ class LIBPROTOBUF_EXPORT Arena {
   template <typename T>
   static void CreateInArenaStorageInternal(
       T* ptr, Arena* arena, google::protobuf::internal::false_type) {
-    new (ptr) T;
+    new (ptr) T();
   }
 
   template <typename T>

+ 0 - 1
src/google/protobuf/arena_unittest.cc

@@ -42,7 +42,6 @@
 
 #include <google/protobuf/stubs/logging.h>
 #include <google/protobuf/stubs/common.h>
-#include <google/protobuf/stubs/scoped_ptr.h>
 #include <google/protobuf/arena_test_util.h>
 #include <google/protobuf/test_util.h>
 #include <google/protobuf/unittest.pb.h>

+ 16 - 11
src/google/protobuf/compiler/command_line_interface.cc

@@ -641,18 +641,23 @@ CommandLineInterface::MemoryOutputStream::~MemoryOutputStream() {
       return;
     }
 
-    // Seek backwards to the beginning of the line, which is where we will
-    // insert the data.  Note that this has the effect of pushing the insertion
-    // point down, so the data is inserted before it.  This is intentional
-    // because it means that multiple insertions at the same point will end
-    // up in the expected order in the final output.
-    pos = target->find_last_of('\n', pos);
-    if (pos == string::npos) {
-      // Insertion point is on the first line.
-      pos = 0;
+    if ((pos > 3) && (target->substr(pos - 3, 2) == "/*")) {
+      // Support for inline "/* @@protoc_insertion_point() */"
+      pos = pos - 3;
     } else {
-      // Advance to character after '\n'.
-      ++pos;
+      // Seek backwards to the beginning of the line, which is where we will
+      // insert the data.  Note that this has the effect of pushing the
+      // insertion point down, so the data is inserted before it.  This is
+      // intentional because it means that multiple insertions at the same point
+      // will end up in the expected order in the final output.
+      pos = target->find_last_of('\n', pos);
+      if (pos == string::npos) {
+        // Insertion point is on the first line.
+        pos = 0;
+      } else {
+        // Advance to character after '\n'.
+        ++pos;
+      }
     }
 
     // Extract indent.

+ 1 - 1
src/google/protobuf/compiler/cpp/cpp_enum_field.cc

@@ -35,8 +35,8 @@
 #include <google/protobuf/compiler/cpp/cpp_enum_field.h>
 #include <google/protobuf/compiler/cpp/cpp_helpers.h>
 #include <google/protobuf/io/printer.h>
-#include <google/protobuf/stubs/strutil.h>
 #include <google/protobuf/wire_format.h>
+#include <google/protobuf/stubs/strutil.h>
 
 namespace google {
 namespace protobuf {

+ 26 - 31
src/google/protobuf/compiler/cpp/cpp_map_field.cc

@@ -182,33 +182,33 @@ GenerateConstructorCode(io::Printer* printer) const {
 
 void MapFieldGenerator::
 GenerateMergeFromCodedStream(io::Printer* printer) const {
+    const FieldDescriptor* key_field =
+        descriptor_->message_type()->FindFieldByName("key");
   const FieldDescriptor* value_field =
       descriptor_->message_type()->FindFieldByName("value");
-  printer->Print(variables_,
-      "::google::protobuf::scoped_ptr<$map_classname$> entry($name$_.NewEntry());\n");
-
+  bool using_entry = false;
+  string key;
+  string value;
   if (IsProto3Field(descriptor_) ||
       value_field->type() != FieldDescriptor::TYPE_ENUM) {
     printer->Print(variables_,
+        "$map_classname$::Parser< ::google::protobuf::internal::MapField$lite$<\n"
+                   "    $key_cpp$, $val_cpp$,\n"
+                   "    $key_wire_type$,\n"
+                   "    $val_wire_type$,\n"
+                   "    $default_enum_value$ >,\n"
+                   "  ::google::protobuf::Map< $key_cpp$, $val_cpp$ > >"
+        " parser(&$name$_);\n"
         "DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(\n"
-        "    input, entry.get()));\n");
-    switch (value_field->cpp_type()) {
-      case FieldDescriptor::CPPTYPE_MESSAGE:
-        printer->Print(variables_,
-            "(*mutable_$name$())[entry->key()].Swap("
-            "entry->mutable_value());\n");
-        break;
-      case FieldDescriptor::CPPTYPE_ENUM:
-        printer->Print(variables_,
-            "(*mutable_$name$())[entry->key()] =\n"
-            "    static_cast< $val_cpp$ >(*entry->mutable_value());\n");
-        break;
-      default:
-        printer->Print(variables_,
-            "(*mutable_$name$())[entry->key()] = *entry->mutable_value();\n");
-        break;
-    }
+        "    input, &parser));\n");
+    key = "parser.key()";
+    value = "parser.value()";
   } else {
+    using_entry = true;
+    key = "entry->key()";
+    value = "entry->value()";
+    printer->Print(variables_,
+        "::google::protobuf::scoped_ptr<$map_classname$> entry($name$_.NewEntry());\n");
     printer->Print(variables_,
         "{\n"
         "  ::std::string data;\n"
@@ -229,28 +229,23 @@ GenerateMergeFromCodedStream(io::Printer* printer) const {
           "    unknown_fields_stream.WriteString(data);\n");
     }
 
-
     printer->Print(variables_,
         "  }\n"
         "}\n");
   }
 
-  const FieldDescriptor* key_field =
-      descriptor_->message_type()->FindFieldByName("key");
   if (key_field->type() == FieldDescriptor::TYPE_STRING) {
     GenerateUtf8CheckCodeForString(
-        key_field, options_, true, variables_,
-        "entry->key().data(), entry->key().length(),\n", printer);
+    key_field, options_, true, variables_,
+        StrCat(key, ".data(), ", key, ".length(),\n").data(), printer);
   }
   if (value_field->type() == FieldDescriptor::TYPE_STRING) {
     GenerateUtf8CheckCodeForString(value_field, options_, true, variables_,
-                                   "entry->mutable_value()->data(),\n"
-                                   "entry->mutable_value()->length(),\n",
-                                   printer);
+        StrCat(value, ".data(), ", value, ".length(),\n").data(), printer);
   }
 
   // If entry is allocated by arena, its desctructor should be avoided.
-  if (SupportsArenas(descriptor_)) {
+  if (using_entry && SupportsArenas(descriptor_)) {
     printer->Print(variables_,
         "if (entry->GetArena() != NULL) entry.release();\n");
   }
@@ -333,8 +328,8 @@ GenerateSerializeWithCachedSizesToArray(io::Printer* printer) const {
   printer->Print(variables_,
       "    entry.reset($name$_.New$wrapper$(it->first, it->second));\n"
       "    target = ::google::protobuf::internal::WireFormatLite::\n"
-      "        Write$declared_type$NoVirtualToArray(\n"
-      "            $number$, *entry, target);\n");
+      "        InternalWrite$declared_type$NoVirtualToArray(\n"
+      "            $number$, *entry, false, target);\n");
 
   printer->Indent();
   printer->Indent();

+ 28 - 11
src/google/protobuf/compiler/cpp/cpp_message.cc

@@ -838,11 +838,13 @@ GenerateDependentBaseClassDefinition(io::Printer* printer) {
 
   map<string, string> vars;
   vars["classname"] = DependentBaseClassTemplateName(descriptor_);
+  vars["full_name"] = descriptor_->full_name();
   vars["superclass"] = SuperClassName(descriptor_, options_);
 
   printer->Print(vars,
     "template <class T>\n"
-    "class $classname$ : public $superclass$ {\n"
+    "class $classname$ : public $superclass$ "
+    "/* @@protoc_insertion_point(dep_base_class_definition:$full_name$) */ {\n"
     " public:\n");
   printer->Indent();
 
@@ -878,6 +880,7 @@ GenerateClassDefinition(io::Printer* printer) {
 
   map<string, string> vars;
   vars["classname"] = classname_;
+  vars["full_name"] = descriptor_->full_name();
   vars["field_count"] = SimpleItoa(descriptor_->field_count());
   vars["oneof_decl_count"] = SimpleItoa(descriptor_->oneof_decl_count());
   if (options_.dllexport_decl.empty()) {
@@ -892,7 +895,9 @@ GenerateClassDefinition(io::Printer* printer) {
     vars["superclass"] = SuperClassName(descriptor_, options_);
   }
   printer->Print(vars,
-    "class $dllexport$$classname$ : public $superclass$ {\n");
+    "class $dllexport$$classname$ : public $superclass$ "
+    "/* @@protoc_insertion_point(class_definition:$full_name$) */ "
+    "{\n");
   printer->Annotate("classname", descriptor_);
   if (use_dependent_base_) {
     printer->Print(vars, "  friend class $superclass$;\n");
@@ -1076,7 +1081,11 @@ GenerateClassDefinition(io::Printer* printer) {
     }
     if (HasFastArraySerialization(descriptor_->file(), options_)) {
       printer->Print(
-        "::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;\n");
+        "::google::protobuf::uint8* InternalSerializeWithCachedSizesToArray(\n"
+        "    bool deterministic, ::google::protobuf::uint8* output) const;\n"
+        "::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const {\n"
+        "  return InternalSerializeWithCachedSizesToArray(false, output);\n"
+        "}\n");
     }
   }
 
@@ -1095,6 +1104,13 @@ GenerateClassDefinition(io::Printer* printer) {
       descriptors.push_back(descriptor_->oneof_decl(i)->field(j));
     }
   }
+  for (int i = 0; i < descriptor_->nested_type_count(); i++) {
+    const Descriptor* nested_type = descriptor_->nested_type(i);
+    if (IsMapEntryMessage(nested_type)) {
+      descriptors.push_back(nested_type->FindFieldByName("key"));
+      descriptors.push_back(nested_type->FindFieldByName("value"));
+    }
+  }
   uses_string_ = false;
   if (PreserveUnknownFields(descriptor_) &&
       !UseUnknownFieldSet(descriptor_->file(), options_)) {
@@ -3267,8 +3283,8 @@ void MessageGenerator::GenerateSerializeOneExtensionRange(
     "// Extension range [$start$, $end$)\n");
   if (to_array) {
     printer->Print(vars,
-      "target = _extensions_.SerializeWithCachedSizesToArray(\n"
-      "    $start$, $end$, target);\n\n");
+      "target = _extensions_.InternalSerializeWithCachedSizesToArray(\n"
+      "    $start$, $end$, false, target);\n\n");
   } else {
     printer->Print(vars,
       "_extensions_.SerializeWithCachedSizes(\n"
@@ -3320,10 +3336,11 @@ GenerateSerializeWithCachedSizesToArray(io::Printer* printer) {
   if (descriptor_->options().message_set_wire_format()) {
     // Special-case MessageSet.
     printer->Print(
-      "::google::protobuf::uint8* $classname$::SerializeWithCachedSizesToArray(\n"
-      "    ::google::protobuf::uint8* target) const {\n"
-      "  target =\n"
-      "      _extensions_.SerializeMessageSetWithCachedSizesToArray(target);\n",
+      "::google::protobuf::uint8* $classname$::InternalSerializeWithCachedSizesToArray(\n"
+      "    bool deterministic, ::google::protobuf::uint8* target) const {\n"
+      "  target = _extensions_."
+      "InternalSerializeMessageSetWithCachedSizesToArray(\n"
+      "               deterministic, target);\n",
       "classname", classname_);
     GOOGLE_CHECK(UseUnknownFieldSet(descriptor_->file(), options_));
     printer->Print(
@@ -3337,8 +3354,8 @@ GenerateSerializeWithCachedSizesToArray(io::Printer* printer) {
   }
 
   printer->Print(
-    "::google::protobuf::uint8* $classname$::SerializeWithCachedSizesToArray(\n"
-    "    ::google::protobuf::uint8* target) const {\n",
+    "::google::protobuf::uint8* $classname$::InternalSerializeWithCachedSizesToArray(\n"
+    "    bool deterministic, ::google::protobuf::uint8* target) const {\n",
     "classname", classname_);
   printer->Indent();
 

部分文件因为文件数量过多而无法显示