Commit 02a9ea00 authored by Andrew Flynn's avatar Andrew Flynn

Fix MessageNanoPrinter for accessors

accessors mode switches proto fields away from being public fields (which is
how MessageNanoPrinter found which fields to print via reflection). Add a
pass through the methods looking for generated accessor methods to print
those as well.

Change-Id: I7c47853ecbd5534086f44b25a89dbbe56f63ed03
parent b3bc6095
......@@ -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() {
......
......@@ -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) {
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment