aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/main/java/com/google/protobuf/nano
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/main/java/com/google/protobuf/nano')
-rw-r--r--java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java268
-rw-r--r--java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java28
-rw-r--r--java/src/main/java/com/google/protobuf/nano/Extension.java6
-rw-r--r--java/src/main/java/com/google/protobuf/nano/FieldArray.java34
-rw-r--r--java/src/main/java/com/google/protobuf/nano/FieldData.java69
-rw-r--r--java/src/main/java/com/google/protobuf/nano/InternalNano.java8
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNano.java34
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java6
-rw-r--r--java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java4
-rw-r--r--java/src/main/java/com/google/protobuf/nano/WireFormatNano.java6
10 files changed, 394 insertions, 69 deletions
diff --git a/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java b/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
index 88df38d..53060da 100644
--- a/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/CodedOutputByteBufferNano.java
@@ -31,7 +31,9 @@
package com.google.protobuf.nano;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.nio.ReadOnlyBufferException;
/**
* Encodes and writes protocol message fields.
@@ -48,15 +50,17 @@ import java.io.UnsupportedEncodingException;
* @author kneton@google.com Kenton Varda
*/
public final class CodedOutputByteBufferNano {
- private final byte[] buffer;
- private final int limit;
- private int position;
+ /* max bytes per java UTF-16 char in UTF-8 */
+ private static final int MAX_UTF8_EXPANSION = 3;
+ private final ByteBuffer buffer;
private CodedOutputByteBufferNano(final byte[] buffer, final int offset,
final int length) {
+ this(ByteBuffer.wrap(buffer, offset, length));
+ }
+
+ private CodedOutputByteBufferNano(final ByteBuffer buffer) {
this.buffer = buffer;
- position = offset;
- limit = offset + length;
}
/**
@@ -288,14 +292,212 @@ public final class CodedOutputByteBufferNano {
/** Write a {@code string} field to the stream. */
public void writeStringNoTag(final String value) throws IOException {
- // Unfortunately there does not appear to be any way to tell Java to encode
- // UTF-8 directly into our buffer, so we have to let it create its own byte
- // array and then copy.
- final byte[] bytes = value.getBytes("UTF-8");
- writeRawVarint32(bytes.length);
- writeRawBytes(bytes);
+ // UTF-8 byte length of the string is at least its UTF-16 code unit length (value.length()),
+ // and at most 3 times of it. Optimize for the case where we know this length results in a
+ // constant varint length - saves measuring length of the string.
+ try {
+ final int minLengthVarIntSize = computeRawVarint32Size(value.length());
+ final int maxLengthVarIntSize = computeRawVarint32Size(value.length() * MAX_UTF8_EXPANSION);
+ if (minLengthVarIntSize == maxLengthVarIntSize) {
+ int oldPosition = buffer.position();
+ // Buffer.position, when passed a position that is past its limit, throws
+ // IllegalArgumentException, and this class is documented to throw
+ // OutOfSpaceException instead.
+ if (buffer.remaining() < minLengthVarIntSize) {
+ throw new OutOfSpaceException(oldPosition + minLengthVarIntSize, buffer.limit());
+ }
+ buffer.position(oldPosition + minLengthVarIntSize);
+ encode(value, buffer);
+ int newPosition = buffer.position();
+ buffer.position(oldPosition);
+ writeRawVarint32(newPosition - oldPosition - minLengthVarIntSize);
+ buffer.position(newPosition);
+ } else {
+ writeRawVarint32(encodedLength(value));
+ encode(value, buffer);
+ }
+ } catch (BufferOverflowException e) {
+ final OutOfSpaceException outOfSpaceException = new OutOfSpaceException(buffer.position(),
+ buffer.limit());
+ outOfSpaceException.initCause(e);
+ throw outOfSpaceException;
+ }
+ }
+
+ // These UTF-8 handling methods are copied from Guava's Utf8 class.
+ /**
+ * Returns the number of bytes in the UTF-8-encoded form of {@code sequence}. For a string,
+ * this method is equivalent to {@code string.getBytes(UTF_8).length}, but is more efficient in
+ * both time and space.
+ *
+ * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
+ * surrogates)
+ */
+ private static int encodedLength(CharSequence sequence) {
+ // Warning to maintainers: this implementation is highly optimized.
+ int utf16Length = sequence.length();
+ int utf8Length = utf16Length;
+ int i = 0;
+
+ // This loop optimizes for pure ASCII.
+ while (i < utf16Length && sequence.charAt(i) < 0x80) {
+ i++;
+ }
+
+ // This loop optimizes for chars less than 0x800.
+ for (; i < utf16Length; i++) {
+ char c = sequence.charAt(i);
+ if (c < 0x800) {
+ utf8Length += ((0x7f - c) >>> 31); // branch free!
+ } else {
+ utf8Length += encodedLengthGeneral(sequence, i);
+ break;
+ }
+ }
+
+ if (utf8Length < utf16Length) {
+ // Necessary and sufficient condition for overflow because of maximum 3x expansion
+ throw new IllegalArgumentException("UTF-8 length does not fit in int: "
+ + (utf8Length + (1L << 32)));
+ }
+ return utf8Length;
+ }
+
+ private static int encodedLengthGeneral(CharSequence sequence, int start) {
+ int utf16Length = sequence.length();
+ int utf8Length = 0;
+ for (int i = start; i < utf16Length; i++) {
+ char c = sequence.charAt(i);
+ if (c < 0x800) {
+ utf8Length += (0x7f - c) >>> 31; // branch free!
+ } else {
+ utf8Length += 2;
+ // jdk7+: if (Character.isSurrogate(c)) {
+ if (Character.MIN_SURROGATE <= c && c <= Character.MAX_SURROGATE) {
+ // Check that we have a well-formed surrogate pair.
+ int cp = Character.codePointAt(sequence, i);
+ if (cp < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
+ throw new IllegalArgumentException("Unpaired surrogate at index " + i);
+ }
+ i++;
+ }
+ }
+ }
+ return utf8Length;
+ }
+
+ /**
+ * Encodes {@code sequence} into UTF-8, in {@code byteBuffer}. For a string, this method is
+ * equivalent to {@code buffer.put(string.getBytes(UTF_8))}, but is more efficient in both time
+ * and space. Bytes are written starting at the current position. This method requires paired
+ * surrogates, and therefore does not support chunking.
+ *
+ * <p>To ensure sufficient space in the output buffer, either call {@link #encodedLength} to
+ * compute the exact amount needed, or leave room for {@code 3 * sequence.length()}, which is the
+ * largest possible number of bytes that any input can be encoded to.
+ *
+ * @throws IllegalArgumentException if {@code sequence} contains ill-formed UTF-16 (unpaired
+ * surrogates)
+ * @throws BufferOverflowException if {@code sequence} encoded in UTF-8 does not fit in
+ * {@code byteBuffer}'s remaining space.
+ * @throws ReadOnlyBufferException if {@code byteBuffer} is a read-only buffer.
+ */
+ private static void encode(CharSequence sequence, ByteBuffer byteBuffer) {
+ if (byteBuffer.isReadOnly()) {
+ throw new ReadOnlyBufferException();
+ } else if (byteBuffer.hasArray()) {
+ try {
+ int encoded = encode(sequence,
+ byteBuffer.array(),
+ byteBuffer.arrayOffset() + byteBuffer.position(),
+ byteBuffer.remaining());
+ byteBuffer.position(encoded - byteBuffer.arrayOffset());
+ } catch (ArrayIndexOutOfBoundsException e) {
+ BufferOverflowException boe = new BufferOverflowException();
+ boe.initCause(e);
+ throw boe;
+ }
+ } else {
+ encodeDirect(sequence, byteBuffer);
+ }
+ }
+
+ private static void encodeDirect(CharSequence sequence, ByteBuffer byteBuffer) {
+ int utf16Length = sequence.length();
+ for (int i = 0; i < utf16Length; i++) {
+ final char c = sequence.charAt(i);
+ if (c < 0x80) { // ASCII
+ byteBuffer.put((byte) c);
+ } else if (c < 0x800) { // 11 bits, two UTF-8 bytes
+ byteBuffer.put((byte) ((0xF << 6) | (c >>> 6)));
+ byteBuffer.put((byte) (0x80 | (0x3F & c)));
+ } else if (c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) {
+ // Maximium single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
+ byteBuffer.put((byte) ((0xF << 5) | (c >>> 12)));
+ byteBuffer.put((byte) (0x80 | (0x3F & (c >>> 6))));
+ byteBuffer.put((byte) (0x80 | (0x3F & c)));
+ } else {
+ final char low;
+ if (i + 1 == sequence.length()
+ || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) {
+ throw new IllegalArgumentException("Unpaired surrogate at index " + (i - 1));
+ }
+ int codePoint = Character.toCodePoint(c, low);
+ byteBuffer.put((byte) ((0xF << 4) | (codePoint >>> 18)));
+ byteBuffer.put((byte) (0x80 | (0x3F & (codePoint >>> 12))));
+ byteBuffer.put((byte) (0x80 | (0x3F & (codePoint >>> 6))));
+ byteBuffer.put((byte) (0x80 | (0x3F & codePoint)));
+ }
+ }
+ }
+
+ private static int encode(CharSequence sequence, byte[] bytes, int offset, int length) {
+ int utf16Length = sequence.length();
+ int j = offset;
+ int i = 0;
+ int limit = offset + length;
+ // Designed to take advantage of
+ // https://wikis.oracle.com/display/HotSpotInternals/RangeCheckElimination
+ for (char c; i < utf16Length && i + j < limit && (c = sequence.charAt(i)) < 0x80; i++) {
+ bytes[j + i] = (byte) c;
+ }
+ if (i == utf16Length) {
+ return j + utf16Length;
+ }
+ j += i;
+ for (char c; i < utf16Length; i++) {
+ c = sequence.charAt(i);
+ if (c < 0x80 && j < limit) {
+ bytes[j++] = (byte) c;
+ } else if (c < 0x800 && j <= limit - 2) { // 11 bits, two UTF-8 bytes
+ bytes[j++] = (byte) ((0xF << 6) | (c >>> 6));
+ bytes[j++] = (byte) (0x80 | (0x3F & c));
+ } else if ((c < Character.MIN_SURROGATE || Character.MAX_SURROGATE < c) && j <= limit - 3) {
+ // Maximum single-char code point is 0xFFFF, 16 bits, three UTF-8 bytes
+ bytes[j++] = (byte) ((0xF << 5) | (c >>> 12));
+ bytes[j++] = (byte) (0x80 | (0x3F & (c >>> 6)));
+ bytes[j++] = (byte) (0x80 | (0x3F & c));
+ } else if (j <= limit - 4) {
+ // Minimum code point represented by a surrogate pair is 0x10000, 17 bits, four UTF-8 bytes
+ final char low;
+ if (i + 1 == sequence.length()
+ || !Character.isSurrogatePair(c, (low = sequence.charAt(++i)))) {
+ throw new IllegalArgumentException("Unpaired surrogate at index " + (i - 1));
+ }
+ int codePoint = Character.toCodePoint(c, low);
+ bytes[j++] = (byte) ((0xF << 4) | (codePoint >>> 18));
+ bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 12)));
+ bytes[j++] = (byte) (0x80 | (0x3F & (codePoint >>> 6)));
+ bytes[j++] = (byte) (0x80 | (0x3F & codePoint));
+ } else {
+ throw new ArrayIndexOutOfBoundsException("Failed writing " + c + " at index " + j);
+ }
+ }
+ return j;
}
+ // End guava UTF-8 methods
+
/** Write a {@code group} field to the stream. */
public void writeGroupNoTag(final MessageNano value) throws IOException {
value.writeTo(this);
@@ -603,13 +805,8 @@ public final class CodedOutputByteBufferNano {
* {@code string} field.
*/
public static int computeStringSizeNoTag(final String value) {
- try {
- final byte[] bytes = value.getBytes("UTF-8");
- return computeRawVarint32Size(bytes.length) +
- bytes.length;
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("UTF-8 not supported.");
- }
+ final int length = encodedLength(value);
+ return computeRawVarint32Size(length) + length;
}
/**
@@ -692,7 +889,7 @@ public final class CodedOutputByteBufferNano {
* Otherwise, throws {@code UnsupportedOperationException}.
*/
public int spaceLeft() {
- return limit - position;
+ return buffer.remaining();
}
/**
@@ -710,6 +907,23 @@ public final class CodedOutputByteBufferNano {
}
/**
+ * Returns the position within the internal buffer.
+ */
+ public int position() {
+ return buffer.position();
+ }
+
+ /**
+ * Resets the position within the internal buffer to zero.
+ *
+ * @see #position
+ * @see #spaceLeft
+ */
+ public void reset() {
+ buffer.clear();
+ }
+
+ /**
* If you create a CodedOutputStream around a simple flat array, you must
* not attempt to write more bytes than the array has space. Otherwise,
* this exception will be thrown.
@@ -725,12 +939,12 @@ public final class CodedOutputByteBufferNano {
/** Write a single byte. */
public void writeRawByte(final byte value) throws IOException {
- if (position == limit) {
+ if (!buffer.hasRemaining()) {
// We're writing to a single buffer.
- throw new OutOfSpaceException(position, limit);
+ throw new OutOfSpaceException(buffer.position(), buffer.limit());
}
- buffer[position++] = value;
+ buffer.put(value);
}
/** Write a single byte, represented by an integer value. */
@@ -746,13 +960,11 @@ public final class CodedOutputByteBufferNano {
/** Write part of an array of bytes. */
public void writeRawBytes(final byte[] value, int offset, int length)
throws IOException {
- if (limit - position >= length) {
- // We have room in the current buffer.
- System.arraycopy(value, offset, buffer, position, length);
- position += length;
+ if (buffer.remaining() >= length) {
+ buffer.put(value, offset, length);
} else {
// We're writing to a single buffer.
- throw new OutOfSpaceException(position, limit);
+ throw new OutOfSpaceException(buffer.position(), buffer.limit());
}
}
diff --git a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
index 46cd86f..4fe8dce 100644
--- a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
@@ -160,28 +160,10 @@ public abstract class ExtendableMessageNano<M extends ExtendableMessageNano<M>>
return true;
}
- /**
- * Returns whether the stored unknown field data in this message is equivalent to that in the
- * other message.
- *
- * @param other the other message.
- * @return whether the two sets of unknown field data are equal.
- */
- protected final boolean unknownFieldDataEquals(M other) {
- if (unknownFieldData == null || unknownFieldData.isEmpty()) {
- return other.unknownFieldData == null || other.unknownFieldData.isEmpty();
- } else {
- return unknownFieldData.equals(other.unknownFieldData);
- }
- }
-
- /**
- * Computes the hashcode representing the unknown field data stored in this message.
- *
- * @return the hashcode for the unknown field data.
- */
- protected final int unknownFieldDataHashCode() {
- return (unknownFieldData == null || unknownFieldData.isEmpty()
- ? 0 : unknownFieldData.hashCode());
+ @Override
+ public M clone() throws CloneNotSupportedException {
+ M cloned = (M) super.clone();
+ InternalNano.cloneUnknownFieldData(this, cloned);
+ return cloned;
}
}
diff --git a/java/src/main/java/com/google/protobuf/nano/Extension.java b/java/src/main/java/com/google/protobuf/nano/Extension.java
index 27bab8c..6e2202e 100644
--- a/java/src/main/java/com/google/protobuf/nano/Extension.java
+++ b/java/src/main/java/com/google/protobuf/nano/Extension.java
@@ -88,7 +88,7 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
}
// Note: these create...() methods take a long for the tag parameter,
- // because tags are represented as unsigned longs, and these values exist
+ // because tags are represented as unsigned ints, and these values exist
// in generated code as long values. However, they can fit in 32-bits, so
// it's safe to cast them to int without loss of precision.
@@ -155,9 +155,9 @@ public class Extension<M extends ExtendableMessageNano<M>, T> {
protected final Class<T> clazz;
/**
- * Tag number of this extension.
+ * Tag number of this extension. The data should be viewed as an unsigned 32-bit value.
*/
- protected final int tag;
+ public final int tag;
/**
* Whether this extension is repeated.
diff --git a/java/src/main/java/com/google/protobuf/nano/FieldArray.java b/java/src/main/java/com/google/protobuf/nano/FieldArray.java
index ab923a4..5e8856d 100644
--- a/java/src/main/java/com/google/protobuf/nano/FieldArray.java
+++ b/java/src/main/java/com/google/protobuf/nano/FieldArray.java
@@ -35,9 +35,12 @@ package com.google.protobuf.nano;
* A custom version of {@link android.util.SparseArray} with the minimal API
* for storing {@link FieldData} objects.
*
+ * <p>This class is an internal implementation detail of nano and should not
+ * be called directly by clients.
+ *
* Based on {@link android.support.v4.util.SpareArrayCompat}.
*/
-class FieldArray {
+public final class FieldArray implements Cloneable {
private static final FieldData DELETED = new FieldData();
private boolean mGarbage = false;
@@ -48,7 +51,7 @@ class FieldArray {
/**
* Creates a new FieldArray containing no fields.
*/
- public FieldArray() {
+ FieldArray() {
this(10);
}
@@ -57,7 +60,7 @@ class FieldArray {
* require any additional memory allocation to store the specified
* number of mappings.
*/
- public FieldArray(int initialCapacity) {
+ FieldArray(int initialCapacity) {
initialCapacity = idealIntArraySize(initialCapacity);
mFieldNumbers = new int[initialCapacity];
mData = new FieldData[initialCapacity];
@@ -68,7 +71,7 @@ class FieldArray {
* Gets the FieldData mapped from the specified fieldNumber, or <code>null</code>
* if no such mapping has been made.
*/
- public FieldData get(int fieldNumber) {
+ FieldData get(int fieldNumber) {
int i = binarySearch(fieldNumber);
if (i < 0 || mData[i] == DELETED) {
@@ -81,7 +84,7 @@ class FieldArray {
/**
* Removes the data from the specified fieldNumber, if there was any.
*/
- public void remove(int fieldNumber) {
+ void remove(int fieldNumber) {
int i = binarySearch(fieldNumber);
if (i >= 0 && mData[i] != DELETED) {
@@ -118,7 +121,7 @@ class FieldArray {
* 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) {
+ void put(int fieldNumber, FieldData data) {
int i = binarySearch(fieldNumber);
if (i >= 0) {
@@ -167,7 +170,7 @@ class FieldArray {
* Returns the number of key-value mappings that this FieldArray
* currently stores.
*/
- public int size() {
+ int size() {
if (mGarbage) {
gc();
}
@@ -184,7 +187,7 @@ class FieldArray {
* the value from the <code>index</code>th key-value mapping that this
* FieldArray stores.
*/
- public FieldData dataAt(int index) {
+ FieldData dataAt(int index) {
if (mGarbage) {
gc();
}
@@ -270,4 +273,19 @@ class FieldArray {
}
return true;
}
+
+ @Override
+ public final FieldArray clone() {
+ // Trigger GC so we compact and don't copy DELETED elements.
+ int size = size();
+ FieldArray clone = new FieldArray(size);
+ System.arraycopy(mFieldNumbers, 0, clone.mFieldNumbers, 0, size);
+ for (int i = 0; i < size; i++) {
+ if (mData[i] != null) {
+ clone.mData[i] = mData[i].clone();
+ }
+ }
+ clone.mSize = size;
+ return clone;
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/nano/FieldData.java b/java/src/main/java/com/google/protobuf/nano/FieldData.java
index 7a5eb4c..20a5142 100644
--- a/java/src/main/java/com/google/protobuf/nano/FieldData.java
+++ b/java/src/main/java/com/google/protobuf/nano/FieldData.java
@@ -39,7 +39,7 @@ import java.util.List;
* Stores unknown fields. These might be extensions or fields that the generated API doesn't
* know about yet.
*/
-class FieldData {
+class FieldData implements Cloneable {
private Extension<?, ?> cachedExtension;
private Object value;
/** The serialised values for this object. Will be cleared if getValue is called */
@@ -58,6 +58,23 @@ class FieldData {
unknownFieldData.add(unknownField);
}
+ UnknownFieldData getUnknownField(int index) {
+ if (unknownFieldData == null) {
+ return null;
+ }
+ if (index < unknownFieldData.size()) {
+ return unknownFieldData.get(index);
+ }
+ return null;
+ }
+
+ int getUnknownFieldSize() {
+ if (unknownFieldData == null) {
+ return 0;
+ }
+ return unknownFieldData.size();
+ }
+
<T> T getValue(Extension<?, T> extension) {
if (value != null){
if (cachedExtension != extension) { // Extension objects are singletons.
@@ -170,4 +187,54 @@ class FieldData {
return result;
}
+ @Override
+ public final FieldData clone() {
+ FieldData clone = new FieldData();
+ try {
+ clone.cachedExtension = cachedExtension;
+ if (unknownFieldData == null) {
+ clone.unknownFieldData = null;
+ } else {
+ clone.unknownFieldData.addAll(unknownFieldData);
+ }
+
+ // Whether we need to deep clone value depends on its type. Primitive reference types
+ // (e.g. Integer, Long etc.) are ok, since they're immutable. We need to clone arrays
+ // and messages.
+ if (value == null) {
+ // No cloning required.
+ } else if (value instanceof MessageNano) {
+ clone.value = ((MessageNano) value).clone();
+ } else if (value instanceof byte[]) {
+ clone.value = ((byte[]) value).clone();
+ } else if (value instanceof byte[][]) {
+ byte[][] valueArray = (byte[][]) value;
+ byte[][] cloneArray = new byte[valueArray.length][];
+ clone.value = cloneArray;
+ for (int i = 0; i < valueArray.length; i++) {
+ cloneArray[i] = valueArray[i].clone();
+ }
+ } else if (value instanceof boolean[]) {
+ clone.value = ((boolean[]) value).clone();
+ } else if (value instanceof int[]) {
+ clone.value = ((int[]) value).clone();
+ } else if (value instanceof long[]) {
+ clone.value = ((long[]) value).clone();
+ } else if (value instanceof float[]) {
+ clone.value = ((float[]) value).clone();
+ } else if (value instanceof double[]) {
+ clone.value = ((double[]) value).clone();
+ } else if (value instanceof MessageNano[]) {
+ MessageNano[] valueArray = (MessageNano[]) value;
+ MessageNano[] cloneArray = new MessageNano[valueArray.length];
+ clone.value = cloneArray;
+ for (int i = 0; i < valueArray.length; i++) {
+ cloneArray[i] = valueArray[i].clone();
+ }
+ }
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError(e);
+ }
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/nano/InternalNano.java b/java/src/main/java/com/google/protobuf/nano/InternalNano.java
index 90ca11d..c4adfa5 100644
--- a/java/src/main/java/com/google/protobuf/nano/InternalNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/InternalNano.java
@@ -330,4 +330,12 @@ public final class InternalNano {
return result;
}
+ // This avoids having to make FieldArray public.
+ public static void cloneUnknownFieldData(ExtendableMessageNano original,
+ ExtendableMessageNano cloned) {
+ if (original.unknownFieldData != null) {
+ cloned.unknownFieldData = (FieldArray) original.unknownFieldData.clone();
+ }
+ }
+
}
diff --git a/java/src/main/java/com/google/protobuf/nano/MessageNano.java b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
index d6288c9..ea91b21 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
@@ -31,6 +31,7 @@
package com.google.protobuf.nano;
import java.io.IOException;
+import java.util.Arrays;
/**
* Abstract interface implemented by Protocol Message objects.
@@ -151,6 +152,31 @@ public abstract class MessageNano {
}
/**
+ * Compares two {@code MessageNano}s and returns true if the message's are the same class and
+ * have serialized form equality (i.e. all of the field values are the same).
+ */
+ public static final boolean messageNanoEquals(MessageNano a, MessageNano b) {
+ if (a == b) {
+ return true;
+ }
+ if (a == null || b == null) {
+ return false;
+ }
+ if (a.getClass() != b.getClass()) {
+ return false;
+ }
+ final int serializedSize = a.getSerializedSize();
+ if (b.getSerializedSize() != serializedSize) {
+ return false;
+ }
+ final byte[] aByteArray = new byte[serializedSize];
+ final byte[] bByteArray = new byte[serializedSize];
+ toByteArray(a, aByteArray, 0, serializedSize);
+ toByteArray(b, bByteArray, 0, serializedSize);
+ return Arrays.equals(aByteArray, bByteArray);
+ }
+
+ /**
* Returns a string that is (mostly) compatible with ProtoBuffer's TextFormat. Note that groups
* (which are deprecated) are not serialized with the correct field name.
*
@@ -161,4 +187,12 @@ public abstract class MessageNano {
public String toString() {
return MessageNanoPrinter.print(this);
}
+
+ /**
+ * Provides support for cloning. This only works if you specify the generate_clone method.
+ */
+ @Override
+ public MessageNano clone() throws CloneNotSupportedException {
+ return (MessageNano) super.clone();
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java b/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
index 572a707..a30f2f3 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
@@ -108,6 +108,10 @@ public final class MessageNanoPrinter {
for (Field field : clazz.getFields()) {
int modifiers = field.getModifiers();
String fieldName = field.getName();
+ if ("cachedSize".equals(fieldName)) {
+ // TODO(bduff): perhaps cachedSize should have a more obscure name.
+ continue;
+ }
if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC
&& (modifiers & Modifier.STATIC) != Modifier.STATIC
@@ -243,7 +247,7 @@ public final class MessageNanoPrinter {
builder.append('"');
for (int i = 0; i < bytes.length; ++i) {
- int ch = bytes[i];
+ int ch = bytes[i] & 0xff;
if (ch == '\\' || ch == '"') {
builder.append('\\').append((char) ch);
} else if (ch >= 32 && ch < 127) {
diff --git a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
index 2032e1a..bf34bed 100644
--- a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
+++ b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
@@ -42,6 +42,10 @@ import java.util.Arrays;
final class UnknownFieldData {
final int tag;
+ /**
+ * Important: this should be treated as immutable, even though it's possible
+ * to change the array values.
+ */
final byte[] bytes;
UnknownFieldData(int tag, byte[] bytes) {
diff --git a/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java b/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
index 1ff8f06..a3405e5 100644
--- a/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/WireFormatNano.java
@@ -113,11 +113,7 @@ public final class WireFormatNano {
int arrayLength = 1;
int startPos = input.getPosition();
input.skipField(tag);
- while (input.getBytesUntilLimit() > 0) {
- int thisTag = input.readTag();
- if (thisTag != tag) {
- break;
- }
+ while (input.readTag() == tag) {
input.skipField(tag);
arrayLength++;
}