summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--java/README.txt7
-rw-r--r--java/pom.xml6
-rw-r--r--java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java71
-rw-r--r--java/src/main/java/com/google/protobuf/nano/Extension.java446
-rw-r--r--java/src/main/java/com/google/protobuf/nano/FieldArray.java273
-rw-r--r--java/src/main/java/com/google/protobuf/nano/FieldData.java173
-rw-r--r--java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java63
-rw-r--r--java/src/test/java/com/google/protobuf/NanoTest.java137
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();