Dumper.java Source code

Java tutorial

Introduction

Here is the source code for Dumper.java

Source

//----------------------------------------------------------------------------//
//                                                                            //
//                                D u m p e r                                 //
//                                                                            //
//  Copyright (C) Herve Bitteur 2000-2009. All rights reserved.               //
//  This software is released under the GNU General Public License.           //
//  Please contact users@audiveris.dev.java.net to report bugs & suggestions. //
//----------------------------------------------------------------------------//
//

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;

/**
 * Class <code>Dumper</code> is a debugging utility that reports, in a brute
 * force manner, any internal data of a class instance.
 *
 * <p> When used on a class instance, all class internal fields which are
 * considered as "relevant" are printed using their toString() method, then we
 * walk up the inheritance tree and repeat the same actions, until there is no
 * more superclass or until the superclass we have reached is considered as
 * non-relevant. </p>
 *
 * <p> A (super)class is considered "relevant" if the static method
 * <code>isClassRelevant(class)</code> returns true. This method can be
 * overridden in a subclass of Dumper to adapt to local needs. </p>
 *
 * <p> A field is considered "relevant" if the following condition if the method
 * <code>isFieldRelevant(field)</code> returns true. Similarly, the behavior of
 * this predicate can be customized by subclassing the Dumper class. </p>
 *
 * <p> There are several kinds of print outs available through subclassing. Each
 * of them export two public methods: <code>dump()</code> which prints the
 * result on default output stream, and <code>dumpOf()</code> which simply
 * returns the generated dump string.
 *
 * <ul> <li> <b>Column</b> a dump with one line per field </li>
 *
 * <li> <b>Row</b> a dump with all information on one row </li>
 *
 * <li> <b>Html</b> an Html stream with fields arranged in tables </li>
 *
 * </ul>
 *
 * Here are some examples of use:
 * <pre>
 * // Using the predefined static helper methods
 * Dumper.dump(myinstance);
 * Dumper.dump(myinstance, "My Title");
 * Dumper.dump(myinstance, "My Title", 2);
 * System.out.println(Dumper.dumpOf(myinstance));
 * System.out.println(Dumper.htmlDumpOf(myinstance));
 *
 *  // Using directly the Dumper subclasses
 * new Dumper.Column(myinstance).print();
 * System.out.println(new Dumper.Row(myinstance).toString());
 * display(new Dumper.Html(myinstance).toString());
 * </pre>
 *
 * @author Herv&eacute; Bitteur
 * @version $Id: Dumper.java,v 1.15 2009/03/03 19:45:51 hbitteur Exp $
 */
public abstract class Dumper {
    //~ Instance fields --------------------------------------------------------

    /**
     * The object to be dumped
     */
    protected final Object obj;

    /**
     * The string buffer used as output
     */
    protected final StringBuffer sb;

    /**
     * Can we use HTML directives?
     */
    protected final boolean useHtml;

    /** Maximum number of collection items printed */
    private final int MAX_COLLECTION_INDEX = 9;

    /**
     * Class (beware, this variable is updated as we walk up the inheritance
     * tree)
     */
    protected Class cl;

    //~ Constructors -----------------------------------------------------------

    /**
     * Creates a new Dumper.
     *
     * @param obj the object instance to be dumped.
     */
    private Dumper(Object obj, boolean useHtml) {
        // (re)Allocate the string buffer
        sb = new StringBuffer(1024);

        // Cache the object & the related class
        this.obj = obj;
        this.useHtml = useHtml;
        cl = obj.getClass();
    }

    //~ Methods ----------------------------------------------------------------

    //-----------------//
    // isClassRelevant //
    //-----------------//
    /**
     * Predicate to determine if a given class is worth being printed. This
     * method could be overridden to reflect customized policy. Note that when
     * walking up the inheritance tree, the browsing is stopped as soon as a
     * non-relevant class is encountered.
     *
     * @param cl the class at stake
     *
     * @return true if found relevant
     */
    public static boolean isClassRelevant(Class cl) {
        //        return (cl != null) && !cl.getName()
        //                                  .startsWith("java.") &&
        //               !cl.getName()
        //                  .startsWith("javax.");
        return (cl != null) && cl.getName().startsWith("omr.");
    }

