Commits

liu...@google.com@630680e5-0e50-0410-840e-4b1c322b438d  committed 124f0e2

Submit recent changes from internal branch. See CHANGES.txt for more details.

  • Participants
  • Parent commits 36e3321

Comments (0)

Files changed (160)

+2010-11-01 version 2.4.0:
+
+  General
+  * The RPC (cc|java|py)_generic_services default value is now false instead of
+    true.
+  * Custom options can have aggregate types. For example,
+      message MyOption {
+        optional string comment = 1;
+        optional string author = 2;
+      }
+      extend google.protobuf.FieldOptions {
+        optional MyOption myoption = 12345;
+      }
+    This option can now be set as follows:
+      message SomeType {
+        optional int32 field = 1 [(myoption) = { comment:'x' author:'y' }];
+      }
+
+  C++
+  * Various speed and code size optimizations.
+  * Added a release_foo() method on string and message fields.
+  * Fixed gzip_output_stream sub-stream handling.
+
+  Java
+  * Builders now maintain sub-builders for sub-messages. Use getFooBuilder() to
+    get the builder for the sub-message "foo". This allows you to repeatedly
+    modify deeply-nested sub-messages without rebuilding them.
+  * Builder.build() no longer invalidates the Builder for generated messages
+    (You may continue to modify it and then build another message).
+  * Code generator will generate efficient equals() and hashCode()
+    implementations if new option java_generate_equals_and_hash is enabled.
+    (Otherwise, reflection-based implementations are used.)
+  * Generated messages now implement Serializable.
+  * Fields with [deprecated=true] will be marked with @Deprecated in Java.
+  * Added lazy conversion of UTF-8 encoded strings to String objects to improve
+    performance.
+  * Various optimizations.
+  * Enum value can be accessed directly, instead of calling getNumber() on the
+    enum member.
+  * For each enum value, an integer constant is also generated with the suffix
+    _VALUE.
+
+  Python
+  * Added an experimental  C++ implementation for Python messages via a Python
+    extension. Implementation type is controlled by an environment variable
+    PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION (valid values: "cpp" and "python")
+    The default value is currently "python" but will be changed to "cpp" in
+    future release.
+  * Improved performance on message instantiation significantly.
+    Most of the work on message instantiation is done just once per message
+    class, instead of once per message instance.
+  * Improved performance on text message parsing.
+  * Allow add() to forward keyword arguments to the concrete class.
+      E.g. instead of
+        item = repeated_field.add()
+        item.foo = bar
+        item.baz = quux
+      You can do:
+        repeated_field.add(foo=bar, baz=quux)
+  * Added a sort() interface to the BaseContainer.
+  * Added an extend() method to repeated composite fields.
+  * Added UTF8 debug string support.
+
 2010-01-08 version 2.3.0:
 
   General
   java/src/main/java/com/google/protobuf/GeneratedMessageLite.java           \
   java/src/main/java/com/google/protobuf/Internal.java                       \
   java/src/main/java/com/google/protobuf/InvalidProtocolBufferException.java \
+  java/src/main/java/com/google/protobuf/LazyStringArrayList.java            \
+  java/src/main/java/com/google/protobuf/LazyStringList.java                 \
   java/src/main/java/com/google/protobuf/Message.java                        \
   java/src/main/java/com/google/protobuf/MessageLite.java                    \
+  java/src/main/java/com/google/protobuf/MessageLiteOrBuilder.java           \
+  java/src/main/java/com/google/protobuf/MessageOrBuilder.java               \
   java/src/main/java/com/google/protobuf/ProtocolMessageEnum.java            \
+  java/src/main/java/com/google/protobuf/RepeatedFieldBuilder.java           \
   java/src/main/java/com/google/protobuf/RpcCallback.java                    \
   java/src/main/java/com/google/protobuf/RpcChannel.java                     \
   java/src/main/java/com/google/protobuf/RpcController.java                  \
   java/src/main/java/com/google/protobuf/RpcUtil.java                        \
   java/src/main/java/com/google/protobuf/Service.java                        \
   java/src/main/java/com/google/protobuf/ServiceException.java               \
+  java/src/main/java/com/google/protobuf/SingleFieldBuilder.java             \
+  java/src/main/java/com/google/protobuf/SmallSortedMap.java                 \
   java/src/main/java/com/google/protobuf/TextFormat.java                     \
   java/src/main/java/com/google/protobuf/UninitializedMessageException.java  \
   java/src/main/java/com/google/protobuf/UnknownFieldSet.java                \
+  java/src/main/java/com/google/protobuf/UnmodifiableLazyStringList.java     \
   java/src/main/java/com/google/protobuf/WireFormat.java                     \
   java/src/test/java/com/google/protobuf/AbstractMessageTest.java            \
   java/src/test/java/com/google/protobuf/CodedInputStreamTest.java           \
   java/src/test/java/com/google/protobuf/CodedOutputStreamTest.java          \
+  java/src/test/java/com/google/protobuf/DeprecatedFieldTest.java            \
   java/src/test/java/com/google/protobuf/DescriptorsTest.java                \
   java/src/test/java/com/google/protobuf/DynamicMessageTest.java             \
+  java/src/test/java/com/google/protobuf/ForceFieldBuildersPreRun.java       \
   java/src/test/java/com/google/protobuf/GeneratedMessageTest.java           \
+  java/src/test/java/com/google/protobuf/LazyStringArrayListTest.java        \
+  java/src/test/java/com/google/protobuf/LazyStringEndToEndTest.java         \
   java/src/test/java/com/google/protobuf/LiteTest.java                       \
   java/src/test/java/com/google/protobuf/MessageTest.java                    \
+  java/src/test/java/com/google/protobuf/NestedBuildersTest.java             \
+  java/src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java       \
   java/src/test/java/com/google/protobuf/ServiceTest.java                    \
+  java/src/test/java/com/google/protobuf/SingleFieldBuilderTest.java         \
+  java/src/test/java/com/google/protobuf/SmallSortedMapTest.java             \
+  java/src/test/java/com/google/protobuf/TestBadIdentifiers.java             \
   java/src/test/java/com/google/protobuf/TestUtil.java                       \
   java/src/test/java/com/google/protobuf/TextFormatTest.java                 \
   java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java            \
+  java/src/test/java/com/google/protobuf/UnmodifiableLazyStringListTest.java \
   java/src/test/java/com/google/protobuf/WireFormatTest.java                 \
   java/src/test/java/com/google/protobuf/multiple_files_test.proto           \
