diff options
author | Brian Duff <bduff@google.com> | 2013-06-19 13:17:43 -0700 |
---|---|---|
committer | Brian Duff <bduff@google.com> | 2013-06-24 16:02:12 -0700 |
commit | 0e055f079f53b07de3705838a7b4742ce56839f8 (patch) | |
tree | d60a02039cea8773f7ba76efd0c7fd6f23768d70 /java | |
parent | 9459b5c45b0ad33bea9376c2afb66cf93da610ac (diff) | |
download | external_protobuf-0e055f079f53b07de3705838a7b4742ce56839f8.zip external_protobuf-0e055f079f53b07de3705838a7b4742ce56839f8.tar.gz external_protobuf-0e055f079f53b07de3705838a7b4742ce56839f8.tar.bz2 |
Nano support for extensions and unknown fields.
You can use the processor option store_unknown_fields to switch
this support on:
aprotoc --javanano_out=store_unknown_fields=true:/tmp/out
A separate option for extensions isn't required. Support
for unknown fields must be turned on to allow storing and
retrieving extensions, because they are just stored as
unknown fields. If unknown fields are switched on, extension
related code will be generated when a proto message includes
an extension range, or an extension is encountered.
By default, store_unknown_fields is false. No additional
code is generated, and the generator will error out if protos
contain extension ranges or extensions.
Change-Id: I1e034c9e8f3305612953f72438189a7da6ed2167
Diffstat (limited to 'java')
7 files changed, 503 insertions, 4 deletions
diff --git a/java/pom.xml b/java/pom.xml index 46fc764..0b9f6e4 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -138,6 +138,12 @@ <arg value="../src/google/protobuf/unittest_import_nano.proto" /> <arg value="../src/google/protobuf/unittest_enum_multiplejava_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_nano.proto" /> + </exec> </tasks> <testSourceRoot>target/generated-test-sources</testSourceRoot> <!--testSourceRoot>target/generated-test-sources/opt-space</testSourceRoot--> 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 ed38788..c5fea5a 100644 --- a/java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java +++ b/java/src/main/java/com/google/protobuf/nano/CodedInputByteBufferNano.java @@ -541,6 +541,23 @@ public final class CodedInputByteBufferNano { } /** + * 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. */ public void rewindToPosition(int position) { 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 0000000..4512b01 --- /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 <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<List<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<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; + } + } +} 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 d6c1e9a..5c1eb2f 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 0000000..0db2a83 --- /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 8fa3636..c901e59 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. + * + * <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, @@ -107,6 +114,30 @@ public final class WireFormatNano { } /** + * 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 * repeated field (but with extra allocations). @@ -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); + } + } } diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index da17a9e..38fafb9 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 <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; + } } |