diff --git a/Android.mk b/Android.mk index 0aeb7b7cdf..53f9836070 100644 --- a/Android.mk +++ b/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 \ diff --git a/java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java b/java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java index ed38788295..c5fea5ae58 100644 --- a/java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java +++ b/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. */ diff --git a/java/src/main/java/com/google/protobuf/nano/Extension.java b/java/src/main/java/com/google/protobuf/nano/Extension.java new file mode 100644 index 0000000000..4512b01a5f --- /dev/null +++ b/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 the type of the extension. + */ +public class Extension { + public final int fieldNumber; + public boolean isRepeatedField; + public Class fieldType; + public Class listType; + + private Extension(int fieldNumber, TypeLiteral 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 Extension create(int fieldNumber, TypeLiteral type) { + return new Extension(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 Extension> createRepeated(int fieldNumber, TypeLiteral> type) { + return new Extension>(fieldNumber, type); + } + + /** + * Represents a generic type literal. We can't typesafely reference a + * Class<List<Foo>>.class in Java, so we use this instead. + * See: http://gafter.blogspot.com/2006/12/super-type-tokens.html + * + *

Somewhat specialized because we only ever have a Foo or a List<Foo>. + */ + public static abstract class TypeLiteral { + 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 getListType() { + return (Class) ((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 getTargetClass() { + if (isList()) { + return (Class) ((ParameterizedType) type).getActualTypeArguments()[0]; + } + return (Class) type; + } + } +} 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 d6c1e9a84e..5c1eb2ff5e 100644 --- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java +++ b/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); } } diff --git a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java new file mode 100644 index 0000000000..0db2a83cf1 --- /dev/null +++ b/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; + } +} 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 8fa3636436..c901e59c92 100644 --- a/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java +++ b/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. + * + *

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. + * + *

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 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 getExtension(Extension extension, List unknownFields) { + if (unknownFields == null) { + return null; + } + List dataForField = new ArrayList(); + for (UnknownFieldData data : unknownFields) { + if (getTagFieldNumber(data.tag) == extension.fieldNumber) { + dataForField.add(data); + } + } + if (dataForField.isEmpty()) { + return null; + } + + if (extension.isRepeatedField) { + List result = new ArrayList(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 readData(Class 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 void setExtension(Extension extension, T value, + List unknownFields) { + // First, remove all unknown fields with this tag. + for (Iterator 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 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 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 da17a9e13f..38fafb9e4c 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/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 List list(T first, T... remaining) { + List list = new ArrayList(); + 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 new file mode 100644 index 0000000000..ea74af9ca6 --- /dev/null +++ b/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 +#include +#include +#include +#include + +namespace google { +namespace protobuf { +namespace compiler { +namespace javanano { + +using internal::WireFormat; + +void SetVariables(const FieldDescriptor* descriptor, const Params params, + map* 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> $name$ = \n" + " com.google.protobuf.nano.Extension.createRepeated($number$,\n" + " new com.google.protobuf.nano.Extension.TypeLiteral>(){});\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 + diff --git a/src/google/protobuf/compiler/javanano/javanano_extension.h b/src/google/protobuf/compiler/javanano/javanano_extension.h new file mode 100644 index 0000000000..c6543ebd19 --- /dev/null +++ b/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 +#include +#include + + +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 variables_; + + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(ExtensionGenerator); +}; + +} // namespace javanano +} // namespace compiler +} // namespace protobuf +} // namespace google + +#endif // GOOGLE_PROTOBUF_COMPILER_JAVA_EXTENSION_H_ diff --git a/src/google/protobuf/compiler/javanano/javanano_file.cc b/src/google/protobuf/compiler/javanano/javanano_file.cc index 2f42fa0e74..6efa2bf327 100644 --- a/src/google/protobuf/compiler/javanano/javanano_file.cc +++ b/src/google/protobuf/compiler/javanano/javanano_file.cc @@ -34,6 +34,7 @@ #include #include +#include #include #include #include @@ -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); diff --git a/src/google/protobuf/compiler/javanano/javanano_generator.cc b/src/google/protobuf/compiler/javanano/javanano_generator.cc index 554f6d4d5d..19a3dccdb2 100644 --- a/src/google/protobuf/compiler/javanano/javanano_generator.cc +++ b/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 { diff --git a/src/google/protobuf/compiler/javanano/javanano_message.cc b/src/google/protobuf/compiler/javanano/javanano_message.cc index f8a4fe7812..27407794ad 100644 --- a/src/google/protobuf/compiler/javanano/javanano_message.cc +++ b/src/google/protobuf/compiler/javanano/javanano_message.cc @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -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\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 getExtension(com.google.protobuf.nano.Extension extension) {\n" + " return com.google.protobuf.nano.WireFormatNano.getExtension(\n" + " extension, unknownFieldData);\n" + "}\n\n" + "public void setExtension(com.google.protobuf.nano.Extension extension, T value) {\n" + " if (unknownFieldData == null) {\n" + " unknownFieldData = \n" + " new java.util.ArrayList();\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 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();\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" diff --git a/src/google/protobuf/compiler/javanano/javanano_params.h b/src/google/protobuf/compiler/javanano/javanano_params.h index f6192eae05..30eedff0e9 100644 --- a/src/google/protobuf/compiler/javanano/javanano_params.h +++ b/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; } diff --git a/src/google/protobuf/unittest_extension_nano.proto b/src/google/protobuf/unittest_extension_nano.proto new file mode 100644 index 0000000000..f2906f0023 --- /dev/null +++ b/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; + } +}