    //-----------------//
    // isFieldRelevant //
    //-----------------//
    /**
     * Predicate to determine if a given field is worth being printed. This
     * method could be overridden to reflect customized policy.
     *
     * @param field the field at stake
     *
     * @return true if found relevant
     */
    public static boolean isFieldRelevant(Field field) {
        // We don't print static field since the Dumper is meant for instances
        if (Modifier.isStatic(field.getModifiers())) {
            return false;
        }

        // We don't print non-user visible entities
        if (field.getName().indexOf('$') != -1) {
            return false;
        }

        return true;
    }

    //------//
    // dump //
    //------//
    /**
     * Helper function that prints the internal data of an object onto the
     * standard output.
     *
     * @param obj the instance to dump
     */
    public static void dump(Object obj) {
        dump(obj, null, 0);
    }

    //------//
    // dump //
    //------//
    /**
     * Helper function that prints the internal data of an object onto the
     * standard output, with a specified left indentation level.
     *
     * @param obj   the instance to dump
     * @param level the indentation level (0 means no indentation)
     */
    public static void dump(Object obj, int level) {
        dump(obj, null, level);
    }

    //------//
    // dump //
    //------//
    /**
     * Helper function that prints the internal data of an object onto the
     * standard output, with the ability to print a related title
     *
     * @param obj   the object to dump
     * @param title the title to print beforehand
     */
    public static void dump(Object obj, String title) {
        dump(obj, title, 0);
    }

    //------//
    // dump //
    //------//
    /**
     * Helper function that prints the internal data of an object onto the
     * standard output, with room for a title and left indentation.
     *
     * @param obj   the object to dump
     * @param title the title to print beforehand
     * @param level the indentation level (0 for no indent)
     */
    public static void dump(Object obj, String title, int level) {
        new Column(obj, title, level).print();
    }

    //--------//
    // dumpOf //
    //--------//
    /**
     * Helper function that returns a line which contains the whole set of
     * internal data
     *
     * @param obj the object whose data is to be printed
     *
     * @return the string of data values
     */
    public static String dumpOf(Object obj) {
        return new Row(obj).toString();
    }

    //------------//
    // htmlDumpOf //
    //------------//
    /**
     * Helper function that prints a special kind of information string, using
     * HTML tags so that an html editor can easily render this.
     *
     * @param obj the object to dump
     *
     * @return the HTML string
     */
    public static String htmlDumpOf(Object obj) {
        return new Html(obj).toString();
    }

    //-------//
    // print //
    //-------//
    /**
     * Print the dump string onto the standard output
     */
    public void print() {
        System.out.println(toString());
    }

    //----------//
    // toString //
    //----------//
    /**
     * Return the string buffer content
     *
     * @return the dump of the object as a string
     */
    @Override
    public String toString() {
        // Do the processing
        processObject();

        // Return the final content of string buffer
        return sb.toString();
    }

    //------------------//
    // printClassEpilog //
    //------------------//
    /**
     * To be overridden so as to print the epilog of class data
     */
    protected void printClassEpilog() {
    }

    //------------------//
    // printClassProlog //
    //------------------//
    /**
     * To be overridden so as to print the prolog of class data
     */
    protected void printClassProlog() {
    }

    //----------------------//
    // printCollectionValue //
    //----------------------//
    protected void printCollectionValue(Collection col) {
        sb.append("[");

        int i = 0;

        for (Object obj : col) {
            if (i++ > 0) {
                sb.append(useHtml ? ",<br/>" : ",");
            }

            // Safeguard action when the object is a big collection
            if (i > MAX_COLLECTION_INDEX) {
                sb.append(" ... " + col.size() + " items");

                break;
            } else {
                sb.append(obj);
            }
        }

        sb.append("]");
    }

    //------------//
    // printField //
    //------------//
    /**
     * Basic printing of field name and value. The method can of course be
     * overridden.
     *
     * @param name  the field name
     * @param value the field value, which may be null
     */
    protected void printField(String name, Object value) {
        if (value == null) {
            sb.append("null");
        } else {
            if (value instanceof Collection) {
                printCollectionValue((Collection) value);
            } else if (value instanceof Map) {
                printCollectionValue(((Map) value).entrySet());
            } else {
                sb.append(value.toString());
            }
        }
    }

    //--------------//
    // processClass //
    //--------------//
    private void processClass() {
        // Class Prolog
        printClassProlog();

        // Process the class Fields
        for (Field field : cl.getDeclaredFields()) {
            processField(field);
        }

        // Class Epilog
        printClassEpilog();
    }

