am daf63839: Merge "Keep pointers to extension values."

* commit 'daf638399bd42122306786e8062f392ddace4363':
  Keep pointers to extension values.
pull/91/head
Max Cai 11 years ago committed by Android Git Automerger
commit 9c2d63c8d3
  1. 7
      java/README.txt
  2. 71
      java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
  3. 446
      java/src/main/java/com/google/protobuf/nano/Extension.java
  4. 273
      java/src/main/java/com/google/protobuf/nano/FieldArray.java
  5. 173
      java/src/main/java/com/google/protobuf/nano/FieldData.java
  6. 63
      java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
  7. 137
      java/src/test/java/com/google/protobuf/NanoTest.java

@ -442,9 +442,10 @@ used simultaneously from multiple threads in a read-only manner.
In other words, an appropriate synchronization mechanism (such as In other words, an appropriate synchronization mechanism (such as
a ReadWriteLock) must be used to ensure that a message, its a ReadWriteLock) must be used to ensure that a message, its
ancestors, and descendants are not accessed by any other threads ancestors, and descendants are not accessed by any other threads
while the message is being modified. Field reads, getter methods, while the message is being modified. Field reads, getter methods
toByteArray(...), writeTo(...), getCachedSize(), and (but not getExtension(...)), toByteArray(...), writeTo(...),
getSerializedSize() are all considered read-only operations. getCachedSize(), and getSerializedSize() are all considered read-only
operations.
IMPORTANT: If you have fields with defaults and opt out of accessors IMPORTANT: If you have fields with defaults and opt out of accessors

