Merge "Extension overhaul."
diff --git a/java/pom.xml b/java/pom.xml
index ba281ff..5b49a72 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -158,6 +158,24 @@
                   <arg value="../src/google/protobuf/unittest_extension_nano.proto" />
                 </exec>
                 <exec executable="../src/protoc">
+                  <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" />
+                  <arg value="--proto_path=../src" />
+                  <arg value="--proto_path=src/test/java" />
+                  <arg value="../src/google/protobuf/unittest_extension_singular_nano.proto" />
+                </exec>
+                <exec executable="../src/protoc">
+                  <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" />
+                  <arg value="--proto_path=../src" />
+                  <arg value="--proto_path=src/test/java" />
+                  <arg value="../src/google/protobuf/unittest_extension_repeated_nano.proto" />
+                </exec>
+                <exec executable="../src/protoc">
+                  <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" />
+                  <arg value="--proto_path=../src" />
+                  <arg value="--proto_path=src/test/java" />
+                  <arg value="../src/google/protobuf/unittest_extension_packed_nano.proto" />
+                </exec>
+                <exec executable="../src/protoc">
                   <arg value="--javanano_out=java_nano_generate_has=true,generate_equals=true:target/generated-test-sources" />
                   <arg value="--proto_path=../src" />
                   <arg value="--proto_path=src/test/java" />
diff --git a/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java b/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
index 769bb19..88df38d 100644
--- a/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
@@ -168,14 +168,6 @@
     writeBytesNoTag(value);
   }
 
-  /** Write a {@code byte} field, including tag, to the stream. */
-  public void writeByteArray(final int fieldNumber, final byte[] value)
-                         throws IOException {
-    writeTag(fieldNumber, WireFormatNano.WIRETYPE_LENGTH_DELIMITED);
-    writeByteArrayNoTag(value);
-  }
-
-
   /** Write a {@code uint32} field, including tag, to the stream. */
   public void writeUInt32(final int fieldNumber, final int value)
                           throws IOException {
@@ -321,12 +313,6 @@
     writeRawBytes(value);
   }
 
