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 0cf8416aa4..e95c51417c 100644 --- a/java/src/main/java/com/google/protobuf/nano/MessageNano.java +++ b/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. * - *
This is implemented using reflection, so it is not especially fast. + *
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() { 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 c4e123c466..572a7075a0 100644 --- a/java/src/main/java/com/google/protobuf/nano/MessageNanoPrinter.java +++ b/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) { diff --git a/java/src/test/java/com/google/protobuf/NanoTest.java b/java/src/test/java/com/google/protobuf/NanoTest.java index 4b8d63d91c..49348441b9 100644 --- a/java/src/test/java/com/google/protobuf/NanoTest.java +++ b/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;