瀏覽代碼

Merge "Fix MessageNanoPrinter for accessors"

Andrew Flynn 11 年之前
父節點
當前提交
0c0f8c7ef0

+ 2 - 1
java/src/main/java/com/google/protobuf/nano/MessageNano.java

@@ -142,7 +142,8 @@ public abstract class MessageNano {
      * 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.
+     * <p>This is implemented using reflection, so it is not especially fast nor is it guaranteed
+     * to find all fields if you have method removal turned on for proguard.
      */
     @Override
     public String toString() {

+ 63 - 22
java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java

@@ -32,6 +32,8 @@ package com.google.protobuf.nano;
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 
 /**
@@ -65,6 +67,8 @@ public final class MessageNanoPrinter {
             print(null, message, new StringBuffer(), buf);
         } catch (IllegalAccessException e) {
             return "Error printing proto: " + e.getMessage();
+        } catch (InvocationTargetException e) {
+            return "Error printing proto: " + e.getMessage();
         }
         return buf.toString();
     }
@@ -81,7 +85,8 @@ public final class MessageNanoPrinter {
      * @param buf the output buffer.
      */
     private static void print(String identifier, Object object,
-            StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException {
+            StringBuffer indentBuf, StringBuffer buf) throws IllegalAccessException,
+            InvocationTargetException {
         if (object == null) {
             // This can happen if...
             //   - we're about to print a message, String, or byte[], but it not present;
@@ -94,35 +99,71 @@ public final class MessageNanoPrinter {
                 buf.append(indentBuf).append(deCamelCaseify(identifier)).append(" <\n");
                 indentBuf.append(INDENT);
             }
+            Class<?> clazz = object.getClass();
 
-            for (Field field : object.getClass().getFields()) {
-                // Proto fields are public, non-static variables that do not begin or end with '_'
+            // Proto fields follow one of two formats:
+            //
+            // 1) Public, non-static variables that do not begin or end with '_'
+            // Find and print these using declared public fields
+            for (Field field : clazz.getFields()) {
                 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(object);
+                if ((modifiers & Modifier.PUBLIC) == Modifier.PUBLIC
+                        && (modifiers & Modifier.STATIC) != Modifier.STATIC
+                        && !fieldName.startsWith("_")
+                        && !fieldName.endsWith("_")) {
+                    Class<?> fieldType = field.getType();
+                    Object value = field.get(object);
 
-                if (fieldType.isArray()) {
-                    Class<?> arrayType = fieldType.getComponentType();
+                    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, value, indentBuf, buf);
-                    } else {
-                        int len = value == null ? 0 : Array.getLength(value);
-                        for (int i = 0; i < len; i++) {
-                            Object elem = Array.get(value, i);
-                            print(fieldName, elem, indentBuf, buf);
+                        // bytes is special since it's not repeated, but is represented by an array
+                        if (arrayType == byte.class) {
+                            print(fieldName, value, indentBuf, buf);
+                        } else {
+                            int len = value == null ? 0 : Array.getLength(value);
+                            for (int i = 0; i < len; i++) {
+                                Object elem = Array.get(value, i);
+                                print(fieldName, elem, indentBuf, buf);
+                            }
                         }
+                    } else {
+                        print(fieldName, value, indentBuf, buf);
                     }
-                } else {
-                    print(fieldName, value, indentBuf, buf);
+                }
+            }
+
+            // 2) Fields that are accessed via getter methods (when accessors
+            //    mode is turned on)
+            // Find and print these using getter methods.
+            for (Method method : clazz.getMethods()) {
+                String name = method.getName();
+                // Check for the setter accessor method since getters and hazzers both have
+                // non-proto-field name collisions (hashCode() and getSerializedSize())
+                if (name.startsWith("set")) {
+                    String subfieldName = name.substring(3);
+
+                    Method hazzer = null;
+                    try {
+                        hazzer = clazz.getMethod("has" + subfieldName);
+                    } catch (NoSuchMethodException e) {
+                        continue;
+                    }
+                    // If hazzer does't exist or returns false, no need to continue
+                    if (!(Boolean) hazzer.invoke(object)) {
+                        continue;
+                    }
+
+                    Method getter = null;
+                    try {
+                        getter = clazz.getMethod("get" + subfieldName);
+                    } catch (NoSuchMethodException e) {
+                        continue;
+                    }
+
+                    print(subfieldName, getter.invoke(object), indentBuf, buf);
                 }
             }
             if (identifier != null) {

+ 37 - 2
java/src/test/java/com/google/protobuf/NanoTest.java

@@ -2553,8 +2553,7 @@ public class NanoTest extends TestCase {
     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"));
+    assertTrue(protoPrint.contains("repeated_int64: 1\nrepeated_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"
@@ -2570,6 +2569,42 @@ public class NanoTest extends TestCase {
     assertTrue(protoPrint.contains("repeated_string_piece: \"world\""));
   }
 
+  public void testMessageNanoPrinterAccessors() throws Exception {
+    TestNanoAccessors msg = new TestNanoAccessors();
+    msg.setOptionalInt32(13);
+    msg.setOptionalString("foo");
+    msg.setOptionalBytes(new byte[] {'"', '\0', 1, 8});
+    msg.optionalNestedMessage = new TestNanoAccessors.NestedMessage();
+    msg.optionalNestedMessage.setBb(7);
+    msg.setOptionalNestedEnum(TestNanoAccessors.BAZ);
+    msg.repeatedInt32 = new int[] { 1, -1 };
+    msg.repeatedString = new String[] { "Hello", "world" };
+    msg.repeatedBytes = new byte[2][];
+    msg.repeatedBytes[1] = new byte[] {'h', 'e', 'l', 'l', 'o'};
+    msg.repeatedNestedMessage = new TestNanoAccessors.NestedMessage[2];
+    msg.repeatedNestedMessage[0] = new TestNanoAccessors.NestedMessage();
+    msg.repeatedNestedMessage[0].setBb(5);
+    msg.repeatedNestedMessage[1] = new TestNanoAccessors.NestedMessage();
+    msg.repeatedNestedMessage[1].setBb(6);
+    msg.repeatedNestedEnum = new int[] { TestNanoAccessors.FOO, TestNanoAccessors.BAR };
+    msg.id = 33;
+
+    String protoPrint = msg.toString();
+    assertTrue(protoPrint.contains("optional_int32: 13"));
+    assertTrue(protoPrint.contains("optional_string: \"foo\""));
+    assertTrue(protoPrint.contains("optional_bytes: \"\\\"\\000\\001\\010\""));
+    assertTrue(protoPrint.contains("optional_nested_message <\n  bb: 7\n>"));
+    assertTrue(protoPrint.contains("optional_nested_enum: 3"));
+    assertTrue(protoPrint.contains("repeated_int32: 1\nrepeated_int32: -1"));
+    assertTrue(protoPrint.contains("repeated_string: \"Hello\"\nrepeated_string: \"world\""));
+    assertFalse(protoPrint.contains("repeated_bytes: \"\"")); // null should be dropped
+    assertTrue(protoPrint.contains("repeated_bytes: \"hello\""));
+    assertTrue(protoPrint.contains("repeated_nested_message <\n  bb: 5\n>\n"
+            + "repeated_nested_message <\n  bb: 6\n>"));
+    assertTrue(protoPrint.contains("repeated_nested_enum: 1\nrepeated_nested_enum: 2"));
+    assertTrue(protoPrint.contains("id: 33"));
+  }
+
   public void testExtensions() throws Exception {
     Extensions.ExtendableMessage message = new Extensions.ExtendableMessage();
     message.field = 5;