Commit 79f19eb9 authored by Juan Silveira's avatar Juan Silveira

Keep pointers to extension values.

The current implementation of getExtension deserialises the field from bytes
and returns a new object every time. This means that changes to those objects
are reflected when the messages is serialised unless setExtension is called. It
also means that every call to getExtension and setExtension is expensive.

This change introduces a FieldData class that contains everything that's known
about the field at the time. This can be all the tag/byte[] pairs associated
with a given field or an Extension and a value object. This is so that two
messages with a repeated extension can be compared even if the extension
has been deserialised in one of them but not the other.

This change also adds FieldArray class based on SparseArray from the Android
compatibility library. This is used in ExtendableMessageNano to make lookup
of FieldDatas by their field number faster.

Implications:
* calling getExtension multiple times deserialises the field only once and
  returns the same object.
* calling setExtension doesn't cause the object to be serialised immediately,
  that only happens when the container message is serialised.
* getExtension is no longer a read-only thread-safe operation. README.txt has
  been updated to relfect that.
* comparison using equals and hashCode continues to work.

Bug: 10863158

Change-Id: I81c7cb0c73cc0611a1f7c1eabf5eed259738e8bc
parent 70ef74aa
......@@ -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;
}
}
// 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;
}
}
// 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;
}
UnknownFieldData(int tag, byte[] bytes) {
this.tag = tag;
this.bytes = bytes;
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
int computeSerializedSize() {
int size = 0;
size += CodedOutputByteBufferNano.computeRawVarint32Size(tag);
size += bytes.length;
return size;
}
if (!(o instanceof UnknownFieldData)) {
return false;
void writeTo(CodedOutputByteBufferNano output) throws IOException {
output.writeRawVarint32(tag);
output.writeRawBytes(bytes);
}
UnknownFieldData other = (UnknownFieldData) o;
return tag == other.tag && Arrays.equals(bytes, other.bytes);
}
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof UnknownFieldData)) {
return false;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + tag;
result = 31 * result + Arrays.hashCode(bytes);
return result;
}
UnknownFieldData other = (UnknownFieldData) o;
return tag == other.tag && Arrays.equals(bytes, other.bytes);
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + tag;
result = 31 * result + Arrays.hashCode(bytes);
return result;
}
}
......@@ -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();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment