Merge "Keep pointers to extension values."

This commit is contained in:
Max Cai 2014-07-15 10:28:20 +00:00 committed by Gerrit Code Review
commit 30b1454d8b
7 changed files with 920 additions and 256 deletions

View File

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

View File

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

View File

@ -152,58 +152,52 @@ 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) {
// This implementation is for message/group extensions.
Class<?> messageType = repeated ? clazz.getComponentType() : clazz;
@ -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);
}
}
void writeTo(Object value, CodedOutputByteBufferNano output) throws IOException {
if (repeated) {
writeRepeatedData(value, output);
} else {
writeSingularData(value, output);
}
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.
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,20 +261,55 @@ 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) {
unknownFields.add(writeData(element));
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) {
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);
}
}
/**
* Represents an extension of a primitive (including enum) type. If there is no primitive
* extensions, this subclass will be removable by ProGuard.
@ -338,15 +335,6 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
this.packedTag = packedTag;
}
@Override
protected boolean isMatch(int unknownDataTag) {
if (repeated) {
return unknownDataTag == nonPackedTag || unknownDataTag == packedTag;
} else {
return unknownDataTag == tag;
}
}
@Override
protected Object readData(CodedInputByteBufferNano input) {
try {
@ -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);
}
}
}
}

View File

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

View File

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

View File

@ -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;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof UnknownFieldData)) {
return false;
UnknownFieldData(int tag, byte[] bytes) {
this.tag = tag;
this.bytes = bytes;
}
UnknownFieldData other = (UnknownFieldData) o;
return tag == other.tag && Arrays.equals(bytes, other.bytes);
}
int computeSerializedSize() {
int size = 0;
size += CodedOutputByteBufferNano.computeRawVarint32Size(tag);
size += bytes.length;
return size;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + tag;
result = 31 * result + Arrays.hashCode(bytes);
return result;
}
void writeTo(CodedOutputByteBufferNano output) throws IOException {
output.writeRawVarint32(tag);
output.writeRawBytes(bytes);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof UnknownFieldData)) {
return false;
}
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;
}
}

View File

@ -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();