@ -31,8 +31,6 @@
package com.google.protobuf.nano; package com.google.protobuf.nano;
import java.io.IOException; 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, * 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 * A container for fields unknown to the message, including extensions. Extension fields can
* can be accessed through the {@link #getExtension} and {@link #setExtension} methods. * can be accessed through the {@link #getExtension} and {@link #setExtension} methods.
*/ */
protected List<UnknownFieldData> unknownFieldData; protected FieldArray unknownFieldData;
@Override @Override
protected int computeSerializedSize() { protected int computeSerializedSize() {
int size = 0; int size = 0;
int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size(); if (unknownFieldData != null) {
for (int i = 0; i < unknownFieldCount; i++) { for (int i = 0; i < unknownFieldData.size(); i++) {
UnknownFieldData unknownField = unknownFieldData.get(i); FieldData field = unknownFieldData.dataAt(i);
size += CodedOutputByteBufferNano.computeRawVarint32Size(unknownField.tag); size += field.computeSerializedSize();
size += unknownField.bytes.length; }
} }
return size; return size;
} }
@Override @Override
public void writeTo(CodedOutputByteBufferNano output) throws IOException { public void writeTo(CodedOutputByteBufferNano output) throws IOException {
int unknownFieldCount = unknownFieldData == null ? 0 : unknownFieldData.size(); if (unknownFieldData == null) {
for (int i = 0; i < unknownFieldCount; i++) { return;
UnknownFieldData unknownField = unknownFieldData.get(i); }
output.writeRawVarint32(unknownField.tag); for (int i = 0; i < unknownFieldData.size(); i++) {
output.writeRawBytes(unknownField.bytes); 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. * Gets the value stored in the specified extension of this message.
*/ */
public final <T> T getExtension(Extension<M, T> extension) { 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. * Sets the value of the specified extension of this message.
*/ */
public final <T> M setExtension(Extension<M, T> extension, T value) { 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 @SuppressWarnings("unchecked") // Generated code should guarantee type safety
M typedThis = (M) this; M typedThis = (M) this;
@ -106,12 +129,22 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>>
if (!input.skipField(tag)) { if (!input.skipField(tag)) {
return false; // This wasn't an unknown field, it's an end-group tag. return false; // This wasn't an unknown field, it's an end-group tag.
} }
if (unknownFieldData == null) { int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
unknownFieldData = new ArrayList<UnknownFieldData>();
}
int endPos = input.getPosition(); int endPos = input.getPosition();
byte[] bytes = input.getData(startPos, endPos - startPos); 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; return true;
} }
} }

@ -152,56 +152,50 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
this.repeated = repeated; 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 * Returns the value of this extension stored in the given list of unknown fields, or
* {@code null} if no unknown fields matches this extension. * {@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) { final T getValueFrom(List<UnknownFieldData> unknownFields) {
if (unknownFields == null) { if (unknownFields == null) {
return null; return null;
} }
return repeated ? getRepeatedValueFrom(unknownFields) : getSingularValueFrom(unknownFields);
}
if (repeated) { private T getRepeatedValueFrom(List<UnknownFieldData> unknownFields) {
// For repeated extensions, read all matching unknown fields in their original order. // For repeated extensions, read all matching unknown fields in their original order.
List<Object> resultList = new ArrayList<Object>(); List<Object> resultList = new ArrayList<Object>();
for (int i = 0; i < unknownFields.size(); i++) { for (int i = 0; i < unknownFields.size(); i++) {
UnknownFieldData data = unknownFields.get(i); UnknownFieldData data = unknownFields.get(i);
if (isMatch(data.tag) && data.bytes.length != 0) { if (data.bytes.length != 0) {
readDataInto(data, resultList); readDataInto(data, resultList);
}
}
int resultSize = resultList.size();
if (resultSize == 0) {
return null;
} }
}
int resultSize = resultList.size();
if (resultSize == 0) {
return null;
} else {
T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize)); T result = clazz.cast(Array.newInstance(clazz.getComponentType(), resultSize));
for (int i = 0; i < resultSize; i++) { for (int i = 0; i < resultSize; i++) {
Array.set(result, i, resultList.get(i)); Array.set(result, i, resultList.get(i));
} }
return result; 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) { protected Object readData(CodedInputByteBufferNano input) {
@ -236,61 +230,29 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
} }
/** void writeTo(Object value, CodedOutputByteBufferNano output) throws IOException {
* Sets the value of this extension to the given list of unknown fields. This removes any if (repeated) {
* previously stored data matching this extension. writeRepeatedData(value, output);
* } else {
* @param value The value of this extension, or {@code null} to clear this extension from the writeSingularData(value, output);
* 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));
}
} }
// 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. // This implementation is for message/group extensions.
byte[] data;
try { try {
out.writeRawVarint32(tag);
switch (type) { switch (type) {
case TYPE_GROUP: case TYPE_GROUP:
MessageNano groupValue = (MessageNano) value; MessageNano groupValue = (MessageNano) value;
int fieldNumber = WireFormatNano.getTagFieldNumber(tag); int fieldNumber = WireFormatNano.getTagFieldNumber(tag);
data = new byte[CodedOutputByteBufferNano.computeGroupSizeNoTag(groupValue)
+ CodedOutputByteBufferNano.computeTagSize(fieldNumber)];
CodedOutputByteBufferNano out = CodedOutputByteBufferNano.newInstance(data);
out.writeGroupNoTag(groupValue); out.writeGroupNoTag(groupValue);
// The endgroup tag must be included in the data payload. // The endgroup tag must be included in the data payload.
out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP); out.writeTag(fieldNumber, WireFormatNano.WIRETYPE_END_GROUP);
break; break;
case TYPE_MESSAGE: case TYPE_MESSAGE:
MessageNano messageValue = (MessageNano) value; MessageNano messageValue = (MessageNano) value;
data = new byte[ out.writeMessageNoTag(messageValue);
CodedOutputByteBufferNano.computeMessageSizeNoTag(messageValue)];
CodedOutputByteBufferNano.newInstance(data).writeMessageNoTag(messageValue);
break; break;
default: default:
throw new IllegalArgumentException("Unknown type " + type); throw new IllegalArgumentException("Unknown type " + type);
@ -299,18 +261,53 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
// Should not happen // Should not happen
throw new IllegalStateException(e); 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. // This implementation is for non-packed extensions.
int size = 0;
int arrayLength = Array.getLength(array); int arrayLength = Array.getLength(array);
for (int i = 0; i < arrayLength; i++) { for (int i = 0; i < arrayLength; i++) {
Object element = Array.get(array, i); Object element = Array.get(array, i);
if (element != null) { 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);
}
} }
/** /**
@ -338,15 +335,6 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
this.packedTag = packedTag; this.packedTag = packedTag;
} }
@Override
protected boolean isMatch(int unknownDataTag) {
if (repeated) {
return unknownDataTag == nonPackedTag || unknownDataTag == packedTag;
} else {
return unknownDataTag == tag;
}
}
@Override @Override
protected Object readData(CodedInputByteBufferNano input) { protected Object readData(CodedInputByteBufferNano input) {
try { try {
@ -398,7 +386,8 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
if (data.tag == nonPackedTag) { if (data.tag == nonPackedTag) {
resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes))); resultList.add(readData(CodedInputByteBufferNano.newInstance(data.bytes)));
} else { } else {
CodedInputByteBufferNano buffer = CodedInputByteBufferNano.newInstance(data.bytes); CodedInputByteBufferNano buffer =
CodedInputByteBufferNano.newInstance(data.bytes);
try { try {
buffer.pushLimit(buffer.readRawVarint32()); // length limit buffer.pushLimit(buffer.readRawVarint32()); // length limit
} catch (IOException e) { } catch (IOException e) {
@ -411,105 +400,73 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
} }
@Override @Override
protected final UnknownFieldData writeData(Object value) { protected final void writeSingularData(Object value, CodedOutputByteBufferNano output) {
byte[] data;
try { try {
output.writeRawVarint32(tag);
switch (type) { switch (type) {
case TYPE_DOUBLE: case TYPE_DOUBLE:
Double doubleValue = (Double) value; Double doubleValue = (Double) value;
data = new byte[ output.writeDoubleNoTag(doubleValue);
CodedOutputByteBufferNano.computeDoubleSizeNoTag(doubleValue)];
CodedOutputByteBufferNano.newInstance(data).writeDoubleNoTag(doubleValue);
break; break;
case TYPE_FLOAT: case TYPE_FLOAT:
Float floatValue = (Float) value; Float floatValue = (Float) value;
data = new byte[ output.writeFloatNoTag(floatValue);
CodedOutputByteBufferNano.computeFloatSizeNoTag(floatValue)];
CodedOutputByteBufferNano.newInstance(data).writeFloatNoTag(floatValue);
break; break;
case TYPE_INT64: case TYPE_INT64:
Long int64Value = (Long) value; Long int64Value = (Long) value;
data = new byte[ output.writeInt64NoTag(int64Value);
CodedOutputByteBufferNano.computeInt64SizeNoTag(int64Value)];
CodedOutputByteBufferNano.newInstance(data).writeInt64NoTag(int64Value);
break; break;
case TYPE_UINT64: case TYPE_UINT64:
Long uint64Value = (Long) value; Long uint64Value = (Long) value;
data = new byte[ output.writeUInt64NoTag(uint64Value);
CodedOutputByteBufferNano.computeUInt64SizeNoTag(uint64Value)];
CodedOutputByteBufferNano.newInstance(data).writeUInt64NoTag(uint64Value);
break; break;
case TYPE_INT32: case TYPE_INT32:
Integer int32Value = (Integer) value; Integer int32Value = (Integer) value;
data = new byte[ output.writeInt32NoTag(int32Value);
CodedOutputByteBufferNano.computeInt32SizeNoTag(int32Value)];
CodedOutputByteBufferNano.newInstance(data).writeInt32NoTag(int32Value);
break; break;
case TYPE_FIXED64: case TYPE_FIXED64:
Long fixed64Value = (Long) value; Long fixed64Value = (Long) value;
data = new byte[ output.writeFixed64NoTag(fixed64Value);
CodedOutputByteBufferNano.computeFixed64SizeNoTag(fixed64Value)];
CodedOutputByteBufferNano.newInstance(data).writeFixed64NoTag(fixed64Value);
break; break;
case TYPE_FIXED32: case TYPE_FIXED32:
Integer fixed32Value = (Integer) value; Integer fixed32Value = (Integer) value;
data = new byte[ output.writeFixed32NoTag(fixed32Value);
CodedOutputByteBufferNano.computeFixed32SizeNoTag(fixed32Value)];
CodedOutputByteBufferNano.newInstance(data).writeFixed32NoTag(fixed32Value);
break; break;
case TYPE_BOOL: case TYPE_BOOL:
Boolean boolValue = (Boolean) value; Boolean boolValue = (Boolean) value;
data = new byte[CodedOutputByteBufferNano.computeBoolSizeNoTag(boolValue)]; output.writeBoolNoTag(boolValue);
CodedOutputByteBufferNano.newInstance(data).writeBoolNoTag(boolValue);
break; break;
case TYPE_STRING: case TYPE_STRING:
String stringValue = (String) value; String stringValue = (String) value;
data = new byte[ output.writeStringNoTag(stringValue);
CodedOutputByteBufferNano.computeStringSizeNoTag(stringValue)];
CodedOutputByteBufferNano.newInstance(data).writeStringNoTag(stringValue);
break; break;
case TYPE_BYTES: case TYPE_BYTES:
byte[] bytesValue = (byte[]) value; byte[] bytesValue = (byte[]) value;
data = new byte[ output.writeBytesNoTag(bytesValue);
CodedOutputByteBufferNano.computeBytesSizeNoTag(bytesValue)];
CodedOutputByteBufferNano.newInstance(data).writeBytesNoTag(bytesValue);
break; break;
case TYPE_UINT32: case TYPE_UINT32:
Integer uint32Value = (Integer) value; Integer uint32Value = (Integer) value;
data = new byte[ output.writeUInt32NoTag(uint32Value);
CodedOutputByteBufferNano.computeUInt32SizeNoTag(uint32Value)];
CodedOutputByteBufferNano.newInstance(data).writeUInt32NoTag(uint32Value);
break; break;
case TYPE_ENUM: case TYPE_ENUM:
Integer enumValue = (Integer) value; Integer enumValue = (Integer) value;
data = new byte[CodedOutputByteBufferNano.computeEnumSizeNoTag(enumValue)]; output.writeEnumNoTag(enumValue);
CodedOutputByteBufferNano.newInstance(data).writeEnumNoTag(enumValue);
break; break;
case TYPE_SFIXED32: case TYPE_SFIXED32:
Integer sfixed32Value = (Integer) value; Integer sfixed32Value = (Integer) value;
data = new byte[ output.writeSFixed32NoTag(sfixed32Value);
CodedOutputByteBufferNano.computeSFixed32SizeNoTag(sfixed32Value)];
CodedOutputByteBufferNano.newInstance(data)
.writeSFixed32NoTag(sfixed32Value);
break; break;
case TYPE_SFIXED64: case TYPE_SFIXED64:
Long sfixed64Value = (Long) value; Long sfixed64Value = (Long) value;
data = new byte[ output.writeSFixed64NoTag(sfixed64Value);
CodedOutputByteBufferNano.computeSFixed64SizeNoTag(sfixed64Value)];
CodedOutputByteBufferNano.newInstance(data)
.writeSFixed64NoTag(sfixed64Value);
break; break;
case TYPE_SINT32: case TYPE_SINT32:
Integer sint32Value = (Integer) value; Integer sint32Value = (Integer) value;
data = new byte[ output.writeSInt32NoTag(sint32Value);
CodedOutputByteBufferNano.computeSInt32SizeNoTag(sint32Value)];
CodedOutputByteBufferNano.newInstance(data).writeSInt32NoTag(sint32Value);
break; break;
case TYPE_SINT64: case TYPE_SINT64:
Long sint64Value = (Long) value; Long sint64Value = (Long) value;
data = new byte[ output.writeSInt64NoTag(sint64Value);
CodedOutputByteBufferNano.computeSInt64SizeNoTag(sint64Value)];
CodedOutputByteBufferNano.newInstance(data).writeSInt64NoTag(sint64Value);
break; break;
default: default:
throw new IllegalArgumentException("Unknown type " + type); throw new IllegalArgumentException("Unknown type " + type);
@ -518,86 +475,21 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
// Should not happen // Should not happen
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
return new UnknownFieldData(tag, data);
} }
@Override @Override
protected void writeDataInto(T array, List<UnknownFieldData> unknownFields) { protected void writeRepeatedData(Object array, CodedOutputByteBufferNano output) {
if (tag == nonPackedTag) { if (tag == nonPackedTag) {
// Use base implementation for non-packed data // Use base implementation for non-packed data
super.writeDataInto(array, unknownFields); super.writeRepeatedData(array, output);
} else if (tag == packedTag) { } else if (tag == packedTag) {
// Packed. Note that the array element type is guaranteed to be primitive, so there // 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 arrayLength = Array.getLength(array);
int dataSize = 0; int dataSize = computePackedDataSize(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);
}
// Then construct payload.
int payloadSize =
dataSize + CodedOutputByteBufferNano.computeRawVarint32Size(dataSize);
byte[] data = new byte[payloadSize];
CodedOutputByteBufferNano output = CodedOutputByteBufferNano.newInstance(data);
try { try {
output.writeRawVarint32(tag);
output.writeRawVarint32(dataSize); output.writeRawVarint32(dataSize);
switch (type) { switch (type) {
case TYPE_BOOL: case TYPE_BOOL:
@ -677,12 +569,154 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
// Should not happen. // Should not happen.
throw new IllegalStateException(e); throw new IllegalStateException(e);
} }
unknownFields.add(new UnknownFieldData(tag, data));
} else { } else {
throw new IllegalArgumentException("Unexpected repeated extension tag " + tag throw new IllegalArgumentException("Unexpected repeated extension tag " + tag
+ ", unequal to both non-packed variant " + nonPackedTag + ", unequal to both non-packed variant " + nonPackedTag
+ " and packed variant " + packedTag); + " 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);
}
}
} }
} }

@ -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;
}
}

@ -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;
}
}

@ -30,42 +30,55 @@
package com.google.protobuf.nano; package com.google.protobuf.nano;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
/** /**
* Stores unknown fields. These might be extensions or fields that the generated API doesn't * Stores unknown fields. These might be extensions or fields that the generated
* know about yet. * API doesn't know about yet.
* *
* @author bduff@google.com (Brian Duff) * @author bduff@google.com (Brian Duff)
*/ */
public final class UnknownFieldData { final class UnknownFieldData {
final int tag; final int tag;
final byte[] bytes; final byte[] bytes;
UnknownFieldData(int tag, byte[] bytes) { UnknownFieldData(int tag, byte[] bytes) {
this.tag = tag; this.tag = tag;
this.bytes = bytes; this.bytes = bytes;
} }
@Override int computeSerializedSize() {
public boolean equals(Object o) { int size = 0;
if (o == this) { size += CodedOutputByteBufferNano.computeRawVarint32Size(tag);
return true; 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; @Override
return tag == other.tag && Arrays.equals(bytes, other.bytes); public boolean equals(Object o) {
} if (o == this) {
return true;
}
if (!(o instanceof UnknownFieldData)) {
return false;
}
@Override UnknownFieldData other = (UnknownFieldData) o;
public int hashCode() { return tag == other.tag && Arrays.equals(bytes, other.bytes);
int result = 17; }
result = 31 * result + tag;
result = 31 * result + Arrays.hashCode(bytes); @Override
return result; public int hashCode() {
} int result = 17;
result = 31 * result + tag;
result = 31 * result + Arrays.hashCode(bytes);
return result;
}
} }

@ -41,6 +41,7 @@ import com.google.protobuf.nano.Extensions.MessageWithGroup;
import com.google.protobuf.nano.FileScopeEnumMultiple; import com.google.protobuf.nano.FileScopeEnumMultiple;
import com.google.protobuf.nano.FileScopeEnumRefNano; import com.google.protobuf.nano.FileScopeEnumRefNano;
import com.google.protobuf.nano.InternalNano; import com.google.protobuf.nano.InternalNano;
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import com.google.protobuf.nano.MessageNano; import com.google.protobuf.nano.MessageNano;
import com.google.protobuf.nano.MessageScopeEnumRefNano; import com.google.protobuf.nano.MessageScopeEnumRefNano;
import com.google.protobuf.nano.MultipleImportingNonMultipleNano1; import com.google.protobuf.nano.MultipleImportingNonMultipleNano1;
@ -2826,6 +2827,8 @@ public class NanoTest extends TestCase {
assertEquals(group2.a, message.getExtension(SingularExtensions.someGroup).a); assertEquals(group2.a, message.getExtension(SingularExtensions.someGroup).a);
// Test reading back using RepeatedExtensions: the arrays should be equal. // 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(int32s, message.getExtension(RepeatedExtensions.repeatedInt32)));
assertTrue(Arrays.equals(uint32s, message.getExtension(RepeatedExtensions.repeatedUint32))); assertTrue(Arrays.equals(uint32s, message.getExtension(RepeatedExtensions.repeatedUint32)));
assertTrue(Arrays.equals(sint32s, message.getExtension(RepeatedExtensions.repeatedSint32))); 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 // Test reading back using PackedExtensions: the arrays should be equal, even the fields
// are non-packed. // are non-packed.
message = Extensions.ExtendableMessage.parseFrom(data);
assertEquals(5, message.field);
assertTrue(Arrays.equals(int32s, message.getExtension(PackedExtensions.packedInt32))); assertTrue(Arrays.equals(int32s, message.getExtension(PackedExtensions.packedInt32)));
assertTrue(Arrays.equals(uint32s, message.getExtension(PackedExtensions.packedUint32))); assertTrue(Arrays.equals(uint32s, message.getExtension(PackedExtensions.packedUint32)));
assertTrue(Arrays.equals(sint32s, message.getExtension(PackedExtensions.packedSint32))); assertTrue(Arrays.equals(sint32s, message.getExtension(PackedExtensions.packedSint32)));
@ -2924,6 +2929,138 @@ public class NanoTest extends TestCase {
assertEquals(0, MessageNano.toByteArray(message).length); 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 { public void testUnknownFields() throws Exception {
// Check that we roundtrip (serialize and deserialize) unrecognized fields. // Check that we roundtrip (serialize and deserialize) unrecognized fields.
AnotherMessage message = new AnotherMessage(); AnotherMessage message = new AnotherMessage();

Loading…
Cancel
Save