+  java/src/test/java/com/google/protobuf/nested_builders_test.proto          \
+  java/src/test/java/com/google/protobuf/nested_extension.proto              \
+  java/src/test/java/com/google/protobuf/nested_extension_lite.proto         \
+  java/src/test/java/com/google/protobuf/non_nested_extension.proto          \
+  java/src/test/java/com/google/protobuf/non_nested_extension_lite.proto     \
+  java/src/test/java/com/google/protobuf/test_bad_identifiers.proto          \
   java/pom.xml                                                               \
   java/README.txt                                                            \
   python/google/protobuf/internal/generator_test.py                          \
   python/google/protobuf/internal/message_test.py                            \
   python/google/protobuf/internal/more_extensions.proto                      \
   python/google/protobuf/internal/more_messages.proto                        \
+  python/google/protobuf/internal/python_message.py                          \
+  python/google/protobuf/internal/cpp_message.py                             \
+  python/google/protobuf/internal/api_implementation.py                      \
   python/google/protobuf/internal/reflection_test.py                         \
   python/google/protobuf/internal/service_reflection_test.py                 \
   python/google/protobuf/internal/test_util.py                               \
   python/google/protobuf/internal/wire_format.py                             \
   python/google/protobuf/internal/wire_format_test.py                        \
   python/google/protobuf/internal/__init__.py                                \
+  python/google/protobuf/pyext/python-proto2.cc                              \
+  python/google/protobuf/pyext/python_descriptor.cc                          \
+  python/google/protobuf/pyext/python_descriptor.h                           \
+  python/google/protobuf/pyext/python_protobuf.cc                            \
+  python/google/protobuf/pyext/python_protobuf.h                             \
   python/google/protobuf/descriptor.py                                       \
   python/google/protobuf/message.py                                          \
   python/google/protobuf/reflection.py                                       \

File java/pom.xml

                   <arg value="../src/google/protobuf/unittest_mset.proto" />
                   <arg
                     value="src/test/java/com/google/protobuf/multiple_files_test.proto" />
+                  <arg value="src/test/java/com/google/protobuf/nested_builders_test.proto" />
+                  <arg value="src/test/java/com/google/protobuf/nested_extension.proto" />
+                  <arg value="src/test/java/com/google/protobuf/nested_extension_lite.proto" />
+                  <arg value="src/test/java/com/google/protobuf/non_nested_extension.proto" />
+                  <arg value="src/test/java/com/google/protobuf/non_nested_extension_lite.proto" />
+                  <arg value="src/test/java/com/google/protobuf/test_bad_identifiers.proto" />
                   <arg
                     value="../src/google/protobuf/unittest_optimize_for.proto" />
                   <arg

File java/src/main/java/com/google/protobuf/AbstractMessage.java

 
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Internal.EnumLite;
 
+import java.io.IOException;
 import java.io.InputStream;
-import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
   public int hashCode() {
     int hash = 41;
     hash = (19 * hash) + getDescriptorForType().hashCode();
-    hash = (53 * hash) + getAllFields().hashCode();
+    hash = hashFields(hash, getAllFields());
     hash = (29 * hash) + getUnknownFields().hashCode();
     return hash;
   }
 
