ソースを参照

am 0a1429cc: Merge "Nano support for extensions and unknown fields."

* commit '0a1429cc5ee4865550bcdddfae681a30cef58a5a':
  Nano support for extensions and unknown fields.
Wink Saville 12 年 前
コミット
50acfdac08

+ 1 - 0
Android.mk

@@ -108,6 +108,7 @@ COMPILER_SRC_FILES :=  \
     src/google/protobuf/compiler/javamicro/javamicro_primitive_field.cc \
     src/google/protobuf/compiler/javanano/javanano_enum.cc \
     src/google/protobuf/compiler/javanano/javanano_enum_field.cc \
+    src/google/protobuf/compiler/javanano/javanano_extension.cc \
     src/google/protobuf/compiler/javanano/javanano_field.cc \
     src/google/protobuf/compiler/javanano/javanano_file.cc \
     src/google/protobuf/compiler/javanano/javanano_generator.cc \

+ 17 - 0
java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java

@@ -540,6 +540,23 @@ public final class CodedInputByteBufferNano {
     return bufferPos - bufferStart;
   }
 
+  /**
+   * Retrieves a subset of data in the buffer. The returned array is not backed by the original
+   * buffer array.
+   *
+   * @param offset the position (relative to the buffer start position) to start at.
+   * @param length the number of bytes to retrieve.
+   */
+  public byte[] getData(int offset, int length) {
+    if (length == 0) {
+      return WireFormatNano.EMPTY_BYTES;
+    }
+    byte[] copy = new byte[length];
+    int start = bufferStart + offset;
+    System.arraycopy(buffer, start, copy, 0, length);
+    return copy;
+  }
+
   /**
    * Rewind to previous position. Cannot go forward.
    */

+ 114 - 0
java/src/main/java/com/google/protobuf/nano/Extension.java

@@ -0,0 +1,114 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2013 Google Inc.  All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf.nano;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * Represents an extension.
+ *
+ * @author bduff@google.com (Brian Duff)
+ * @param <T> the type of the extension.
+ */
+public class Extension<T> {
+  public final int fieldNumber;
+  public boolean isRepeatedField;
+  public Class<T> fieldType;
+  public Class<T> listType;
+
+  private Extension(int fieldNumber, TypeLiteral<T> type) {
+    this.fieldNumber = fieldNumber;
+    isRepeatedField = type.isList();
+    fieldType = type.getTargetClass();
+    listType = isRepeatedField ? type.getListType() : null;
+  }
+
+  /**
+   * 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);
+  }
+
+  /**
+   * 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];
+    }
+
+    /**
+     * If the generic type is a list, returns {@code true}.
+     */
+    private boolean isList() {
+      return type instanceof ParameterizedType;
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<T> getListType() {
+      return (Class<T>) ((ParameterizedType) type).getRawType();
+    }
+
+    /**
+     * If the generic type is a list, returns the type of element in the list. Otherwise,
+     * returns the actual type.
+     */
+    @SuppressWarnings("unchecked")
+    private Class<T> getTargetClass() {
+      if (isList()) {
+        return (Class<T>) ((ParameterizedType) type).getActualTypeArguments()[0];
+      }
+      return (Class<T>) type;
+    }
+  }
+}

+ 1 - 1
java/src/main/java/com/google/protobuf/nano/MessageNano.java

@@ -93,7 +93,7 @@ public abstract class MessageNano {
             output.checkNoSpaceLeft();
         } catch (IOException e) {
             throw new RuntimeException("Serializing to a byte array threw an IOException "
-                    + "(should never happen).");
+                    + "(should never happen).", e);
         }
     }
 

+ 47 - 0
java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java

@@ -0,0 +1,47 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2013 Google Inc.  All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf.nano;
+
+/**
+ * Stores unknown fields. These might be extensions or fields that the generated API doesn't
+ * know about yet.
+ *
+ * @author bduff@google.com (Brian Duff)
+ */
+public final class UnknownFieldData {
+  final int tag;
+  final byte[] bytes;
+
+  UnknownFieldData(int tag, byte[] bytes) {
+    this.tag = tag;
+    this.bytes = bytes;
+  }
+}

+ 223 - 2
java/src/main/java/com/google/protobuf/nano/WireFormatNano.java

