diff options
author | Max Cai <maxtroy@google.com> | 2014-08-21 17:30:42 +0000 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2014-08-21 17:30:42 +0000 |
commit | 2e6d4ae02a955d33a75c0642b8ffead20e11fd9a (patch) | |
tree | ccea2bfb1ab3fbb41e34fb907af20e7544f21c0c | |
parent | cca3823db855ecbcad350332a274f253be7e9662 (diff) | |
parent | f39998c2c5754ec6e2ff5ddfa529a079093b41e9 (diff) | |
download | external_protobuf-2e6d4ae02a955d33a75c0642b8ffead20e11fd9a.zip external_protobuf-2e6d4ae02a955d33a75c0642b8ffead20e11fd9a.tar.gz external_protobuf-2e6d4ae02a955d33a75c0642b8ffead20e11fd9a.tar.bz2 |
am f39998c2: am daf63839: Merge "Keep pointers to extension values."
* commit 'f39998c2c5754ec6e2ff5ddfa529a079093b41e9':
Keep pointers to extension values.
-rw-r--r-- | java/README.txt | 7 | ||||
-rw-r--r-- | java/pom.xml | 6 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java | 71 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/nano/Extension.java | 446 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/nano/FieldArray.java | 273 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/nano/FieldData.java | 173 | ||||
-rw-r--r-- | java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java | 63 | ||||
-rw-r--r-- | java/src/test/java/com/google/protobuf/NanoTest.java | 137 |
8 files changed, 920 insertions, 256 deletions
diff --git a/java/README.txt b/java/README.txt index c693313..4dfef14 100644 --- a/java/README.txt +++ b/java/README.txt @@ -442,9 +442,10 @@ used simultaneously from multiple threads in a read-only manner. In other words, an appropriate synchronization mechanism (such as a ReadWriteLock) must be used to ensure that a message, its ancestors, and descendants are not accessed by any other threads -while the message is being modified. Field reads, getter methods, -toByteArray(...), writeTo(...), getCachedSize(), and -getSerializedSize() are all considered read-only operations. +while the message is being modified. Field reads, getter methods +(but not getExtension(...)), toByteArray(...), writeTo(...), +getCachedSize(), and getSerializedSize() are all considered read-only +operations. IMPORTANT: If you have fields with defaults and opt out of accessors diff --git a/java/pom.xml b/java/pom.xml index 5b49a72..0a29f31 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -152,19 +152,19 @@ <arg value="../src/google/protobuf/unittest_repeated_merge_nano.proto" /> </exec> <exec executable="../src/protoc"> - <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" /> + <arg value="--javanano_out=store_unknown_fields=true,generate_equals=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> <exec executable="../src/protoc"> - <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" /> + <arg value="--javanano_out=store_unknown_fields=true,generate_equals=true:target/generated-test-sources" /> <arg value="--proto_path=../src" /> <arg value="--proto_path=src/test/java" /> <arg value="../src/google/protobuf/unittest_extension_singular_nano.proto" /> </exec> <exec executable="../src/protoc"> - <arg value="--javanano_out=store_unknown_fields=true:target/generated-test-sources" /> + <arg value="--javanano_out=store_unknown_fields=true,generate_equals=true:target/generated-test-sources" /> <arg value="--proto_path=../src" /> <arg value="--proto_path=src/test/java" /> <arg value="../src/google/protobuf/unittest_extension_repeated_nano.proto" /> diff --git a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java index 63c8afc..a0c2731 100644 --- a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java +++ b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java @@ -31,8 +31,6 @@ package com.google.protobuf.nano; import java.io.IOException; -import java.util.ArrayList; -import java.util.List; /** * Base class of those Protocol Buffer messages that need to store unknown fields, @@ -44,27 +42,28 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>> * A container for fields unknown to the message, including extensions. Extension fields can * can be accessed through the {@link #getExtension} and {@link #setExtension} methods. */ - protected List<UnknownFieldData> unknownFieldData; + protected FieldArray unknownFieldData; @Override protected int computeSerializedSize() { int size = 0; - int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size(); - for (int i = 0; i < unknownFieldCount; i++) { - UnknownFieldData unknownField = unknownFieldData.get(i); - size += CodedOutputByteBufferNano.computeRawVarint32Size(unknownField.tag); - size += unknownField.bytes.length; + if (unknownFieldData != null) { + for (int i = 0; i < unknownFieldData.size(); i++) { + FieldData field = unknownFieldData.dataAt(i); + size += field.computeSerializedSize(); + } } return size; } @Override public void writeTo(CodedOutputByteBufferNano output) throws IOException { - int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size(); - for (int i = 0; i < unknownFieldCount; i++) { - UnknownFieldData unknownField = unknownFieldData.get(i); - output.writeRawVarint32(unknownField.tag); - output.writeRawBytes(unknownField.bytes); + if (unknownFieldData == null) { + return; + } + for (int i = 0; i < unknownFieldData.size(); i++) { + FieldData field = unknownFieldData.dataAt(i); + field.writeTo(output); } } @@ -72,14 +71,38 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>> * Gets the value stored in the specified extension of this message. */ public final <T> T getExtension(Extension<M, T> extension) { - return extension.getValueFrom(unknownFieldData); + if (unknownFieldData == null) { + return null; + } + FieldData field = unknownFieldData.get(WireFormatNano.getTagFieldNumber(extension.tag)); + return field == null ? null : field.getValue(extension); } /** * Sets the value of the specified extension of this message. */ public final <T> M setExtension(Extension<M, T> extension, T value) { - unknownFieldData = extension.setValueTo(value, unknownFieldData); + int fieldNumber = WireFormatNano.getTagFieldNumber(extension.tag); + if (value == null) { + if (unknownFieldData != null) { + unknownFieldData.remove(fieldNumber); + if (unknownFieldData.isEmpty()) { + unknownFieldData = null; + } + } + } else { + FieldData field = null; + if (unknownFieldData == null) { + unknownFieldData = new FieldArray(); + } else { + field = unknownFieldData.get(fieldNumber); + } + if (field == null) { + unknownFieldData.put(fieldNumber, new FieldData(extension, value)); + } else { + field.setValue(extension, value); + } + } @SuppressWarnings("unchecked") // Generated code should guarantee type safety M typedThis = (M) this; @@ -106,12 +129,22 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>> if (!input.skipField(tag)) { return false; // This wasn't an unknown field, it's an end-group tag. } - if (unknownFieldData == null) { - unknownFieldData = new ArrayList<UnknownFieldData>(); - } + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); int endPos = input.getPosition(); byte[] bytes = input.getData(startPos, endPos - startPos); - unknownFieldData.add(new UnknownFieldData(tag, bytes)); + UnknownFieldData unknownField = new UnknownFieldData(tag, bytes); + + FieldData field = null; + if (unknownFieldData == null) { + unknownFieldData = new FieldArray(); + } else { + field = unknownFieldData.get(fieldNumber); + } + if (field == null) { + field = new FieldData(); + unknownFieldData.put(fieldNumber, field); + } + field.addUnknownField(unknownField); return true; } } diff --git a/java/src/main/java/com/google/protobuf/nano/Extension.java b/java/src/main/java/com/google/protobuf/nano/Extension.java index dfe4f87..962f66e 100644 --- a/java/src/main/java/com/google/protobuf/nano/Extension.java +++ b/java/src/main/java/com/google/protobuf/nano/Extension.java @@ -152,56 +152,50 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { this.repeated = repeated; } - protected boolean isMatch(int unknownDataTag) { - // This implementation is for message/group extensions. - return unknownDataTag == tag; - } - /** * Returns the value of this extension stored in the given list of unknown fields, or * {@code null} if no unknown fields matches this extension. + * + * @param unknownFields a list of {@link UnknownFieldData}. All of the elements must have a tag + * that matches this Extension's tag. + * */ final T getValueFrom(List<UnknownFieldData> unknownFields) { if (unknownFields == null) { return null; } + return repeated ? getRepeatedValueFrom(unknownFields) : getSingularValueFrom(unknownFields); + } - if (repeated) { - // For repeated extensions, read all matching unknown fields in their original order. - List<Object> resultList = new ArrayList<Object>(); - for (int i = 0; i < unknownFields.size(); i++) { - UnknownFieldData data = unknownFields.get(i); - if (isMatch(data.tag) && data.bytes.length != 0) { - readDataInto(data, resultList); - } - } - - int resultSize = resultList.size(); - if (resultSize == 0) { - return null; + private T getRepeatedValueFrom(List<UnknownFieldData> unknownFields) { + // For repeated extensions, read all matching unknown fields in their original order. + List<Object> resultList = new ArrayList<Object>(); + for (int i = 0; i < unknownFields.size(); i++) { + UnknownFieldData data = unknownFields.get(i); + if (data.bytes.length != 0) { + readDataInto(data, resultList); } + } + int resultSize = resultList.size(); + if (resultSize == 0) { + return null; + } else { T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize)); for (int i = 0; i < resultSize; i++) { Array.set(result, i, resultList.get(i)); } return result; - } else { - // For singular extensions, get the last piece of data stored under this extension. - UnknownFieldData lastData = null; - for (int i = unknownFields.size() - 1; lastData == null && i >= 0; i--) { - UnknownFieldData data = unknownFields.get(i); - if (isMatch(data.tag) && data.bytes.length != 0) { - lastData = data; - } - } - - if (lastData == null) { - return null; - } + } + } - return clazz.cast(readData(CodedInputByteBufferNano.newInstance(lastData.bytes))); + private T getSingularValueFrom(List<UnknownFieldData> unknownFields) { + // For singular extensions, get the last piece of data stored under this extension. + if (unknownFields.isEmpty()) { + return null; } + UnknownFieldData lastData = unknownFields.get(unknownFields.size() - 1); + return clazz.cast(readData(CodedInputByteBufferNano.newInstance(lastData.bytes))); } protected Object readData(CodedInputByteBufferNano input) { @@ -236,61 +230,29 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); } - /** - * Sets the value of this extension to the given list of unknown fields. This removes any - * previously stored data matching this extension. - * - * @param value The value of this extension, or {@code null} to clear this extension from the - * unknown fields. - * @return The same {@code unknownFields} list, or a new list storing the extension value if - * the argument was null. - */ - final List<UnknownFieldData> setValueTo(T value, List<UnknownFieldData> unknownFields) { - if (unknownFields != null) { - // Delete all data matching this extension - for (int i = unknownFields.size() - 1; i >= 0; i--) { - if (isMatch(unknownFields.get(i).tag)) { - unknownFields.remove(i); - } - } - } - - if (value != null) { - if (unknownFields == null) { - unknownFields = new ArrayList<UnknownFieldData>(); - } - if (repeated) { - writeDataInto(value, unknownFields); - } else { - unknownFields.add(writeData(value)); - } + void writeTo(Object value, CodedOutputByteBufferNano output) throws IOException { + if (repeated) { + writeRepeatedData(value, output); + } else { + writeSingularData(value, output); } - - // After deletion or no-op addition (due to 'value' being an array of empty or - // null-only elements), unknownFields may be empty. Discard the ArrayList if so. - return (unknownFields == null || unknownFields.isEmpty()) ? null : unknownFields; } - protected UnknownFieldData writeData(Object value) { + protected void writeSingularData(Object value, CodedOutputByteBufferNano out) { // This implementation is for message/group extensions. - byte[] data; try { + out.writeRawVarint32(tag); switch (type) { case TYPE_GROUP: MessageNano groupValue = (MessageNano) value; int fieldNumber = WireFormatNano.getTagFieldNumber(tag); - data = new byte[CodedOutputByteBufferNano.computeGroupSizeNoTag(groupValue) - + CodedOutputByteBufferNano.computeTagSize(fieldNumber)]; - CodedOutputByteBufferNano out = CodedOutputByteBufferNano.newInstance(data); out.writeGroupNoTag(groupValue); // The endgroup tag must be included in the data payload. out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP); break; case TYPE_MESSAGE: MessageNano messageValue = (MessageNano) value; - data = new byte[ - CodedOutputByteBufferNano.computeMessageSizeNoTag(messageValue)]; - CodedOutputByteBufferNano.newInstance(data).writeMessageNoTag(messageValue); + out.writeMessageNoTag(messageValue); break; default: throw new IllegalArgumentException("Unknown type " + type); @@ -299,18 +261,53 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { // Should not happen throw new IllegalStateException(e); } - return new UnknownFieldData(tag, data); } - protected void writeDataInto(T array, List<UnknownFieldData> unknownFields) { + protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) { + // This implementation is for non-packed extensions. + int arrayLength = Array.getLength(array); + for (int i = 0; i < arrayLength; i++) { + Object element = Array.get(array, i); + if (element != null) { + writeSingularData(element, output); + } + } + } + + int computeSerializedSize(Object value) { + if (repeated) { + return computeRepeatedSerializedSize(value); + } else { + return computeSingularSerializedSize(value); + } + } + + protected int computeRepeatedSerializedSize(Object array) { // This implementation is for non-packed extensions. + int size = 0; int arrayLength = Array.getLength(array); for (int i = 0; i < arrayLength; i++) { Object element = Array.get(array, i); if (element != null) { - unknownFields.add(writeData(element)); + size += computeSingularSerializedSize(Array.get(array, i)); } } + return size; + } + + protected int computeSingularSerializedSize(Object value) { + // This implementation is for message/group extensions. + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + switch (type) { + case TYPE_GROUP: + MessageNano groupValue = (MessageNano) value; + return CodedOutputByteBufferNano.computeGroupSize(fieldNumber, groupValue); + case TYPE_MESSAGE: + MessageNano messageValue = (MessageNano) value; + return CodedOutputByteBufferNano.computeMessageSize(fieldNumber, messageValue); + default: + throw new IllegalArgumentException("Unknown type " + type); + } } /** @@ -339,15 +336,6 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { } @Override - protected boolean isMatch(int unknownDataTag) { - if (repeated) { - return unknownDataTag == nonPackedTag || unknownDataTag == packedTag; - } else { - return unknownDataTag == tag; - } - } - - @Override protected Object readData(CodedInputByteBufferNano input) { try { switch (type) { @@ -398,7 +386,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { if (data.tag == nonPackedTag) { resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); } else { - CodedInputByteBufferNano buffer = CodedInputByteBufferNano.newInstance(data.bytes); + CodedInputByteBufferNano buffer = + CodedInputByteBufferNano.newInstance(data.bytes); try { buffer.pushLimit(buffer.readRawVarint32()); // length limit } catch (IOException e) { @@ -411,105 +400,73 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { } @Override - protected final UnknownFieldData writeData(Object value) { - byte[] data; + protected final void writeSingularData(Object value, CodedOutputByteBufferNano output) { try { + output.writeRawVarint32(tag); switch (type) { case TYPE_DOUBLE: Double doubleValue = (Double) value; - data = new byte[ - CodedOutputByteBufferNano.computeDoubleSizeNoTag(doubleValue)]; - CodedOutputByteBufferNano.newInstance(data).writeDoubleNoTag(doubleValue); + output.writeDoubleNoTag(doubleValue); break; case TYPE_FLOAT: Float floatValue = (Float) value; - data = new byte[ - CodedOutputByteBufferNano.computeFloatSizeNoTag(floatValue)]; - CodedOutputByteBufferNano.newInstance(data).writeFloatNoTag(floatValue); + output.writeFloatNoTag(floatValue); break; case TYPE_INT64: Long int64Value = (Long) value; - data = new byte[ - CodedOutputByteBufferNano.computeInt64SizeNoTag(int64Value)]; - CodedOutputByteBufferNano.newInstance(data).writeInt64NoTag(int64Value); + output.writeInt64NoTag(int64Value); break; case TYPE_UINT64: Long uint64Value = (Long) value; - data = new byte[ - CodedOutputByteBufferNano.computeUInt64SizeNoTag(uint64Value)]; - CodedOutputByteBufferNano.newInstance(data).writeUInt64NoTag(uint64Value); + output.writeUInt64NoTag(uint64Value); break; case TYPE_INT32: Integer int32Value = (Integer) value; - data = new byte[ - CodedOutputByteBufferNano.computeInt32SizeNoTag(int32Value)]; - CodedOutputByteBufferNano.newInstance(data).writeInt32NoTag(int32Value); + output.writeInt32NoTag(int32Value); break; case TYPE_FIXED64: Long fixed64Value = (Long) value; - data = new byte[ - CodedOutputByteBufferNano.computeFixed64SizeNoTag(fixed64Value)]; - CodedOutputByteBufferNano.newInstance(data).writeFixed64NoTag(fixed64Value); + output.writeFixed64NoTag(fixed64Value); break; case TYPE_FIXED32: Integer fixed32Value = (Integer) value; - data = new byte[ - CodedOutputByteBufferNano.computeFixed32SizeNoTag(fixed32Value)]; - CodedOutputByteBufferNano.newInstance(data).writeFixed32NoTag(fixed32Value); + output.writeFixed32NoTag(fixed32Value); break; case TYPE_BOOL: Boolean boolValue = (Boolean) value; - data = new byte[CodedOutputByteBufferNano.computeBoolSizeNoTag(boolValue)]; - CodedOutputByteBufferNano.newInstance(data).writeBoolNoTag(boolValue); + output.writeBoolNoTag(boolValue); break; case TYPE_STRING: String stringValue = (String) value; - data = new byte[ - CodedOutputByteBufferNano.computeStringSizeNoTag(stringValue)]; - CodedOutputByteBufferNano.newInstance(data).writeStringNoTag(stringValue); + output.writeStringNoTag(stringValue); break; case TYPE_BYTES: byte[] bytesValue = (byte[]) value; - data = new byte[ - CodedOutputByteBufferNano.computeBytesSizeNoTag(bytesValue)]; - CodedOutputByteBufferNano.newInstance(data).writeBytesNoTag(bytesValue); + output.writeBytesNoTag(bytesValue); break; case TYPE_UINT32: Integer uint32Value = (Integer) value; - data = new byte[ - CodedOutputByteBufferNano.computeUInt32SizeNoTag(uint32Value)]; - CodedOutputByteBufferNano.newInstance(data).writeUInt32NoTag(uint32Value); + output.writeUInt32NoTag(uint32Value); break; case TYPE_ENUM: Integer enumValue = (Integer) value; - data = new byte[CodedOutputByteBufferNano.computeEnumSizeNoTag(enumValue)]; - CodedOutputByteBufferNano.newInstance(data).writeEnumNoTag(enumValue); + output.writeEnumNoTag(enumValue); break; case TYPE_SFIXED32: Integer sfixed32Value = (Integer) value; - data = new byte[ - CodedOutputByteBufferNano.computeSFixed32SizeNoTag(sfixed32Value)]; - CodedOutputByteBufferNano.newInstance(data) - .writeSFixed32NoTag(sfixed32Value); + output.writeSFixed32NoTag(sfixed32Value); break; case TYPE_SFIXED64: Long sfixed64Value = (Long) value; - data = new byte[ - CodedOutputByteBufferNano.computeSFixed64SizeNoTag(sfixed64Value)]; - CodedOutputByteBufferNano.newInstance(data) - .writeSFixed64NoTag(sfixed64Value); + output.writeSFixed64NoTag(sfixed64Value); break; case TYPE_SINT32: Integer sint32Value = (Integer) value; - data = new byte[ - CodedOutputByteBufferNano.computeSInt32SizeNoTag(sint32Value)]; - CodedOutputByteBufferNano.newInstance(data).writeSInt32NoTag(sint32Value); + output.writeSInt32NoTag(sint32Value); break; case TYPE_SINT64: Long sint64Value = (Long) value; - data = new byte[ - CodedOutputByteBufferNano.computeSInt64SizeNoTag(sint64Value)]; - CodedOutputByteBufferNano.newInstance(data).writeSInt64NoTag(sint64Value); + output.writeSInt64NoTag(sint64Value); break; default: throw new IllegalArgumentException("Unknown type " + type); @@ -518,86 +475,21 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { // Should not happen throw new IllegalStateException(e); } - return new UnknownFieldData(tag, data); } @Override - protected void writeDataInto(T array, List<UnknownFieldData> unknownFields) { + protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) { if (tag == nonPackedTag) { // Use base implementation for non-packed data - super.writeDataInto(array, unknownFields); + super.writeRepeatedData(array, output); } else if (tag == packedTag) { // Packed. Note that the array element type is guaranteed to be primitive, so there - // won't be any null elements, so no null check in this block. First get data size. + // won't be any null elements, so no null check in this block. int arrayLength = Array.getLength(array); - int dataSize = 0; - switch (type) { - case TYPE_BOOL: - // Bools are stored as int32 but just as 0 or 1, so 1 byte each. - dataSize = arrayLength; - break; - case TYPE_FIXED32: - case TYPE_SFIXED32: - case TYPE_FLOAT: - dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_32_SIZE; - break; - case TYPE_FIXED64: - case TYPE_SFIXED64: - case TYPE_DOUBLE: - dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_64_SIZE; - break; - case TYPE_INT32: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeInt32SizeNoTag( - Array.getInt(array, i)); - } - break; - case TYPE_SINT32: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeSInt32SizeNoTag( - Array.getInt(array, i)); - } - break; - case TYPE_UINT32: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeUInt32SizeNoTag( - Array.getInt(array, i)); - } - break; - case TYPE_INT64: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeInt64SizeNoTag( - Array.getLong(array, i)); - } - break; - case TYPE_SINT64: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeSInt64SizeNoTag( - Array.getLong(array, i)); - } - break; - case TYPE_UINT64: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeUInt64SizeNoTag( - Array.getLong(array, i)); - } - break; - case TYPE_ENUM: - for (int i = 0; i < arrayLength; i++) { - dataSize += CodedOutputByteBufferNano.computeEnumSizeNoTag( - Array.getInt(array, i)); - } - break; - default: - throw new IllegalArgumentException("Unexpected non-packable type " + type); - } + int dataSize = computePackedDataSize(array); - // Then construct payload. - int payloadSize = - dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize); - byte[] data = new byte[payloadSize]; - CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(data); try { + output.writeRawVarint32(tag); output.writeRawVarint32(dataSize); switch (type) { case TYPE_BOOL: @@ -677,12 +569,154 @@ public class Extension<M extends ExtendableMessageNano<M>, T> { // Should not happen. throw new IllegalStateException(e); } - unknownFields.add(new UnknownFieldData(tag, data)); } else { throw new IllegalArgumentException("Unexpected repeated extension tag " + tag + ", unequal to both non-packed variant " + nonPackedTag + " and packed variant " + packedTag); } } + + private int computePackedDataSize(Object array) { + int dataSize = 0; + int arrayLength = Array.getLength(array); + switch (type) { + case TYPE_BOOL: + // Bools are stored as int32 but just as 0 or 1, so 1 byte each. + dataSize = arrayLength; + break; + case TYPE_FIXED32: + case TYPE_SFIXED32: + case TYPE_FLOAT: + dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_32_SIZE; + break; + case TYPE_FIXED64: + case TYPE_SFIXED64: + case TYPE_DOUBLE: + dataSize = arrayLength * CodedOutputByteBufferNano.LITTLE_ENDIAN_64_SIZE; + break; + case TYPE_INT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_SINT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeSInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_UINT32: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeUInt32SizeNoTag( + Array.getInt(array, i)); + } + break; + case TYPE_INT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_SINT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeSInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_UINT64: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeUInt64SizeNoTag( + Array.getLong(array, i)); + } + break; + case TYPE_ENUM: + for (int i = 0; i < arrayLength; i++) { + dataSize += CodedOutputByteBufferNano.computeEnumSizeNoTag( + Array.getInt(array, i)); + } + break; + default: + throw new IllegalArgumentException("Unexpected non-packable type " + type); + } + return dataSize; + } + + @Override + protected int computeRepeatedSerializedSize(Object array) { + if (tag == nonPackedTag) { + // Use base implementation for non-packed data + return super.computeRepeatedSerializedSize(array); + } else if (tag == packedTag) { + // Packed. + int dataSize = computePackedDataSize(array); + int payloadSize = + dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize); + return payloadSize + CodedOutputByteBufferNano.computeRawVarint32Size(tag); + } else { + throw new IllegalArgumentException("Unexpected repeated extension tag " + tag + + ", unequal to both non-packed variant " + nonPackedTag + + " and packed variant " + packedTag); + } + } + + @Override + protected final int computeSingularSerializedSize(Object value) { + int fieldNumber = WireFormatNano.getTagFieldNumber(tag); + switch (type) { + case TYPE_DOUBLE: + Double doubleValue = (Double) value; + return CodedOutputByteBufferNano.computeDoubleSize(fieldNumber, doubleValue); + case TYPE_FLOAT: + Float floatValue = (Float) value; + return CodedOutputByteBufferNano.computeFloatSize(fieldNumber, floatValue); + case TYPE_INT64: + Long int64Value = (Long) value; + return CodedOutputByteBufferNano.computeInt64Size(fieldNumber, int64Value); + case TYPE_UINT64: + Long uint64Value = (Long) value; + return CodedOutputByteBufferNano.computeUInt64Size(fieldNumber, uint64Value); + case TYPE_INT32: + Integer int32Value = (Integer) value; + return CodedOutputByteBufferNano.computeInt32Size(fieldNumber, int32Value); + case TYPE_FIXED64: + Long fixed64Value = (Long) value; + return CodedOutputByteBufferNano.computeFixed64Size(fieldNumber, fixed64Value); + case TYPE_FIXED32: + Integer fixed32Value = (Integer) value; + return CodedOutputByteBufferNano.computeFixed32Size(fieldNumber, fixed32Value); + case TYPE_BOOL: + Boolean boolValue = (Boolean) value; + return CodedOutputByteBufferNano.computeBoolSize(fieldNumber, boolValue); + case TYPE_STRING: + String stringValue = (String) value; + return CodedOutputByteBufferNano.computeStringSize(fieldNumber, stringValue); + case TYPE_BYTES: + byte[] bytesValue = (byte[]) value; + return CodedOutputByteBufferNano.computeBytesSize(fieldNumber, bytesValue); + case TYPE_UINT32: + Integer uint32Value = (Integer) value; + return CodedOutputByteBufferNano.computeUInt32Size(fieldNumber, uint32Value); + case TYPE_ENUM: + Integer enumValue = (Integer) value; + return CodedOutputByteBufferNano.computeEnumSize(fieldNumber, enumValue); + case TYPE_SFIXED32: + Integer sfixed32Value = (Integer) value; + return CodedOutputByteBufferNano.computeSFixed32Size(fieldNumber, + sfixed32Value); + case TYPE_SFIXED64: + Long sfixed64Value = (Long) value; + return CodedOutputByteBufferNano.computeSFixed64Size(fieldNumber, + sfixed64Value); + case TYPE_SINT32: + Integer sint32Value = (Integer) value; + return CodedOutputByteBufferNano.computeSInt32Size(fieldNumber, sint32Value); + case TYPE_SINT64: + Long sint64Value = (Long) value; + return CodedOutputByteBufferNano.computeSInt64Size(fieldNumber, sint64Value); + default: + throw new IllegalArgumentException("Unknown type " + type); + } + } } } diff --git a/java/src/main/java/com/google/protobuf/nano/FieldArray.java b/java/src/main/java/com/google/protobuf/nano/FieldArray.java new file mode 100644 index 0000000..ab923a4 --- /dev/null +++ b/java/src/main/java/com/google/protobuf/nano/FieldArray.java @@ -0,0 +1,273 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2014 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; + + +/** + * A custom version of {@link android.util.SparseArray} with the minimal API + * for storing {@link FieldData} objects. + * + * Based on {@link android.support.v4.util.SpareArrayCompat}. + */ +class FieldArray { + private static final FieldData DELETED = new FieldData(); + private boolean mGarbage = false; + + private int[] mFieldNumbers; + private FieldData[] mData; + private int mSize; + + /** + * Creates a new FieldArray containing no fields. + */ + public FieldArray() { + this(10); + } + + /** + * Creates a new FieldArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public FieldArray(int initialCapacity) { + initialCapacity = idealIntArraySize(initialCapacity); + mFieldNumbers = new int[initialCapacity]; + mData = new FieldData[initialCapacity]; + mSize = 0; + } + + /** + * Gets the FieldData mapped from the specified fieldNumber, or <code>null</code> + * if no such mapping has been made. + */ + public FieldData get(int fieldNumber) { + int i = binarySearch(fieldNumber); + + if (i < 0 || mData[i] == DELETED) { + return null; + } else { + return mData[i]; + } + } + + /** + * Removes the data from the specified fieldNumber, if there was any. + */ + public void remove(int fieldNumber) { + int i = binarySearch(fieldNumber); + + if (i >= 0 && mData[i] != DELETED) { + mData[i] = DELETED; + mGarbage = true; + } + } + + private void gc() { + int n = mSize; + int o = 0; + int[] keys = mFieldNumbers; + FieldData[] values = mData; + + for (int i = 0; i < n; i++) { + FieldData val = values[i]; + + if (val != DELETED) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + values[i] = null; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + } + + /** + * Adds a mapping from the specified fieldNumber to the specified data, + * replacing the previous mapping if there was one. + */ + public void put(int fieldNumber, FieldData data) { + int i = binarySearch(fieldNumber); + + if (i >= 0) { + mData[i] = data; + } else { + i = ~i; + + if (i < mSize && mData[i] == DELETED) { + mFieldNumbers[i] = fieldNumber; + mData[i] = data; + return; + } + + if (mGarbage && mSize >= mFieldNumbers.length) { + gc(); + + // Search again because indices may have changed. + i = ~ binarySearch(fieldNumber); + } + + if (mSize >= mFieldNumbers.length) { + int n = idealIntArraySize(mSize + 1); + + int[] nkeys = new int[n]; + FieldData[] nvalues = new FieldData[n]; + + System.arraycopy(mFieldNumbers, 0, nkeys, 0, mFieldNumbers.length); + System.arraycopy(mData, 0, nvalues, 0, mData.length); + + mFieldNumbers = nkeys; + mData = nvalues; + } + + if (mSize - i != 0) { + System.arraycopy(mFieldNumbers, i, mFieldNumbers, i + 1, mSize - i); + System.arraycopy(mData, i, mData, i + 1, mSize - i); + } + + mFieldNumbers[i] = fieldNumber; + mData[i] = data; + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this FieldArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + public boolean isEmpty() { + return size() == 0; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * FieldArray stores. + */ + public FieldData dataAt(int index) { + if (mGarbage) { + gc(); + } + + return mData[index]; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof FieldArray)) { + return false; + } + + FieldArray other = (FieldArray) o; + if (size() != other.size()) { // size() will call gc() if necessary. + return false; + } + return arrayEquals(mFieldNumbers, other.mFieldNumbers, mSize) && + arrayEquals(mData, other.mData, mSize); + } + + @Override + public int hashCode() { + if (mGarbage) { + gc(); + } + int result = 17; + for (int i = 0; i < mSize; i++) { + result = 31 * result + mFieldNumbers[i]; + result = 31 * result + mData[i].hashCode(); + } + return result; + } + + private int idealIntArraySize(int need) { + return idealByteArraySize(need * 4) / 4; + } + + private int idealByteArraySize(int need) { + for (int i = 4; i < 32; i++) + if (need <= (1 << i) - 12) + return (1 << i) - 12; + + return need; + } + + private int binarySearch(int value) { + int lo = 0; + int hi = mSize - 1; + + while (lo <= hi) { + int mid = (lo + hi) >>> 1; + int midVal = mFieldNumbers[mid]; + + if (midVal < value) { + lo = mid + 1; + } else if (midVal > value) { + hi = mid - 1; + } else { + return mid; // value found + } + } + return ~lo; // value not present + } + + private boolean arrayEquals(int[] a, int[] b, int size) { + for (int i = 0; i < size; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + + private boolean arrayEquals(FieldData[] a, FieldData[] b, int size) { + for (int i = 0; i < size; i++) { + if (!a[i].equals(b[i])) { + return false; + } + } + return true; + } +} diff --git a/java/src/main/java/com/google/protobuf/nano/FieldData.java b/java/src/main/java/com/google/protobuf/nano/FieldData.java new file mode 100644 index 0000000..7a5eb4c --- /dev/null +++ b/java/src/main/java/com/google/protobuf/nano/FieldData.java @@ -0,0 +1,173 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2014 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.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Stores unknown fields. These might be extensions or fields that the generated API doesn't + * know about yet. + */ +class FieldData { + private Extension<?, ?> cachedExtension; + private Object value; + /** The serialised values for this object. Will be cleared if getValue is called */ + private List<UnknownFieldData> unknownFieldData; + + <T> FieldData(Extension<?, T> extension, T newValue) { + cachedExtension = extension; + value = newValue; + } + + FieldData() { + unknownFieldData = new ArrayList<UnknownFieldData>(); + } + + void addUnknownField(UnknownFieldData unknownField) { + unknownFieldData.add(unknownField); + } + + <T> T getValue(Extension<?, T> extension) { + if (value != null){ + if (cachedExtension != extension) { // Extension objects are singletons. + throw new IllegalStateException( + "Tried to getExtension with a differernt Extension."); + } + } else { + cachedExtension = extension; + value = extension.getValueFrom(unknownFieldData); + unknownFieldData = null; + } + return (T) value; + } + + <T> void setValue(Extension<?, T> extension, T newValue) { + cachedExtension = extension; + value = newValue; + unknownFieldData = null; + } + + int computeSerializedSize() { + int size = 0; + if (value != null) { + size = cachedExtension.computeSerializedSize(value); + } else { + for (UnknownFieldData unknownField : unknownFieldData) { + size += unknownField.computeSerializedSize(); + } + } + return size; + } + + void writeTo(CodedOutputByteBufferNano output) throws IOException { + if (value != null) { + cachedExtension.writeTo(value, output); + } else { + for (UnknownFieldData unknownField : unknownFieldData) { + unknownField.writeTo(output); + } + } + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof FieldData)) { + return false; + } + + FieldData other = (FieldData) o; + if (value != null && other.value != null) { + // If both objects have deserialized values, compare those. + // Since unknown fields are only compared if messages have generated equals methods + // we know this will be a meaningful comparison (not identity) for all values. + if (cachedExtension != other.cachedExtension) { // Extension objects are singletons. + return false; + } + if (!cachedExtension.clazz.isArray()) { + // Can't test (!cachedExtension.repeated) due to 'bytes' -> 'byte[]' + return value.equals(other.value); + } + if (value instanceof byte[]) { + return Arrays.equals((byte[]) value, (byte[]) other.value); + } else if (value instanceof int[]) { + return Arrays.equals((int[]) value, (int[]) other.value); + } else if (value instanceof long[]) { + return Arrays.equals((long[]) value, (long[]) other.value); + } else if (value instanceof float[]) { + return Arrays.equals((float[]) value, (float[]) other.value); + } else if (value instanceof double[]) { + return Arrays.equals((double[]) value, (double[]) other.value); + } else if (value instanceof boolean[]) { + return Arrays.equals((boolean[]) value, (boolean[]) other.value); + } else { + return Arrays.deepEquals((Object[]) value, (Object[]) other.value); + } + } + if (unknownFieldData != null && other.unknownFieldData != null) { + // If both objects have byte arrays compare those directly. + return unknownFieldData.equals(other.unknownFieldData); + } + try { + // As a last resort, serialize and compare the resulting byte arrays. + return Arrays.equals(toByteArray(), other.toByteArray()); + } catch (IOException e) { + // Should not happen. + throw new IllegalStateException(e); + } + } + + @Override + public int hashCode() { + int result = 17; + try { + // The only way to generate a consistent hash is to use the serialized form. + result = 31 * result + Arrays.hashCode(toByteArray()); + } catch (IOException e) { + // Should not happen. + throw new IllegalStateException(e); + } + return result; + } + + private byte[] toByteArray() throws IOException { + byte[] result = new byte[computeSerializedSize()]; + CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(result); + writeTo(output); + return result; + } + +} diff --git a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java index 833ed2a..2032e1a 100644 --- a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java +++ b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java @@ -30,42 +30,55 @@ package com.google.protobuf.nano; +import java.io.IOException; import java.util.Arrays; /** - * Stores unknown fields. These might be extensions or fields that the generated API doesn't - * know about yet. + * 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 class UnknownFieldData { - final int tag; - final byte[] bytes; + final int tag; + final byte[] bytes; - UnknownFieldData(int tag, byte[] bytes) { - this.tag = tag; - this.bytes = bytes; - } + UnknownFieldData(int tag, byte[] bytes) { + this.tag = tag; + this.bytes = bytes; + } - @Override - public boolean equals(Object o) { - if (o == this) { - return true; + int computeSerializedSize() { + int size = 0; + size += CodedOutputByteBufferNano.computeRawVarint32Size(tag); + size += bytes.length; + return size; } - if (!(o instanceof UnknownFieldData)) { - return false; + + void writeTo(CodedOutputByteBufferNano output) throws IOException { + output.writeRawVarint32(tag); + output.writeRawBytes(bytes); } - UnknownFieldData other = (UnknownFieldData) o; - return tag == other.tag && Arrays.equals(bytes, other.bytes); - } + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof UnknownFieldData)) { + return false; + } - @Override - public int hashCode() { - int result = 17; - result = 31 * result + tag; - result = 31 * result + Arrays.hashCode(bytes); - return result; - } + UnknownFieldData other = (UnknownFieldData) o; + return tag == other.tag && Arrays.equals(bytes, other.bytes); + } + + @Override + public int hashCode() { + int result = 17; + result = 31 * result + tag; + result = 31 * result + Arrays.hashCode(bytes); + return result; + } } diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index 00e2597..93c9dc4 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/java/src/test/java/com/google/protobuf/NanoTest.java @@ -41,6 +41,7 @@ import com.google.protobuf.nano.Extensions.MessageWithGroup; import com.google.protobuf.nano.FileScopeEnumMultiple; import com.google.protobuf.nano.FileScopeEnumRefNano; import com.google.protobuf.nano.InternalNano; +import com.google.protobuf.nano.InvalidProtocolBufferNanoException; import com.google.protobuf.nano.MessageNano; import com.google.protobuf.nano.MessageScopeEnumRefNano; import com.google.protobuf.nano.MultipleImportingNonMultipleNano1; @@ -2826,6 +2827,8 @@ public class NanoTest extends TestCase { assertEquals(group2.a, message.getExtension(SingularExtensions.someGroup).a); // Test reading back using RepeatedExtensions: the arrays should be equal. + message = Extensions.ExtendableMessage.parseFrom(data); + assertEquals(5, message.field); assertTrue(Arrays.equals(int32s, message.getExtension(RepeatedExtensions.repeatedInt32))); assertTrue(Arrays.equals(uint32s, message.getExtension(RepeatedExtensions.repeatedUint32))); assertTrue(Arrays.equals(sint32s, message.getExtension(RepeatedExtensions.repeatedSint32))); @@ -2860,6 +2863,8 @@ public class NanoTest extends TestCase { // Test reading back using PackedExtensions: the arrays should be equal, even the fields // are non-packed. + message = Extensions.ExtendableMessage.parseFrom(data); + assertEquals(5, message.field); assertTrue(Arrays.equals(int32s, message.getExtension(PackedExtensions.packedInt32))); assertTrue(Arrays.equals(uint32s, message.getExtension(PackedExtensions.packedUint32))); assertTrue(Arrays.equals(sint32s, message.getExtension(PackedExtensions.packedSint32))); @@ -2924,6 +2929,138 @@ public class NanoTest extends TestCase { assertEquals(0, MessageNano.toByteArray(message).length); } + public void testExtensionsMutation() { + Extensions.ExtendableMessage extendableMessage = new Extensions.ExtendableMessage(); + extendableMessage.setExtension(SingularExtensions.someMessage, + new Extensions.AnotherMessage()); + + extendableMessage.getExtension(SingularExtensions.someMessage).string = "not empty"; + + assertEquals("not empty", + extendableMessage.getExtension(SingularExtensions.someMessage).string); + } + + public void testExtensionsMutation_Equals() throws InvalidProtocolBufferNanoException { + Extensions.ExtendableMessage extendableMessage = new Extensions.ExtendableMessage(); + extendableMessage.field = 5; + int int32 = 42; + int[] uint32s = {3, 4}; + int[] sint32s = {-5, -6}; + long[] int64s = {7, 8}; + long[] uint64s = {9, 10}; + long[] sint64s = {-11, -12}; + int[] fixed32s = {13, 14}; + int[] sfixed32s = {-15, -16}; + long[] fixed64s = {17, 18}; + long[] sfixed64s = {-19, -20}; + boolean[] bools = {true, false}; + float[] floats = {2.1f, 2.2f}; + double[] doubles = {2.3, 2.4}; + int[] enums = {Extensions.SECOND_VALUE, Extensions.FIRST_VALUE}; + String[] strings = {"vijfentwintig", "twenty-six"}; + byte[][] bytess = {{2, 7}, {2, 8}}; + AnotherMessage another1 = new AnotherMessage(); + another1.string = "er shi jiu"; + another1.value = false; + AnotherMessage another2 = new AnotherMessage(); + another2.string = "trente"; + another2.value = true; + AnotherMessage[] messages = {another1, another2}; + RepeatedExtensions.RepeatedGroup group1 = new RepeatedExtensions.RepeatedGroup(); + group1.a = 31; + RepeatedExtensions.RepeatedGroup group2 = new RepeatedExtensions.RepeatedGroup(); + group2.a = 32; + RepeatedExtensions.RepeatedGroup[] groups = {group1, group2}; + extendableMessage.setExtension(SingularExtensions.someInt32, int32); + extendableMessage.setExtension(RepeatedExtensions.repeatedUint32, uint32s); + extendableMessage.setExtension(RepeatedExtensions.repeatedSint32, sint32s); + extendableMessage.setExtension(RepeatedExtensions.repeatedInt64, int64s); + extendableMessage.setExtension(RepeatedExtensions.repeatedUint64, uint64s); + extendableMessage.setExtension(RepeatedExtensions.repeatedSint64, sint64s); + extendableMessage.setExtension(RepeatedExtensions.repeatedFixed32, fixed32s); + extendableMessage.setExtension(RepeatedExtensions.repeatedSfixed32, sfixed32s); + extendableMessage.setExtension(RepeatedExtensions.repeatedFixed64, fixed64s); + extendableMessage.setExtension(RepeatedExtensions.repeatedSfixed64, sfixed64s); + extendableMessage.setExtension(RepeatedExtensions.repeatedBool, bools); + extendableMessage.setExtension(RepeatedExtensions.repeatedFloat, floats); + extendableMessage.setExtension(RepeatedExtensions.repeatedDouble, doubles); + extendableMessage.setExtension(RepeatedExtensions.repeatedEnum, enums); + extendableMessage.setExtension(RepeatedExtensions.repeatedString, strings); + extendableMessage.setExtension(RepeatedExtensions.repeatedBytes, bytess); + extendableMessage.setExtension(RepeatedExtensions.repeatedMessage, messages); + extendableMessage.setExtension(RepeatedExtensions.repeatedGroup, groups); + + byte[] data = MessageNano.toByteArray(extendableMessage); + + extendableMessage = Extensions.ExtendableMessage.parseFrom(data); + Extensions.ExtendableMessage messageCopy = Extensions.ExtendableMessage.parseFrom(data); + + // Without deserialising. + assertEquals(extendableMessage, messageCopy); + assertEquals(extendableMessage.hashCode(), messageCopy.hashCode()); + + // Only one deserialized. + extendableMessage.getExtension(SingularExtensions.someInt32); + extendableMessage.getExtension(RepeatedExtensions.repeatedUint32); + extendableMessage.getExtension(RepeatedExtensions.repeatedSint32); + extendableMessage.getExtension(RepeatedExtensions.repeatedInt64); + extendableMessage.getExtension(RepeatedExtensions.repeatedUint64); + extendableMessage.getExtension(RepeatedExtensions.repeatedSint64); + extendableMessage.getExtension(RepeatedExtensions.repeatedFixed32); + extendableMessage.getExtension(RepeatedExtensions.repeatedSfixed32); + extendableMessage.getExtension(RepeatedExtensions.repeatedFixed64); + extendableMessage.getExtension(RepeatedExtensions.repeatedSfixed64); + extendableMessage.getExtension(RepeatedExtensions.repeatedBool); + extendableMessage.getExtension(RepeatedExtensions.repeatedFloat); + extendableMessage.getExtension(RepeatedExtensions.repeatedDouble); + extendableMessage.getExtension(RepeatedExtensions.repeatedEnum); + extendableMessage.getExtension(RepeatedExtensions.repeatedString); + extendableMessage.getExtension(RepeatedExtensions.repeatedBytes); + extendableMessage.getExtension(RepeatedExtensions.repeatedMessage); + extendableMessage.getExtension(RepeatedExtensions.repeatedGroup); + assertEquals(extendableMessage, messageCopy); + assertEquals(extendableMessage.hashCode(), messageCopy.hashCode()); + + // Both deserialized. + messageCopy.getExtension(SingularExtensions.someInt32); + messageCopy.getExtension(RepeatedExtensions.repeatedUint32); + messageCopy.getExtension(RepeatedExtensions.repeatedSint32); + messageCopy.getExtension(RepeatedExtensions.repeatedInt64); + messageCopy.getExtension(RepeatedExtensions.repeatedUint64); + messageCopy.getExtension(RepeatedExtensions.repeatedSint64); + messageCopy.getExtension(RepeatedExtensions.repeatedFixed32); + messageCopy.getExtension(RepeatedExtensions.repeatedSfixed32); + messageCopy.getExtension(RepeatedExtensions.repeatedFixed64); + messageCopy.getExtension(RepeatedExtensions.repeatedSfixed64); + messageCopy.getExtension(RepeatedExtensions.repeatedBool); + messageCopy.getExtension(RepeatedExtensions.repeatedFloat); + messageCopy.getExtension(RepeatedExtensions.repeatedDouble); + messageCopy.getExtension(RepeatedExtensions.repeatedEnum); + messageCopy.getExtension(RepeatedExtensions.repeatedString); + messageCopy.getExtension(RepeatedExtensions.repeatedBytes); + messageCopy.getExtension(RepeatedExtensions.repeatedMessage); + messageCopy.getExtension(RepeatedExtensions.repeatedGroup); + assertEquals(extendableMessage, messageCopy); + assertEquals(extendableMessage.hashCode(), messageCopy.hashCode()); + + // Change one, make sure they are still different. + messageCopy.getExtension(RepeatedExtensions.repeatedMessage)[0].string = "not empty"; + assertFalse(extendableMessage.equals(messageCopy)); + + // Even if the extension hasn't been deserialized. + extendableMessage = Extensions.ExtendableMessage.parseFrom(data); + assertFalse(extendableMessage.equals(messageCopy)); + } + + public void testExtensionsCaching() { + Extensions.ExtendableMessage extendableMessage = new Extensions.ExtendableMessage(); + extendableMessage.setExtension(SingularExtensions.someMessage, + new Extensions.AnotherMessage()); + assertSame("Consecutive calls to getExtensions should return the same object", + extendableMessage.getExtension(SingularExtensions.someMessage), + extendableMessage.getExtension(SingularExtensions.someMessage)); + } + public void testUnknownFields() throws Exception { // Check that we roundtrip (serialize and deserialize) unrecognized fields. AnotherMessage message = new AnotherMessage(); |