+  /** Get a hash code for given fields and values, using the given seed. */
+  @SuppressWarnings("unchecked")
+  protected int hashFields(int hash, Map<FieldDescriptor, Object> map) {
+    for (Map.Entry<FieldDescriptor, Object> entry : map.entrySet()) {
+      FieldDescriptor field = entry.getKey();
+      Object value = entry.getValue();
+      hash = (37 * hash) + field.getNumber();
+      if (field.getType() != FieldDescriptor.Type.ENUM){
+        hash = (53 * hash) + value.hashCode();
+      } else if (field.isRepeated()) {
+        List<? extends EnumLite> list = (List<? extends EnumLite>) value;
+        hash = (53 * hash) + hashEnumList(list);
+      } else {
+        hash = (53 * hash) + hashEnum((EnumLite) value);
+      }
+    }
+    return hash;
+  }
+
+  /**
+   * Helper method for implementing {@link Message#hashCode()}.
+   * @see Boolean#hashCode()
+   */
+  protected static int hashLong(long n) {
+    return (int) (n ^ (n >>> 32));
+  }
+
+  /**
+   * Helper method for implementing {@link Message#hashCode()}.
+   * @see Boolean#hashCode()
+   */
+  protected static int hashBoolean(boolean b) {
+    return b ? 1231 : 1237;
+  }
+
+  /**
+   * Helper method for implementing {@link Message#hashCode()}.
+   * <p>
+   * This is needed because {@link java.lang.Enum#hashCode()} is final, but we
+   * need to use the field number as the hash code to ensure compatibility
+   * between statically and dynamically generated enum objects.
+   */
+  protected static int hashEnum(EnumLite e) {
+    return e.getNumber();
+  }
+
+  /** Helper method for implementing {@link Message#hashCode()}. */
+  protected static int hashEnumList(List<? extends EnumLite> list) {
+    int hash = 1;
+    for (EnumLite e : list) {
+      hash = 31 * hash + hashEnum(e);
+    }
+    return hash;
+  }
+
   // =================================================================
 
   /**

File java/src/main/java/com/google/protobuf/ByteString.java

   }
 
   /**
+   * Copies bytes into a ByteBuffer.
+   *
+   * @param target ByteBuffer to copy into.
+   * @throws ReadOnlyBufferException if the {@code target} is read-only
+   * @throws BufferOverflowException if the {@code target}'s remaining()
+   *         space is not large enough to hold the data.
+   */
+  public void copyTo(ByteBuffer target) {
+    target.put(bytes, 0, bytes.length);
+  }
+
+  /**
    * Copies bytes to a {@code byte[]}.
    */
   public byte[] toByteArray() {

File java/src/main/java/com/google/protobuf/CodedInputStream.java

    */
   public static CodedInputStream newInstance(final byte[] buf, final int off,
                                              final int len) {
-    return new CodedInputStream(buf, off, len);
+    CodedInputStream result = new CodedInputStream(buf, off, len);
+    try {
+      // Some uses of CodedInputStream can be more efficient if they know
+      // exactly how many bytes are available.  By pushing the end point of the
+      // buffer as a limit, we allow them to get this information via
+      // getBytesUntilLimit().  Pushing a limit that we know is at the end of
+      // the stream can never hurt, since we can never past that point anyway.
+      result.pushLimit(len);
+    } catch (InvalidProtocolBufferException ex) {
+      // The only reason pushLimit() might throw an exception here is if len
+      // is negative. Normally pushLimit()'s parameter comes directly off the
+      // wire, so it's important to catch exceptions in case of corrupt or
+      // malicious data. However, in this case, we expect that len is not a
+      // user-supplied value, so we can assume that it being negative indicates
+      // a programming error. Therefore, throwing an unchecked exception is
+      // appropriate.
+      throw new IllegalArgumentException(ex);
+    }
+    return result;
   }
 
   // -----------------------------------------------------------------
   /** Read a {@code bytes} field value from the stream. */
   public ByteString readBytes() throws IOException {
     final int size = readRawVarint32();
-    if (size <= (bufferSize - bufferPos) && size > 0) {
+    if (size == 0) {
+      return ByteString.EMPTY;
+    } else if (size <= (bufferSize - bufferPos) && size > 0) {
       // Fast path:  We already have the bytes in a contiguous buffer, so
       //   just copy directly from it.
       final ByteString result = ByteString.copyFrom(buffer, bufferPos, size);
    * has already read one byte.  This allows the caller to determine if EOF
    * has been reached before attempting to read.
    */
-  static int readRawVarint32(final int firstByte,
-                             final InputStream input) throws IOException {
+  public static int readRawVarint32(
+      final int firstByte, final InputStream input) throws IOException {
     if ((firstByte & 0x80) == 0) {
       return firstByte;
     }
     } else {
       // Skipping more bytes than are in the buffer.  First skip what we have.
       int pos = bufferSize - bufferPos;
-      totalBytesRetired += pos;
-      bufferPos = 0;
-      bufferSize = 0;
+      bufferPos = bufferSize;
 
-      // Then skip directly from the InputStream for the rest.
-      while (pos < size) {
-        final int n = (input == null) ? -1 : (int) input.skip(size - pos);
-        if (n <= 0) {
-          throw InvalidProtocolBufferException.truncatedMessage();
-        }
-        pos += n;
-        totalBytesRetired += n;
+      // Keep refilling the buffer until we get to the point we wanted to skip
+      // to.  This has the side effect of ensuring the limits are updated
+      // correctly.
+      refillBuffer(true);
+      while (size - pos > bufferSize) {
+        pos += bufferSize;
+        bufferPos = bufferSize;
+        refillBuffer(true);
       }
+
+      bufferPos = size - pos; 
     }
   }
 }

File java/src/main/java/com/google/protobuf/CodedOutputStream.java

 import java.io.OutputStream;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.io.InputStream;
 
 /**
  * Encodes and writes protocol message fields.
 
   /** Write a {@code bytes} field to the stream. */
   public void writeBytesNoTag(final ByteString value) throws IOException {
-    final byte[] bytes = value.toByteArray();
-    writeRawVarint32(bytes.length);
-    writeRawBytes(bytes);
+    writeRawVarint32(value.size());
+    writeRawBytes(value);
   }
 
   /** Write a {@code uint32} field to the stream. */
     writeRawByte((byte) value);
   }
 
+  /** Write a byte string. */
+  public void writeRawBytes(final ByteString value) throws IOException {
+    writeRawBytes(value, 0, value.size());
+  }
+
   /** Write an array of bytes. */
   public void writeRawBytes(final byte[] value) throws IOException {
     writeRawBytes(value, 0, value.length);
     }
   }
 
+  /** Write part of a byte string. */
+  public void writeRawBytes(final ByteString value, int offset, int length)
+                            throws IOException {
+    if (limit - position >= length) {
+      // We have room in the current buffer.
+      value.copyTo(buffer, offset, position, length);
+      position += length;
+    } else {
+      // Write extends past current buffer.  Fill the rest of this buffer and
+      // flush.
+      final int bytesWritten = limit - position;
+      value.copyTo(buffer, offset, position, bytesWritten);
+      offset += bytesWritten;
+      length -= bytesWritten;
+      position = limit;
+      refreshBuffer();
+
+      // Now deal with the rest.
+      // Since we have an output stream, this is our buffer
+      // and buffer offset == 0
+      if (length <= limit) {
+        // Fits in new buffer.
+        value.copyTo(buffer, offset, 0, length);
+        position = length;
+      } else {
+        // Write is very big, but we can't do it all at once without allocating
+        // an a copy of the byte array since ByteString does not give us access
+        // to the underlying bytes. Use the InputStream interface on the
+        // ByteString and our buffer to copy between the two.
+        InputStream inputStreamFrom = value.newInput();
+        if (offset != inputStreamFrom.skip(offset)) {
+          throw new IllegalStateException("Skip failed? Should never happen.");
+        }
+        // Use the buffer as the temporary buffer to avoid allocating memory.
+        while (length > 0) {
+          int bytesToRead = Math.min(length, limit);
+          int bytesRead = inputStreamFrom.read(buffer, 0, bytesToRead);
+          if (bytesRead != bytesToRead) {
+            throw new IllegalStateException("Read failed? Should never happen");
+          }
+          output.write(buffer, 0, bytesRead);
+          length -= bytesRead;
+        }
+      }
+    }
+  }
+
   /** Encode and write a tag. */
   public void writeTag(final int fieldNumber, final int wireType)
                        throws IOException {

File java/src/main/java/com/google/protobuf/Descriptors.java

  * its fields and other information about a type.  You can get a message
  * type's descriptor by calling {@code MessageType.getDescriptor()}, or
  * (given a message object of the type) {@code message.getDescriptorForType()}.
+ * Furthermore, each message is associated with a {@link FileDescriptor} for
+ * a relevant {@code .proto} file. You can obtain it by calling
+ * {@code Descriptor.getFile()}. A {@link FileDescriptor} contains descriptors
+ * for all the messages defined in that file, and file descriptors for all the
+ * imported {@code .proto} files.
  *
  * Descriptors are built from DescriptorProtos, as defined in
  * {@code google/protobuf/descriptor.proto}.
 public final class Descriptors {
   /**
    * Describes a {@code .proto} file, including everything defined within.
+   * That includes, in particular, descriptors for all the messages and
+   * file descriptors for all other imported {@code .proto} files
+   * (dependencies).
    */
   public static final class FileDescriptor {
     /** Convert the descriptor to its protocol message representation. */

File java/src/main/java/com/google/protobuf/FieldSet.java

 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.TreeMap;
 import java.util.List;
 import java.util.Map;
 import java.io.IOException;
         MessageLite.Builder to, MessageLite from);
   }
 
-  private Map<FieldDescriptorType, Object> fields;
+  private final SmallSortedMap<FieldDescriptorType, Object> fields;
+  private boolean isImmutable;
 
   /** Construct a new FieldSet. */
   private FieldSet() {
-    // Use a TreeMap because fields need to be in canonical order when
-    // serializing.
-    // TODO(kenton):  Maybe use some sort of sparse array instead?  It would
-    //   even make sense to store the first 16 or so tags in a flat array
-    //   to make DynamicMessage faster.
-    fields = new TreeMap<FieldDescriptorType, Object>();
+    this.fields = SmallSortedMap.newFieldMap(16);
   }
 
   /**
    * DEFAULT_INSTANCE.
    */
   private FieldSet(final boolean dummy) {
-    this.fields = Collections.emptyMap();
+    this.fields = SmallSortedMap.newFieldMap(0);
+    makeImmutable();
   }
 
   /** Construct a new FieldSet. */
   /** Make this FieldSet immutable from this point forward. */
   @SuppressWarnings("unchecked")
   public void makeImmutable() {
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
-      if (entry.getKey().isRepeated()) {
-        final List value = (List)entry.getValue();
-        fields.put(entry.getKey(), Collections.unmodifiableList(value));
-      }
+    if (isImmutable) {
+      return;
     }
-    fields = Collections.unmodifiableMap(fields);
+    fields.makeImmutable();
+    isImmutable = true;
+  }
+
+  /**
+   * Retuns whether the FieldSet is immutable. This is true if it is the
+   * {@link #emptySet} or if {@link #makeImmutable} were called.
+   *
+   * @return whether the FieldSet is immutable.
+   */
+  public boolean isImmutable() {
+    return isImmutable;
+  }
+
+  /**
+   * Clones the FieldSet. The returned FieldSet will be mutable even if the
+   * original FieldSet was immutable.
+   *
+   * @return the newly cloned FieldSet
+   */
+  @Override
+  public FieldSet<FieldDescriptorType> clone() {
+    // We can't just call fields.clone because List objects in the map
+    // should not be shared.
+    FieldSet<FieldDescriptorType> clone = FieldSet.newFieldSet();
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      Map.Entry<FieldDescriptorType, Object> entry = fields.getArrayEntryAt(i);
+      FieldDescriptorType descriptor = entry.getKey();
+      clone.setField(descriptor, entry.getValue());
+    }
+    for (Map.Entry<FieldDescriptorType, Object> entry :
+             fields.getOverflowEntries()) {
+      FieldDescriptorType descriptor = entry.getKey();
+      clone.setField(descriptor, entry.getValue());
+    }
+    return clone;
   }
 
   // =================================================================
    * Get a simple map containing all the fields.
    */
   public Map<FieldDescriptorType, Object> getAllFields() {
-    return Collections.unmodifiableMap(fields);
+    return fields.isImmutable() ? fields : Collections.unmodifiableMap(fields);
   }
 
   /**
-   * Get an iterator to the field map.  This iterator should not be leaked
-   * out of the protobuf library as it is not protected from mutation.
+   * Get an iterator to the field map. This iterator should not be leaked out
+   * of the protobuf library as it is not protected from mutation when
+   * fields is not immutable.
    */
   public Iterator<Map.Entry<FieldDescriptorType, Object>> iterator() {
     return fields.entrySet().iterator();
    * aren't actually present in the set, it is up to the caller to check
    * that all required fields are present.
    */
+  public boolean isInitialized() {
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      if (!isInitialized(fields.getArrayEntryAt(i))) {
+        return false;
+      }
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+             fields.getOverflowEntries()) {
+      if (!isInitialized(entry)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   @SuppressWarnings("unchecked")
-  public boolean isInitialized() {
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
-      final FieldDescriptorType descriptor = entry.getKey();
-      if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
-        if (descriptor.isRepeated()) {
-          for (final MessageLite element:
-               (List<MessageLite>) entry.getValue()) {
-            if (!element.isInitialized()) {
-              return false;
-            }
-          }
-        } else {
-          if (!((MessageLite) entry.getValue()).isInitialized()) {
+  private boolean isInitialized(
+      final Map.Entry<FieldDescriptorType, Object> entry) {
+    final FieldDescriptorType descriptor = entry.getKey();
+    if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
+      if (descriptor.isRepeated()) {
+        for (final MessageLite element:
+                 (List<MessageLite>) entry.getValue()) {
+          if (!element.isInitialized()) {
             return false;
           }
         }
+      } else {
+        if (!((MessageLite) entry.getValue()).isInitialized()) {
+          return false;
+        }
       }
     }
-
     return true;
   }
 
   /**
    * Like {@link #mergeFrom(Message)}, but merges from another {@link FieldSet}.
    */
+  public void mergeFrom(final FieldSet<FieldDescriptorType> other) {
+    for (int i = 0; i < other.fields.getNumArrayEntries(); i++) {
+      mergeFromField(other.fields.getArrayEntryAt(i));
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+             other.fields.getOverflowEntries()) {
+      mergeFromField(entry);
+    }
+  }
+
   @SuppressWarnings("unchecked")
-  public void mergeFrom(final FieldSet<FieldDescriptorType> other) {
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         other.fields.entrySet()) {
-      final FieldDescriptorType descriptor = entry.getKey();
-      final Object otherValue = entry.getValue();
+  private void mergeFromField(
+      final Map.Entry<FieldDescriptorType, Object> entry) {
+    final FieldDescriptorType descriptor = entry.getKey();
+    final Object otherValue = entry.getValue();
 
-      if (descriptor.isRepeated()) {
-        Object value = fields.get(descriptor);
-        if (value == null) {
-          // Our list is empty, but we still need to make a defensive copy of
-          // the other list since we don't know if the other FieldSet is still
-          // mutable.
-          fields.put(descriptor, new ArrayList((List) otherValue));
-        } else {
-          // Concatenate the lists.
-          ((List) value).addAll((List) otherValue);
-        }
-      } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
-        Object value = fields.get(descriptor);
-        if (value == null) {
-          fields.put(descriptor, otherValue);
-        } else {
-          // Merge the messages.
-          fields.put(descriptor,
-              descriptor.internalMergeFrom(
+    if (descriptor.isRepeated()) {
+      Object value = fields.get(descriptor);
+      if (value == null) {
+        // Our list is empty, but we still need to make a defensive copy of
+        // the other list since we don't know if the other FieldSet is still
+        // mutable.
+        fields.put(descriptor, new ArrayList((List) otherValue));
+      } else {
+        // Concatenate the lists.
+        ((List) value).addAll((List) otherValue);
+      }
+    } else if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE) {
+      Object value = fields.get(descriptor);
+      if (value == null) {
+        fields.put(descriptor, otherValue);
+      } else {
+        // Merge the messages.
+        fields.put(
+            descriptor,
+            descriptor.internalMergeFrom(
                 ((MessageLite) value).toBuilder(), (MessageLite) otherValue)
-              .build());
-        }
+            .build());
+      }
 
-      } else {
-        fields.put(descriptor, otherValue);
-      }
+    } else {
+      fields.put(descriptor, otherValue);
     }
   }
 
   /** See {@link Message#writeTo(CodedOutputStream)}. */
   public void writeTo(final CodedOutputStream output)
                       throws IOException {
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      final Map.Entry<FieldDescriptorType, Object> entry =
+          fields.getArrayEntryAt(i);
+      writeField(entry.getKey(), entry.getValue(), output);
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+         fields.getOverflowEntries()) {
       writeField(entry.getKey(), entry.getValue(), output);
     }
   }
    */
   public void writeMessageSetTo(final CodedOutputStream output)
                                 throws IOException {
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
-      final FieldDescriptorType descriptor = entry.getKey();
-      if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
-          !descriptor.isRepeated() && !descriptor.isPacked()) {
-        output.writeMessageSetExtension(entry.getKey().getNumber(),
-                                        (MessageLite) entry.getValue());
-      } else {
-        writeField(descriptor, entry.getValue(), output);
-      }
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      writeMessageSetTo(fields.getArrayEntryAt(i), output);
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+             fields.getOverflowEntries()) {
+      writeMessageSetTo(entry, output);
+    }
+  }
+
+  private void writeMessageSetTo(
+      final Map.Entry<FieldDescriptorType, Object> entry,
+      final CodedOutputStream output) throws IOException {
+    final FieldDescriptorType descriptor = entry.getKey();
+    if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
+        !descriptor.isRepeated() && !descriptor.isPacked()) {
+      output.writeMessageSetExtension(entry.getKey().getNumber(),
+                                      (MessageLite) entry.getValue());
+    } else {
+      writeField(descriptor, entry.getValue(), output);
     }
   }
 
    */
   public int getSerializedSize() {
     int size = 0;
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      final Map.Entry<FieldDescriptorType, Object> entry =
+          fields.getArrayEntryAt(i);
+      size += computeFieldSize(entry.getKey(), entry.getValue());
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+         fields.getOverflowEntries()) {
       size += computeFieldSize(entry.getKey(), entry.getValue());
     }
     return size;
    */
   public int getMessageSetSerializedSize() {
     int size = 0;
-    for (final Map.Entry<FieldDescriptorType, Object> entry:
-         fields.entrySet()) {
-      final FieldDescriptorType descriptor = entry.getKey();
-      if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
-          !descriptor.isRepeated() && !descriptor.isPacked()) {
-        size += CodedOutputStream.computeMessageSetExtensionSize(
-                  entry.getKey().getNumber(), (MessageLite) entry.getValue());
-      } else {
-        size += computeFieldSize(descriptor, entry.getValue());
-      }
+    for (int i = 0; i < fields.getNumArrayEntries(); i++) {
+      size += getMessageSetSerializedSize(fields.getArrayEntryAt(i));
+    }
+    for (final Map.Entry<FieldDescriptorType, Object> entry :
+             fields.getOverflowEntries()) {
+      size += getMessageSetSerializedSize(entry);
     }
     return size;
   }
 
+  private int getMessageSetSerializedSize(
+      final Map.Entry<FieldDescriptorType, Object> entry) {
+    final FieldDescriptorType descriptor = entry.getKey();
+    if (descriptor.getLiteJavaType() == WireFormat.JavaType.MESSAGE &&
+        !descriptor.isRepeated() && !descriptor.isPacked()) {
+      return CodedOutputStream.computeMessageSetExtensionSize(
+          entry.getKey().getNumber(), (MessageLite) entry.getValue());
+    } else {
+      return computeFieldSize(descriptor, entry.getValue());
+    }
+  }
+
   /**
    * Compute the number of bytes that would be needed to encode a
    * single tag/value pair of arbitrary type.

File java/src/main/java/com/google/protobuf/GeneratedMessage.java

 import com.google.protobuf.Descriptors.FieldDescriptor;
 
 import java.io.IOException;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
  *
  * @author kenton@google.com Kenton Varda
  */
-public abstract class GeneratedMessage extends AbstractMessage {
-  protected GeneratedMessage() {}
+public abstract class GeneratedMessage extends AbstractMessage
+    implements Serializable {
 
-  private UnknownFieldSet unknownFields = UnknownFieldSet.getDefaultInstance();
+  private final UnknownFieldSet unknownFields;
+
+  /**
+   * For testing. Allows a test to disable the optimization that avoids using
+   * field builders for nested messages until they are requested. By disabling
+   * this optimization, existing tests can be reused to test the field builders.
+   */
+  protected static boolean alwaysUseFieldBuilders = false;
+
+  protected GeneratedMessage() {
+    this.unknownFields = UnknownFieldSet.getDefaultInstance();
+  }
+
+  protected GeneratedMessage(Builder<?> builder) {
+    this.unknownFields = builder.getUnknownFields();
+  }
+
+ /**
+  * For testing. Allows a test to disable the optimization that avoids using
+  * field builders for nested messages until they are requested. By disabling
+  * this optimization, existing tests can be reused to test the field builders.
+  * See {@link RepeatedFieldBuilder} and {@link SingleFieldBuilder}.
+  */
+  static void enableAlwaysUseFieldBuildersForTesting() {
+    alwaysUseFieldBuilders = true;
+  }
 
   /**
    * Get the FieldAccessorTable for this type.  We can't have the message
    */
   protected abstract FieldAccessorTable internalGetFieldAccessorTable();
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public Descriptor getDescriptorForType() {
     return internalGetFieldAccessorTable().descriptor;
   }
     return true;
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public Map<FieldDescriptor, Object> getAllFields() {
     return Collections.unmodifiableMap(getAllFieldsMutable());
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public boolean hasField(final FieldDescriptor field) {
     return internalGetFieldAccessorTable().getField(field).has(this);
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public Object getField(final FieldDescriptor field) {
     return internalGetFieldAccessorTable().getField(field).get(this);
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public int getRepeatedFieldCount(final FieldDescriptor field) {
     return internalGetFieldAccessorTable().getField(field)
       .getRepeatedCount(this);
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public Object getRepeatedField(final FieldDescriptor field, final int index) {
     return internalGetFieldAccessorTable().getField(field)
       .getRepeated(this, index);
   }
 
+  //@Override (Java 1.6 override semantics, but we must support 1.5)
   public final UnknownFieldSet getUnknownFields() {
     return unknownFields;
   }
 
+  protected abstract Message.Builder newBuilderForType(BuilderParent parent);
+
+  /**
+   * 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 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();
+  }
+
   @SuppressWarnings("unchecked")
   public abstract static class Builder <BuilderType extends Builder>
       extends AbstractMessage.Builder<BuilderType> {
-    protected Builder() {}
+
+    private BuilderParent builderParent;
+
+    private BuilderParentImpl meAsParent;
+
+    // Indicates that we've built a message and so we are now obligated
+    // to dispatch dirty invalidations. See GeneratedMessage.BuilderListener.
+    private boolean isClean;
+
+    private UnknownFieldSet unknownFields =
+        UnknownFieldSet.getDefaultInstance();
+
+    protected Builder() {
+      this(null);
+    }
+
+    protected Builder(BuilderParent builderParent) {
+      this.builderParent = builderParent;
+    }
+
+    void dispose() {
+      builderParent = null;
+    }
+
+    /**
+     * Called by the subclass when a message is built.
+     */
+    protected void onBuilt() {
+      if (builderParent != null) {
+        markClean();
+      }
+    }
+
+    /**
+     * Called by the subclass or a builder to notify us that a message was
+     * built and may be cached and therefore invalidations are needed.
+     */
+    protected void markClean() {
+      this.isClean = true;
+    }
+
+    /**
+     * Gets whether invalidations are needed
+     *
+     * @return whether invalidations are needed
+     */
+    protected boolean isClean() {
+      return isClean;
+    }
 
     // This is implemented here only to work around an apparent bug in the
     // Java compiler and/or build system.  See bug #1898463.  The mere presence
     }
 
     /**
-     * Get the message being built.  We don't just pass this to the
-     * constructor because it becomes null when build() is called.
+     * Called by the initialization and clear code paths to allow subclasses to
+     * reset any of their builtin fields back to the initial values.
      */
-    protected abstract GeneratedMessage internalGetResult();
+    public BuilderType clear() {
+      unknownFields = UnknownFieldSet.getDefaultInstance();
+      onChanged();
+      return (BuilderType) this;
+    }
 
     /**
      * Get the FieldAccessorTable for this type.  We can't have the message
      * class pass this in to the constructor because of bootstrapping trouble
      * with DescriptorProtos.
      */
-    private FieldAccessorTable internalGetFieldAccessorTable() {
-      return internalGetResult().internalGetFieldAccessorTable();
-    }
+    protected abstract FieldAccessorTable internalGetFieldAccessorTable();
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public Descriptor getDescriptorForType() {
       return internalGetFieldAccessorTable().descriptor;
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public Map<FieldDescriptor, Object> getAllFields() {
-      return internalGetResult().getAllFields();
+      return Collections.unmodifiableMap(getAllFieldsMutable());
+    }
+
+    /** Internal helper which returns a mutable map. */
+    private Map<FieldDescriptor, Object> getAllFieldsMutable() {
+      final TreeMap<FieldDescriptor, Object> result =
+        new TreeMap<FieldDescriptor, Object>();
+      final Descriptor descriptor = internalGetFieldAccessorTable().descriptor;
+      for (final FieldDescriptor field : descriptor.getFields()) {
+        if (field.isRepeated()) {
+          final List value = (List) getField(field);
+          if (!value.isEmpty()) {
+            result.put(field, value);
+          }
+        } else {
+          if (hasField(field)) {
+            result.put(field, getField(field));
+          }
+        }
+      }
+      return result;
     }
 
     public Message.Builder newBuilderForField(
       return internalGetFieldAccessorTable().getField(field).newBuilder();
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public boolean hasField(final FieldDescriptor field) {
-      return internalGetResult().hasField(field);
+      return internalGetFieldAccessorTable().getField(field).has(this);
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public Object getField(final FieldDescriptor field) {
+      Object object = internalGetFieldAccessorTable().getField(field).get(this);
       if (field.isRepeated()) {
         // The underlying list object is still modifiable at this point.
         // Make sure not to expose the modifiable list to the caller.
-        return Collections.unmodifiableList(
-          (List) internalGetResult().getField(field));
+        return Collections.unmodifiableList((List) object);
       } else {
-        return internalGetResult().getField(field);
+        return object;
       }
     }
 
       return (BuilderType) this;
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public BuilderType clearField(final FieldDescriptor field) {
       internalGetFieldAccessorTable().getField(field).clear(this);
       return (BuilderType) this;
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public int getRepeatedFieldCount(final FieldDescriptor field) {
-      return internalGetResult().getRepeatedFieldCount(field);
+      return internalGetFieldAccessorTable().getField(field)
+          .getRepeatedCount(this);
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public Object getRepeatedField(final FieldDescriptor field,
                                    final int index) {
-      return internalGetResult().getRepeatedField(field, index);
+      return internalGetFieldAccessorTable().getField(field)
+          .getRepeated(this, index);
     }
 
     public BuilderType setRepeatedField(final FieldDescriptor field,
       return (BuilderType) this;
     }
 
-    public final UnknownFieldSet getUnknownFields() {
-      return internalGetResult().unknownFields;
-    }
-
     public final BuilderType setUnknownFields(
         final UnknownFieldSet unknownFields) {
-      internalGetResult().unknownFields = unknownFields;
+      this.unknownFields = unknownFields;
+      onChanged();
       return (BuilderType) this;
     }
 
     @Override
     public final BuilderType mergeUnknownFields(
         final UnknownFieldSet unknownFields) {
-      final GeneratedMessage result = internalGetResult();
-      result.unknownFields =
-        UnknownFieldSet.newBuilder(result.unknownFields)
+      this.unknownFields =
+        UnknownFieldSet.newBuilder(this.unknownFields)
                        .mergeFrom(unknownFields)
                        .build();
+      onChanged();
       return (BuilderType) this;
     }
 
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public boolean isInitialized() {
-      return internalGetResult().isInitialized();
+      for (final FieldDescriptor field : getDescriptorForType().getFields()) {
+        // Check that all required fields are present.
+        if (field.isRequired()) {
+          if (!hasField(field)) {
+            return false;
+          }
+        }
+        // Check that embedded messages are initialized.
+        if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+          if (field.isRepeated()) {
+            @SuppressWarnings("unchecked") final
+            List<Message> messageList = (List<Message>) getField(field);
+            for (final Message element : messageList) {
+              if (!element.isInitialized()) {
+                return false;
+              }
+            }
+          } else {
+            if (hasField(field) &&
+                !((Message) getField(field)).isInitialized()) {
+              return false;
+            }
+          }
+        }
+      }
+      return true;
+    }
+
+    @Override
+    public final UnknownFieldSet getUnknownFields() {
+      return unknownFields;
     }
 
     /**
         final int tag) throws IOException {
       return unknownFields.mergeFieldFrom(tag, input);
     }
+
+    /**
+     * Implementation of {@link BuilderParent} for giving to our children. This
+     * small inner class makes it so we don't publicly expose the BuilderParent
+     * methods.
+     */
+    private class BuilderParentImpl implements BuilderParent {
+
+      @Override
+      public void markDirty() {
+        onChanged();
+      }
+    }
+
+    /**
+     * Gets the {@link BuilderParent} for giving to our children.
+     * @return The builder parent for our children.
+     */
+    protected BuilderParent getParentForChildren() {
+      if (meAsParent == null) {
+        meAsParent = new BuilderParentImpl();
+      }
+      return meAsParent;
+    }
+
+    /**
+     * Called when a the builder or one of its nested children has changed
+     * and any parent should be notified of its invalidation.
+     */
+    protected final void onChanged() {
+      if (isClean && builderParent != null) {
+        builderParent.markDirty();
+
+        // Don't keep dispatching invalidations until build is called again.
+        isClean = false;
+      }
+    }
   }
 
   // =================================================================
   // Extensions-related stuff
 
+  public interface ExtendableMessageOrBuilder<
+      MessageType extends ExtendableMessage> extends MessageOrBuilder {
+
+    /** 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(
+        GeneratedExtension<MessageType, List<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(
+        GeneratedExtension<MessageType, List<Type>> extension,
+        int index);
+  }
+
   /**
    * Generated message classes for message types that contain extension ranges
    * subclass this.
    */
   public abstract static class ExtendableMessage<
         MessageType extends ExtendableMessage>
-      extends GeneratedMessage {
-    protected ExtendableMessage() {}
-    private final FieldSet<FieldDescriptor> extensions = FieldSet.newFieldSet();
+      extends GeneratedMessage
+      implements ExtendableMessageOrBuilder<MessageType> {
+
+    private final FieldSet<FieldDescriptor> extensions;
+
+    protected ExtendableMessage() {
+      this.extensions = FieldSet.newFieldSet();
+    }
+
+    protected ExtendableMessage(
+        ExtendableBuilder<MessageType, ?> builder) {
+      super(builder);
+      this.extensions = builder.buildExtensions();
+    }
 
     private void verifyExtensionContainingType(
         final GeneratedExtension<MessageType, ?> extension) {
     }
 
     /** Check if a singular extension is present. */
-    public final boolean hasExtension(
-        final GeneratedExtension<MessageType, ?> extension) {
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
+    public final <Type> boolean hasExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
       verifyExtensionContainingType(extension);
       return extensions.hasField(extension.getDescriptor());
     }
 
     /** Get the number of elements in a repeated extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public final <Type> int getExtensionCount(
         final GeneratedExtension<MessageType, List<Type>> extension) {
       verifyExtensionContainingType(extension);
     }
 
     /** Get the value of an extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     @SuppressWarnings("unchecked")
     public final <Type> Type getExtension(
         final GeneratedExtension<MessageType, Type> extension) {
     }
 
     /** Get one element of a repeated extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     @SuppressWarnings("unchecked")
     public final <Type> Type getExtension(
         final GeneratedExtension<MessageType, List<Type>> extension,
     // ---------------------------------------------------------------
     // Reflection
 
+    protected Map<FieldDescriptor, Object> getExtensionFields() {
+      return extensions.getAllFields();
+    }
+
     @Override
     public Map<FieldDescriptor, Object> getAllFields() {
       final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable();
-      result.putAll(extensions.getAllFields());
+      result.putAll(getExtensionFields());
       return Collections.unmodifiableMap(result);
     }
 
   public abstract static class ExtendableBuilder<
         MessageType extends ExtendableMessage,
         BuilderType extends ExtendableBuilder>
-      extends Builder<BuilderType> {
+      extends Builder<BuilderType>
+      implements ExtendableMessageOrBuilder<MessageType> {
+
+    private FieldSet<FieldDescriptor> extensions = FieldSet.emptySet();
+
     protected ExtendableBuilder() {}
 
+    protected ExtendableBuilder(
+        BuilderParent parent) {
+      super(parent);
+    }
+
+    @Override
+    public BuilderType clear() {
+      extensions = FieldSet.emptySet();
+      return super.clear();
+    }
+
     // This is implemented here only to work around an apparent bug in the
     // Java compiler and/or build system.  See bug #1898463.  The mere presence
     // of this dummy clone() implementation makes it go away.
           "This is supposed to be overridden by subclasses.");
     }
 
-    @Override
-    protected abstract ExtendableMessage<MessageType> internalGetResult();
+    private void ensureExtensionsIsMutable() {
+      if (extensions.isImmutable()) {
+        extensions = extensions.clone();
+      }
+    }
+
+    private void verifyExtensionContainingType(
+        final GeneratedExtension<MessageType, ?> extension) {
+      if (extension.getDescriptor().getContainingType() !=
+          getDescriptorForType()) {
+        // This can only happen if someone uses unchecked operations.
+        throw new IllegalArgumentException(
+          "Extension is for type \"" +
+          extension.getDescriptor().getContainingType().getFullName() +
+          "\" which does not match message type \"" +
+          getDescriptorForType().getFullName() + "\".");
+      }
+    }
 
     /** Check if a singular extension is present. */
-    public final boolean hasExtension(
-        final GeneratedExtension<MessageType, ?> extension) {
-      return internalGetResult().hasExtension(extension);
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
+    public final <Type> boolean hasExtension(
+        final GeneratedExtension<MessageType, Type> extension) {
+      verifyExtensionContainingType(extension);
+      return extensions.hasField(extension.getDescriptor());
     }
 
     /** Get the number of elements in a repeated extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public final <Type> int getExtensionCount(
         final GeneratedExtension<MessageType, List<Type>> extension) {
-      return internalGetResult().getExtensionCount(extension);
+      verifyExtensionContainingType(extension);
+      final FieldDescriptor descriptor = extension.getDescriptor();
+      return extensions.getRepeatedFieldCount(descriptor);
     }
 
     /** Get the value of an extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public final <Type> Type getExtension(
         final GeneratedExtension<MessageType, Type> extension) {
-      return internalGetResult().getExtension(extension);
+      verifyExtensionContainingType(extension);
+      FieldDescriptor descriptor = extension.getDescriptor();
+      final Object value = extensions.getField(descriptor);
+      if (value == null) {
+        if (descriptor.isRepeated()) {
+          return (Type) Collections.emptyList();
+        } else if (descriptor.getJavaType() ==
+                   FieldDescriptor.JavaType.MESSAGE) {
+          return (Type) extension.getMessageDefaultInstance();
+        } else {
+          return (Type) extension.fromReflectionType(
+              descriptor.getDefaultValue());
+        }
+      } else {
+        return (Type) extension.fromReflectionType(value);
+      }
     }
 
     /** Get one element of a repeated extension. */
+    //@Override (Java 1.6 override semantics, but we must support 1.5)
     public final <Type> Type getExtension(
         final GeneratedExtension<MessageType, List<Type>> extension,
         final int index) {
-      return internalGetResult().getExtension(extension, index);
+      verifyExtensionContainingType(extension);
+      FieldDescriptor descriptor = extension.getDescriptor();
+      return (Type) extension.singularFromReflectionType(
+          extensions.getRepeatedField(descriptor, index));
     }
 
     /** Set the value of an extension. */
     public final <Type> BuilderType setExtension(
         final GeneratedExtension<MessageType, Type> extension,
         final Type value) {
-      final ExtendableMessage<MessageType> message = internalGetResult();
-      message.verifyExtensionContainingType(extension);
+      verifyExtensionContainingType(extension);
+      ensureExtensionsIsMutable();
       final FieldDescriptor descriptor = extension.getDescriptor();
-      message.extensions.setField(descriptor,
-                                  extension.toReflectionType(value));
+      extensions.setField(descriptor, extension.toReflectionType(value));
+      onChanged();
       return (BuilderType) this;
     }
 
     public final <Type> BuilderType setExtension(
         final GeneratedExtension<MessageType, List<Type>> extension,
         final int index, final Type value) {
-      final ExtendableMessage<MessageType> message = internalGetResult();
-      message.verifyExtensionContainingType(extension);
+      verifyExtensionContainingType(extension);
+      ensureExtensionsIsMutable();
       final FieldDescriptor descriptor = extension.getDescriptor();
-      message.extensions.setRepeatedField(
+      extensions.setRepeatedField(
         descriptor, index,
         extension.singularToReflectionType(value));
+      onChanged();
       return (BuilderType) this;
     }
 
     public final <Type> BuilderType addExtension(
         final GeneratedExtension<MessageType, List<Type>> extension,
         final Type value) {
-      final ExtendableMessage<MessageType> message = internalGetResult();
-      message.verifyExtensionContainingType(extension);
+      verifyExtensionContainingType(extension);
+      ensureExtensionsIsMutable();
       final FieldDescriptor descriptor = extension.getDescriptor();
-      message.extensions.addRepeatedField(
+      extensions.addRepeatedField(
           descriptor, extension.singularToReflectionType(value));
+      onChanged();
       return (BuilderType) this;
     }
 
     /** Clear an extension. */
     public final <Type> BuilderType clearExtension(
         final GeneratedExtension<MessageType, ?> extension) {
-      final ExtendableMessage<MessageType> message = internalGetResult();
-      message.verifyExtensionContainingType(extension);
-      message.extensions.clearField(extension.getDescriptor());
+      verifyExtensionContainingType(extension);
+      ensureExtensionsIsMutable();
+      extensions.clearField(extension.getDescriptor());
+      onChanged();
       return (BuilderType) this;
     }
 
+    /** Called by subclasses to check if all extensions are initialized. */
+    protected boolean extensionsAreInitialized() {
+      return extensions.isInitialized();
+    }
+
+    /**
+     * Called by the build code path to create a copy of the extensions for
+     * building the message.
+     */
+    private FieldSet<FieldDescriptor> buildExtensions() {
+      extensions.makeImmutable();
+      return extensions;
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return super.isInitialized() && extensionsAreInitialized();
+    }
+
     /**
      * Called by subclasses to parse an unknown field or an extension.
      * @return {@code true} unless the tag is an end-group tag.
     // ---------------------------------------------------------------
     // Reflection
 
-    // We don't have to override the get*() methods here because they already
-    // just forward to the underlying message.
+    @Override
+    public Map<FieldDescriptor, Object> getAllFields() {
+      final Map<FieldDescriptor, Object> result = super.getAllFieldsMutable();
+      result.putAll(extensions.getAllFields());
+      return Collections.unmodifiableMap(result);
+    }
+
+    @Override
+    public Object getField(final FieldDescriptor field) {
+      if (field.isExtension()) {
+        verifyContainingType(field);
+        final Object value = extensions.getField(field);
+        if (value == null) {
+          if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) {
+            // Lacking an ExtensionRegistry, we have no way to determine the
+            // extension's real type, so we return a DynamicMessage.
+            return DynamicMessage.getDefaultInstance(field.getMessageType());
+          } else {
+            return field.getDefaultValue();
+          }
+        } else {
+          return value;
+        }
+      } else {
+        return super.getField(field);
+      }
+    }
+
+    @Override
+    public int getRepeatedFieldCount(final FieldDescriptor field) {
+      if (field.isExtension()) {
+        verifyContainingType(field);
+        return extensions.getRepeatedFieldCount(field);
+      } else {
+        return super.getRepeatedFieldCount(field);
+      }
+    }
+
+    @Override
+    public Object getRepeatedField(final FieldDescriptor field,
+                                   final int index) {
+      if (field.isExtension()) {
+        verifyContainingType(field);
+        return extensions.getRepeatedField(field, index);
+      } else {
+        return super.getRepeatedField(field, index);
+      }
+    }
+
+    @Override
+    public boolean hasField(final FieldDescriptor field) {
+      if (field.isExtension()) {
+        verifyContainingType(field);
+        return extensions.hasField(field);
+      } else {
+        return super.hasField(field);
+      }
+    }
 
     @Override
     public BuilderType setField(final FieldDescriptor field,
                                 final Object value) {
       if (field.isExtension()) {
-        final ExtendableMessage<MessageType> message = internalGetResult();
-        message.verifyContainingType(field);
-        message.extensions.setField(field, value);
+        verifyContainingType(field);
+        ensureExtensionsIsMutable();
+        extensions.setField(field, value);
+        onChanged();
         return (BuilderType) this;
       } else {
         return super.setField(field, value);
     @Override
     public BuilderType clearField(final FieldDescriptor field) {
       if (field.isExtension()) {
-        final ExtendableMessage<MessageType> message = internalGetResult();
-        message.verifyContainingType(field);
-        message.extensions.clearField(field);
+        verifyContainingType(field);
+        ensureExtensionsIsMutable();
+        extensions.clearField(field);
+        onChanged();
         return (BuilderType) this;
       } else {
         return super.clearField(field);
     public BuilderType setRepeatedField(final FieldDescriptor field,
                                         final int index, final Object value) {
       if (field.isExtension()) {
-        final ExtendableMessage<MessageType> message = internalGetResult();
-        message.verifyContainingType(field);
-        message.extensions.setRepeatedField(field, index, value);
+        verifyContainingType(field);
+        ensureExtensionsIsMutable();
+        extensions.setRepeatedField(field, index, value);
+        onChanged();
         return (BuilderType) this;
       } else {
         return super.setRepeatedField(field, index, value);
     public BuilderType addRepeatedField(final FieldDescriptor field,
                                         final Object value) {
       if (field.isExtension()) {
-        final ExtendableMessage<MessageType> message = internalGetResult();
-        message.verifyContainingType(field);
-        message.extensions.addRepeatedField(field, value);
+        verifyContainingType(field);
+        ensureExtensionsIsMutable();
+        extensions.addRepeatedField(field, value);
+        onChanged();
         return (BuilderType) this;
       } else {
         return super.addRepeatedField(field, value);
     }
 
     protected final void mergeExtensionFields(final ExtendableMessage other) {
-      internalGetResult().extensions.mergeFrom(other.extensions);
+      ensureExtensionsIsMutable();
+      extensions.mergeFrom(other.extensions);
+      onChanged();
+    }
+
+    private void verifyContainingType(final FieldDescriptor field) {
+      if (field.getContainingType() != getDescriptorForType()) {
+        throw new IllegalArgumentException(
+          "FieldDescriptor does not match message type.");
+      }
     }
   }
 
   // -----------------------------------------------------------------
 
+  /**
+   * Gets the descriptor for an extension. The implementation depends on whether
+   * the extension is scoped in the top level of a file or scoped in a Message.
+   */
+  private static interface ExtensionDescriptorRetriever {
+    FieldDescriptor getDescriptor();
+  }
+
   /** For use by generated code only. */
   public static <ContainingType extends Message, Type>
       GeneratedExtension<ContainingType, Type>
-      newGeneratedExtension() {
-    return new GeneratedExtension<ContainingType, Type>();
+      newMessageScopedGeneratedExtension(final Message scope,
+                                         final int descriptorIndex,
+                                         final Class singularType,
+                                         final Message defaultInstance) {
+    // For extensions scoped within a Message, we use the Message to resolve
+    // the outer class's descriptor, from wh