-  /** Write a {@code byte[]} field to the stream. */
-  public void writeByteArrayNoTag(final byte [] value) throws IOException {
-    writeRawVarint32(value.length);
-    writeRawBytes(value);
-  }
-
   /** Write a {@code uint32} field to the stream. */
   public void writeUInt32NoTag(final int value) throws IOException {
     writeRawVarint32(value);
@@ -468,15 +454,6 @@
 
   /**
    * Compute the number of bytes that would be needed to encode a
-   * {@code byte[]} field, including tag.
-   */
-  public static int computeByteArraySize(final int fieldNumber,
-                                     final byte[] value) {
-    return computeTagSize(fieldNumber) + computeByteArraySizeNoTag(value);
-  }
-
-  /**
-   * Compute the number of bytes that would be needed to encode a
    * {@code uint32} field, including tag.
    */
   public static int computeUInt32Size(final int fieldNumber, final int value) {
@@ -662,14 +639,6 @@
 
   /**
    * Compute the number of bytes that would be needed to encode a
-   * {@code byte[]} field.
-   */
-  public static int computeByteArraySizeNoTag(final byte[] value) {
-    return computeRawVarint32Size(value.length) + value.length;
-  }
-
-  /**
-   * Compute the number of bytes that would be needed to encode a
    * {@code uint32} field.
    */
   public static int computeUInt32SizeNoTag(final int value) {
diff --git a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
index 066774d..839f21c 100644
--- a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
@@ -30,6 +30,7 @@
 
 package com.google.protobuf.nano;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -37,34 +38,81 @@
  * Base class of those Protocol Buffer messages that need to store unknown fields,
  * such as extensions.
  */
-public abstract class ExtendableMessageNano extends MessageNano {
+public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>>
+        extends MessageNano {
     /**
      * A container for fields unknown to the message, including extensions. Extension fields can
-     * can be accessed through the {@link getExtension()} and {@link setExtension()} methods.
+     * can be accessed through the {@link #getExtension} and {@link #setExtension} methods.
      */
     protected List<UnknownFieldData> unknownFieldData;
 
     @Override
     public int getSerializedSize() {
-        int size = WireFormatNano.computeWireSize(unknownFieldData);
+        int size = 0;
+        int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size();
+        for (int i = 0; i < unknownFieldCount; i++) {
+            UnknownFieldData unknownField = unknownFieldData.get(i);
+            size += CodedOutputByteBufferNano.computeRawVarint32Size(unknownField.tag);
+            size += unknownField.bytes.length;
+        }
         cachedSize = size;
         return size;
     }
 
+    @Override
+    public void writeTo(CodedOutputByteBufferNano output) throws IOException {
+        int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size();
+        for (int i = 0; i < unknownFieldCount; i++) {
+            UnknownFieldData unknownField = unknownFieldData.get(i);
+            output.writeRawVarint32(unknownField.tag);
+            output.writeRawBytes(unknownField.bytes);
+        }
+    }
+
     /**
      * Gets the value stored in the specified extension of this message.
      */
-    public <T> T getExtension(Extension<T> extension) {
-        return WireFormatNano.getExtension(extension, unknownFieldData);
+    public final <T> T getExtension(Extension<M, T> extension) {
+        return extension.getValueFrom(unknownFieldData);
     }
 
     /**
      * Sets the value of the specified extension of this message.
      */
-    public <T> void setExtension(Extension<T> extension, T value) {
+    public final <T> M setExtension(Extension<M, T> extension, T value) {
+        unknownFieldData = extension.setValueTo(value, unknownFieldData);
+
+        @SuppressWarnings("unchecked") // Generated code should guarantee type safety
+        M typedThis = (M) this;
+        return typedThis;
+    }
+
+    /**
+     * Stores the binary data of an unknown field.
+     *
+     * <p>Generated messages will call this for unknown fields if the store_unknown_fields
+     * option is on.
+     *
+     * <p>Note that the tag might be a end-group tag (rather than the start of an unknown field) in
+     * which case we do not want to add an unknown field entry.
+     *
+     * @param input the input buffer.
+     * @param tag the tag of the field.
+
+     * @return {@literal true} unless the tag is an end-group tag.
+     */
+    protected final boolean storeUnknownField(CodedInputByteBufferNano input, int tag)
+            throws IOException {
+        int startPos = input.getPosition();
+        if (!input.skipField(tag)) {
+            return false;  // This wasn't an unknown field, it's an end-group tag.
+        }
         if (unknownFieldData == null) {
             unknownFieldData = new ArrayList<UnknownFieldData>();
         }
-        WireFormatNano.setExtension(extension, value, unknownFieldData);
+        int endPos = input.getPosition();
+        byte[] bytes = input.getData(startPos, endPos - startPos);
+        unknownFieldData.add(new UnknownFieldData(tag, bytes));
+        return true;
     }
-}
\ No newline at end of file
+}
diff --git a/java/src/main/java/com/google/protobuf/nano/Extension.java b/java/src/main/java/com/google/protobuf/nano/Extension.java
index 4512b01..177a9cc 100644
--- a/java/src/main/java/com/google/protobuf/nano/Extension.java
+++ b/java/src/main/java/com/google/protobuf/nano/Extension.java
@@ -30,85 +30,659 @@
 
 package com.google.protobuf.nano;
 
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Represents an extension.
  *
  * @author bduff@google.com (Brian Duff)
- * @param <T> the type of the extension.
+ * @author maxtroy@google.com (Max Cai)
+ * @param <M> the type of the extendable message this extension is for.
+ * @param <T> the Java type of the extension; see {@link #clazz}.
  */
-public class Extension<T> {
-  public final int fieldNumber;
-  public boolean isRepeatedField;
-  public Class<T> fieldType;
-  public Class<T> listType;
+public class Extension<M extends ExtendableMessageNano<M>, T> {
 
-  private Extension(int fieldNumber, TypeLiteral<T> type) {
-    this.fieldNumber = fieldNumber;
-    isRepeatedField = type.isList();
-    fieldType = type.getTargetClass();
-    listType = isRepeatedField ? type.getListType() : null;
-  }
+    /*
+     * Because we typically only define message-typed extensions, the Extension class hierarchy is
+     * designed as follows, to allow a big amount of code in this file to be removed by ProGuard:
+     *
+     *            Extension          // ready to use for message/group typed extensions
+     *                Δ
+     *                |
+     *       PrimitiveExtension      // for primitive/enum typed extensions
+     */
 
-  /**
-   * Creates a new instance of {@code Extension} for the specified {@code fieldNumber} and
-   * {@code type}.
-   */
-  public static <T> Extension<T> create(int fieldNumber, TypeLiteral<T> type) {
-    return new Extension<T>(fieldNumber, type);
-  }
+    public static final int TYPE_DOUBLE   = 1;
+    public static final int TYPE_FLOAT    = 2;
+    public static final int TYPE_INT64    = 3;
+    public static final int TYPE_UINT64   = 4;
+    public static final int TYPE_INT32    = 5;
+    public static final int TYPE_FIXED64  = 6;
+    public static final int TYPE_FIXED32  = 7;
+    public static final int TYPE_BOOL     = 8;
+    public static final int TYPE_STRING   = 9;
+    public static final int TYPE_GROUP    = 10;
+    public static final int TYPE_MESSAGE  = 11;
+    public static final int TYPE_BYTES    = 12;
+    public static final int TYPE_UINT32   = 13;
+    public static final int TYPE_ENUM     = 14;
+    public static final int TYPE_SFIXED32 = 15;
+    public static final int TYPE_SFIXED64 = 16;
+    public static final int TYPE_SINT32   = 17;
+    public static final int TYPE_SINT64   = 18;
 
-  /**
-   * Creates a new instance of {@code Extension} for the specified {@code fieldNumber} and
-   * {@code type}. This version is used for repeated fields.
-   */
-  public static <T> Extension<List<T>> createRepeated(int fieldNumber, TypeLiteral<List<T>> type) {
-    return new Extension<List<T>>(fieldNumber, type);
-  }
-
-  /**
-   * Represents a generic type literal. We can't typesafely reference a
-   * Class&lt;List&lt;Foo>>.class in Java, so we use this instead.
-   * See: http://gafter.blogspot.com/2006/12/super-type-tokens.html
-   *
-   * <p>Somewhat specialized because we only ever have a Foo or a List&lt;Foo>.
-   */
-  public static abstract class TypeLiteral<T> {
-    private final Type type;
-
-    protected TypeLiteral() {
-      Type superclass = getClass().getGenericSuperclass();
-      if (superclass instanceof Class) {
-        throw new RuntimeException("Missing type parameter");
-      }
-      this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
+    /**
+     * Creates an {@code Extension} of the given message type and tag number.
+     * Should be used by the generated code only.
+     *
+     * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
+     */
+    public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
+            Extension<M, T> createMessageTyped(int type, Class<T> clazz, int tag) {
+        return new Extension<M, T>(type, clazz, tag, false);
     }
 
     /**
-     * If the generic type is a list, returns {@code true}.
+     * Creates a repeated {@code Extension} of the given message type and tag number.
+     * Should be used by the generated code only.
+     *
+     * @param type {@link #TYPE_MESSAGE} or {@link #TYPE_GROUP}
      */
-    private boolean isList() {
-      return type instanceof ParameterizedType;
-    }
-
-    @SuppressWarnings("unchecked")
-    private Class<T> getListType() {
-      return (Class<T>) ((ParameterizedType) type).getRawType();
+    public static <M extends ExtendableMessageNano<M>, T extends MessageNano>
+            Extension<M, T[]> createRepeatedMessageTyped(int type, Class<T[]> clazz, int tag) {
+        return new Extension<M, T[]>(type, clazz, tag, true);
     }
 
     /**
-     * If the generic type is a list, returns the type of element in the list. Otherwise,
-     * returns the actual type.
+     * Creates an {@code Extension} of the given primitive type and tag number.
+     * Should be used by the generated code only.
+     *
+     * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP}
+     * @param clazz the boxed Java type of this extension
      */
-    @SuppressWarnings("unchecked")
-    private Class<T> getTargetClass() {
-      if (isList()) {
-        return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
-      }
-      return (Class<T>) type;
+    public static <M extends ExtendableMessageNano<M>, T>
+            Extension<M, T> createPrimitiveTyped(int type, Class<T> clazz, int tag) {
+        return new PrimitiveExtension<M, T>(type, clazz, tag, false, 0, 0);
     }
-  }
+
+    /**
+     * Creates a repeated {@code Extension} of the given primitive type and tag number.
+     * Should be used by the generated code only.
+     *
+     * @param type one of {@code TYPE_*}, except {@link #TYPE_MESSAGE} and {@link #TYPE_GROUP}
+     * @param clazz the Java array type of this extension, with an unboxed component type
+     */
+    public static <M extends ExtendableMessageNano<M>, T>
+            Extension<M, T> createRepeatedPrimitiveTyped(
+                    int type, Class<T> clazz, int tag, int nonPackedTag, int packedTag) {
+        return new PrimitiveExtension<M, T>(type, clazz, tag, true, nonPackedTag, packedTag);
+    }
+
+    /**
+     * Protocol Buffer type of this extension; one of the {@code TYPE_} constants.
+     */
+    protected final int type;
+
+    /**
+     * Java type of this extension. For a singular extension, this is the boxed Java type for the
+     * Protocol Buffer {@link #type}; for a repeated extension, this is an array type whose
+     * component type is the unboxed Java type for {@link #type}. For example, for a singular
+     * {@code int32}/{@link #TYPE_INT32} extension, this equals {@code Integer.class}; for a
+     * repeated {@code int32} extension, this equals {@code int[].class}.
+     */
+    protected final Class<T> clazz;
+
+    /**
+     * Tag number of this extension.
+     */
+    protected final int tag;
+
+    /**
+     * Whether this extension is repeated.
+     */
+    protected final boolean repeated;
+
+    private Extension(int type, Class<T> clazz, int tag, boolean repeated) {
+        this.type = type;
+        this.clazz = clazz;
+        this.tag = tag;
+        this.repeated = repeated;
+    }
+
+    protected boolean isMatch(int unknownDataTag) {
+        // This implementation is for message/group extensions.
+        return unknownDataTag == tag;
+    }
+
+    /**
+     * Returns the value of this extension stored in the given list of unknown fields, or
+     * {@code null} if no unknown fields matches this extension.
+     */
+    final T getValueFrom(List<UnknownFieldData> unknownFields) {
+        if (unknownFields == null) {
+            return null;
+        }
+
+        if (repeated) {
+            // For repeated extensions, read all matching unknown fields in their original order.
+            List<Object> resultList = new ArrayList<Object>();
+            for (int i = 0; i < unknownFields.size(); i++) {
+                UnknownFieldData data = unknownFields.get(i);
+                if (isMatch(data.tag) && data.bytes.length != 0) {
+                    readDataInto(data, resultList);
+                }
+            }
+
+            int resultSize = resultList.size();
+            if (resultSize == 0) {
+                return null;
+            }
+
+            T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize));
+            for (int i = 0; i < resultSize; i++) {
+                Array.set(result, i, resultList.get(i));
+            }
+            return result;
+        } else {
+            // For singular extensions, get the last piece of data stored under this extension.
+            UnknownFieldData lastData = null;
+            for (int i = unknownFields.size() - 1; lastData == null && i >= 0; i--) {
+                UnknownFieldData data = unknownFields.get(i);
+                if (isMatch(data.tag) && data.bytes.length != 0) {
+                    lastData = data;
+                }
+            }
+
+            if (lastData == null) {
+                return null;
+            }
+
+            return clazz.cast(readData(CodedInputByteBufferNano.newInstance(lastData.bytes)));
+        }
+    }
+
+    protected Object readData(CodedInputByteBufferNano input) {
+        // This implementation is for message/group extensions.
+        Class<?> messageType = repeated ? clazz.getComponentType() : clazz;
+        try {
+            switch (type) {
+                case TYPE_GROUP:
+                    MessageNano group = (MessageNano) messageType.newInstance();
+                    input.readGroup(group, WireFormatNano.getTagFieldNumber(tag));
+                    return group;
+                case TYPE_MESSAGE:
+                    MessageNano message = (MessageNano) messageType.newInstance();
+                    input.readMessage(message);
+                    return message;
+                default:
+                    throw new IllegalArgumentException("Unknown type " + type);
+            }
+        } catch (InstantiationException e) {
+            throw new IllegalArgumentException(
+                    "Error creating instance of class " + messageType, e);
+        } catch (IllegalAccessException e) {
+            throw new IllegalArgumentException(
+                    "Error creating instance of class " + messageType, e);
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Error reading extension field", e);
+        }
+    }
+
+    protected void readDataInto(UnknownFieldData data, List<Object> resultList) {
+        // This implementation is for message/group extensions.
+        resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
+    }
+
+    /**
+     * Sets the value of this extension to the given list of unknown fields. This removes any
+     * previously stored data matching this extension.
+     *
+     * @param value The value of this extension, or {@code null} to clear this extension from the
+     *     unknown fields.
+     * @return The same {@code unknownFields} list, or a new list storing the extension value if
+     *     the argument was null.
+     */
+    final List<UnknownFieldData> setValueTo(T value, List<UnknownFieldData> unknownFields) {
+        if (unknownFields != null) {
+            // Delete all data matching this extension
+            for (int i = unknownFields.size() - 1; i >= 0; i--) {
+                if (isMatch(unknownFields.get(i).tag)) {
+                    unknownFields.remove(i);
+                }
+            }
+        }
+
+        if (value != null) {
+            if (unknownFields == null) {
+                unknownFields = new ArrayList<UnknownFieldData>();
+            }
+            if (repeated) {
+                writeDataInto(value, unknownFields);
+            } else {
+                unknownFields.add(writeData(value));
+            }
+        }
+
+        // After deletion or no-op addition (due to 'value' being an array of empty or
+        // null-only elements), unknownFields may be empty. Discard the ArrayList if so.
+        return (unknownFields.size() == 0) ? null : unknownFields;
+    }
+
+    protected UnknownFieldData writeData(Object value) {
+        // This implementation is for message/group extensions.
+        byte[] data;
+        try {
+            switch (type) {
+                case TYPE_GROUP:
+                    MessageNano groupValue = (MessageNano) value;
+                    int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
+                    data = new byte[CodedOutputByteBufferNano.computeGroupSizeNoTag(groupValue)
+                            + CodedOutputByteBufferNano.computeTagSize(fieldNumber)];
+                    CodedOutputByteBufferNano out = CodedOutputByteBufferNano.newInstance(data);
+                    out.writeGroupNoTag(groupValue);
+                    // The endgroup tag must be included in the data payload.
+                    out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP);
+                    break;
+                case TYPE_MESSAGE:
+                    MessageNano messageValue = (MessageNano) value;
+                    data = new byte[
+                            CodedOutputByteBufferNano.computeMessageSizeNoTag(messageValue)];
+                    CodedOutputByteBufferNano.newInstance(data).writeMessageNoTag(messageValue);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown type " + type);
+            }
+        } catch (IOException e) {
+            // Should not happen
+            throw new IllegalStateException(e);
+        }
+        return new UnknownFieldData(tag, data);
+    }
+
+    protected void writeDataInto(T array, List<UnknownFieldData> unknownFields) {
+        // This implementation is for non-packed extensions.
+        int arrayLength = Array.getLength(array);
+        for (int i = 0; i < arrayLength; i++) {
+            Object element = Array.get(array, i);
+            if (element != null) {
+                unknownFields.add(writeData(element));
+            }
+        }
+    }
+
+    /**
+     * Represents an extension of a primitive (including enum) type. If there is no primitive
+     * extensions, this subclass will be removable by ProGuard.
+     */
+    private static class PrimitiveExtension<M extends ExtendableMessageNano<M>, T>
+            extends Extension<M, T> {
+
+        /**
+         * Tag of a piece of non-packed data from the wire compatible with this extension.
+         */
+        private final int nonPackedTag;
+
+        /**
+         * Tag of a piece of packed data from the wire compatible with this extension.
+         * 0 if the type of this extension is not packable.
+         */
+        private final int packedTag;
+
+        public PrimitiveExtension(int type, Class<T> clazz, int tag, boolean repeated,
+                int nonPackedTag, int packedTag) {
+            super(type, clazz, tag, repeated);
+            this.nonPackedTag = nonPackedTag;
+            this.packedTag = packedTag;
+        }
+
+        @Override
+        protected boolean isMatch(int unknownDataTag) {
+            if (repeated) {
+                return unknownDataTag == nonPackedTag || unknownDataTag == packedTag;
+            } else {
+                return unknownDataTag == tag;
+            }
+        }
+
+        @Override
+        protected Object readData(CodedInputByteBufferNano input) {
+            try {
+                switch (type) {
+                    case TYPE_DOUBLE:
+                        return input.readDouble();
+                    case TYPE_FLOAT:
+                        return input.readFloat();
+                    case TYPE_INT64:
+                        return input.readInt64();
+                    case TYPE_UINT64:
+                        return input.readUInt64();
+                    case TYPE_INT32:
+                        return input.readInt32();
+                    case TYPE_FIXED64:
+                        return input.readFixed64();
+                    case TYPE_FIXED32:
+                        return input.readFixed32();
+                    case TYPE_BOOL:
+                        return input.readBool();
+                    case TYPE_STRING:
+                        return input.readString();
+                    case TYPE_BYTES:
+                        return input.readBytes();
+                    case TYPE_UINT32:
+                        return input.readUInt32();
+                    case TYPE_ENUM:
+                        return input.readEnum();
+                    case TYPE_SFIXED32:
+                        return input.readSFixed32();
+                    case TYPE_SFIXED64:
+                        return input.readSFixed64();
+                    case TYPE_SINT32:
+                        return input.readSInt32();
+                    case TYPE_SINT64:
+                        return input.readSInt64();
+                    default:
+                        throw new IllegalArgumentException("Unknown type " + type);
+                }
+            } catch (IOException e) {
+                throw new IllegalArgumentException("Error reading extension field", e);
+            }
+        }
+
+        @Override
+        protected void readDataInto(UnknownFieldData data, List<Object> resultList) {
+            // This implementation is for primitive typed extensions,
+            // which can read both packed and non-packed data.
+            if (data.tag == nonPackedTag) {
+                resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
+            } else {
+                CodedInputByteBufferNano buffer = CodedInputByteBufferNano.newInstance(data.bytes);
+                try {
+                    buffer.pushLimit(buffer.readRawVarint32()); // length limit
+                } catch (IOException e) {
+                    throw new IllegalArgumentException("Error reading extension field", e);
+                }
+                while (!buffer.isAtEnd()) {
+                    resultList.add(readData(buffer));
+                }
+            }
+        }
+
+        @Override
+        protected final UnknownFieldData writeData(Object value) {
+            byte[] data;
+            try {
+                switch (type) {
+                    case TYPE_DOUBLE:
+                        Double doubleValue = (Double) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeDoubleSizeNoTag(doubleValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeDoubleNoTag(doubleValue);
+                        break;
+                    case TYPE_FLOAT:
+                        Float floatValue = (Float) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeFloatSizeNoTag(floatValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeFloatNoTag(floatValue);
+                        break;
+                    case TYPE_INT64:
+                        Long int64Value = (Long) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeInt64SizeNoTag(int64Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeInt64NoTag(int64Value);
+                        break;
+                    case TYPE_UINT64:
+                        Long uint64Value = (Long) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeUInt64SizeNoTag(uint64Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeUInt64NoTag(uint64Value);
+                        break;
+                    case TYPE_INT32:
+                        Integer int32Value = (Integer) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeInt32SizeNoTag(int32Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeInt32NoTag(int32Value);
+                        break;
+                    case TYPE_FIXED64:
+                        Long fixed64Value = (Long) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeFixed64SizeNoTag(fixed64Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeFixed64NoTag(fixed64Value);
+                        break;
+                    case TYPE_FIXED32:
+                        Integer fixed32Value = (Integer) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeFixed32SizeNoTag(fixed32Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeFixed32NoTag(fixed32Value);
+                        break;
+                    case TYPE_BOOL:
+                        Boolean boolValue = (Boolean) value;
+                        data = new byte[CodedOutputByteBufferNano.computeBoolSizeNoTag(boolValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeBoolNoTag(boolValue);
+                        break;
+                    case TYPE_STRING:
+                        String stringValue = (String) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeStringSizeNoTag(stringValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeStringNoTag(stringValue);
+                        break;
+                    case TYPE_BYTES:
+                        byte[] bytesValue = (byte[]) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeBytesSizeNoTag(bytesValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeBytesNoTag(bytesValue);
+                        break;
+                    case TYPE_UINT32:
+                        Integer uint32Value = (Integer) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeUInt32SizeNoTag(uint32Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeUInt32NoTag(uint32Value);
+                        break;
+                    case TYPE_ENUM:
+                        Integer enumValue = (Integer) value;
+                        data = new byte[CodedOutputByteBufferNano.computeEnumSizeNoTag(enumValue)];
+                        CodedOutputByteBufferNano.newInstance(data).writeEnumNoTag(enumValue);
+                        break;
+                    case TYPE_SFIXED32:
+                        Integer sfixed32Value = (Integer) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeSFixed32SizeNoTag(sfixed32Value)];
+                        CodedOutputByteBufferNano.newInstance(data)
+                                .writeSFixed32NoTag(sfixed32Value);
+                        break;
+                    case TYPE_SFIXED64:
+                        Long sfixed64Value = (Long) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeSFixed64SizeNoTag(sfixed64Value)];
+                        CodedOutputByteBufferNano.newInstance(data)
+                                .writeSFixed64NoTag(sfixed64Value);
+                        break;
+                    case TYPE_SINT32:
+                        Integer sint32Value = (Integer) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeSInt32SizeNoTag(sint32Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeSInt32NoTag(sint32Value);
+                        break;
+                    case TYPE_SINT64:
+                        Long sint64Value = (Long) value;
+                        data = new byte[
+                                CodedOutputByteBufferNano.computeSInt64SizeNoTag(sint64Value)];
+                        CodedOutputByteBufferNano.newInstance(data).writeSInt64NoTag(sint64Value);
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unknown type " + type);
+                }
+            } catch (IOException e) {
+                // Should not happen
+                throw new IllegalStateException(e);
+            }
+            return new UnknownFieldData(tag, data);
+        }
+
+        @Override
+        protected void writeDataInto(T array, List<UnknownFieldData> unknownFields) {
+            if (tag == nonPackedTag) {
+                // Use base implementation for non-packed data
+                super.writeDataInto(array, unknownFields);
+            } else if (tag == packedTag) {
+                // Packed. Note that the array element type is guaranteed to be primitive, so there
+                // won't be any null elements, so no null check in this block. First get data size.
+                int arrayLength = Array.getLength(array);
+                int dataSize = 0;
+                switch (type) {
+                    case TYPE_BOOL:
+                        // Bools are stored as int32 but just as 0 or 1, so 1 byte each.
+                        dataSize = arrayLength;
+                        break;
+                    case TYPE_FIXED32:
+                    case TYPE_SFIXED32:
+                    case TYPE_FLOAT:
+                        dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_32_SIZE;
+                        break;
+                    case TYPE_FIXED64:
+                    case TYPE_SFIXED64:
+                    case TYPE_DOUBLE:
+                        dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_64_SIZE;
+                        break;
+                    case TYPE_INT32:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeInt32SizeNoTag(
+                                    Array.getInt(array, i));
+                        }
+                        break;
+                    case TYPE_SINT32:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeSInt32SizeNoTag(
+                                    Array.getInt(array, i));
+                        }
+                        break;
+                    case TYPE_UINT32:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeUInt32SizeNoTag(
+                                    Array.getInt(array, i));
+                        }
+                        break;
+                    case TYPE_INT64:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeInt64SizeNoTag(
+                                    Array.getLong(array, i));
+                        }
+                        break;
+                    case TYPE_SINT64:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeSInt64SizeNoTag(
+                                    Array.getLong(array, i));
+                        }
+                        break;
+                    case TYPE_UINT64:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeUInt64SizeNoTag(
+                                    Array.getLong(array, i));
+                        }
+                        break;
+                    case TYPE_ENUM:
+                        for (int i = 0; i < arrayLength; i++) {
+                            dataSize += CodedOutputByteBufferNano.computeEnumSizeNoTag(
+                                    Array.getInt(array, i));
+                        }
+                        break;
+                    default:
+                        throw new IllegalArgumentException("Unexpected non-packable type " + type);
+                }
+
+                // Then construct payload.
+                int payloadSize =
+                        dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize);
+                byte[] data = new byte[payloadSize];
+                CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(data);
+                try {
+                    output.writeRawVarint32(dataSize);
+                    switch (type) {
+                        case TYPE_BOOL:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeBoolNoTag(Array.getBoolean(array, i));
+                            }
+                            break;
+                        case TYPE_FIXED32:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeFixed32NoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        case TYPE_SFIXED32:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeSFixed32NoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        case TYPE_FLOAT:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeFloatNoTag(Array.getFloat(array, i));
+                            }
+                            break;
+                        case TYPE_FIXED64:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeFixed64NoTag(Array.getLong(array, i));
+                            }
+                            break;
+                        case TYPE_SFIXED64:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeSFixed64NoTag(Array.getLong(array, i));
+                            }
+                            break;
+                        case TYPE_DOUBLE:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeDoubleNoTag(Array.getDouble(array, i));
+                            }
+                            break;
+                        case TYPE_INT32:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeInt32NoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        case TYPE_SINT32:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeSInt32NoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        case TYPE_UINT32:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeUInt32NoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        case TYPE_INT64:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeInt64NoTag(Array.getLong(array, i));
+                            }
+                            break;
+                        case TYPE_SINT64:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeSInt64NoTag(Array.getLong(array, i));
+                            }
+                            break;
+                        case TYPE_UINT64:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeUInt64NoTag(Array.getLong(array, i));
+                            }
+                            break;
+                        case TYPE_ENUM:
+                            for (int i = 0; i < arrayLength; i++) {
+                                output.writeEnumNoTag(Array.getInt(array, i));
+                            }
+                            break;
+                        default:
+                            throw new IllegalArgumentException("Unpackable type " + type);
+                    }
+                } catch (IOException e) {
+                    // Should not happen.
+                    throw new IllegalStateException(e);
+                }
+                unknownFields.add(new UnknownFieldData(tag, data));
+            } else {
+                throw new IllegalArgumentException("Unexpected repeated extension tag " + tag
+                        + ", unequal to both non-packed variant " + nonPackedTag
+                        + " and packed variant " + packedTag);
+            }
+        }
+    }
 }
diff --git a/java/src/main/java/com/google/protobuf/nano/MessageNano.java b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
index e95c514..82dc6cc 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
@@ -67,16 +67,20 @@
     }
 
     /**
-     * Serializes the message and writes it to {@code output}.  This does not
-     * flush or close the stream.
+     * Serializes the message and writes it to {@code output}.
+     *
+     * @param output the output to receive the serialized form.
+     * @throws IOException if an error occurred writing to {@code output}.
      */
-    abstract public void writeTo(CodedOutputByteBufferNano output) throws java.io.IOException;
+    public void writeTo(CodedOutputByteBufferNano output) throws IOException {
+        // Does nothing by default. Overridden by subclasses which have data to write.
+    }
 
     /**
      * Parse {@code input} as a message of this type and merge it with the
      * message being built.
      */
-    abstract public MessageNano mergeFrom(final CodedInputByteBufferNano input) throws IOException;
+    public abstract MessageNano mergeFrom(CodedInputByteBufferNano input) throws IOException;
 
     /**
      * Serialize to a byte array.
@@ -95,9 +99,8 @@
      * write more than length bytes OutOfSpaceException will be thrown
      * and if length bytes are not written then IllegalStateException
      * is thrown.
-     * @return byte array with the serialized data.
      */
-    public static final void toByteArray(MessageNano msg, byte [] data, int offset, int length) {
+    public static final void toByteArray(MessageNano msg, byte[] data, int offset, int length) {
         try {
             final CodedOutputByteBufferNano output =
                 CodedOutputByteBufferNano.newInstance(data, offset, length);
diff --git a/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java b/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
index 301ff1d..1ff8f06 100644
--- a/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
@@ -31,9 +31,6 @@
 package com.google.protobuf.nano;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
 
 /**
  * This class is used internally by the Protocol Buffer library and generated
@@ -75,21 +72,6 @@
     return (fieldNumber << TAG_TYPE_BITS) | wireType;
   }
 
-  // Field numbers for feilds in MessageSet wire format.
-  static final int MESSAGE_SET_ITEM    = 1;
-  static final int MESSAGE_SET_TYPE_ID = 2;
-  static final int MESSAGE_SET_MESSAGE = 3;
-
-  // Tag numbers.
-  static final int MESSAGE_SET_ITEM_TAG =
-    makeTag(MESSAGE_SET_ITEM, WIRETYPE_START_GROUP);
-  static final int MESSAGE_SET_ITEM_END_TAG =
-    makeTag(MESSAGE_SET_ITEM, WIRETYPE_END_GROUP);
-  static final int MESSAGE_SET_TYPE_ID_TAG =
-    makeTag(MESSAGE_SET_TYPE_ID, WIRETYPE_VARINT);
-  static final int MESSAGE_SET_MESSAGE_TAG =
-    makeTag(MESSAGE_SET_MESSAGE, WIRETYPE_LENGTH_DELIMITED);
-
   public static final int EMPTY_INT_ARRAY[] = {};
   public static final long EMPTY_LONG_ARRAY[] = {};
   public static final float EMPTY_FLOAT_ARRAY[] = {};
@@ -114,35 +96,6 @@
   }
 
   /**
-   * Stores the binary data of an unknown field.
-   *
-   * <p>Generated messages will call this for unknown fields if the store_unknown_fields
-   * option is on.
-   *
-   * <p>Note that the tag might be a end-group tag (rather than the start of an unknown field) in
-   * which case we do not want to add an unknown field entry.
-   *
-   * @param data a Collection in which to store the data.
-   * @param input the input buffer.
-   * @param tag the tag of the field.
-
-   * @return {@literal true} unless the tag is an end-group tag.
-   */
-  public static boolean storeUnknownField(
-      final List<UnknownFieldData> data,
-      final CodedInputByteBufferNano input,
-      final int tag) throws IOException {
-    int startPos = input.getPosition();
-    if (!input.skipField(tag)) {
-      return false;  // This wasn't an unknown field, it's an end-group tag.
-    }
-    int endPos = input.getPosition();
-    byte[] bytes = input.getData(startPos, endPos - startPos);
-    data.add(new UnknownFieldData(tag, bytes));
-    return true;
-  }
-
-  /**
    * Computes the array length of a repeated field. We assume that in the common case repeated
    * fields are contiguously serialized but we still correctly handle interspersed values of a
    * repeated field (but with extra allocations).
@@ -172,193 +125,4 @@
     return arrayLength;
   }
 
-  /**
-   * Decodes the value of an extension.
-   */
-  public static <T> T getExtension(Extension<T> extension, List<UnknownFieldData> unknownFields) {
-    if (unknownFields == null) {
-      return null;
-    }
-    List<UnknownFieldData> dataForField = new ArrayList<UnknownFieldData>();
-    for (UnknownFieldData data : unknownFields) {
-      if (getTagFieldNumber(data.tag) == extension.fieldNumber) {
-        dataForField.add(data);
-      }
-    }
-    if (dataForField.isEmpty()) {
-      return null;
-    }
-
-    if (extension.isRepeatedField) {
-      List<Object> result = new ArrayList<Object>(dataForField.size());
-      for (UnknownFieldData data : dataForField) {
-        result.add(readData(extension.fieldType, data.bytes));
-      }
-      return extension.listType.cast(result);
-    }
-
-    // Normal fields. Note that the protobuf docs require us to handle multiple instances
-    // of the same field even for fields that are not repeated.
-    UnknownFieldData lastData = dataForField.get(dataForField.size() - 1);
-    return readData(extension.fieldType, lastData.bytes);
-  }
-
-  /**
-   * Reads (extension) data of the specified type from the specified byte array.
-   *
-   * @throws IllegalArgumentException if an error occurs while reading the data.
-   */
-  private static <T> T readData(Class<T> clazz, byte[] data) {
-    if (data.length == 0) {
-      return null;
-    }
-    CodedInputByteBufferNano buffer = CodedInputByteBufferNano.newInstance(data);
-    try {
-      if (clazz == String.class) {
-        return clazz.cast(buffer.readString());
-      } else if (clazz == Integer.class) {
-        return clazz.cast(buffer.readInt32());
-      } else if (clazz == Long.class) {
-        return clazz.cast(buffer.readInt64());
-      } else if (clazz == Boolean.class) {
-        return clazz.cast(buffer.readBool());
-      } else if (clazz == Float.class) {
-        return clazz.cast(buffer.readFloat());
-      } else if (clazz == Double.class) {
-        return clazz.cast(buffer.readDouble());
-      } else if (clazz == byte[].class) {
-        return clazz.cast(buffer.readBytes());
-      } else if (MessageNano.class.isAssignableFrom(clazz)) {
-        try {
-          MessageNano message = (MessageNano) clazz.newInstance();
-          buffer.readMessage(message);
-          return clazz.cast(message);
-        } catch (IllegalAccessException e) {
-          throw new IllegalArgumentException("Error creating instance of class " + clazz, e);
-        } catch (InstantiationException e) {
-          throw new IllegalArgumentException("Error creating instance of class " + clazz, e);
-        }
-      } else {
-        throw new IllegalArgumentException("Unhandled extension field type: " + clazz);
-      }
-    } catch (IOException e) {
-      throw new IllegalArgumentException("Error reading extension field", e);
-    }
-  }
-
-  public static <T> void setExtension(Extension<T> extension, T value,
-      List<UnknownFieldData> unknownFields) {
-    // First, remove all unknown fields with this tag.
-    for (Iterator<UnknownFieldData> i = unknownFields.iterator(); i.hasNext();) {
-      UnknownFieldData data = i.next();
-      if (extension.fieldNumber == getTagFieldNumber(data.tag)) {
-        i.remove();
-      }
-    }
-    if (value == null) {
-      return;
-    }
-    // Repeated field.
-    if (value instanceof List) {
-      for (Object item : (List<?>) value) {
-        unknownFields.add(write(extension.fieldNumber, item));
-      }
-    } else {
-      unknownFields.add(write(extension.fieldNumber, value));
-    }
-  }
-
-  /**
-   * Writes extension data and returns an {@link UnknownFieldData} containing
-   * bytes and a tag.
-   *
-   * @throws IllegalArgumentException if an error occurs while writing.
-   */
-  private static UnknownFieldData write(int fieldNumber, Object object) {
-    byte[] data;
-    int tag;
-    Class<?> clazz = object.getClass();
-    try {
-      if (clazz == String.class) {
-        String str = (String) object;
-        data = new byte[CodedOutputByteBufferNano.computeStringSizeNoTag(str)];
-        CodedOutputByteBufferNano.newInstance(data).writeStringNoTag(str);
-        tag = makeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
-      } else if (clazz == Integer.class) {
-        Integer integer = (Integer) object;
-        data = new byte[CodedOutputByteBufferNano.computeInt32SizeNoTag(integer)];
-        CodedOutputByteBufferNano.newInstance(data).writeInt32NoTag(integer);
-        tag = makeTag(fieldNumber, WIRETYPE_VARINT);
-      } else if (clazz == Long.class) {
-        Long longValue = (Long) object;
-        data = new byte[CodedOutputByteBufferNano.computeInt64SizeNoTag(longValue)];
-        CodedOutputByteBufferNano.newInstance(data).writeInt64NoTag(longValue);
-        tag = makeTag(fieldNumber, WIRETYPE_VARINT);
-      } else if (clazz == Boolean.class) {
-        Boolean boolValue = (Boolean) object;
-        data = new byte[CodedOutputByteBufferNano.computeBoolSizeNoTag(boolValue)];
-        CodedOutputByteBufferNano.newInstance(data).writeBoolNoTag(boolValue);
-        tag = makeTag(fieldNumber, WIRETYPE_VARINT);
-      } else if (clazz == Float.class) {
-        Float floatValue = (Float) object;
-        data = new byte[CodedOutputByteBufferNano.computeFloatSizeNoTag(floatValue)];
-        CodedOutputByteBufferNano.newInstance(data).writeFloatNoTag(floatValue);
-        tag = makeTag(fieldNumber, WIRETYPE_FIXED32);
-      } else if (clazz == Double.class) {
-        Double doubleValue = (Double) object;
-        data = new byte[CodedOutputByteBufferNano.computeDoubleSizeNoTag(doubleValue)];
-        CodedOutputByteBufferNano.newInstance(data).writeDoubleNoTag(doubleValue);
-        tag = makeTag(fieldNumber, WIRETYPE_FIXED64);
-      } else if (clazz == byte[].class) {
-        byte[] byteArrayValue = (byte[]) object;
-        data = new byte[CodedOutputByteBufferNano.computeByteArraySizeNoTag(byteArrayValue)];
-        CodedOutputByteBufferNano.newInstance(data).writeByteArrayNoTag(byteArrayValue);
-        tag = makeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
-      } else if (MessageNano.class.isAssignableFrom(clazz)) {
-        MessageNano messageValue = (MessageNano) object;
-
-        int messageSize = messageValue.getSerializedSize();
-        int delimiterSize = CodedOutputByteBufferNano.computeRawVarint32Size(messageSize);
-        data = new byte[messageSize + delimiterSize];
-        CodedOutputByteBufferNano buffer = CodedOutputByteBufferNano.newInstance(data);
-        buffer.writeRawVarint32(messageSize);
-        buffer.writeRawBytes(MessageNano.toByteArray(messageValue));
-        tag = makeTag(fieldNumber, WIRETYPE_LENGTH_DELIMITED);
-      } else {
-        throw new IllegalArgumentException("Unhandled extension field type: " + clazz);
-      }
-    } catch (IOException e) {
-      throw new IllegalArgumentException(e);
-    }
-    return new UnknownFieldData(tag, data);
-  }
-
-  /**
-   * Given a set of unknown field data, compute the wire size.
-   */
-  public static int computeWireSize(List<UnknownFieldData> unknownFields) {
-    if (unknownFields == null) {
-      return 0;
-    }
-    int size = 0;
-    for (UnknownFieldData unknownField : unknownFields) {
-      size += CodedOutputByteBufferNano.computeRawVarint32Size(unknownField.tag);
-      size += unknownField.bytes.length;
-    }
-    return size;
-  }
-
-  /**
-   * Write unknown fields.
-   */
-  public static void writeUnknownFields(List<UnknownFieldData> unknownFields,
-      CodedOutputByteBufferNano outBuffer) throws IOException {
-    if (unknownFields == null) {
-      return;
-    }
-    for (UnknownFieldData data : unknownFields) {
-      outBuffer.writeTag(getTagFieldNumber(data.tag), getTagWireType(data.tag));
-      outBuffer.writeRawBytes(data.bytes);
-    }
-  }
 }
diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java
index 32a331e..9987cac 100644
--- a/java/src/test/java/com/google/protobuf/NanoTest.java
+++ b/java/src/test/java/com/google/protobuf/NanoTest.java
@@ -52,6 +52,9 @@
 import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
 import com.google.protobuf.nano.NanoReferenceTypes;
 import com.google.protobuf.nano.NanoRepeatedPackables;
+import com.google.protobuf.nano.PackedExtensions;
+import com.google.protobuf.nano.RepeatedExtensions;
+import com.google.protobuf.nano.SingularExtensions;
 import com.google.protobuf.nano.TestRepeatedMergeNano;
 import com.google.protobuf.nano.UnittestImportNano;
 import com.google.protobuf.nano.UnittestMultipleNano;
@@ -61,10 +64,8 @@
 
 import junit.framework.TestCase;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.List;
 
 /**
  * Test nano runtime.
@@ -520,12 +521,12 @@
     byte [] serialized = MessageNano.toByteArray(msg);
 
     MessageWithGroup parsed = MessageWithGroup.parseFrom(serialized);
-    assertTrue(msg.group != null);
-    assertEquals(1, msg.group.a);
+    assertEquals(1, parsed.group.a);
 
     byte [] serialized2 = MessageNano.toByteArray(parsed);
-    assertEquals(serialized2.length, serialized.length);
+    assertEquals(serialized.length, serialized2.length);
     MessageWithGroup parsed2 = MessageWithGroup.parseFrom(serialized2);
+    assertEquals(1, parsed2.group.a);
   }
 
   public void testNanoOptionalNestedMessage() throws Exception {
@@ -2222,6 +2223,7 @@
    */
   public void testNanoSingle() throws Exception {
     SingleMessageNano msg = new SingleMessageNano();
+    assertNotNull(msg);
   }
 
   /**
@@ -2597,12 +2599,14 @@
     msg.defaultFloatNan = 0;
     byte[] result = MessageNano.toByteArray(msg);
     int msgSerializedSize = msg.getSerializedSize();
+    assertTrue(result.length == msgSerializedSize);
     assertTrue(msgSerializedSize > 3);
 
     msg.defaultDoubleNan = Double.NaN;
     msg.defaultFloatNan = Float.NaN;
     result = MessageNano.toByteArray(msg);
     msgSerializedSize = msg.getSerializedSize();
+    assertEquals(3, result.length);
     assertEquals(3, msgSerializedSize);
   }
 
@@ -2735,57 +2739,163 @@
   public void testExtensions() throws Exception {
     Extensions.ExtendableMessage message = new Extensions.ExtendableMessage();
     message.field = 5;
-    message.setExtension(Extensions.someString, "Hello World!");
-    message.setExtension(Extensions.someBool, true);
-    message.setExtension(Extensions.someInt, 42);
-    message.setExtension(Extensions.someLong, 124234234234L);
-    message.setExtension(Extensions.someFloat, 42.0f);
-    message.setExtension(Extensions.someDouble, 422222.0);
-    message.setExtension(Extensions.someEnum, Extensions.FIRST_VALUE);
-    AnotherMessage another = new AnotherMessage();
-    another.string = "Foo";
-    another.value = true;
-    message.setExtension(Extensions.someMessage, another);
-
-    message.setExtension(Extensions.someRepeatedString, list("a", "bee", "seeya"));
-    message.setExtension(Extensions.someRepeatedBool, list(true, false, true));
-    message.setExtension(Extensions.someRepeatedInt, list(4, 8, 15, 16, 23, 42));
-    message.setExtension(Extensions.someRepeatedLong, list(4L, 8L, 15L, 16L, 23L, 42L));
-    message.setExtension(Extensions.someRepeatedFloat, list(1.0f, 3.0f));
-    message.setExtension(Extensions.someRepeatedDouble, list(55.133, 3.14159));
-    message.setExtension(Extensions.someRepeatedEnum, list(Extensions.FIRST_VALUE,
-        Extensions.SECOND_VALUE));
-    AnotherMessage second = new AnotherMessage();
-    second.string = "Whee";
-    second.value = false;
-    message.setExtension(Extensions.someRepeatedMessage, list(another, second));
+    int[] int32s = {1, 2};
+    int[] uint32s = {3, 4};
+    int[] sint32s = {-5, -6};
+    long[] int64s = {7, 8};
+    long[] uint64s = {9, 10};
+    long[] sint64s = {-11, -12};
+    int[] fixed32s = {13, 14};
+    int[] sfixed32s = {-15, -16};
+    long[] fixed64s = {17, 18};
+    long[] sfixed64s = {-19, -20};
+    boolean[] bools = {true, false};
+    float[] floats = {2.1f, 2.2f};
+    double[] doubles = {2.3, 2.4};
+    int[] enums = {Extensions.SECOND_VALUE, Extensions.FIRST_VALUE};
+    String[] strings = {"vijfentwintig", "twenty-six"};
+    byte[][] bytess = {{2, 7}, {2, 8}};
+    AnotherMessage another1 = new AnotherMessage();
+    another1.string = "er shi jiu";
+    another1.value = false;
+    AnotherMessage another2 = new AnotherMessage();
+    another2.string = "trente";
+    another2.value = true;
+    AnotherMessage[] messages = {another1, another2};
+    RepeatedExtensions.RepeatedGroup group1 = new RepeatedExtensions.RepeatedGroup();
+    group1.a = 31;
+    RepeatedExtensions.RepeatedGroup group2 = new RepeatedExtensions.RepeatedGroup();
+    group2.a = 32;
+    RepeatedExtensions.RepeatedGroup[] groups = {group1, group2};
+    message.setExtension(RepeatedExtensions.repeatedInt32, int32s);
+    message.setExtension(RepeatedExtensions.repeatedUint32, uint32s);
+    message.setExtension(RepeatedExtensions.repeatedSint32, sint32s);
+    message.setExtension(RepeatedExtensions.repeatedInt64, int64s);
+    message.setExtension(RepeatedExtensions.repeatedUint64, uint64s);
+    message.setExtension(RepeatedExtensions.repeatedSint64, sint64s);
+    message.setExtension(RepeatedExtensions.repeatedFixed32, fixed32s);
+    message.setExtension(RepeatedExtensions.repeatedSfixed32, sfixed32s);
+    message.setExtension(RepeatedExtensions.repeatedFixed64, fixed64s);
+    message.setExtension(RepeatedExtensions.repeatedSfixed64, sfixed64s);
+    message.setExtension(RepeatedExtensions.repeatedBool, bools);
+    message.setExtension(RepeatedExtensions.repeatedFloat, floats);
+    message.setExtension(RepeatedExtensions.repeatedDouble, doubles);
+    message.setExtension(RepeatedExtensions.repeatedEnum, enums);
+    message.setExtension(RepeatedExtensions.repeatedString, strings);
+    message.setExtension(RepeatedExtensions.repeatedBytes, bytess);
+    message.setExtension(RepeatedExtensions.repeatedMessage, messages);
+    message.setExtension(RepeatedExtensions.repeatedGroup, groups);
 
     byte[] data = MessageNano.toByteArray(message);
+    message = Extensions.ExtendableMessage.parseFrom(data);
+    assertEquals(5, message.field);
 
-    Extensions.ExtendableMessage deserialized = Extensions.ExtendableMessage.parseFrom(data);
-    assertEquals(5, deserialized.field);
-    assertEquals("Hello World!", deserialized.getExtension(Extensions.someString));
-    assertEquals(Boolean.TRUE, deserialized.getExtension(Extensions.someBool));
-    assertEquals(Integer.valueOf(42), deserialized.getExtension(Extensions.someInt));
-    assertEquals(Long.valueOf(124234234234L), deserialized.getExtension(Extensions.someLong));
-    assertEquals(Float.valueOf(42.0f), deserialized.getExtension(Extensions.someFloat));
-    assertEquals(Double.valueOf(422222.0), deserialized.getExtension(Extensions.someDouble));
-    assertEquals(Integer.valueOf(Extensions.FIRST_VALUE),
-        deserialized.getExtension(Extensions.someEnum));
-    assertEquals(another.string, deserialized.getExtension(Extensions.someMessage).string);
-    assertEquals(another.value, deserialized.getExtension(Extensions.someMessage).value);
-    assertEquals(list("a", "bee", "seeya"), deserialized.getExtension(Extensions.someRepeatedString));
-    assertEquals(list(true, false, true), deserialized.getExtension(Extensions.someRepeatedBool));
-    assertEquals(list(4, 8, 15, 16, 23, 42), deserialized.getExtension(Extensions.someRepeatedInt));
-    assertEquals(list(4L, 8L, 15L, 16L, 23L, 42L), deserialized.getExtension(Extensions.someRepeatedLong));
-    assertEquals(list(1.0f, 3.0f), deserialized.getExtension(Extensions.someRepeatedFloat));
-    assertEquals(list(55.133, 3.14159), deserialized.getExtension(Extensions.someRepeatedDouble));
-    assertEquals(list(Extensions.FIRST_VALUE,
-        Extensions.SECOND_VALUE), deserialized.getExtension(Extensions.someRepeatedEnum));
-    assertEquals("Foo", deserialized.getExtension(Extensions.someRepeatedMessage).get(0).string);
-    assertEquals(true, deserialized.getExtension(Extensions.someRepeatedMessage).get(0).value);
-    assertEquals("Whee", deserialized.getExtension(Extensions.someRepeatedMessage).get(1).string);
-    assertEquals(false, deserialized.getExtension(Extensions.someRepeatedMessage).get(1).value);
+    // Test reading back using SingularExtensions: the retrieved value should equal the last
+    // in each array.
+    assertEquals(int32s[1], (int) message.getExtension(SingularExtensions.someInt32));
+    assertEquals(uint32s[1], (int) message.getExtension(SingularExtensions.someUint32));
+    assertEquals(sint32s[1], (int) message.getExtension(SingularExtensions.someSint32));
+    assertEquals(int64s[1], (long) message.getExtension(SingularExtensions.someInt64));
+    assertEquals(uint64s[1], (long) message.getExtension(SingularExtensions.someUint64));
+    assertEquals(sint64s[1], (long) message.getExtension(SingularExtensions.someSint64));
+    assertEquals(fixed32s[1], (int) message.getExtension(SingularExtensions.someFixed32));
+    assertEquals(sfixed32s[1], (int) message.getExtension(SingularExtensions.someSfixed32));
+    assertEquals(fixed64s[1], (long) message.getExtension(SingularExtensions.someFixed64));
+    assertEquals(sfixed64s[1], (long) message.getExtension(SingularExtensions.someSfixed64));
+    assertEquals(bools[1], (boolean) message.getExtension(SingularExtensions.someBool));
+    assertEquals(floats[1], (float) message.getExtension(SingularExtensions.someFloat));
+    assertEquals(doubles[1], (double) message.getExtension(SingularExtensions.someDouble));
+    assertEquals(enums[1], (int) message.getExtension(SingularExtensions.someEnum));
+    assertEquals(strings[1], message.getExtension(SingularExtensions.someString));
+    assertTrue(Arrays.equals(bytess[1], message.getExtension(SingularExtensions.someBytes)));
+    AnotherMessage deserializedMessage = message.getExtension(SingularExtensions.someMessage);
+    assertEquals(another2.string, deserializedMessage.string);
+    assertEquals(another2.value, deserializedMessage.value);
+    assertEquals(group2.a, message.getExtension(SingularExtensions.someGroup).a);
+
+    // Test reading back using RepeatedExtensions: the arrays should be equal.
+    assertTrue(Arrays.equals(int32s, message.getExtension(RepeatedExtensions.repeatedInt32)));
+    assertTrue(Arrays.equals(uint32s, message.getExtension(RepeatedExtensions.repeatedUint32)));
+    assertTrue(Arrays.equals(sint32s, message.getExtension(RepeatedExtensions.repeatedSint32)));
+    assertTrue(Arrays.equals(int64s, message.getExtension(RepeatedExtensions.repeatedInt64)));
+    assertTrue(Arrays.equals(uint64s, message.getExtension(RepeatedExtensions.repeatedUint64)));
+    assertTrue(Arrays.equals(sint64s, message.getExtension(RepeatedExtensions.repeatedSint64)));
+    assertTrue(Arrays.equals(fixed32s, message.getExtension(RepeatedExtensions.repeatedFixed32)));
+    assertTrue(Arrays.equals(sfixed32s, message.getExtension(RepeatedExtensions.repeatedSfixed32)));
+    assertTrue(Arrays.equals(fixed64s, message.getExtension(RepeatedExtensions.repeatedFixed64)));
+    assertTrue(Arrays.equals(sfixed64s, message.getExtension(RepeatedExtensions.repeatedSfixed64)));
+    assertTrue(Arrays.equals(bools, message.getExtension(RepeatedExtensions.repeatedBool)));
+    assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat)));
+    assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble)));
+    assertTrue(Arrays.equals(enums, message.getExtension(RepeatedExtensions.repeatedEnum)));
+    assertTrue(Arrays.equals(strings, message.getExtension(RepeatedExtensions.repeatedString)));
+    byte[][] deserializedRepeatedBytes = message.getExtension(RepeatedExtensions.repeatedBytes);
+    assertEquals(2, deserializedRepeatedBytes.length);
+    assertTrue(Arrays.equals(bytess[0], deserializedRepeatedBytes[0]));
+    assertTrue(Arrays.equals(bytess[1], deserializedRepeatedBytes[1]));
+    AnotherMessage[] deserializedRepeatedMessage =
+        message.getExtension(RepeatedExtensions.repeatedMessage);
+    assertEquals(2, deserializedRepeatedMessage.length);
+    assertEquals(another1.string, deserializedRepeatedMessage[0].string);
+    assertEquals(another1.value, deserializedRepeatedMessage[0].value);
+    assertEquals(another2.string, deserializedRepeatedMessage[1].string);
+    assertEquals(another2.value, deserializedRepeatedMessage[1].value);
+    RepeatedExtensions.RepeatedGroup[] deserializedRepeatedGroup =
+        message.getExtension(RepeatedExtensions.repeatedGroup);
+    assertEquals(2, deserializedRepeatedGroup.length);
+    assertEquals(group1.a, deserializedRepeatedGroup[0].a);
+    assertEquals(group2.a, deserializedRepeatedGroup[1].a);
+
+    // Test reading back using PackedExtensions: the arrays should be equal, even the fields
+    // are non-packed.
+    assertTrue(Arrays.equals(int32s, message.getExtension(PackedExtensions.packedInt32)));
+    assertTrue(Arrays.equals(uint32s, message.getExtension(PackedExtensions.packedUint32)));
+    assertTrue(Arrays.equals(sint32s, message.getExtension(PackedExtensions.packedSint32)));
+    assertTrue(Arrays.equals(int64s, message.getExtension(PackedExtensions.packedInt64)));
+    assertTrue(Arrays.equals(uint64s, message.getExtension(PackedExtensions.packedUint64)));
+    assertTrue(Arrays.equals(sint64s, message.getExtension(PackedExtensions.packedSint64)));
+    assertTrue(Arrays.equals(fixed32s, message.getExtension(PackedExtensions.packedFixed32)));
+    assertTrue(Arrays.equals(sfixed32s, message.getExtension(PackedExtensions.packedSfixed32)));
+    assertTrue(Arrays.equals(fixed64s, message.getExtension(PackedExtensions.packedFixed64)));
+    assertTrue(Arrays.equals(sfixed64s, message.getExtension(PackedExtensions.packedSfixed64)));
+    assertTrue(Arrays.equals(bools, message.getExtension(PackedExtensions.packedBool)));
+    assertTrue(Arrays.equals(floats, message.getExtension(PackedExtensions.packedFloat)));
+    assertTrue(Arrays.equals(doubles, message.getExtension(PackedExtensions.packedDouble)));
+    assertTrue(Arrays.equals(enums, message.getExtension(PackedExtensions.packedEnum)));
+
+    // Now set the packable extension values using PackedExtensions so they're serialized packed.
+    message.setExtension(PackedExtensions.packedInt32, int32s);
+    message.setExtension(PackedExtensions.packedUint32, uint32s);
+    message.setExtension(PackedExtensions.packedSint32, sint32s);
+    message.setExtension(PackedExtensions.packedInt64, int64s);
+    message.setExtension(PackedExtensions.packedUint64, uint64s);
+    message.setExtension(PackedExtensions.packedSint64, sint64s);
+    message.setExtension(PackedExtensions.packedFixed32, fixed32s);
+    message.setExtension(PackedExtensions.packedSfixed32, sfixed32s);
+    message.setExtension(PackedExtensions.packedFixed64, fixed64s);
+    message.setExtension(PackedExtensions.packedSfixed64, sfixed64s);
+    message.setExtension(PackedExtensions.packedBool, bools);
+    message.setExtension(PackedExtensions.packedFloat, floats);
+    message.setExtension(PackedExtensions.packedDouble, doubles);
+    message.setExtension(PackedExtensions.packedEnum, enums);
+
+    // And read back using non-packed RepeatedExtensions.
+    byte[] data2 = MessageNano.toByteArray(message);
+    message = MessageNano.mergeFrom(new Extensions.ExtendableMessage(), data2);
+    assertTrue(Arrays.equals(int32s, message.getExtension(RepeatedExtensions.repeatedInt32)));
+    assertTrue(Arrays.equals(uint32s, message.getExtension(RepeatedExtensions.repeatedUint32)));
+    assertTrue(Arrays.equals(sint32s, message.getExtension(RepeatedExtensions.repeatedSint32)));
+    assertTrue(Arrays.equals(int64s, message.getExtension(RepeatedExtensions.repeatedInt64)));
+    assertTrue(Arrays.equals(uint64s, message.getExtension(RepeatedExtensions.repeatedUint64)));
+    assertTrue(Arrays.equals(sint64s, message.getExtension(RepeatedExtensions.repeatedSint64)));
+    assertTrue(Arrays.equals(fixed32s, message.getExtension(RepeatedExtensions.repeatedFixed32)));
+    assertTrue(Arrays.equals(sfixed32s, message.getExtension(RepeatedExtensions.repeatedSfixed32)));
+    assertTrue(Arrays.equals(fixed64s, message.getExtension(RepeatedExtensions.repeatedFixed64)));
+    assertTrue(Arrays.equals(sfixed64s, message.getExtension(RepeatedExtensions.repeatedSfixed64)));
+    assertTrue(Arrays.equals(bools, message.getExtension(RepeatedExtensions.repeatedBool)));
+    assertTrue(Arrays.equals(floats, message.getExtension(RepeatedExtensions.repeatedFloat)));
+    assertTrue(Arrays.equals(doubles, message.getExtension(RepeatedExtensions.repeatedDouble)));
+    assertTrue(Arrays.equals(enums, message.getExtension(RepeatedExtensions.repeatedEnum)));
   }
 
   public void testUnknownFields() throws Exception {
@@ -3462,13 +3572,4 @@
     }
     return sb.toString();
   }
-
-  private <T> List<T> list(T first, T... remaining) {
-    List<T> list = new ArrayList<T>();
-    list.add(first);
-    for (T item : remaining) {
-      list.add(item);
-    }
-    return list;
-  }
 }
diff --git a/src/google/protobuf/compiler/javanano/javanano_extension.cc b/src/google/protobuf/compiler/javanano/javanano_extension.cc
index 0bc9c9d..754ed55 100644
--- a/src/google/protobuf/compiler/javanano/javanano_extension.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_extension.cc
@@ -42,28 +42,84 @@
 namespace javanano {
 
 using internal::WireFormat;
+using internal::WireFormatLite;
+
+namespace {
+
+const char* GetTypeConstantName(const FieldDescriptor::Type type) {
+  switch (type) {
+    case FieldDescriptor::TYPE_INT32   : return "TYPE_INT32"   ;
+    case FieldDescriptor::TYPE_UINT32  : return "TYPE_UINT32"  ;
+    case FieldDescriptor::TYPE_SINT32  : return "TYPE_SINT32"  ;
+    case FieldDescriptor::TYPE_FIXED32 : return "TYPE_FIXED32" ;
+    case FieldDescriptor::TYPE_SFIXED32: return "TYPE_SFIXED32";
+    case FieldDescriptor::TYPE_INT64   : return "TYPE_INT64"   ;
+    case FieldDescriptor::TYPE_UINT64  : return "TYPE_UINT64"  ;
+    case FieldDescriptor::TYPE_SINT64  : return "TYPE_SINT64"  ;
+    case FieldDescriptor::TYPE_FIXED64 : return "TYPE_FIXED64" ;
+    case FieldDescriptor::TYPE_SFIXED64: return "TYPE_SFIXED64";
+    case FieldDescriptor::TYPE_FLOAT   : return "TYPE_FLOAT"   ;
+    case FieldDescriptor::TYPE_DOUBLE  : return "TYPE_DOUBLE"  ;
+    case FieldDescriptor::TYPE_BOOL    : return "TYPE_BOOL"    ;
+    case FieldDescriptor::TYPE_STRING  : return "TYPE_STRING"  ;
+    case FieldDescriptor::TYPE_BYTES   : return "TYPE_BYTES"   ;
+    case FieldDescriptor::TYPE_ENUM    : return "TYPE_ENUM"    ;
+    case FieldDescriptor::TYPE_GROUP   : return "TYPE_GROUP"   ;
+    case FieldDescriptor::TYPE_MESSAGE : return "TYPE_MESSAGE" ;
+
+    // No default because we want the compiler to complain if any new
+    // types are added.
+  }
+
+  GOOGLE_LOG(FATAL) << "Can't get here.";
+  return NULL;
+}
+
+}  // namespace
 
 void SetVariables(const FieldDescriptor* descriptor, const Params params,
                   map<string, string>* variables) {
-  (*variables)["name"] = 
-    RenameJavaKeywords(UnderscoresToCamelCase(descriptor));
-  (*variables)["number"] = SimpleItoa(descriptor->number());
   (*variables)["extends"] = ClassName(params, descriptor->containing_type());
-
-  string type;
+  (*variables)["name"] = RenameJavaKeywords(UnderscoresToCamelCase(descriptor));
+  bool repeated = descriptor->is_repeated();
+  (*variables)["repeated"] = repeated ? "Repeated" : "";
+  (*variables)["type"] = GetTypeConstantName(descriptor->type());
   JavaType java_type = GetJavaType(descriptor->type());
-  switch (java_type) {
-    case JAVATYPE_ENUM:
-      type = "java.lang.Integer";
-      break;
-    case JAVATYPE_MESSAGE:
-      type = ClassName(params, descriptor->message_type());
-      break;
-    default:
-      type = BoxedPrimitiveTypeName(java_type);
-      break;
+  string tag = SimpleItoa(WireFormat::MakeTag(descriptor));
+  if (java_type == JAVATYPE_MESSAGE) {
+    (*variables)["ext_type"] = "MessageTyped";
+    string message_type = ClassName(params, descriptor->message_type());
+    if (repeated) {
+      message_type += "[]";
+    }
+    (*variables)["class"] = message_type;
+    // For message typed extensions, tags_params contains a single tag
+    // for both singular and repeated cases.
+    (*variables)["tag_params"] = tag;
+  } else {
+    (*variables)["ext_type"] = "PrimitiveTyped";
+    if (!repeated) {
+      (*variables)["class"] = BoxedPrimitiveTypeName(java_type);
+      (*variables)["tag_params"] = tag;
+    } else {
+      (*variables)["class"] = PrimitiveTypeName(java_type) + "[]";
+      if (!descriptor->is_packable()) {
+        // Non-packable: nonPackedTag == tag, packedTag == 0
+        (*variables)["tag_params"] = tag + ", " + tag + ", 0";
+      } else if (descriptor->options().packed()) {
+        // Packable and packed: tag == packedTag
+        string non_packed_tag = SimpleItoa(WireFormatLite::MakeTag(
+            descriptor->number(),
+            WireFormat::WireTypeForFieldType(descriptor->type())));
+        (*variables)["tag_params"] = tag + ", " + non_packed_tag + ", " + tag;
+      } else {
+        // Packable and not packed: tag == nonPackedTag
+        string packed_tag = SimpleItoa(WireFormatLite::MakeTag(
+            descriptor->number(), WireFormatLite::WIRETYPE_LENGTH_DELIMITED));
+        (*variables)["tag_params"] = tag + ", " + tag + ", " + packed_tag;
+      }
+    }
   }
-  (*variables)["type"] = type;
 }
 
 ExtensionGenerator::
@@ -75,21 +131,16 @@
 ExtensionGenerator::~ExtensionGenerator() {}
 
 void ExtensionGenerator::Generate(io::Printer* printer) const {
-  if (descriptor_->is_repeated()) {
-    printer->Print(variables_,
-      "\n"
-      "// extends $extends$\n"
-      "public static final com.google.protobuf.nano.Extension<java.util.List<$type$>> $name$ = \n"
-      "    com.google.protobuf.nano.Extension.createRepeated($number$,\n"
-      "        new com.google.protobuf.nano.Extension.TypeLiteral<java.util.List<$type$>>(){});\n");
-  } else {
-    printer->Print(variables_,
-      "\n"
-      "// extends $extends$\n"
-      "public static final com.google.protobuf.nano.Extension<$type$> $name$ =\n"
-      "    com.google.protobuf.nano.Extension.create($number$,\n"
-      "        new com.google.protobuf.nano.Extension.TypeLiteral<$type$>(){});\n");
-  }
+  printer->Print("\n");
+  PrintFieldComment(printer, descriptor_);
+  printer->Print(variables_,
+    "public static final com.google.protobuf.nano.Extension<\n"
+    "    $extends$,\n"
+    "    $class$> $name$ =\n"
+    "        com.google.protobuf.nano.Extension.create$repeated$$ext_type$(\n"
+    "            com.google.protobuf.nano.Extension.$type$,\n"
+    "            $class$.class,\n"
+    "            $tag_params$);\n");
 }
 
 }  // namespace javanano
diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.cc b/src/google/protobuf/compiler/javanano/javanano_helpers.cc
index ede4c6f..e8326a4 100644
--- a/src/google/protobuf/compiler/javanano/javanano_helpers.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_helpers.cc
@@ -264,6 +264,22 @@
   return "_" + RenameJavaKeywords(UnderscoresToCamelCase(field)) + "Default";
 }
 
+void PrintFieldComment(io::Printer* printer, const FieldDescriptor* field) {
+  // We don't want to print group bodies so we cut off after the first line
+  // (the second line for extensions).
+  string def = field->DebugString();
+  string::size_type first_line_end = def.find_first_of('\n');
+  printer->Print("// $def$\n",
+    "def", def.substr(0, first_line_end));
+  if (field->is_extension()) {
+    string::size_type second_line_start = first_line_end + 1;
+    string::size_type second_line_length =
+        def.find('\n', second_line_start) - second_line_start;
+    printer->Print("// $def$\n",
+      "def", def.substr(second_line_start, second_line_length));
+  }
+}
+
 JavaType GetJavaType(FieldDescriptor::Type field_type) {
   switch (field_type) {
     case FieldDescriptor::TYPE_INT32:
@@ -310,7 +326,27 @@
   return JAVATYPE_INT;
 }
 
-const char* BoxedPrimitiveTypeName(JavaType type) {
+string PrimitiveTypeName(JavaType type) {
+  switch (type) {
+    case JAVATYPE_INT    : return "int";
+    case JAVATYPE_LONG   : return "long";
+    case JAVATYPE_FLOAT  : return "float";
+    case JAVATYPE_DOUBLE : return "double";
+    case JAVATYPE_BOOLEAN: return "boolean";
+    case JAVATYPE_STRING : return "java.lang.String";
+    case JAVATYPE_BYTES  : return "byte[]";
+    case JAVATYPE_ENUM   : return "int";
+    case JAVATYPE_MESSAGE: return NULL;
+
+    // No default because we want the compiler to complain if any new
+    // JavaTypes are added.
+  }
+
+  GOOGLE_LOG(FATAL) << "Can't get here.";
+  return NULL;
+}
+
+string BoxedPrimitiveTypeName(JavaType type) {
   switch (type) {
     case JAVATYPE_INT    : return "java.lang.Integer";
     case JAVATYPE_LONG   : return "java.lang.Long";
diff --git a/src/google/protobuf/compiler/javanano/javanano_helpers.h b/src/google/protobuf/compiler/javanano/javanano_helpers.h
index 753a4bd4..886bff8 100644
--- a/src/google/protobuf/compiler/javanano/javanano_helpers.h
+++ b/src/google/protobuf/compiler/javanano/javanano_helpers.h
@@ -39,6 +39,7 @@
 #include <google/protobuf/compiler/javanano/javanano_params.h>
 #include <google/protobuf/descriptor.pb.h>
 #include <google/protobuf/descriptor.h>
+#include <google/protobuf/io/printer.h>
 
 namespace google {
 namespace protobuf {
@@ -111,6 +112,9 @@
 
 string FieldDefaultConstantName(const FieldDescriptor *field);
 
+// Print the field's proto-syntax definition as a comment.
+void PrintFieldComment(io::Printer* printer, const FieldDescriptor* field);
+
 enum JavaType {
   JAVATYPE_INT,
   JAVATYPE_LONG,
@@ -129,10 +133,12 @@
   return GetJavaType(field->type());
 }
 
+string PrimitiveTypeName(JavaType type);
+
 // Get the fully-qualified class name for a boxed primitive type, e.g.
 // "java.lang.Integer" for JAVATYPE_INT.  Returns NULL for enum and message
 // types.
-const char* BoxedPrimitiveTypeName(JavaType type);
+string BoxedPrimitiveTypeName(JavaType type);
 
 string EmptyArrayName(const Params& params, const FieldDescriptor* field);
 
diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc
index c09670a..008bec2 100644
--- a/src/google/protobuf/compiler/javanano/javanano_message.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_message.cc
@@ -54,14 +54,6 @@
 
 namespace {
 
-void PrintFieldComment(io::Printer* printer, const FieldDescriptor* field) {
-  // Print the field's proto-syntax definition as a comment.  We don't want to
-  // print group bodies so we cut off after the first line.
-  string def = field->DebugString();
-  printer->Print("// $def$\n",
-    "def", def.substr(0, def.find_first_of('\n')));
-}
-
 struct FieldOrderingByNumber {
   inline bool operator()(const FieldDescriptor* a,
                          const FieldDescriptor* b) const {
@@ -82,13 +74,6 @@
   return fields;
 }
 
-// Get an identifier that uniquely identifies this type within the file.
-// This is used to declare static variables related to this type at the
-// outermost file scope.
-string UniqueFileScopeIdentifier(const Descriptor* descriptor) {
-  return "static_" + StringReplace(descriptor->full_name(), ".", "_", true);
-}
-
 }  // namespace
 
 // ===================================================================
@@ -149,7 +134,8 @@
   }
   if (params_.store_unknown_fields()) {
     printer->Print(
-      "    com.google.protobuf.nano.ExtendableMessageNano {\n");
+      "    com.google.protobuf.nano.ExtendableMessageNano<$classname$> {\n",
+      "classname", descriptor_->name());
   } else {
     printer->Print(
       "    com.google.protobuf.nano.MessageNano {\n");
@@ -285,22 +271,20 @@
 
 void MessageGenerator::
 GenerateMessageSerializationMethods(io::Printer* printer) {
+  // Rely on the parent implementations of writeTo() and getSerializedSize()
+  // if there are no fields to serialize in this message.
+  if (descriptor_->field_count() == 0) {
+    return;
+  }
+
   scoped_array<const FieldDescriptor*> sorted_fields(
     SortFieldsByNumber(descriptor_));
 
-  // writeTo only throws an exception if it contains one or more fields to write
-  if (descriptor_->field_count() > 0 || params_.store_unknown_fields()) {
-    printer->Print(
-      "\n"
-      "@Override\n"
-      "public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output)\n"
-      "    throws java.io.IOException {\n");
-  } else {
-    printer->Print(
-      "\n"
-      "@Override\n"
-      "public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output) {\n");
-  }
+  printer->Print(
+    "\n"
+    "@Override\n"
+    "public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output)\n"
+    "    throws java.io.IOException {\n");
   printer->Indent();
 
   // Output the fields in sorted order
@@ -308,36 +292,31 @@
     GenerateSerializeOneField(printer, sorted_fields[i]);
   }
 
-  // Write unknown fields.
-  if (params_.store_unknown_fields()) {
-    printer->Print(
-      "com.google.protobuf.nano.WireFormatNano.writeUnknownFields(\n"
-      "    unknownFieldData, output);\n");
-  }
+  // The parent implementation will write any unknown fields if necessary.
+  printer->Print(
+    "super.writeTo(output);\n");
 
   printer->Outdent();
   printer->Print("}\n");
 
-  // Rely on the parent implementation of getSerializedSize if there are no fields to
-  // serialize in this MessageNano.
-  if (descriptor_->field_count() != 0) {
-    printer->Print(
-      "\n"
-      "@Override\n"
-      "public int getSerializedSize() {\n"
-      "  int size = super.getSerializedSize();\n");
-    printer->Indent();
+  // The parent implementation will get the serialized size for unknown
+  // fields if necessary.
+  printer->Print(
+    "\n"
+    "@Override\n"
+    "public int getSerializedSize() {\n"
+    "  int size = super.getSerializedSize();\n");
+  printer->Indent();
 
-    for (int i = 0; i < descriptor_->field_count(); i++) {
-      field_generators_.get(sorted_fields[i]).GenerateSerializedSizeCode(printer);
-    }
-
-    printer->Outdent();
-    printer->Print(
-      "  cachedSize = size;\n"
-      "  return size;\n"
-      "}\n");
+  for (int i = 0; i < descriptor_->field_count(); i++) {
+    field_generators_.get(sorted_fields[i]).GenerateSerializedSizeCode(printer);
   }
+
+  printer->Outdent();
+  printer->Print(
+    "  cachedSize = size;\n"
+    "  return size;\n"
+    "}\n");
 }
 
 void MessageGenerator::GenerateMergeFromMethods(io::Printer* printer) {
@@ -371,12 +350,7 @@
   printer->Indent();
   if (params_.store_unknown_fields()) {
     printer->Print(
-        "if (unknownFieldData == null) {\n"
-        "  unknownFieldData =\n"
-        "      new java.util.ArrayList<com.google.protobuf.nano.UnknownFieldData>();\n"
-        "}\n"
-        "if (!com.google.protobuf.nano.WireFormatNano.storeUnknownField(\n"
-        "    unknownFieldData, input, tag)) {\n"
+        "if (!storeUnknownField(input, tag)) {\n"
         "  return this;\n"
         "}\n");
   } else {
diff --git a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
index e044e89..a3bc3a8 100644
--- a/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
+++ b/src/google/protobuf/compiler/javanano/javanano_primitive_field.cc
@@ -54,26 +54,6 @@
 
 namespace {
 
-const char* PrimitiveTypeName(JavaType type) {
-  switch (type) {
-    case JAVATYPE_INT    : return "int";
-    case JAVATYPE_LONG   : return "long";
-    case JAVATYPE_FLOAT  : return "float";
-    case JAVATYPE_DOUBLE : return "double";
-    case JAVATYPE_BOOLEAN: return "boolean";
-    case JAVATYPE_STRING : return "java.lang.String";
-    case JAVATYPE_BYTES  : return "byte[]";
-    case JAVATYPE_ENUM   : return NULL;
-    case JAVATYPE_MESSAGE: return NULL;
-
-    // No default because we want the compiler to complain if any new
-    // JavaTypes are added.
-  }
-
-  GOOGLE_LOG(FATAL) << "Can't get here.";
-  return NULL;
-}
-
 bool IsReferenceType(JavaType type) {
   switch (type) {
     case JAVATYPE_INT    : return false;
diff --git a/src/google/protobuf/unittest_extension_nano.proto b/src/google/protobuf/unittest_extension_nano.proto
index 104cfa7..0a775f4 100644
--- a/src/google/protobuf/unittest_extension_nano.proto
+++ b/src/google/protobuf/unittest_extension_nano.proto
@@ -18,31 +18,14 @@
   optional bool value = 2;
 }
 
-extend ExtendableMessage {
-  optional string some_string = 10;
-  optional int32 some_int = 11;
-  optional int64 some_long = 12;
-  optional float some_float = 13;
-  optional double some_double = 14;
-  optional bool some_bool = 15;
-  optional AnEnum some_enum = 16;
-  optional AnotherMessage some_message = 17;
-  repeated string some_repeated_string = 18;
-  repeated int32 some_repeated_int = 19;
-  repeated int64 some_repeated_long = 20;
-  repeated float some_repeated_float = 21;
-  repeated double some_repeated_double = 22;
-  repeated bool some_repeated_bool = 23;
-  repeated AnEnum some_repeated_enum = 24;
-  repeated AnotherMessage some_repeated_message = 25;
-}
-
 message ContainerMessage {
   extend ExtendableMessage {
     optional bool another_thing = 100;
   }
 }
 
+// For testNanoOptionalGroupWithUnknownFieldsEnabled;
+// not part of the extensions tests.
 message MessageWithGroup {
   optional group Group = 1 {
     optional int32 a = 2;
diff --git a/src/google/protobuf/unittest_extension_packed_nano.proto b/src/google/protobuf/unittest_extension_packed_nano.proto
new file mode 100644
index 0000000..3586de7
--- /dev/null
+++ b/src/google/protobuf/unittest_extension_packed_nano.proto
@@ -0,0 +1,29 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.nano";
+
+import "google/protobuf/unittest_extension_nano.proto";
+
+// Must be compiled separately due to extension number reuse.
+// The reuse is deliberate, for testing wire compatibility.
+
+message PackedExtensions {
+  extend ExtendableMessage {
+    repeated int32    packed_int32    = 10 [ packed = true ];
+    repeated uint32   packed_uint32   = 11 [ packed = true ];
+    repeated sint32   packed_sint32   = 12 [ packed = true ];
+    repeated int64    packed_int64    = 13 [ packed = true ];
+    repeated uint64   packed_uint64   = 14 [ packed = true ];
+    repeated sint64   packed_sint64   = 15 [ packed = true ];
+    repeated fixed32  packed_fixed32  = 16 [ packed = true ];
+    repeated sfixed32 packed_sfixed32 = 17 [ packed = true ];
+    repeated fixed64  packed_fixed64  = 18 [ packed = true ];
+    repeated sfixed64 packed_sfixed64 = 19 [ packed = true ];
+    repeated bool     packed_bool     = 20 [ packed = true ];
+    repeated float    packed_float    = 21 [ packed = true ];
+    repeated double   packed_double   = 22 [ packed = true ];
+    repeated AnEnum   packed_enum     = 23 [ packed = true ];
+    // Non-packable types omitted.
+  }
+}
diff --git a/src/google/protobuf/unittest_extension_repeated_nano.proto b/src/google/protobuf/unittest_extension_repeated_nano.proto
new file mode 100644
index 0000000..546c2df
--- /dev/null
+++ b/src/google/protobuf/unittest_extension_repeated_nano.proto
@@ -0,0 +1,34 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.nano";
+
+import "google/protobuf/unittest_extension_nano.proto";
+
+// Must be compiled separately due to extension number reuse.
+// The reuse is deliberate, for testing wire compatibility.
+
+message RepeatedExtensions {
+  extend ExtendableMessage {
+    repeated int32          repeated_int32    = 10;
+    repeated uint32         repeated_uint32   = 11;
+    repeated sint32         repeated_sint32   = 12;
+    repeated int64          repeated_int64    = 13;
+    repeated uint64         repeated_uint64   = 14;
+    repeated sint64         repeated_sint64   = 15;
+    repeated fixed32        repeated_fixed32  = 16;
+    repeated sfixed32       repeated_sfixed32 = 17;
+    repeated fixed64        repeated_fixed64  = 18;
+    repeated sfixed64       repeated_sfixed64 = 19;
+    repeated bool           repeated_bool     = 20;
+    repeated float          repeated_float    = 21;
+    repeated double         repeated_double   = 22;
+    repeated AnEnum         repeated_enum     = 23;
+    repeated string         repeated_string   = 24;
+    repeated bytes          repeated_bytes    = 25;
+    repeated AnotherMessage repeated_message  = 26;
+    repeated group          RepeatedGroup     = 27 {
+      optional int32 a = 1;
+    }
+  }
+}
diff --git a/src/google/protobuf/unittest_extension_singular_nano.proto b/src/google/protobuf/unittest_extension_singular_nano.proto
new file mode 100644
index 0000000..35d9e6e
--- /dev/null
+++ b/src/google/protobuf/unittest_extension_singular_nano.proto
@@ -0,0 +1,34 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.google.protobuf.nano";
+
+import "google/protobuf/unittest_extension_nano.proto";
+
+// Must be compiled separately due to extension number reuse.
+// The reuse is deliberate, for testing wire compatibility.
+
+message SingularExtensions {
+  extend ExtendableMessage {
+    optional int32          some_int32    = 10;
+    optional uint32         some_uint32   = 11;
+    optional sint32         some_sint32   = 12;
+    optional int64          some_int64    = 13;
+    optional uint64         some_uint64   = 14;
+    optional sint64         some_sint64   = 15;
+    optional fixed32        some_fixed32  = 16;
+    optional sfixed32       some_sfixed32 = 17;
+    optional fixed64        some_fixed64  = 18;
+    optional sfixed64       some_sfixed64 = 19;
+    optional bool           some_bool     = 20;
+    optional float          some_float    = 21;
+    optional double         some_double   = 22;
+    optional AnEnum         some_enum     = 23;
+    optional string         some_string   = 24;
+    optional bytes          some_bytes    = 25;
+    optional AnotherMessage some_message  = 26;
+    optional group          SomeGroup     = 27 {
+      optional int32 a = 1;
+    }
+  }
+}