Java tutorial
//----------------------------------------------------------------------------// // // // 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é 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); } } }