package com.vladium.utils; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.text.DecimalFormat; import java.text.NumberFormat; // ---------------------------------------------------------------------------- /** * A Factory for a few stock node visitors. See the implementation for details. * * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003 */ public abstract class ObjectProfileVisitors { // public: ................................................................ /** * Factory method for creating the default plain text node node print visitor. * It is up to the caller to buffer 'out'. * * @param out writer to dump the nodes into [may not be null] * @param indent indent increment string [null is equivalent to " "] * @param format percentage formatter to use [null is equivalent to * NumberFormat.getPercentInstance (), with a single fraction digit] * @param shortClassNames 'true' causes all class names to be dumped in * compact [no package prefix] form */ public static ObjectProfileNode.INodeVisitor newDefaultNodePrinter (final PrintWriter out, final String indent, final DecimalFormat format, final boolean shortClassNames) { return new DefaultNodePrinter (out, indent, format, shortClassNames); } /** * Factory method for creating the XML output visitor. To create a valid * XML document, start the traversal on the profile root node. It is up to * the caller to buffer 'out'. * * @param out stream to dump the nodes into [may not be null] * @param indent indent increment string [null is equivalent to " "] * @param format percentage formatter to use [null is equivalent to * NumberFormat.getPercentInstance (), with a single fraction digit] * @param shortClassNames 'true' causes all class names to be dumped in * compact [no package prefix] form */ public static ObjectProfileNode.INodeVisitor newXMLNodePrinter (final OutputStream out, final String indent, final DecimalFormat format, final boolean shortClassNames) { return new XMLNodePrinter (out, indent, format, shortClassNames); } // protected: ............................................................. // package: ............................................................... // private: ............................................................... private ObjectProfileVisitors () {} // this class is not extendible private static abstract class AbstractProfileNodeVisitor implements IObjectProfileNode.INodeVisitor { public void previsit (final IObjectProfileNode node) { } public void postvisit (final IObjectProfileNode node) { } } // end of nested class /** * This visitor prints out a node in plain text format. The output is * indented according to the length of the node's path within its * profile tree. */ private static final class DefaultNodePrinter extends AbstractProfileNodeVisitor { public void previsit (final IObjectProfileNode node) { final StringBuffer sb = new StringBuffer (); for (int p = 0, pLimit = node.pathlength (); p < pLimit; ++ p) sb.append (m_indent); final IObjectProfileNode root = node.root (); sb.append (node.size ()); if (node != root) // root node is always 100% of the overall size { sb.append (" ("); sb.append (m_format.format ((double) node.size () / root.size ())); sb.append (")"); } sb.append (" -> "); sb.append (node.name ()); if (node.object () != null) // skip shell pseudo-nodes { sb.append (" : "); sb.append (ObjectProfiler.typeName (node.object ().getClass (), m_shortClassNames)); if (node.refcount () > 1) // show refcount only when it's > 1 { sb.append (", refcount="); sb.append (node.refcount ()); } } m_out.println (sb); m_out.flush (); } DefaultNodePrinter (final PrintWriter out, final String indent, final DecimalFormat format, final boolean shortClassNames) { //assert out != null : "null input: out"; m_out = out; m_indent = indent != null ? indent : " "; if (format != null) m_format = format; else { m_format = (DecimalFormat) NumberFormat.getPercentInstance (); m_format.setMaximumFractionDigits (1); } m_shortClassNames = shortClassNames; } private final PrintWriter m_out; private final String m_indent; private final DecimalFormat m_format; private final boolean m_shortClassNames; } // end of nested class /* * This visitor can dump a profile tree in an XML file, which can be handy * for examination of very large object graphs. */ private static final class XMLNodePrinter extends AbstractProfileNodeVisitor { public void previsit (final IObjectProfileNode node) { final IObjectProfileNode root = node.root (); final boolean isRoot = root == node; if (isRoot) { m_out.println ("<?xml version=\"1.0\" encoding=\"" + ENCODING + "\"?>"); m_out.println ("<input>"); } final StringBuffer indent = new StringBuffer (); for (int p = 0, pLimit = node.pathlength (); p < pLimit; ++ p) indent.append (m_indent); final StringBuffer sb = new StringBuffer (); sb.append ("<object"); sb.append (" size=\""); sb.append (node.size ()); sb.append ('\"'); if (! isRoot) { sb.append (" part=\""); sb.append (m_format.format ((double) node.size () / root.size ())); sb.append ('\"'); } sb.append (" name=\""); XMLEscape (node.name (), sb); sb.append ('\"'); if (node.object () != null) // skip shell pseudo-nodes { sb.append (" objclass=\""); XMLEscape (ObjectProfiler.typeName (node.object ().getClass (), m_shortClassNames), sb); sb.append ('\"'); if (node.refcount () > 1) { sb.append (" refcount=\""); sb.append (node.refcount ()); sb.append ('\"'); } } sb.append ('>'); m_out.print (indent); m_out.println (sb); } public void postvisit (final IObjectProfileNode node) { final StringBuffer indent = new StringBuffer (); for (int p = 0, pLimit = node.pathlength (); p < pLimit; ++ p) indent.append (m_indent); m_out.print (indent); m_out.println ("</object>"); if (node.root () == node) { m_out.println ("</input>"); m_out.flush (); } } XMLNodePrinter (final OutputStream out, final String indent, final DecimalFormat format, final boolean shortClassNames) { //assert out != null : "null input: out"; try { m_out = new PrintWriter (new OutputStreamWriter (out, ENCODING)); } catch (UnsupportedEncodingException uee) { throw new Error (uee); } m_indent = indent != null ? indent : " "; if (format != null) m_format = format; else { m_format = (DecimalFormat) NumberFormat.getPercentInstance (); m_format.setMaximumFractionDigits (2); } m_shortClassNames = shortClassNames; } private static void XMLEscape (final String s, final StringBuffer append) { final char [] chars = s.toCharArray (); for (int i = 0, iLimit = s.length (); i < iLimit; ++ i) { final char c = chars [i]; switch (c) { case '<': append.append ("<"); break; case '>': append.append (">"); break; case '"': append.append ("""); break; case '&': append.append ("&"); break; default: append.append (c); } // end of switch } } private final PrintWriter m_out; private final String m_indent; private final DecimalFormat m_format; private final boolean m_shortClassNames; private static final String ENCODING = "UTF-8"; } // end of nested class } // end of class // ----------------------------------------------------------------------------