summaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorAndrew Flynn <flynn@google.com>2013-05-28 16:07:32 -0700
committerAndrew Flynn <flynn@google.com>2013-06-04 13:26:58 -0700
commit47107914acbad70ff4db1664d3664ccc994315af (patch)
treeef66f1740d261023f551b45593dbe44622a31705 /java
parentd29f87c79f34ecd6b7889cc49ca9e9d90c413634 (diff)
downloadexternal_protobuf-47107914acbad70ff4db1664d3664ccc994315af.zip
external_protobuf-47107914acbad70ff4db1664d3664ccc994315af.tar.gz
external_protobuf-47107914acbad70ff4db1664d3664ccc994315af.tar.bz2
Add toString() method to MessageNano.
- All of the real work for printing the proto is actually done in MessageNanoPrinter. - Uses reflection to find proto-defined fields and prints those. - Prints all fields, even defaults and nulls. - Also added a simple test to make sure it handles all proto types well. Tried not to make the test too brittle (but hey it's testing a toString() so how flexible can it be) Change-Id: I3e360ef8b0561041e010c1f3445ec45ecdcd2559
Diffstat (limited to 'java')
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNano.java8
-rw-r--r--java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java179
-rw-r--r--java/src/test/java/com/google/protobuf/NanoTest.java54
3 files changed, 241 insertions, 0 deletions
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 66080cc..d6c1e9a 100644
--- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNano.java
@@ -125,4 +125,12 @@ public abstract class MessageNano {
+ "never happen).");
}
}
+
+ /**
+ * Intended for debugging purposes only. It does not use ASCII protobuf formatting.
+ */
+ @Override
+ public String toString() {
+ return MessageNanoPrinter.print(this);
+ }
}
diff --git a/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java b/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
new file mode 100644
index 0000000..d873462
--- /dev/null
+++ b/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java
@@ -0,0 +1,179 @@
+// 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.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+
+/**
+ * Static helper methods for printing nano protos.
+ *
+ * @author flynn@google.com Andrew Flynn
+ */
+public final class MessageNanoPrinter {
+ // Do not allow instantiation
+ private MessageNanoPrinter() {}
+
+ private static final String INDENT = " ";
+ private static final int MAX_STRING_LEN = 200;
+
+ /**
+ * Returns an text representation of a MessageNano suitable for debugging.
+ *
+ * <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";
+ }
+
+ StringBuffer buf = new StringBuffer();
+ try {
+ print(message.getClass().getSimpleName(), message.getClass(), message,
+ new StringBuffer(), buf);
+ } catch (IllegalAccessException e) {
+ return "Error printing proto: " + e.getMessage();
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Function that will print the given message/class into the StringBuffer.
+ * Meant to be called recursively.
+ */
+ 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;
+ }
+
+ 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();
+ String fieldName = field.getName();
+ if ((modifiers & Modifier.PUBLIC) != Modifier.PUBLIC
+ || (modifiers & Modifier.STATIC) == Modifier.STATIC
+ || fieldName.startsWith("_") || fieldName.endsWith("_")) {
+ continue;
+ }
+
+ Class <?> fieldType = field.getType();
+ Object value = field.get(message);
+
+ if (fieldType.isArray()) {
+ Class<?> arrayType = fieldType.getComponentType();
+
+ // bytes is special since it's not repeated, but is represented by an array
+ if (arrayType == byte.class) {
+ print(fieldName, fieldType, value, indentBuf, buf);
+ } else {
+ int len = Array.getLength(value);
+ for (int i = 0; i < len; i++) {
+ Object elem = Array.get(value, i);
+ print(fieldName, arrayType, elem, indentBuf, buf);
+ }
+ }
+ } else {
+ print(fieldName, fieldType, value, indentBuf, buf);
+ }
+ }
+ indentBuf.delete(indentBuf.length() - INDENT.length(), indentBuf.length());
+ buf.append(indentBuf).append(">\n");
+ } else {
+ // 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 {
+ buf.append(message);
+ }
+ buf.append("\n");
+ }
+ }
+
+ /**
+ * Converts an identifier of the format "FieldName" into "field_name".
+ */
+ private static String deCamelCaseify(String identifier) {
+ StringBuffer out = new StringBuffer();
+ for (int i = 0; i < identifier.length(); i++) {
+ char currentChar = identifier.charAt(i);
+ if (i == 0) {
+ out.append(Character.toLowerCase(currentChar));
+ } else if (Character.isUpperCase(currentChar)) {
+ out.append('_').append(Character.toLowerCase(currentChar));
+ } else {
+ out.append(currentChar);
+ }
+ }
+ return out.toString();
+ }
+
+ /**
+ * Shortens and escapes the given string.
+ */
+ private static String sanitizeString(String str) {
+ if (!str.startsWith("http") && str.length() > MAX_STRING_LEN) {
+ // Trim non-URL strings.
+ str = str.substring(0, MAX_STRING_LEN) + "[...]";
+ }
+ return escapeString(str);
+ }
+
+ /**
+ * Escape everything except for low ASCII code points.
+ */
+ private static String escapeString(String str) {
+ int strLen = str.length();
+ StringBuilder b = new StringBuilder(strLen);
+ for (int i = 0; i < strLen; i++) {
+ char original = str.charAt(i);
+ if (original >= ' ' && original <= '~' && original != '"' && original != '\'') {
+ b.append(original);
+ } else {
+ b.append(String.format("\\u%04x", (int) original));
+ }
+ }
+ return b.toString();
+ }
+}
diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java
index 85389d1..da17a9e 100644
--- a/java/src/test/java/com/google/protobuf/NanoTest.java
+++ b/java/src/test/java/com/google/protobuf/NanoTest.java
@@ -2101,4 +2101,58 @@ public class NanoTest extends TestCase {
input.popLimit(limit);
assertEquals(5, input.readRawByte());
}
+
+ // Test a smattering of various proto types for printing
+ public void testMessageNanoPrinter() {
+ TestAllTypesNano msg = new TestAllTypesNano();
+ msg.optionalInt32 = 14;
+ msg.optionalFloat = 42.3f;
+ msg.optionalString = "String \"with' both quotes";
+ msg.optionalBytes = new byte[5];
+ 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.repeatedGroup = new TestAllTypesNano.RepeatedGroup[2];
+ msg.repeatedGroup[0] = new TestAllTypesNano.RepeatedGroup();
+ msg.repeatedGroup[0].a = -27;
+ msg.repeatedGroup[1] = new TestAllTypesNano.RepeatedGroup();
+ msg.repeatedGroup[1].a = -72;
+ msg.optionalNestedMessage = new TestAllTypesNano.NestedMessage();
+ msg.optionalNestedMessage.bb = 7;
+ msg.repeatedNestedMessage = new TestAllTypesNano.NestedMessage[2];
+ msg.repeatedNestedMessage[0] = new TestAllTypesNano.NestedMessage();
+ msg.repeatedNestedMessage[0].bb = 77;
+ msg.repeatedNestedMessage[1] = new TestAllTypesNano.NestedMessage();
+ msg.repeatedNestedMessage[1].bb = 88;
+ msg.optionalNestedEnum = TestAllTypesNano.BAZ;
+ msg.repeatedNestedEnum = new int[2];
+ msg.repeatedNestedEnum[0] = TestAllTypesNano.BAR;
+ msg.repeatedNestedEnum[1] = TestAllTypesNano.FOO;
+
+ 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\""));
+ }
}