aboutsummaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
Diffstat (limited to 'java')
-rw-r--r--java/README.txt1
-rw-r--r--java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java70
-rw-r--r--java/src/main/java/com/google/protobuf/nano/InternalNano.java216
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNano.java21
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java72
-rw-r--r--java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java24
-rw-r--r--java/src/test/java/com/google/protobuf/NanoTest.java367
7 files changed, 725 insertions, 46 deletions
diff --git a/java/README.txt b/java/README.txt
index 3a004c7e..976ec842 100644
--- a/java/README.txt
+++ b/java/README.txt
@@ -429,6 +429,7 @@ Except:
- Similar rename from CodedOutputStreamMicro to
CodedOutputByteBufferNano.
- Repeated fields are in arrays, not ArrayList or Vector.
+- Full support of serializing/deserializing repeated packed fields.
- Unset messages/groups are null, not an immutable empty default
instance.
- Required fields are always serialized.
diff --git a/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
new file mode 100644
index 00000000..066774d5
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/nano/ExtendableMessageNano.java
@@ -0,0 +1,70 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2013 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.util.ArrayList;
+import java.util.List;
+
+/**
+ * Base class of those Protocol Buffer messages that need to store unknown fields,
+ * such as extensions.
+ */
+public abstract class ExtendableMessageNano extends MessageNano {
+ /**
+ * 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;
+
+ @Override
+ public int getSerializedSize() {
+ int size = WireFormatNano.computeWireSize(unknownFieldData);
+ cachedSize = size;
+ return size;
+ }
+
+ /**
+ * Gets the value stored in the specified extension of this message.
+ */
+ public <T> T getExtension(Extension<T> extension) {
+ return WireFormatNano.getExtension(extension, unknownFieldData);
+ }
+
+ /**
+ * Sets the value of the specified extension of this message.
+ */
+ public <T> void setExtension(Extension<T> extension, T value) {
+ if (unknownFieldData == null) {
+ unknownFieldData = new ArrayList<UnknownFieldData>();
+ }
+ WireFormatNano.setExtension(extension, value, unknownFieldData);
+ }
+} \ No newline at end of file
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 49309516..ce4a206c 100644
--- a/java/src/main/java/com/google/protobuf/nano/InternalNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/InternalNano.java
@@ -31,6 +31,7 @@
package com.google.protobuf.nano;
import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
/**
* The classes contained within are used internally by the Protocol Buffer
@@ -40,7 +41,10 @@ import java.io.UnsupportedEncodingException;
*
* @author kenton@google.com (Kenton Varda)
*/
-public class InternalNano {
+public final class InternalNano {
+
+ private InternalNano() {}
+
/**
* Helper called by generated code to construct default values for string
* fields.
@@ -69,7 +73,7 @@ public class InternalNano {
* converts from the generated string to the string we actually want. The
* generated code calls this automatically.
*/
- public static final String stringDefaultValue(String bytes) {
+ public static String stringDefaultValue(String bytes) {
try {
return new String(bytes.getBytes("ISO-8859-1"), "UTF-8");
} catch (UnsupportedEncodingException e) {
@@ -88,7 +92,7 @@ public class InternalNano {
* In this case we only need the second of the two hacks -- allowing us to
* embed raw bytes as a string literal with ISO-8859-1 encoding.
*/
- public static final byte[] bytesDefaultValue(String bytes) {
+ public static byte[] bytesDefaultValue(String bytes) {
try {
return bytes.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e) {
@@ -103,11 +107,215 @@ public class InternalNano {
* Helper function to convert a string into UTF-8 while turning the
* UnsupportedEncodingException to a RuntimeException.
*/
- public static final byte[] copyFromUtf8(final String text) {
+ public static byte[] copyFromUtf8(final String text) {
try {
return text.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("UTF-8 not supported?");
}
}
+
+ /**
+ * Checks repeated int field equality; null-value and 0-length fields are
+ * considered equal.
+ */
+ public static boolean equals(int[] field1, int[] field2) {
+ if (field1 == null || field1.length == 0) {
+ return field2 == null || field2.length == 0;
+ } else {
+ return Arrays.equals(field1, field2);
+ }
+ }
+
+ /**
+ * Checks repeated long field equality; null-value and 0-length fields are
+ * considered equal.
+ */
+ public static boolean equals(long[] field1, long[] field2) {
+ if (field1 == null || field1.length == 0) {
+ return field2 == null || field2.length == 0;
+ } else {
+ return Arrays.equals(field1, field2);
+ }
+ }
+
+ /**
+ * Checks repeated float field equality; null-value and 0-length fields are
+ * considered equal.
+ */
+ public static boolean equals(float[] field1, float[] field2) {
+ if (field1 == null || field1.length == 0) {
+ return field2 == null || field2.length == 0;
+ } else {
+ return Arrays.equals(field1, field2);
+ }
+ }
+
+ /**
+ * Checks repeated double field equality; null-value and 0-length fields are
+ * considered equal.
+ */
+ public static boolean equals(double[] field1, double[] field2) {
+ if (field1 == null || field1.length == 0) {
+ return field2 == null || field2.length == 0;
+ } else {
+ return Arrays.equals(field1, field2);
+ }
+ }
+
+ /**
+ * Checks repeated boolean field equality; null-value and 0-length fields are
+ * considered equal.
+ */
+ public static boolean equals(boolean[] field1, boolean[] field2) {
+ if (field1 == null || field1.length == 0) {
+ return field2 == null || field2.length == 0;
+ } else {
+ return Arrays.equals(field1, field2);
+ }
+ }
+
+ /**
+ * Checks repeated bytes field equality. Only non-null elements are tested.
+ * Returns true if the two fields have the same sequence of non-null
+ * elements. Null-value fields and fields of any length with only null
+ * elements are considered equal.
+ */
+ public static boolean equals(byte[][] field1, byte[][] field2) {
+ int index1 = 0;
+ int length1 = field1 == null ? 0 : field1.length;
+ int index2 = 0;
+ int length2 = field2 == null ? 0 : field2.length;
+ while (true) {
+ while (index1 < length1 && field1[index1] == null) {
+ index1++;
+ }
+ while (index2 < length2 && field2[index2] == null) {
+ index2++;
+ }
+ boolean atEndOf1 = index1 >= length1;
+ boolean atEndOf2 = index2 >= length2;
+ if (atEndOf1 && atEndOf2) {
+ // no more non-null elements to test in both arrays
+ return true;
+ } else if (atEndOf1 != atEndOf2) {
+ // one of the arrays have extra non-null elements
+ return false;
+ } else if (!Arrays.equals(field1[index1], field2[index2])) {
+ // element mismatch
+ return false;
+ }
+ index1++;
+ index2++;
+ }
+ }
+
+ /**
+ * Checks repeated string/message field equality. Only non-null elements are
+ * tested. Returns true if the two fields have the same sequence of non-null
+ * elements. Null-value fields and fields of any length with only null
+ * elements are considered equal.
+ */
+ public static boolean equals(Object[] field1, Object[] field2) {
+ int index1 = 0;
+ int length1 = field1 == null ? 0 : field1.length;
+ int index2 = 0;
+ int length2 = field2 == null ? 0 : field2.length;
+ while (true) {
+ while (index1 < length1 && field1[index1] == null) {
+ index1++;
+ }
+ while (index2 < length2 && field2[index2] == null) {
+ index2++;
+ }
+ boolean atEndOf1 = index1 >= length1;
+ boolean atEndOf2 = index2 >= length2;
+ if (atEndOf1 && atEndOf2) {
+ // no more non-null elements to test in both arrays
+ return true;
+ } else if (atEndOf1 != atEndOf2) {
+ // one of the arrays have extra non-null elements
+ return false;
+ } else if (!field1[index1].equals(field2[index2])) {
+ // element mismatch
+ return false;
+ }
+ index1++;
+ index2++;
+ }
+ }
+
+ /**
+ * Computes the hash code of a repeated int field. Null-value and 0-length
+ * fields have the same hash code.
+ */
+ public static int hashCode(int[] field) {
+ return field == null || field.length == 0 ? 0 : Arrays.hashCode(field);
+ }
+
+ /**
+ * Computes the hash code of a repeated long field. Null-value and 0-length
+ * fields have the same hash code.
+ */
+ public static int hashCode(long[] field) {
+ return field == null || field.length == 0 ? 0 : Arrays.hashCode(field);
+ }
+
+ /**
+ * Computes the hash code of a repeated float field. Null-value and 0-length
+ * fields have the same hash code.
+ */
+ public static int hashCode(float[] field) {
+ return field == null || field.length == 0 ? 0 : Arrays.hashCode(field);
+ }
+
+ /**
+ * Computes the hash code of a repeated double field. Null-value and 0-length
+ * fields have the same hash code.
+ */
+ public static int hashCode(double[] field) {
+ return field == null || field.length == 0 ? 0 : Arrays.hashCode(field);
+ }
+
+ /**
+ * Computes the hash code of a repeated boolean field. Null-value and 0-length
+ * fields have the same hash code.
+ */
+ public static int hashCode(boolean[] field) {
+ return field == null || field.length == 0 ? 0 : Arrays.hashCode(field);
+ }
+
+ /**
+ * Computes the hash code of a repeated bytes field. Only the sequence of all
+ * non-null elements are used in the computation. Null-value fields and fields
+ * of any length with only null elements have the same hash code.
+ */
+ public static int hashCode(byte[][] field) {
+ int result = 0;
+ for (int i = 0, size = field == null ? 0 : field.length; i < size; i++) {
+ byte[] element = field[i];
+ if (element != null) {
+ result = 31 * result + Arrays.hashCode(element);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Computes the hash code of a repeated string/message field. Only the
+ * sequence of all non-null elements are used in the computation. Null-value
+ * fields and fields of any length with only null elements have the same hash
+ * code.
+ */
+ public static int hashCode(Object[] field) {
+ int result = 0;
+ for (int i = 0, size = field == null ? 0 : field.length; i < size; i++) {
+ Object element = field[i];
+ if (element != null) {
+ result = 31 * result + element.hashCode();
+ }
+ }
+ return result;
+ }
+
}
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 8d4ec39e..0cf8416a 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
@@ -38,6 +38,8 @@ import java.io.IOException;
* @author wink@google.com Wink Saville
*/
public abstract class MessageNano {
+ protected int cachedSize = -1;
+
/**
* Get the number of bytes required to encode this message.
* Returns the cached size or calls getSerializedSize which
@@ -45,14 +47,24 @@ public abstract class MessageNano {
* so the size is only computed once. If a member is modified
* then this could be stale call getSerializedSize if in doubt.
*/
- abstract public int getCachedSize();
+ public int getCachedSize() {
+ if (cachedSize < 0) {
+ // getSerializedSize sets cachedSize
+ getSerializedSize();
+ }
+ return cachedSize;
+ }
/**
* Computes the number of bytes required to encode this message.
* The size is cached and the cached result can be retrieved
* using getCachedSize().
*/
- abstract public int getSerializedSize();
+ public int getSerializedSize() {
+ // This is overridden if the generated message has serialized fields.
+ cachedSize = 0;
+ return 0;
+ }
/**
* Serializes the message and writes it to {@code output}. This does not
@@ -127,7 +139,10 @@ public abstract class MessageNano {
}
/**
- * Intended for debugging purposes only. It does not use ASCII protobuf formatting.
+ * 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.
+ *
+ * <p>This is implemented using reflection, so it is not especially fast.
*/
@Override
public String toString() {
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 3a5ee7c1..d135a514 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
@@ -47,20 +47,22 @@ public final class MessageNanoPrinter {
private static final int MAX_STRING_LEN = 200;
/**
- * Returns an text representation of a MessageNano suitable for debugging.
+ * Returns an text representation of a MessageNano suitable for debugging. The returned string
+ * is mostly compatible with Protocol Buffer's TextFormat (as provided by non-nano protocol
+ * buffers) -- groups (which are deprecated) are output with an underscore name (e.g. foo_bar
+ * instead of FooBar) and will thus not parse.
*
* <p>Employs Java reflection on the given object and recursively prints primitive fields,
* groups, and messages.</p>
*/
public static <T extends MessageNano> String print(T message) {
if (message == null) {
- return "null";
+ return "";
}
StringBuffer buf = new StringBuffer();
try {
- print(message.getClass().getSimpleName(), message.getClass(), message,
- new StringBuffer(), buf);
+ print(null, message.getClass(), message, new StringBuffer(), buf);
} catch (IllegalAccessException e) {
return "Error printing proto: " + e.getMessage();
}
@@ -70,21 +72,30 @@ public final class MessageNanoPrinter {
/**
* Function that will print the given message/class into the StringBuffer.
* Meant to be called recursively.
+ *
+ * @param identifier the identifier to use, or {@code null} if this is the root message to
+ * print.
+ * @param clazz the class of {@code message}.
+ * @param message the value to print. May in fact be a primitive value or byte array and not a
+ * message.
+ * @param indentBuf the indentation each line should begin with.
+ * @param buf the output buffer.
*/
private static void print(String identifier, Class<?> clazz, Object message,
StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException {
- if (MessageNano.class.isAssignableFrom(clazz)) {
- // Nano proto message
- buf.append(indentBuf).append(identifier);
-
- // If null, just print it and return
- if (message == null) {
- buf.append(": ").append(message).append("\n");
- return;
+ if (message == null) {
+ // This can happen if...
+ // - we're about to print a message, String, or byte[], but it not present;
+ // - we're about to print a primitive, but "reftype" optional style is enabled, and
+ // the field is unset.
+ // In both cases the appropriate behavior is to output nothing.
+ } else if (MessageNano.class.isAssignableFrom(clazz)) { // Nano proto message
+ int origIndentBufLength = indentBuf.length();
+ if (identifier != null) {
+ buf.append(indentBuf).append(deCamelCaseify(identifier)).append(" <\n");
+ indentBuf.append(INDENT);
}
- indentBuf.append(INDENT);
- buf.append(" <\n");
for (Field field : clazz.getFields()) {
// Proto fields are public, non-static variables that do not begin or end with '_'
int modifiers = field.getModifiers();
@@ -115,15 +126,19 @@ public final class MessageNanoPrinter {
print(fieldName, fieldType, value, indentBuf, buf);
}
}
- indentBuf.delete(indentBuf.length() - INDENT.length(), indentBuf.length());
- buf.append(indentBuf).append(">\n");
+ if (identifier != null) {
+ indentBuf.setLength(origIndentBufLength);
+ buf.append(indentBuf).append(">\n");
+ }
} else {
- // Primitive value
+ // Non-null primitive value
identifier = deCamelCaseify(identifier);
buf.append(indentBuf).append(identifier).append(": ");
if (message instanceof String) {
String stringMessage = sanitizeString((String) message);
buf.append("\"").append(stringMessage).append("\"");
+ } else if (message instanceof byte[]) {
+ appendQuotedBytes((byte[]) message, buf);
} else {
buf.append(message);
}
@@ -176,4 +191,27 @@ public final class MessageNanoPrinter {
}
return b.toString();
}
+
+ /**
+ * Appends a quoted byte array to the provided {@code StringBuffer}.
+ */
+ private static void appendQuotedBytes(byte[] bytes, StringBuffer builder) {
+ if (bytes == null) {
+ builder.append("\"\"");
+ return;
+ }
+
+ builder.append('"');
+ for (int i = 0; i < bytes.length; ++i) {
+ int ch = bytes[i];
+ if (ch == '\\' || ch == '"') {
+ builder.append('\\').append((char) ch);
+ } else if (ch >= 32 && ch < 127) {
+ builder.append((char) ch);
+ } else {
+ builder.append(String.format("\\%03o", ch));
+ }
+ }
+ builder.append('"');
+ }
}
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 0db2a83c..833ed2a9 100644
--- a/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
+++ b/java/src/main/java/com/google/protobuf/nano/UnknownFieldData.java
@@ -30,6 +30,8 @@
package com.google.protobuf.nano;
+import java.util.Arrays;
+
/**
* Stores unknown fields. These might be extensions or fields that the generated API doesn't
* know about yet.
@@ -37,6 +39,7 @@ package com.google.protobuf.nano;
* @author bduff@google.com (Brian Duff)
*/
public final class UnknownFieldData {
+
final int tag;
final byte[] bytes;
@@ -44,4 +47,25 @@ public final class UnknownFieldData {
this.tag = tag;
this.bytes = 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;
+ }
}
diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java
index 1149c40b..0e1c9bc9 100644
--- a/java/src/test/java/com/google/protobuf/NanoTest.java
+++ b/java/src/test/java/com/google/protobuf/NanoTest.java
@@ -49,6 +49,7 @@ import com.google.protobuf.nano.NanoHasOuterClass.TestAllTypesNanoHas;
import com.google.protobuf.nano.NanoOuterClass;
import com.google.protobuf.nano.NanoOuterClass.TestAllTypesNano;
import com.google.protobuf.nano.NanoReferenceTypes;
+import com.google.protobuf.nano.NanoRepeatedPackables;
import com.google.protobuf.nano.TestRepeatedMergeNano;
import com.google.protobuf.nano.UnittestImportNano;
import com.google.protobuf.nano.UnittestMultipleNano;
@@ -60,6 +61,7 @@ import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
/**
@@ -2489,14 +2491,14 @@ public class NanoTest extends TestCase {
msg.optionalInt32 = 14;
msg.optionalFloat = 42.3f;
msg.optionalString = "String \"with' both quotes";
- msg.optionalBytes = new byte[5];
+ msg.optionalBytes = new byte[] {'"', '\0', 1, 8};
msg.optionalGroup = new TestAllTypesNano.OptionalGroup();
msg.optionalGroup.a = 15;
msg.repeatedInt64 = new long[2];
msg.repeatedInt64[0] = 1L;
msg.repeatedInt64[1] = -1L;
msg.repeatedBytes = new byte[2][];
- msg.repeatedBytes[1] = new byte[5];
+ msg.repeatedBytes[1] = new byte[] {'h', 'e', 'l', 'l', 'o'};
msg.repeatedGroup = new TestAllTypesNano.RepeatedGroup[2];
msg.repeatedGroup[0] = new TestAllTypesNano.RepeatedGroup();
msg.repeatedGroup[0].a = -27;
@@ -2513,28 +2515,31 @@ public class NanoTest extends TestCase {
msg.repeatedNestedEnum = new int[2];
msg.repeatedNestedEnum[0] = TestAllTypesNano.BAR;
msg.repeatedNestedEnum[1] = TestAllTypesNano.FOO;
+ msg.repeatedStringPiece = new String[] {null, "world"};
String protoPrint = msg.toString();
- assertTrue(protoPrint.contains("TestAllTypesNano <"));
- assertTrue(protoPrint.contains(" optional_int32: 14"));
- assertTrue(protoPrint.contains(" optional_float: 42.3"));
- assertTrue(protoPrint.contains(" optional_double: 0.0"));
- assertTrue(protoPrint.contains(" optional_string: \"String \\u0022with\\u0027 both quotes\""));
- assertTrue(protoPrint.contains(" optional_bytes: [B@"));
- assertTrue(protoPrint.contains(" optionalGroup <\n a: 15\n >"));
-
- assertTrue(protoPrint.contains(" repeated_int64: 1"));
- assertTrue(protoPrint.contains(" repeated_int64: -1"));
- assertTrue(protoPrint.contains(" repeated_bytes: null\n repeated_bytes: [B@"));
- assertTrue(protoPrint.contains(" repeatedGroup <\n a: -27\n >\n"
- + " repeatedGroup <\n a: -72\n >"));
- assertTrue(protoPrint.contains(" optionalNestedMessage <\n bb: 7\n >"));
- assertTrue(protoPrint.contains(" repeatedNestedMessage <\n bb: 77\n >\n"
- + " repeatedNestedMessage <\n bb: 88\n >"));
- assertTrue(protoPrint.contains(" optional_nested_enum: 3"));
- assertTrue(protoPrint.contains(" repeated_nested_enum: 2\n repeated_nested_enum: 1"));
- assertTrue(protoPrint.contains(" default_int32: 41"));
- assertTrue(protoPrint.contains(" default_string: \"hello\""));
+ assertTrue(protoPrint.contains("optional_int32: 14"));
+ assertTrue(protoPrint.contains("optional_float: 42.3"));
+ assertTrue(protoPrint.contains("optional_double: 0.0"));
+ assertTrue(protoPrint.contains("optional_string: \"String \\u0022with\\u0027 both quotes\""));
+ assertTrue(protoPrint.contains("optional_bytes: \"\\\"\\000\\001\\010\""));
+ assertTrue(protoPrint.contains("optional_group <\n a: 15\n>"));
+
+ assertTrue(protoPrint.contains("repeated_int64: 1"));
+ assertTrue(protoPrint.contains("repeated_int64: -1"));
+ assertFalse(protoPrint.contains("repeated_bytes: \"\"")); // null should be dropped
+ assertTrue(protoPrint.contains("repeated_bytes: \"hello\""));
+ assertTrue(protoPrint.contains("repeated_group <\n a: -27\n>\n"
+ + "repeated_group <\n a: -72\n>"));
+ assertTrue(protoPrint.contains("optional_nested_message <\n bb: 7\n>"));
+ assertTrue(protoPrint.contains("repeated_nested_message <\n bb: 77\n>\n"
+ + "repeated_nested_message <\n bb: 88\n>"));
+ assertTrue(protoPrint.contains("optional_nested_enum: 3"));
+ assertTrue(protoPrint.contains("repeated_nested_enum: 2\nrepeated_nested_enum: 1"));
+ assertTrue(protoPrint.contains("default_int32: 41"));
+ assertTrue(protoPrint.contains("default_string: \"hello\""));
+ assertFalse(protoPrint.contains("repeated_string_piece: \"\"")); // null should be dropped
+ assertTrue(protoPrint.contains("repeated_string_piece: \"world\""));
}
public void testExtensions() throws Exception {
@@ -2684,6 +2689,222 @@ public class NanoTest extends TestCase {
assertHasWireData(message, false);
}
+ public void testHashCodeEquals() throws Exception {
+ // Complete equality:
+ TestAllTypesNano a = createMessageForHashCodeEqualsTest();
+ TestAllTypesNano aEquivalent = createMessageForHashCodeEqualsTest();
+
+ // Null and empty array for repeated fields equality:
+ TestAllTypesNano b = createMessageForHashCodeEqualsTest();
+ b.repeatedBool = null;
+ b.repeatedFloat = new float[0];
+ TestAllTypesNano bEquivalent = createMessageForHashCodeEqualsTest();
+ bEquivalent.repeatedBool = new boolean[0];
+ bEquivalent.repeatedFloat = null;
+
+ // Ref-element-type repeated fields use non-null subsequence equality:
+ TestAllTypesNano c = createMessageForHashCodeEqualsTest();
+ c.repeatedString = null;
+ c.repeatedStringPiece = new String[] {null, "one", null, "two"};
+ c.repeatedBytes = new byte[][] {{3, 4}, null};
+ TestAllTypesNano cEquivalent = createMessageForHashCodeEqualsTest();
+ cEquivalent.repeatedString = new String[3];
+ cEquivalent.repeatedStringPiece = new String[] {"one", "two", null};
+ cEquivalent.repeatedBytes = new byte[][] {{3, 4}};
+
+ // Complete equality for messages with has fields:
+ TestAllTypesNanoHas d = createMessageWithHasForHashCodeEqualsTest();
+ TestAllTypesNanoHas dEquivalent = createMessageWithHasForHashCodeEqualsTest();
+
+ // If has-fields exist, fields with the same default values but
+ // different has-field values are different.
+ TestAllTypesNanoHas e = createMessageWithHasForHashCodeEqualsTest();
+ e.optionalInt32++; // make different from d
+ e.hasDefaultString = false;
+ TestAllTypesNanoHas eDifferent = createMessageWithHasForHashCodeEqualsTest();
+ eDifferent.optionalInt32 = e.optionalInt32;
+ eDifferent.hasDefaultString = true;
+
+ // Complete equality for messages with accessors:
+ TestNanoAccessors f = createMessageWithAccessorsForHashCodeEqualsTest();
+ TestNanoAccessors fEquivalent = createMessageWithAccessorsForHashCodeEqualsTest();
+
+ // If using accessors, explicitly setting a field to its default value
+ // should make the message different.
+ TestNanoAccessors g = createMessageWithAccessorsForHashCodeEqualsTest();
+ g.setOptionalInt32(g.getOptionalInt32() + 1); // make different from f
+ g.clearDefaultString();
+ TestNanoAccessors gDifferent = createMessageWithAccessorsForHashCodeEqualsTest();
+ gDifferent.setOptionalInt32(g.getOptionalInt32());
+ gDifferent.setDefaultString(g.getDefaultString());
+
+ // Complete equality for reference typed messages:
+ NanoReferenceTypes.TestAllTypesNano h = createRefTypedMessageForHashCodeEqualsTest();
+ NanoReferenceTypes.TestAllTypesNano hEquivalent = createRefTypedMessageForHashCodeEqualsTest();
+
+ // Inequality of null and default value for reference typed messages:
+ NanoReferenceTypes.TestAllTypesNano i = createRefTypedMessageForHashCodeEqualsTest();
+ i.optionalInt32 = 1; // make different from h
+ i.optionalFloat = null;
+ NanoReferenceTypes.TestAllTypesNano iDifferent = createRefTypedMessageForHashCodeEqualsTest();
+ iDifferent.optionalInt32 = i.optionalInt32;
+ iDifferent.optionalFloat = 0.0f;
+
+ HashMap<MessageNano, String> hashMap = new HashMap<MessageNano, String>();
+ hashMap.put(a, "a");
+ hashMap.put(b, "b");
+ hashMap.put(c, "c");
+ hashMap.put(d, "d");
+ hashMap.put(e, "e");
+ hashMap.put(f, "f");
+ hashMap.put(g, "g");
+ hashMap.put(h, "h");
+ hashMap.put(i, "i");
+
+ assertEquals(9, hashMap.size()); // a-i should be different from each other.
+
+ assertEquals("a", hashMap.get(a));
+ assertEquals("a", hashMap.get(aEquivalent));
+
+ assertEquals("b", hashMap.get(b));
+ assertEquals("b", hashMap.get(bEquivalent));
+
+ assertEquals("c", hashMap.get(c));
+ assertEquals("c", hashMap.get(cEquivalent));
+
+ assertEquals("d", hashMap.get(d));
+ assertEquals("d", hashMap.get(dEquivalent));
+
+ assertEquals("e", hashMap.get(e));
+ assertNull(hashMap.get(eDifferent));
+
+ assertEquals("f", hashMap.get(f));
+ assertEquals("f", hashMap.get(fEquivalent));
+
+ assertEquals("g", hashMap.get(g));
+ assertNull(hashMap.get(gDifferent));
+
+ assertEquals("h", hashMap.get(h));
+ assertEquals("h", hashMap.get(hEquivalent));
+
+ assertEquals("i", hashMap.get(i));
+ assertNull(hashMap.get(iDifferent));
+ }
+
+ private TestAllTypesNano createMessageForHashCodeEqualsTest() {
+ TestAllTypesNano message = new TestAllTypesNano();
+ message.optionalInt32 = 5;
+ message.optionalInt64 = 777;
+ message.optionalFloat = 1.0f;
+ message.optionalDouble = 2.0;
+ message.optionalBool = true;
+ message.optionalString = "Hello";
+ message.optionalBytes = new byte[] { 1, 2, 3 };
+ message.optionalNestedMessage = new TestAllTypesNano.NestedMessage();
+ message.optionalNestedMessage.bb = 27;
+ message.optionalNestedEnum = TestAllTypesNano.BAR;
+ message.repeatedInt32 = new int[] { 5, 6, 7, 8 };
+ message.repeatedInt64 = new long[] { 27L, 28L, 29L };
+ message.repeatedFloat = new float[] { 5.0f, 6.0f };
+ message.repeatedDouble = new double[] { 99.1, 22.5 };
+ message.repeatedBool = new boolean[] { true, false, true };
+ message.repeatedString = new String[] { "One", "Two" };
+ message.repeatedBytes = new byte[][] { { 2, 7 }, { 2, 7 } };
+ message.repeatedNestedMessage = new TestAllTypesNano.NestedMessage[] {
+ message.optionalNestedMessage,
+ message.optionalNestedMessage
+ };
+ message.repeatedNestedEnum = new int[] {
+ TestAllTypesNano.BAR,
+ TestAllTypesNano.BAZ
+ };
+ // We set the _nan fields to something other than nan, because equality
+ // is defined for nan such that Float.NaN != Float.NaN, which makes any
+ // instance of TestAllTypesNano unequal to any other instance unless
+ // these fields are set. This is also the behavior of the regular java
+ // generator when the value of a field is NaN.
+ message.defaultFloatNan = 1.0f;
+ message.defaultDoubleNan = 1.0;
+ return message;
+ }
+
+ private TestAllTypesNanoHas createMessageWithHasForHashCodeEqualsTest() {
+ TestAllTypesNanoHas message = new TestAllTypesNanoHas();
+ message.optionalInt32 = 5;
+ message.optionalString = "Hello";
+ message.optionalBytes = new byte[] { 1, 2, 3 };
+ message.optionalNestedMessage = new TestAllTypesNanoHas.NestedMessage();
+ message.optionalNestedMessage.bb = 27;
+ message.optionalNestedEnum = TestAllTypesNano.BAR;
+ message.repeatedInt32 = new int[] { 5, 6, 7, 8 };
+ message.repeatedString = new String[] { "One", "Two" };
+ message.repeatedBytes = new byte[][] { { 2, 7 }, { 2, 7 } };
+ message.repeatedNestedMessage = new TestAllTypesNanoHas.NestedMessage[] {
+ message.optionalNestedMessage,
+ message.optionalNestedMessage
+ };
+ message.repeatedNestedEnum = new int[] {
+ TestAllTypesNano.BAR,
+ TestAllTypesNano.BAZ
+ };
+ message.defaultFloatNan = 1.0f;
+ return message;
+ }
+
+ private TestNanoAccessors createMessageWithAccessorsForHashCodeEqualsTest() {
+ TestNanoAccessors message = new TestNanoAccessors()
+ .setOptionalInt32(5)
+ .setOptionalString("Hello")
+ .setOptionalBytes(new byte[] {1, 2, 3})
+ .setOptionalNestedMessage(new TestNanoAccessors.NestedMessage().setBb(27))
+ .setOptionalNestedEnum(TestNanoAccessors.BAR)
+ .setDefaultFloatNan(1.0f);
+ message.repeatedInt32 = new int[] { 5, 6, 7, 8 };
+ message.repeatedString = new String[] { "One", "Two" };
+ message.repeatedBytes = new byte[][] { { 2, 7 }, { 2, 7 } };
+ message.repeatedNestedMessage = new TestNanoAccessors.NestedMessage[] {
+ message.getOptionalNestedMessage(),
+ message.getOptionalNestedMessage()
+ };
+ message.repeatedNestedEnum = new int[] {
+ TestAllTypesNano.BAR,
+ TestAllTypesNano.BAZ
+ };
+ return message;
+ }
+
+ private NanoReferenceTypes.TestAllTypesNano createRefTypedMessageForHashCodeEqualsTest() {
+ NanoReferenceTypes.TestAllTypesNano message = new NanoReferenceTypes.TestAllTypesNano();
+ message.optionalInt32 = 5;
+ message.optionalInt64 = 777L;
+ message.optionalFloat = 1.0f;
+ message.optionalDouble = 2.0;
+ message.optionalBool = true;
+ message.optionalString = "Hello";
+ message.optionalBytes = new byte[] { 1, 2, 3 };
+ message.optionalNestedMessage =
+ new NanoReferenceTypes.TestAllTypesNano.NestedMessage();
+ message.optionalNestedMessage.foo = 27;
+ message.optionalNestedEnum = NanoReferenceTypes.TestAllTypesNano.BAR;
+ message.repeatedInt32 = new int[] { 5, 6, 7, 8 };
+ message.repeatedInt64 = new long[] { 27L, 28L, 29L };
+ message.repeatedFloat = new float[] { 5.0f, 6.0f };
+ message.repeatedDouble = new double[] { 99.1, 22.5 };
+ message.repeatedBool = new boolean[] { true, false, true };
+ message.repeatedString = new String[] { "One", "Two" };
+ message.repeatedBytes = new byte[][] { { 2, 7 }, { 2, 7 } };
+ message.repeatedNestedMessage =
+ new NanoReferenceTypes.TestAllTypesNano.NestedMessage[] {
+ message.optionalNestedMessage,
+ message.optionalNestedMessage
+ };
+ message.repeatedNestedEnum = new int[] {
+ NanoReferenceTypes.TestAllTypesNano.BAR,
+ NanoReferenceTypes.TestAllTypesNano.BAZ
+ };
+ return message;
+ }
+
public void testNullRepeatedFields() throws Exception {
// Check that serialization after explicitly setting a repeated field
// to null doesn't NPE.
@@ -2746,6 +2967,37 @@ public class NanoTest extends TestCase {
assertEquals(TestAllTypesNano.BAR, message.repeatedPackedNestedEnum[1]);
}
+ public void testNullRepeatedFieldElements() throws Exception {
+ // Check that serialization with null array elements doesn't NPE.
+ String string1 = "1";
+ String string2 = "2";
+ byte[] bytes1 = {3, 4};
+ byte[] bytes2 = {5, 6};
+ TestAllTypesNano.NestedMessage msg1 = new TestAllTypesNano.NestedMessage();
+ msg1.bb = 7;
+ TestAllTypesNano.NestedMessage msg2 = new TestAllTypesNano.NestedMessage();
+ msg2.bb = 8;
+
+ TestAllTypesNano message = new TestAllTypesNano();
+ message.repeatedString = new String[] {null, string1, string2};
+ message.repeatedBytes = new byte[][] {bytes1, null, bytes2};
+ message.repeatedNestedMessage = new TestAllTypesNano.NestedMessage[] {msg1, msg2, null};
+ message.repeatedGroup = new TestAllTypesNano.RepeatedGroup[] {null, null, null};
+
+ byte[] serialized = MessageNano.toByteArray(message); // should not NPE
+ TestAllTypesNano deserialized = MessageNano.mergeFrom(new TestAllTypesNano(), serialized);
+ assertEquals(2, deserialized.repeatedString.length);
+ assertEquals(string1, deserialized.repeatedString[0]);
+ assertEquals(string2, deserialized.repeatedString[1]);
+ assertEquals(2, deserialized.repeatedBytes.length);
+ assertTrue(Arrays.equals(bytes1, deserialized.repeatedBytes[0]));
+ assertTrue(Arrays.equals(bytes2, deserialized.repeatedBytes[1]));
+ assertEquals(2, deserialized.repeatedNestedMessage.length);
+ assertEquals(msg1.bb, deserialized.repeatedNestedMessage[0].bb);
+ assertEquals(msg2.bb, deserialized.repeatedNestedMessage[1].bb);
+ assertEquals(0, deserialized.repeatedGroup.length);
+ }
+
public void testRepeatedMerge() throws Exception {
// Check that merging repeated fields cause the arrays to expand with
// new data.
@@ -2813,6 +3065,77 @@ public class NanoTest extends TestCase {
assertEquals(30, firstContainer.contained.repeatedInt32[2]);
}
+ public void testRepeatedPackables() throws Exception {
+ // Check that repeated fields with packable types can accept both packed and unpacked
+ // serialized forms.
+ NanoRepeatedPackables.NonPacked nonPacked = new NanoRepeatedPackables.NonPacked();
+ nonPacked.int32S = new int[] {1, 2, 3};
+ nonPacked.int64S = new long[] {4, 5, 6};
+ nonPacked.uint32S = new int[] {7, 8, 9};
+ nonPacked.uint64S = new long[] {10, 11, 12};
+ nonPacked.sint32S = new int[] {13, 14, 15};
+ nonPacked.sint64S = new long[] {16, 17, 18};
+ nonPacked.fixed32S = new int[] {19, 20, 21};
+ nonPacked.fixed64S = new long[] {22, 23, 24};
+ nonPacked.sfixed32S = new int[] {25, 26, 27};
+ nonPacked.sfixed64S = new long[] {28, 29, 30};
+ nonPacked.floats = new float[] {31, 32, 33};
+ nonPacked.doubles = new double[] {34, 35, 36};
+ nonPacked.bools = new boolean[] {false, true};
+ nonPacked.enums = new int[] {
+ NanoRepeatedPackables.Enum.OPTION_ONE,
+ NanoRepeatedPackables.Enum.OPTION_TWO,
+ };
+ nonPacked.noise = 13579;
+
+ byte[] nonPackedSerialized = MessageNano.toByteArray(nonPacked);
+
+ NanoRepeatedPackables.Packed packed =
+ MessageNano.mergeFrom(new NanoRepeatedPackables.Packed(), nonPackedSerialized);
+ assertRepeatedPackablesEqual(nonPacked, packed);
+
+ byte[] packedSerialized = MessageNano.toByteArray(packed);
+ // Just a cautious check that the two serialized forms are different,
+ // to make sure the remaining of this test is useful:
+ assertFalse(Arrays.equals(nonPackedSerialized, packedSerialized));
+
+ nonPacked = MessageNano.mergeFrom(new NanoRepeatedPackables.NonPacked(), packedSerialized);
+ assertRepeatedPackablesEqual(nonPacked, packed);
+
+ // Test mixed serialized form.
+ byte[] mixedSerialized = new byte[nonPackedSerialized.length + packedSerialized.length];
+ System.arraycopy(nonPackedSerialized, 0, mixedSerialized, 0, nonPackedSerialized.length);
+ System.arraycopy(packedSerialized, 0,
+ mixedSerialized, nonPackedSerialized.length, packedSerialized.length);
+
+ nonPacked = MessageNano.mergeFrom(new NanoRepeatedPackables.NonPacked(), mixedSerialized);
+ packed = MessageNano.mergeFrom(new NanoRepeatedPackables.Packed(), mixedSerialized);
+ assertRepeatedPackablesEqual(nonPacked, packed);
+ assertTrue(Arrays.equals(new int[] {1, 2, 3, 1, 2, 3}, nonPacked.int32S));
+ assertTrue(Arrays.equals(new int[] {13, 14, 15, 13, 14, 15}, nonPacked.sint32S));
+ assertTrue(Arrays.equals(new int[] {25, 26, 27, 25, 26, 27}, nonPacked.sfixed32S));
+ assertTrue(Arrays.equals(new boolean[] {false, true, false, true}, nonPacked.bools));
+ }
+
+ private void assertRepeatedPackablesEqual(
+ NanoRepeatedPackables.NonPacked nonPacked, NanoRepeatedPackables.Packed packed) {
+ // Not using MessageNano.equals() -- that belongs to a separate test.
+ assertTrue(Arrays.equals(nonPacked.int32S, packed.int32S));
+ assertTrue(Arrays.equals(nonPacked.int64S, packed.int64S));
+ assertTrue(Arrays.equals(nonPacked.uint32S, packed.uint32S));
+ assertTrue(Arrays.equals(nonPacked.uint64S, packed.uint64S));
+ assertTrue(Arrays.equals(nonPacked.sint32S, packed.sint32S));
+ assertTrue(Arrays.equals(nonPacked.sint64S, packed.sint64S));
+ assertTrue(Arrays.equals(nonPacked.fixed32S, packed.fixed32S));
+ assertTrue(Arrays.equals(nonPacked.fixed64S, packed.fixed64S));
+ assertTrue(Arrays.equals(nonPacked.sfixed32S, packed.sfixed32S));
+ assertTrue(Arrays.equals(nonPacked.sfixed64S, packed.sfixed64S));
+ assertTrue(Arrays.equals(nonPacked.floats, packed.floats));
+ assertTrue(Arrays.equals(nonPacked.doubles, packed.doubles));
+ assertTrue(Arrays.equals(nonPacked.bools, packed.bools));
+ assertTrue(Arrays.equals(nonPacked.enums, packed.enums));
+ }
+
private void assertHasWireData(MessageNano message, boolean expected) {
byte[] bytes = MessageNano.toByteArray(message);
int wireLength = bytes.length;