am 3e0d99fa: Merge "Update MessageNano#toString() to return mostly valid TextFormat."

* commit '3e0d99fab8c1da2a8f36637b7bf5e8581143e36a':
  Update MessageNano#toString() to return mostly valid TextFormat.
pull/91/head
Max Cai 11 years ago committed by Android Git Automerger
commit 5bafef2a49
  1. 5
      java/src/main/java/com/google/protobuf/nano/MessageNano.java
  2. 72
      java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
  3. 47
      java/src/test/java/com/google/protobuf/NanoTest.java

@ -139,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 @Override
public String toString() { public String toString() {

@ -47,20 +47,22 @@ public final class MessageNanoPrinter {
private static final int MAX_STRING_LEN = 200; 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, * <p>Employs Java reflection on the given object and recursively prints primitive fields,
* groups, and messages.</p> * groups, and messages.</p>
*/ */
public static <T extends MessageNano> String print(T message) { public static <T extends MessageNano> String print(T message) {
if (message == null) { if (message == null) {
return "null"; return "";
} }
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
try { try {
print(message.getClass().getSimpleName(), message.getClass(), message, print(null, message.getClass(), message, new StringBuffer(), buf);
new StringBuffer(), buf);
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
return "Error printing proto: " + e.getMessage(); 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. * Function that will print the given message/class into the StringBuffer.
* Meant to be called recursively. * 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, private static void print(String identifier, Class<?> clazz, Object message,
StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException { StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException {
if (MessageNano.class.isAssignableFrom(clazz)) { if (message == null) {
// Nano proto message // This can happen if...
buf.append(indentBuf).append(identifier); // - 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
// If null, just print it and return // the field is unset.
if (message == null) { // In both cases the appropriate behavior is to output nothing.
buf.append(": ").append(message).append("\n"); } else if (MessageNano.class.isAssignableFrom(clazz)) { // Nano proto message
return; 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()) { for (Field field : clazz.getFields()) {
// Proto fields are public, non-static variables that do not begin or end with '_' // Proto fields are public, non-static variables that do not begin or end with '_'
int modifiers = field.getModifiers(); int modifiers = field.getModifiers();
@ -115,15 +126,19 @@ public final class MessageNanoPrinter {
print(fieldName, fieldType, value, indentBuf, buf); print(fieldName, fieldType, value, indentBuf, buf);
} }
} }
indentBuf.delete(indentBuf.length() - INDENT.length(), indentBuf.length()); if (identifier != null) {
buf.append(indentBuf).append(">\n"); indentBuf.setLength(origIndentBufLength);
buf.append(indentBuf).append(">\n");
}
} else { } else {
// Primitive value // Non-null primitive value
identifier = deCamelCaseify(identifier); identifier = deCamelCaseify(identifier);
buf.append(indentBuf).append(identifier).append(": "); buf.append(indentBuf).append(identifier).append(": ");
if (message instanceof String) { if (message instanceof String) {
String stringMessage = sanitizeString((String) message); String stringMessage = sanitizeString((String) message);
buf.append("\"").append(stringMessage).append("\""); buf.append("\"").append(stringMessage).append("\"");
} else if (message instanceof byte[]) {
appendQuotedBytes((byte[]) message, buf);
} else { } else {
buf.append(message); buf.append(message);
} }
@ -176,4 +191,27 @@ public final class MessageNanoPrinter {
} }
return b.toString(); 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('"');
}
} }

@ -2490,14 +2490,14 @@ public class NanoTest extends TestCase {
msg.optionalInt32 = 14; msg.optionalInt32 = 14;
msg.optionalFloat = 42.3f; msg.optionalFloat = 42.3f;
msg.optionalString = "String \"with' both quotes"; 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 = new TestAllTypesNano.OptionalGroup();
msg.optionalGroup.a = 15; msg.optionalGroup.a = 15;
msg.repeatedInt64 = new long[2]; msg.repeatedInt64 = new long[2];
msg.repeatedInt64[0] = 1L; msg.repeatedInt64[0] = 1L;
msg.repeatedInt64[1] = -1L; msg.repeatedInt64[1] = -1L;
msg.repeatedBytes = new byte[2][]; 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 = new TestAllTypesNano.RepeatedGroup[2];
msg.repeatedGroup[0] = new TestAllTypesNano.RepeatedGroup(); msg.repeatedGroup[0] = new TestAllTypesNano.RepeatedGroup();
msg.repeatedGroup[0].a = -27; msg.repeatedGroup[0].a = -27;
@ -2514,28 +2514,31 @@ public class NanoTest extends TestCase {
msg.repeatedNestedEnum = new int[2]; msg.repeatedNestedEnum = new int[2];
msg.repeatedNestedEnum[0] = TestAllTypesNano.BAR; msg.repeatedNestedEnum[0] = TestAllTypesNano.BAR;
msg.repeatedNestedEnum[1] = TestAllTypesNano.FOO; msg.repeatedNestedEnum[1] = TestAllTypesNano.FOO;
msg.repeatedStringPiece = new String[] {null, "world"};
String protoPrint = msg.toString(); String protoPrint = msg.toString();
assertTrue(protoPrint.contains("TestAllTypesNano <")); assertTrue(protoPrint.contains("optional_int32: 14"));
assertTrue(protoPrint.contains(" optional_int32: 14")); assertTrue(protoPrint.contains("optional_float: 42.3"));
assertTrue(protoPrint.contains(" optional_float: 42.3")); assertTrue(protoPrint.contains("optional_double: 0.0"));
assertTrue(protoPrint.contains(" optional_double: 0.0")); assertTrue(protoPrint.contains("optional_string: \"String \\u0022with\\u0027 both quotes\""));
assertTrue(protoPrint.contains(" optional_string: \"String \\u0022with\\u0027 both quotes\"")); assertTrue(protoPrint.contains("optional_bytes: \"\\\"\\000\\001\\010\""));
assertTrue(protoPrint.contains(" optional_bytes: [B@")); assertTrue(protoPrint.contains("optional_group <\n a: 15\n>"));
assertTrue(protoPrint.contains(" optionalGroup <\n a: 15\n >"));
assertTrue(protoPrint.contains("repeated_int64: 1"));
assertTrue(protoPrint.contains(" repeated_int64: 1")); 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: null\n repeated_bytes: [B@")); assertTrue(protoPrint.contains("repeated_bytes: \"hello\""));
assertTrue(protoPrint.contains(" repeatedGroup <\n a: -27\n >\n" assertTrue(protoPrint.contains("repeated_group <\n a: -27\n>\n"
+ " repeatedGroup <\n a: -72\n >")); + "repeated_group <\n a: -72\n>"));
assertTrue(protoPrint.contains(" optionalNestedMessage <\n bb: 7\n >")); assertTrue(protoPrint.contains("optional_nested_message <\n bb: 7\n>"));
assertTrue(protoPrint.contains(" repeatedNestedMessage <\n bb: 77\n >\n" assertTrue(protoPrint.contains("repeated_nested_message <\n bb: 77\n>\n"
+ " repeatedNestedMessage <\n bb: 88\n >")); + "repeated_nested_message <\n bb: 88\n>"));
assertTrue(protoPrint.contains(" optional_nested_enum: 3")); assertTrue(protoPrint.contains("optional_nested_enum: 3"));
assertTrue(protoPrint.contains(" repeated_nested_enum: 2\n repeated_nested_enum: 1")); assertTrue(protoPrint.contains("repeated_nested_enum: 2\nrepeated_nested_enum: 1"));
assertTrue(protoPrint.contains(" default_int32: 41")); assertTrue(protoPrint.contains("default_int32: 41"));
assertTrue(protoPrint.contains(" default_string: \"hello\"")); 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 { public void testExtensions() throws Exception {

Loading…
Cancel
Save