Monday, August 20, 2007

Recursive "toString"

Sometimes for debugging I want to dump an object recursively. That is, I'd like to see not just an Apache Commons Lang-style toString, but a recursive version which descends fields to display their inner bits as well.

Rather than complain, I coded my own. Enjoy!

public static String recursiveToString(final Object o) {
    final StringBuilder buffer = new StringBuilder();

    recursiveToString(new StringBuilder(), o, buffer,
            new HashSet<Object>());

    return buffer.toString();
}

private static void recursiveToString(final StringBuilder prefix,
        final Object o, final StringBuilder buffer,
        final Set<Object> seen) {
    if (null == o) {
        buffer.append("null\n");
        return;
    }

    // Mark back references with angle brackets
    if (seen.contains(o)) {
        buffer.append('<');
        objectToString(o, buffer);
        buffer.append(">\n");
        return;
    }

    seen.add(o);

    objectToString(o, buffer);

    // TODO: More clever to see if protection domain is in the JDK
    if (shouldNotRecurse(o, o.getClass().getPackage().getName())) {
        buffer.append('=');
        buffer.append(o);
        buffer.append('\n');
        return;
    }

    buffer.append("={");
    buffer.append('\n');

    final StringBuilder fieldPrefix = new StringBuilder(prefix);
    fieldPrefix.append("  ");

    for (final Field field : o.getClass().getDeclaredFields()) {
        field.setAccessible(true);
        buffer.append(fieldPrefix);
        buffer.append(field.getName());
        buffer.append('(');
        buffer.append(Modifier.toString(field.getModifiers()));
        buffer.append(")=");

        try {
            recursiveToString(fieldPrefix, field.get(o), buffer, seen);
        } catch (final IllegalAccessException e) {
            buffer.append("? (");
            buffer.append(e.getMessage());
            buffer.append(')');
        }
    }

    buffer.append(prefix);
    buffer.append("}\n");
}

private static boolean shouldNotRecurse(final Object o,
        final String packageName) {
    try {
        return (packageName.startsWith("java.") || packageName
                .startsWith("javax.")) && !Object.class
                .getMethod("toString")
                .equals(o.getClass().getMethod("toString"));
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    }
}

private static void objectToString(final Object o,
        final StringBuilder buffer) {
    buffer.append(o.getClass().getName());
    buffer.append('@');
    buffer.append(Integer.toHexString(System.identityHashCode(o)));
}
Post a Comment