Merge "Keep pointers to extension values."
This commit is contained in:
commit
30b1454d8b
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
273
java/src/main/java/com/google/protobuf/nano/FieldArray.java
Normal file
273
java/src/main/java/com/google/protobuf/nano/FieldArray.java
Normal 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;
|
||||
}
|
||||
}
|
173
java/src/main/java/com/google/protobuf/nano/FieldData.java
Normal file
173
java/src/main/java/com/google/protobuf/nano/FieldData.java
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user