    //--------------//
    // processField //
    //--------------//
    private void processField(Field field) {
        // Check that we are really interested in printing this field out
        if (isFieldRelevant(field)) {
            // Override any access limitation
            field.setAccessible(true);

            try {
                // Retrieve field value in the object instance
                Object value = field.get(obj);

                // Print the field value as requested
                printField(field.getName(), value);
            } catch (IllegalAccessException ex) {
                // Cannot occur in fact, thanks to setAccessible
            }
        }
    }

    //---------------//
    // processObject //
    //---------------//
    private void processObject() {
        do {
            // Process the class at hand
            processClass();

            // Walk up the inheritance tree
            cl = cl.getSuperclass();
        } while (isClassRelevant(cl));
    }

    //~ Inner Classes ----------------------------------------------------------

    //--------//
    // Column //
    //--------//
    /**
     * Class <code>Column</code> implements a Dumper where all fields are
     * presented in one column, each field on a separate line. The column can be
     * left indented, according to the specified indentation level.
     */
    public static class Column extends Dumper {
        //~ Static fields/initializers -----------------------------------------

        private static final String MEMBER_GAP = "   ";
        private static final String INDENT_GAP = ".  ";

        //~ Instance fields ----------------------------------------------------

        private final String title;
        private final StringBuffer prefix;

        //~ Constructors -------------------------------------------------------

        public Column(Object obj, String title, int level) {
            super(obj, false);

            // Cache the title
            if (title != null) {
                this.title = title;
            } else {
                this.title = "";
            }

            // Prepare indent prefix
            prefix = new StringBuffer(level * INDENT_GAP.length());

            for (int i = level; i > 0; i--) {
                prefix.append(INDENT_GAP);
            }
        }

        //~ Methods ------------------------------------------------------------

        @Override
        protected void printClassProlog() {
            // We print the class name only for the lowest class in
            // heritance hierarchy
            if (obj.getClass() == cl) {
                sb.append("\n");
                sb.append(prefix).append(cl.getName());
                sb.append(" ").append(title).append(":");
            }
        }

        @Override
        protected void printField(String name, Object value) {
            sb.append("\n");
            sb.append(prefix).append(MEMBER_GAP);
            sb.append(name).append("=");
            super.printField(name, value);
        }
    }

    //------//
    // Html //
    //------//
    /**
     * Class <code>Html</code> implements a Dumper using HTML tags to present
     * fields in a table.
     */
    public static class Html extends Dumper {
        //~ Constructors -------------------------------------------------------

        protected Html(Object obj) {
            super(obj, true);
        }

        //~ Methods ------------------------------------------------------------

        @Override
        public String toString() {
            // Style
            sb.append("<style> td {").append(" font-family: Lucida Console, Verdana, sans-serif;")
                    .append(" font-size: 9px;").append(" font-style: normal;").append("} </style>");

            // Table begin
            sb.append("<table border=0 cellpadding=3>");

            // The object
            super.processObject();

            // Table end
            sb.append("</table>");

            // Return the final content of string buffer
            return sb.toString();
        }

        @Override
        protected void printClassProlog() {
            // Class name
            sb.append("<tr><td colspan=2><font color='BLUE'>").append(cl.getName()).append("</font></td></tr>");
        }

        @Override
        protected void printField(String name, Object value) {
            // One table row per field
            sb.append("<tr>");

            // First the field name
            sb.append("<td align='right'><font color='RED'>").append(name).append("</font></td>");

            // Then the field value
            sb.append("<td>");
            super.printField(name, value);

            sb.append("</td>").append("</tr>");
        }
    }

    //-----//
    // Row //
    //-----//
    /**
     * Class <code>Row</code> implements a Dumper where all fields are presented
     * on the same line.
     */
    public static class Row extends Dumper {
        //~ Constructors -------------------------------------------------------

        protected Row(Object obj) {
            super(obj, false);
        }

        //~ Methods ------------------------------------------------------------

        @Override
        protected void printClassEpilog() {
            sb.append("}");
        }

        @Override
        protected void printClassProlog() {
            // Class name
            sb.append("{");

            // Special annotation for superclass
            if (obj.getClass() != cl) {
                sb.append("from ");
            }

            sb.append(cl.getName()).append(":");
        }

        @Override
        protected void printField(String name, Object value) {
            sb.append(" ");
            sb.append(name).append("=");
            super.printField(name, value);
        }
    }
}