@@ -31,6 +31,9 @@
 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
@@ -97,8 +100,12 @@ public final class WireFormatNano {
   public static final byte[] EMPTY_BYTES = {};
 
   /**
-   * Called by subclasses to parse an unknown field.
-   * @return {@code true} unless the tag is an end-group tag.
+   * Parses an unknown field. This implementation skips the field.
+   *
+   * <p>Generated messages will call this for unknown fields if the store_unknown_fields
+   * option is off.
+   *
+   * @return {@literal true} unless the tag is an end-group tag.
    */
   public static boolean parseUnknownField(
       final CodedInputByteBufferNano input,
@@ -106,6 +113,30 @@ public final class WireFormatNano {
     return input.skipField(tag);
   }
 
+  /**
+   * 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.
+   *
+   * @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();
+    boolean skip = input.skipField(tag);
+    int endPos = input.getPosition();
+    byte[] bytes = input.getData(startPos, endPos - startPos);
+    data.add(new UnknownFieldData(tag, bytes));
+    return skip;
+  }
+
   /**
    * 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
@@ -135,4 +166,194 @@ public final class WireFormatNano {
     input.rewindToPosition(startPos);
     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);
+    }
+  }
 }

+ 95 - 1
java/src/test/java/com/google/protobuf/NanoTest.java

@@ -30,6 +30,9 @@
 
 package com.google.protobuf;
 
+import com.google.protobuf.nano.CodedInputByteBufferNano;
+import com.google.protobuf.nano.Extensions;
+import com.google.protobuf.nano.Extensions.AnotherMessage;
 import com.google.protobuf.nano.InternalNano;
 import com.google.protobuf.nano.MessageNano;
 import com.google.protobuf.nano.NanoOuterClass;
@@ -37,10 +40,12 @@ import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
 import com.google.protobuf.nano.RecursiveMessageNano;
 import com.google.protobuf.nano.SimpleMessageNano;
 import com.google.protobuf.nano.UnittestImportNano;
-import com.google.protobuf.nano.CodedInputByteBufferNano;
 
 import junit.framework.TestCase;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Test nano runtime.
  *
@@ -2155,4 +2160,93 @@ public class NanoTest extends TestCase {
     assertTrue(protoPrint.contains("  default_int32: 41"));
     assertTrue(protoPrint.contains("  default_string: \"hello\""));
   }
+
+  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));
+
+    byte[] data = MessageNano.toByteArray(message);
+
+    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);
+  }
+
+  public void testUnknownFields() throws Exception {
+    // Check that we roundtrip (serialize and deserialize) unrecognized fields.
+    AnotherMessage message = new AnotherMessage();
+    message.string = "Hello World";
+    message.value = false;
+
+    byte[] bytes = MessageNano.toByteArray(message);
+    int extraFieldSize = CodedOutputStream.computeStringSize(1001, "This is an unknown field");
+    byte[] newBytes = new byte[bytes.length + extraFieldSize];
+    System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
+    CodedOutputStream.newInstance(newBytes, bytes.length, extraFieldSize).writeString(1001,
+        "This is an unknown field");
+
+    // Deserialize with an unknown field.
+    AnotherMessage deserialized = AnotherMessage.parseFrom(newBytes);
+    byte[] serialized = MessageNano.toByteArray(deserialized);
+
+    assertEquals(newBytes.length, serialized.length);
+
+    // Clear, and make sure it clears everything.
+    deserialized.clear();
+    assertEquals(0, MessageNano.toByteArray(deserialized).length);
+  }
+
+  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;
+  }
 }

+ 96 - 0
src/google/protobuf/compiler/javanano/javanano_extension.cc

@@ -0,0 +1,96 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: bduff@google.com (Brian Duff)
+
+#include <google/protobuf/compiler/javanano/javanano_extension.h>
+#include <google/protobuf/compiler/javanano/javanano_helpers.h>
+#include <google/protobuf/io/printer.h>
+#include <google/protobuf/stubs/strutil.h>
+#include <google/protobuf/wire_format.h>
+
+namespace google {
+namespace protobuf {
+namespace compiler {
+namespace javanano {
+
+using internal::WireFormat;
+
+void SetVariables(const FieldDescriptor* descriptor, const Params params,
+                  map<string, string>* variables) {
+  (*variables)["name"] = UnderscoresToCamelCase(descriptor);
+  (*variables)["number"] = SimpleItoa(descriptor->number());
+  (*variables)["extends"] = ClassName(params, descriptor->containing_type());
+
+  string 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;
+  }
+  (*variables)["type"] = type;
+}
+
+ExtensionGenerator::
+ExtensionGenerator(const FieldDescriptor* descriptor, const Params& params)
+  : params_(params), descriptor_(descriptor) {
+  SetVariables(descriptor, params, &variables_);
+}
+
+ExtensionGenerator::~ExtensionGenerator() {}
+
+void ExtensionGenerator::Generate(io::Printer* printer) const {
+  if (descriptor_->is_repeated()) {
+    printer->Print(variables_,
+      "// 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_,
+      "// 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");
+  }
+}
+
+}  // namespace javanano
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+

+ 74 - 0
src/google/protobuf/compiler/javanano/javanano_extension.h

@@ -0,0 +1,74 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// http://code.google.com/p/protobuf/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Author: bduff@google.com (Brian Duff)
+//  Based on original Protocol Buffers design by
+//  Sanjay Ghemawat, Jeff Dean, and others.
+
+#ifndef GOOGLE_PROTOBUF_COMPILER_JAVA_EXTENSION_H_
+#define GOOGLE_PROTOBUF_COMPILER_JAVA_EXTENSION_H_
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/compiler/javanano/javanano_params.h>
+#include <google/protobuf/descriptor.pb.h>
+
+
+namespace google {
+namespace protobuf {
+  namespace io {
+    class Printer;             // printer.h
+  }
+}
+
+namespace protobuf {
+namespace compiler {
+namespace javanano {
+
+class ExtensionGenerator {
+ public:
+  explicit ExtensionGenerator(const FieldDescriptor* descriptor, const Params& params);
+  ~ExtensionGenerator();
+
+  void Generate(io::Printer* printer) const;
+
+ private:
+  const Params& params_;
+  const FieldDescriptor* descriptor_;
+  map<string, string> variables_;
+
+  GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ExtensionGenerator);
+};
+
+}  // namespace javanano
+}  // namespace compiler
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_COMPILER_JAVA_EXTENSION_H_

+ 9 - 2
src/google/protobuf/compiler/javanano/javanano_file.cc

@@ -34,6 +34,7 @@
 
 #include <google/protobuf/compiler/javanano/javanano_file.h>
 #include <google/protobuf/compiler/javanano/javanano_enum.h>
+#include <google/protobuf/compiler/javanano/javanano_extension.h>
 #include <google/protobuf/compiler/javanano/javanano_helpers.h>
 #include <google/protobuf/compiler/javanano/javanano_message.h>
 #include <google/protobuf/compiler/code_generator.h>
@@ -94,10 +95,11 @@ bool FileGenerator::Validate(string* error) {
   // Check for extensions
   FileDescriptorProto file_proto;
   file_->CopyTo(&file_proto);
-  if (UsesExtensions(file_proto)) {
+  if (UsesExtensions(file_proto) && !params_.store_unknown_fields()) {
     error->assign(file_->name());
     error->append(
-      ": Java NANO_RUNTIME does not support extensions\"");
+        ": Java NANO_RUNTIME only supports extensions when the "
+        "'store_unknown_fields' generator option is 'true'.");
     return false;
   }
 
@@ -179,6 +181,11 @@ void FileGenerator::Generate(io::Printer* printer) {
 
   // -----------------------------------------------------------------
 
+  // Extensions.
+  for (int i = 0; i < file_->extension_count(); i++) {
+    ExtensionGenerator(file_->extension(i), params_).Generate(printer);
+  }
+
   if (!params_.java_multiple_files()) {
     for (int i = 0; i < file_->enum_type_count(); i++) {
       EnumGenerator(file_->enum_type(i), params_).Generate(printer);

+ 2 - 0
src/google/protobuf/compiler/javanano/javanano_generator.cc

@@ -115,6 +115,8 @@ bool JavaNanoGenerator::Generate(const FileDescriptor* file,
           return false;
         }
         params.set_java_outer_classname(parts[0], parts[1]);
+    } else if (options[i].first == "store_unknown_fields") {
+      params.set_store_unknown_fields(options[i].second == "true");
     } else if (options[i].first == "java_multiple_files") {
         params.set_java_multiple_files(options[i].second == "true");
     } else {

+ 74 - 19
src/google/protobuf/compiler/javanano/javanano_message.cc

@@ -36,6 +36,7 @@
 #include <google/protobuf/stubs/hash.h>
 #include <google/protobuf/compiler/javanano/javanano_message.h>
 #include <google/protobuf/compiler/javanano/javanano_enum.h>
+#include <google/protobuf/compiler/javanano/javanano_extension.h>
 #include <google/protobuf/compiler/javanano/javanano_helpers.h>
 #include <google/protobuf/stubs/strutil.h>
 #include <google/protobuf/io/printer.h>
@@ -117,10 +118,6 @@ void MessageGenerator::GenerateStaticVariableInitializers(
     MessageGenerator(descriptor_->nested_type(i), params_)
       .GenerateStaticVariableInitializers(printer);
   }
-
-  if (descriptor_->extension_count() != 0) {
-    GOOGLE_LOG(FATAL) << "Extensions not supported in NANO_RUNTIME\n";
-  }
 }
 
 void MessageGenerator::Generate(io::Printer* printer) {
@@ -135,9 +132,10 @@ void MessageGenerator::Generate(io::Printer* printer) {
   GOOGLE_LOG(INFO) << "has_java_outer_classname()=" << params_.has_java_outer_classname(file_->name());
 #endif
 
-  if ((descriptor_->extension_count() != 0)
-      || (descriptor_->extension_range_count() != 0)) {
-    GOOGLE_LOG(FATAL) << "Extensions not supported in NANO_RUNTIME\n";
+  if (!params_.store_unknown_fields() &&
+      (descriptor_->extension_count() != 0 || descriptor_->extension_range_count() != 0)) {
+    GOOGLE_LOG(FATAL) << "Extensions are only supported in NANO_RUNTIME if the "
+        "'store_unknown_fields' generator option is 'true'\n";
   }
 
   // Note: Fields (which will be emitted in the loop, below) may have the same names as fields in
@@ -156,7 +154,17 @@ void MessageGenerator::Generate(io::Printer* printer) {
     "\n",
     "classname", descriptor_->name());
 
+  if (params_.store_unknown_fields()) {
+    printer->Print(
+        "private java.util.List<com.google.protobuf.nano.UnknownFieldData>\n"
+        "    unknownFieldData;\n");
+  }
+
   // Nested types and extensions
+  for (int i = 0; i < descriptor_->extension_count(); i++) {
+    ExtensionGenerator(descriptor_->extension(i), params_).Generate(printer);
+  }
+
   for (int i = 0; i < descriptor_->enum_type_count(); i++) {
     EnumGenerator(descriptor_->enum_type(i), params_).Generate(printer);
   }
@@ -173,6 +181,24 @@ void MessageGenerator::Generate(io::Printer* printer) {
   }
 
   GenerateClear(printer);
+
+  // If we have an extension range, generate accessors for extensions.
+  if (params_.store_unknown_fields()
+      && descriptor_->extension_range_count() > 0) {
+    printer->Print(
+      "public <T> T getExtension(com.google.protobuf.nano.Extension<T> extension) {\n"
+      "  return com.google.protobuf.nano.WireFormatNano.getExtension(\n"
+      "      extension, unknownFieldData);\n"
+      "}\n\n"
+      "public <T> void setExtension(com.google.protobuf.nano.Extension<T> extension, T value) {\n"
+      "  if (unknownFieldData == null) {\n"
+      "    unknownFieldData = \n"
+      "        new java.util.ArrayList<com.google.protobuf.nano.UnknownFieldData>();\n"
+      "  }\n"
+      "  com.google.protobuf.nano.WireFormatNano.setExtension(\n"
+      "      extension, value, unknownFieldData);\n"
+      "}\n\n");
+  }
   GenerateMessageSerializationMethods(printer);
   GenerateMergeFromMethods(printer);
   GenerateParseFromMethods(printer);
@@ -188,12 +214,8 @@ GenerateMessageSerializationMethods(io::Printer* printer) {
   scoped_array<const FieldDescriptor*> sorted_fields(
     SortFieldsByNumber(descriptor_));
 
-  if (descriptor_->extension_range_count() != 0) {
-    GOOGLE_LOG(FATAL) << "Extensions not supported in NANO_RUNTIME\n";
-  }
-
   // writeTo only throws an exception if it contains one or more fields to write
-  if (descriptor_->field_count() > 0) {
+  if (descriptor_->field_count() > 0 || params_.store_unknown_fields()) {
     printer->Print(
       "@Override\n"
       "public void writeTo(com.google.protobuf.nano.CodedOutputByteBufferNano output)\n"
@@ -207,7 +229,14 @@ GenerateMessageSerializationMethods(io::Printer* printer) {
 
   // Output the fields in sorted order
   for (int i = 0; i < descriptor_->field_count(); i++) {
-      GenerateSerializeOneField(printer, sorted_fields[i]);
+    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");
   }
 
   printer->Outdent();
@@ -233,6 +262,11 @@ GenerateMessageSerializationMethods(io::Printer* printer) {
     field_generators_.get(sorted_fields[i]).GenerateSerializedSizeCode(printer);
   }
 
+  if (params_.store_unknown_fields()) {
+    printer->Print(
+      "size += com.google.protobuf.nano.WireFormatNano.computeWireSize(unknownFieldData);\n");
+  }
+
   printer->Outdent();
   printer->Print(
     "  cachedSize = size;\n"
@@ -266,12 +300,28 @@ void MessageGenerator::GenerateMergeFromMethods(io::Printer* printer) {
   printer->Print(
     "case 0:\n"          // zero signals EOF / limit reached
     "  return this;\n"
-    "default: {\n"
-    "  if (!com.google.protobuf.nano.WireFormatNano.parseUnknownField(input, tag)) {\n"
-    "    return this;\n"   // it's an endgroup tag
-    "  }\n"
-    "  break;\n"
-    "}\n");
+    "default: {\n");
+
+  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(unknownFieldData, \n"
+        "    input, tag)) {\n"
+        "  return this;\n"
+        "}\n");
+  } else {
+    printer->Print(
+        "if (!com.google.protobuf.nano.WireFormatNano.parseUnknownField(input, tag)) {\n"
+        "  return this;\n"   // it's an endgroup tag
+        "}\n");
+  }
+  printer->Print("break;\n");
+  printer->Outdent();
+  printer->Print("}\n");
 
   for (int i = 0; i < descriptor_->field_count(); i++) {
     const FieldDescriptor* field = sorted_fields[i];
@@ -356,6 +406,11 @@ void MessageGenerator::GenerateClear(io::Printer* printer) {
     }
   }
 
+  // Clear unknown fields.
+  if (params_.store_unknown_fields()) {
+    printer->Print("unknownFieldData = null;\n");
+  }
+
   printer->Outdent();
   printer->Print(
     "  cachedSize = -1;\n"

+ 9 - 0
src/google/protobuf/compiler/javanano/javanano_params.h

@@ -49,6 +49,7 @@ class Params {
   string empty_;
   string base_name_;
   bool java_multiple_files_;
+  bool store_unknown_fields_;
   NameMap java_packages_;
   NameMap java_outer_classnames_;
 
@@ -56,6 +57,7 @@ class Params {
   Params(const string & base_name) :
     empty_(""),
     base_name_(base_name),
+    store_unknown_fields_(false),
     java_multiple_files_(false) {
   }
 
@@ -107,6 +109,13 @@ class Params {
     return java_outer_classnames_;
   }
 
+  void set_store_unknown_fields(bool value) {
+    store_unknown_fields_ = value;
+  }
+  bool store_unknown_fields() const {
+    return store_unknown_fields_;
+  }
+
   void set_java_multiple_files(bool value) {
     java_multiple_files_ = value;
   }

+ 44 - 0
src/google/protobuf/unittest_extension_nano.proto

@@ -0,0 +1,44 @@
+syntax = "proto2";
+
+option java_outer_classname = "Extensions";
+option java_package = "com.google.protobuf.nano";
+
+message ExtendableMessage {
+  optional int32 field = 1;
+  extensions 10 to max;
+}
+
+enum AnEnum {
+  FIRST_VALUE = 1;
+  SECOND_VALUE = 2;
+}
+
+message AnotherMessage {
+  optional string string = 1;
+  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;
+  }
+}