Browse Source

Merge "Add toString() method to MessageNano."

Wink Saville 12 years ago
parent
commit
63d4b5fd57

+ 8 - 0
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);
+    }
 }

+ 179 - 0
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();
+    }
+}

+ 54 - 0
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\""));
+  }
 }