add sizeof util
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractProfileNode.java
new file mode 100644
index 0000000..a02c4c9
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractProfileNode.java
@@ -0,0 +1,120 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.LinkedList;
+
+// ----------------------------------------------------------------------------
+/**
+ * Abstract base class for all node implementations in this package.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+abstract class AbstractProfileNode implements IObjectProfileNode, Comparable
+{
+ // public: ................................................................
+
+ // IObjectProfileNode:
+
+ public final int size ()
+ {
+ return m_size;
+ }
+
+ public final IObjectProfileNode parent ()
+ {
+ return m_parent;
+ }
+
+ public final IObjectProfileNode [] path ()
+ {
+ IObjectProfileNode [] path = m_path;
+ if (path != null)
+ return path;
+ else
+ {
+ final LinkedList /* IObjectProfileNode */ _path = new LinkedList ();
+ for (IObjectProfileNode node = this; node != null; node = node.parent ())
+ _path.addFirst (node);
+
+ path = new IObjectProfileNode [_path.size ()];
+ _path.toArray (path);
+
+ m_path = path;
+ return path;
+ }
+ }
+
+ public final IObjectProfileNode root ()
+ {
+ IObjectProfileNode node = this;
+ for (IObjectProfileNode parent = parent (); parent != null;
+ node = parent, parent = parent.parent ());
+
+ return node;
+ }
+
+ public final int pathlength ()
+ {
+ final IObjectProfileNode [] path = m_path;
+ if (path != null)
+ return path.length;
+ else
+ {
+ int result = 0;
+ for (IObjectProfileNode node = this; node != null; node = node.parent ())
+ ++ result;
+
+ return result;
+ }
+ }
+
+ public final String dump ()
+ {
+ final StringWriter sw = new StringWriter ();
+ final PrintWriter out = new PrintWriter (sw);
+
+ final INodeVisitor visitor =
+ ObjectProfileVisitors.newDefaultNodePrinter (out, null, null, ObjectProfiler.SHORT_TYPE_NAMES);
+ traverse (null, visitor);
+
+ out.flush ();
+ return sw.toString ();
+ }
+
+ // Comparable:
+
+ public final int compareTo (final Object obj)
+ {
+ return ((AbstractProfileNode) obj).m_size - m_size;
+ }
+
+ public String toString ()
+ {
+ return super.toString () + ": name = " + name () + ", size = " + size ();
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ AbstractProfileNode (final IObjectProfileNode parent)
+ {
+ m_parent = parent;
+ }
+
+
+ int m_size;
+
+ static final IObjectProfileNode [] EMPTY_OBJECTPROFILENODE_ARRAY = new IObjectProfileNode [0];
+
+ // private: ...............................................................
+
+
+ private final IObjectProfileNode m_parent;
+ private transient IObjectProfileNode [] m_path;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractShellProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractShellProfileNode.java
new file mode 100644
index 0000000..0e9d662
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/AbstractShellProfileNode.java
@@ -0,0 +1,61 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * Abstract base class for all shell pseudo-node implementations in this package.
+ * It is used primarily to lower memory consumption by shell nodes.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+abstract class AbstractShellProfileNode extends AbstractProfileNode
+{
+ // public: ................................................................
+
+ public final Object object ()
+ {
+ return null;
+ }
+
+ public final IObjectProfileNode shell ()
+ {
+ return null;
+ }
+
+ public final IObjectProfileNode [] children ()
+ {
+ return EMPTY_OBJECTPROFILENODE_ARRAY;
+ }
+
+ public final int refcount ()
+ {
+ return 0;
+ }
+
+ public final boolean traverse (final INodeFilter filter, final INodeVisitor visitor)
+ {
+ if ((visitor != null) && ((filter == null) || filter.accept (this)))
+ {
+ visitor.previsit (this);
+ visitor.postvisit (this);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ AbstractShellProfileNode (final IObjectProfileNode parent)
+ {
+ super (parent);
+ }
+
+ // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayIndexLink.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayIndexLink.java
new file mode 100644
index 0000000..74c0389
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayIndexLink.java
@@ -0,0 +1,55 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * An {@link ILink} implementation for tree links created by array fields.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+final class ArrayIndexLink implements ILink
+{
+ // public: ................................................................
+
+ // ILink:
+
+ public String name ()
+ {
+ final StringBuffer s = new StringBuffer ();
+
+ ILink l = this;
+ while (l instanceof ArrayIndexLink)
+ {
+ final ArrayIndexLink asl = (ArrayIndexLink) l;
+
+ s.insert (0, ']');
+ s.insert (0, asl.m_index);
+ s.insert (0, '[');
+
+ l = asl.m_container;
+ }
+
+ s.insert (0, l != null ? l.name () : ObjectProfiler.INPUT_OBJECT_NAME);
+
+ return s.toString ();
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ ArrayIndexLink (final ILink container, final int index)
+ {
+ m_container = container;
+ m_index = index;
+ }
+
+ // private: ...............................................................
+
+
+ private final ILink m_container;
+ private final int m_index;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayShellProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayShellProfileNode.java
new file mode 100644
index 0000000..3b14679
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ArrayShellProfileNode.java
@@ -0,0 +1,39 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * A shell pseudo-node implementation for an array class.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+final class ArrayShellProfileNode extends AbstractShellProfileNode
+{
+ // public: ................................................................
+
+ public String name ()
+ {
+ return "<shell: " + ObjectProfiler.typeName (m_type, ObjectProfiler.SHORT_TYPE_NAMES) + ", length=" + m_length + ">";
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ ArrayShellProfileNode (final IObjectProfileNode parent, final Class type, final int length)
+ {
+ super (parent);
+
+ m_type = type;
+ m_length = length;
+ }
+
+ // private: ...............................................................
+
+
+ private final Class m_type;
+ private final int m_length;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ClassFieldLink.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ClassFieldLink.java
new file mode 100644
index 0000000..de76e16
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ClassFieldLink.java
@@ -0,0 +1,39 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+import java.lang.reflect.Field;
+
+// ----------------------------------------------------------------------------
+/**
+ * An {@link ILink} implementation for tree links created by class instance fields.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+final class ClassFieldLink implements ILink
+{
+ // public: ................................................................
+
+ // ILink:
+
+ public String name ()
+ {
+ return ObjectProfiler.fieldName (m_field, ObjectProfiler.SHORT_TYPE_NAMES);
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ ClassFieldLink (final Field field)
+ {
+ m_field = field;
+ }
+
+ // private: ...............................................................
+
+
+ private final Field m_field;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ILink.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ILink.java
new file mode 100644
index 0000000..28fb1bc
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ILink.java
@@ -0,0 +1,23 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * Interface used internally for memory-efficient representations of names
+ * of profile tree links between profile tree nodes.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+interface ILink
+{
+ // public: ................................................................
+
+ /**
+ * Returns the string that will be used for a {@link IObjectProfileNode#name()}
+ * implementation. It is expected that the implementation will generate
+ * the return on every call to this method and not keep in memory.
+ */
+ String name ();
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/IObjectProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/IObjectProfileNode.java
new file mode 100644
index 0000000..5ed7807
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/IObjectProfileNode.java
@@ -0,0 +1,179 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * The main interface for exploring an object profile tree. See individual methods
+ * for details.
+ *
+ * @see ObjectProfiler#profile(Object)
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+public
+interface IObjectProfileNode
+{
+ // public: ................................................................
+
+ /**
+ * A generic interface for defining node filters. A node filter is used
+ * as a guard to determine whether a given visitor should be given a shot
+ * as doing something with a profile tree node.
+ */
+ interface INodeFilter
+ {
+ /**
+ * @param node about to be visited [never null]
+ * @return 'true' if 'node' and its children should be visited
+ */
+ boolean accept (IObjectProfileNode node);
+
+ } // end of nested interface
+
+ /**
+ * A generic interface for defining node visitors. A node visitor is applied
+ * to a profile tree node both before and after visiting the node's children,
+ * if any.
+ */
+ interface INodeVisitor
+ {
+ /**
+ * Pre-order visit.
+ *
+ * @param node being visited [never null]
+ */
+ void previsit (IObjectProfileNode node);
+
+ /**
+ * Post-order visit.
+ *
+ * @param node being visited [never null]
+ */
+ void postvisit (IObjectProfileNode node);
+
+ } // end of nested interface
+
+
+ /**
+ * Returns the object associated with this node. This is never null except
+ * for shell pseudo-nodes.
+ *
+ * @return object instance [null only for shell nodes]
+ */
+ Object object ();
+
+ /**
+ * Returns a human-readable name for this node, usually derived from the
+ * class field or array index that was used to reach the {@link #object() object}
+ * associated with this node.
+ *
+ * @return node name [never null]
+ */
+ String name ();
+
+ /**
+ * Returns the full size (in bytes) assigned to this node in its profile
+ * tree. This is the sum of sizes of the node class's {@link #shell() shell}
+ * and its non-primitive non-null {@link #children() instance fields},
+ * computed as a closure over the spanning tree produced by {@link ObjectProfiler#profile}.
+ *
+ * @return total node size [always positive]
+ */
+ int size ();
+ /**
+ * Returns the reference count for the associated {@link #object()}. This is
+ * exactly the number of unique references to this object in the object
+ * graph submitted to {@link ObjectProfiler#profile}.
+ *
+ * @return reference count [always positive]
+ */
+ int refcount ();
+
+ /**
+ * Returns the assigned ownership parent for this node. This is null for the
+ * root node.
+ *
+ * @return parent node [null only for the root node]
+ */
+ IObjectProfileNode parent ();
+ /**
+ * Returns all children of this node. These are non-null references found
+ * in this object's class fields (or array slots if the object is of an
+ * array type). The result is sorted in decreasing {@link #size() size} order.<P>
+ *
+ * Note: the returned array also contains the {@link #shell() shell} pseudo-node.
+ *
+ * @return array of children nodes, sorted by size [never null, may be empty]
+ */
+ IObjectProfileNode [] children ();
+ /**
+ * Returns the shell pseudo-node for this node. This represents all instance
+ * data fields that are "inlined" in the class definition represented by
+ * this node (including all superclasses all the way to java.lang.Object).
+ * This includes primitive data fields, object references representing
+ * non-primitive fields, and (for arrays) the array length field and storage
+ * required for the array slots.<P>
+ *
+ * Another way to describe this is that node.shell().size() is the minimum
+ * size an instance of node.object().getClass() can be (when all non-primitive
+ * instance fields are set to 'null').<P>
+ *
+ * The returned reference is also guaranteed to be present somewhere in
+ * the array returned by {@link #children()}. This data is kept in a
+ * separate node instance to simplify tree visiting and node filtering.
+ *
+ * @return shell pseudo-node [null only for shell nodes]
+ */
+ IObjectProfileNode shell ();
+
+ /**
+ * Returns the full path from the profile tree root to this node, in that
+ * direction. The result includes the root node as well as the current node.<P>
+ *
+ * Invariant: node.root() == node.path()[0]
+ * Invariant: node.path()[node.path().length - 1] == node
+ * Invariant: node.path().length == node.pathlength()
+ *
+ * @return node tree path [never null/empty]
+ */
+ IObjectProfileNode [] path ();
+ /**
+ * A convenience method for retrieving the root node from any node in a profile
+ * tree.<P>
+ *
+ * Invariant: node.root() == node iff 'node' is the root of its profile tree
+ * Invariant: node.root() == node.path()[0]
+ *
+ * @return the root node for the profile tree that the current node is
+ * a part of [never null]
+ */
+ IObjectProfileNode root ();
+ /**
+ * A convenience method for retrieving this node's tree path length.
+ *
+ * @return path length [always positive]
+ */
+ int pathlength ();
+
+ /**
+ * A generic hook for traversing profile trees using {@link INodeFilter filters}
+ * and {@link INodeVisitor visitors}. See IObjectProfileNode.INodeFilter and
+ * IObjectProfileNode.INodeVisitor for more details
+ *
+ * @param filter [null is equivalent to no filtering]
+ * @param visitor [may not be null]
+ *
+ * @return 'true' iff either 'filter' was null or it returned 'true' for this node
+ */
+ boolean traverse (INodeFilter filter, INodeVisitor visitor);
+
+ /**
+ * Dumps this node into a flat-text format used by the {@link ObjectProfileVisitors#newDefaultNodePrinter()}
+ * default node visitor.
+ *
+ * @return indented dump string [could be very large]
+ */
+ String dump ();
+
+} // end of interface
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileFilters.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileFilters.java
new file mode 100644
index 0000000..ff1d6e7
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileFilters.java
@@ -0,0 +1,166 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * A Factory for a few stock node filters. 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 ObjectProfileFilters
+{
+ // public: ................................................................
+
+ /**
+ * Factory method for creating a visitor that only accepts profile nodes
+ * with sizes larger than a given threshold value.
+ *
+ * @param threshold node size in bytes
+ */
+ public static ObjectProfileNode.INodeFilter newSizeFilter (final int threshold)
+ {
+ return new SizeFilter (threshold);
+ }
+
+ /**
+ * Factory method for creating a visitor that accepts a profile node only if
+ * it is at least the k-th largest child of its parent for a given value
+ * of k. E.g., newRankFilter(1) will prune the profile tree so that only the
+ * largest child is visited for every node.
+ *
+ * @param rank acceptable size rank [must be >= 0]
+ */
+ public static ObjectProfileNode.INodeFilter newRankFilter (final int rank)
+ {
+ return new RankFilter (rank);
+ }
+
+ /**
+ * Factory method for creating a visitor that accepts a profile node only if
+ * its size is larger than a given threshold relative to the size of the root
+ * node (i.e., size of the entire profile tree).
+ *
+ * @param threshold size fraction threshold
+ */
+ public static ObjectProfileNode.INodeFilter newSizeFractionFilter (final double threshold)
+ {
+ return new SizeFractionFilter (threshold);
+ }
+
+ /**
+ * Factory method for creating a visitor that accepts a profile node only if
+ * its size is larger than a given threshold relative to the size of its
+ * parent node. This is useful for pruning the profile tree to show the
+ * largest contributors at every tree level.
+ *
+ * @param threshold size fraction threshold
+ */
+ public static ObjectProfileNode.INodeFilter newParentSizeFractionFilter (final double threshold)
+ {
+ return new ParentSizeFractionFilter (threshold);
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+ // private: ...............................................................
+
+
+ private ObjectProfileFilters () {} // this class is not extendible
+
+
+ private static final class SizeFilter implements IObjectProfileNode.INodeFilter
+ {
+ public boolean accept (final IObjectProfileNode node)
+ {
+ return node.size () >= m_threshold;
+ }
+
+
+ SizeFilter (final int threshold)
+ {
+ m_threshold = threshold;
+ }
+
+
+ private final int m_threshold;
+
+ } // end of nested class
+
+
+ private static final class RankFilter implements IObjectProfileNode.INodeFilter
+ {
+ public boolean accept (final IObjectProfileNode node)
+ {
+ final IObjectProfileNode parent = node.parent ();
+ if (parent == null) return true;
+
+ final IObjectProfileNode [] siblings = parent.children ();
+ for (int r = 0, rLimit = Math.min (siblings.length, m_threshold); r< rLimit; ++ r)
+ if (siblings [r] == node) return true;
+
+ return false;
+ }
+
+
+ RankFilter (final int threshold)
+ {
+ m_threshold = threshold;
+ }
+
+
+ private final int m_threshold;
+
+ } // end of nested class
+
+
+ private static final class SizeFractionFilter implements IObjectProfileNode.INodeFilter
+ {
+ public boolean accept (final IObjectProfileNode node)
+ {
+ if (node.size () >= m_threshold * node.root ().size ())
+ return true;
+ else
+ return false;
+ }
+
+
+ SizeFractionFilter (final double threshold)
+ {
+ m_threshold = threshold;
+ }
+
+
+ private final double m_threshold;
+
+ } // end of nested class
+
+
+ private static final class ParentSizeFractionFilter implements IObjectProfileNode.INodeFilter
+ {
+ public boolean accept (final IObjectProfileNode node)
+ {
+ final IObjectProfileNode parent = node.parent ();
+ if (parent == null)
+ return true; // always accept root node
+ else if (node.size () >= m_threshold * parent.size ())
+ return true;
+ else
+ return false;
+ }
+
+
+ ParentSizeFractionFilter (final double threshold)
+ {
+ m_threshold = threshold;
+ }
+
+
+ private final double m_threshold;
+
+ } // end of nested class
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileNode.java
new file mode 100644
index 0000000..33c9bd2
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileNode.java
@@ -0,0 +1,134 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+import java.util.Arrays;
+
+// ----------------------------------------------------------------------------
+/**
+ * A non-shell profile tree node implementation. This implementation trades off
+ * some object orientation "niceness" to achieve more memory compactness.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+final class ObjectProfileNode extends AbstractProfileNode
+{
+ // public: ................................................................
+
+ public Object object ()
+ {
+ return m_obj;
+ }
+
+ public String name ()
+ {
+ return m_link == null ? ObjectProfiler.INPUT_OBJECT_NAME : m_link.name ();
+ }
+
+ public IObjectProfileNode shell ()
+ {
+ return m_shell;
+ }
+
+ public IObjectProfileNode [] children ()
+ {
+ return m_children;
+ }
+
+ public int refcount ()
+ {
+ return m_refcount;
+ }
+
+ public boolean traverse (final INodeFilter filter, final INodeVisitor visitor)
+ {
+ if ((visitor != null) && ((filter == null) || filter.accept (this)))
+ {
+ visitor.previsit (this);
+
+ final IObjectProfileNode [] children = m_children;
+ for (int i = 0; i < children.length; ++ i)
+ {
+ children [i].traverse (filter, visitor);
+ }
+
+ visitor.postvisit (this);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+ /*
+ * This method manages the vector in m_children field for an unfinished node.
+ */
+ void addFieldRef (final IObjectProfileNode node)
+ {
+ // [m_size is the child count]
+
+ IObjectProfileNode [] children = m_children;
+ final int childrenLength = children.length;
+ if (m_size >= childrenLength)
+ {
+ final IObjectProfileNode [] newchildren = new IObjectProfileNode [Math.max (1, childrenLength << 1)];
+ System.arraycopy (children, 0, newchildren, 0, childrenLength);
+ m_children = children = newchildren;
+ }
+ children [m_size ++] = node;
+ }
+
+ /*
+ * This method is called once on every node to lock it down into its
+ * immutable and most compact representation during phase 2 of profile
+ * tree construction.
+ */
+ void finish ()
+ {
+ final int childCount = m_size; // m_size is the child count for a non-shell node
+ if (childCount > 0)
+ {
+ if (childCount < m_children.length)
+ {
+ final IObjectProfileNode [] newadj = new IObjectProfileNode [childCount];
+ System.arraycopy (m_children, 0, newadj, 0, childCount);
+
+ m_children = newadj;
+ }
+
+ Arrays.sort (m_children);
+
+ int size = 0;
+ for (int i = 0; i < childCount; ++ i)
+ {
+ size += m_children [i].size ();
+ }
+ m_size = size; // m_size is the full node size for all nodes
+ }
+ }
+
+
+ ObjectProfileNode (final ObjectProfileNode parent, final Object obj, final ILink link)
+ {
+ super (parent);
+
+ m_obj = obj;
+ m_link = link;
+ m_refcount = 1;
+ m_children = EMPTY_OBJECTPROFILENODE_ARRAY;
+ }
+
+
+ final ILink m_link;
+ final Object m_obj;
+ int m_refcount;
+ AbstractShellProfileNode m_shell;
+ IObjectProfileNode [] m_children;
+
+ // private: ...............................................................
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileVisitors.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileVisitors.java
new file mode 100644
index 0000000..35daf78
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfileVisitors.java
@@ -0,0 +1,298 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+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
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfiler.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfiler.java
new file mode 100644
index 0000000..9f32b71
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectProfiler.java
@@ -0,0 +1,619 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+// ----------------------------------------------------------------------------
+/**
+ * This non-instantiable class presents an API for object sizing and profiling
+ * as described in the article. See individual methods for details.<P>
+ *
+ * This implementation is J2SE 1.4+ only. You would need to code your own
+ * identity hashmap to port this to earlier Java versions.<P>
+ *
+ * Security: this implementation uses AccessController.doPrivileged() so it
+ * could be granted privileges to access non-public class fields separately
+ * from your main application code. The minimum set of persmissions necessary
+ * for this class to function correctly follows:
+ * <pre>
+ * permission java.lang.RuntimePermission "accessDeclaredMembers";
+ * permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
+ * </pre>
+ *
+ * @see IObjectProfileNode
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+public
+abstract class ObjectProfiler
+{
+ // public: ................................................................
+
+ // the following constants are physical sizes (in bytes) and are JVM-dependent:
+ // [the current values are Ok for most 32-bit JVMs]
+
+ public static final int OBJECT_SHELL_SIZE = 8; // java.lang.Object shell size in bytes
+ public static final int OBJREF_SIZE = 4;
+
+ public static final int LONG_FIELD_SIZE = 8;
+ public static final int INT_FIELD_SIZE = 4;
+ public static final int SHORT_FIELD_SIZE = 2;
+ public static final int CHAR_FIELD_SIZE = 2;
+ public static final int BYTE_FIELD_SIZE = 1;
+ public static final int BOOLEAN_FIELD_SIZE = 1;
+ public static final int DOUBLE_FIELD_SIZE = 8;
+ public static final int FLOAT_FIELD_SIZE = 4;
+
+
+ /** Set this to 'true' to make the node names default to using
+ * class names without package prefixing for more compact dumps */
+ public static final boolean SHORT_TYPE_NAMES = false;
+
+ /** If this is 'true', node names will use short class names
+ * for common classes in java.lang.* and java.util.*, regardless
+ * of {@link #SHORT_TYPE_NAMES} setting. */
+ public static final boolean SHORT_COMMON_TYPE_NAMES = true;
+
+
+ /**
+ * Estimates the full size of the object graph rooted at 'obj'.
+ * Duplicate data instances are correctly accounted for. The implementation
+ * is not recursive.<P>
+ *
+ * Invariant: sizeof(obj) == profile(obj).size() if 'obj' is not null
+ *
+ * @param obj input object instance to be measured
+ * @return 'obj' size [0 if 'obj' is null']
+ */
+ public static int sizeof (final Object obj)
+ {
+ if (obj == null) return 0;
+
+ final IdentityHashMap visited = new IdentityHashMap ();
+
+ return computeSizeof (obj, visited, CLASS_METADATA_CACHE);
+ }
+
+ /**
+ * Estimates the full size of the object graph rooted at 'obj' by
+ * pre-populating the "visited" set with the object graph rooted
+ * at 'base'. The net effect is to compute the size of 'obj' by summing
+ * over all instance data contained in 'obj' but not in 'base'.
+ *
+ * @param base graph boundary [may not be null]
+ * @param obj input object instance to be measured
+ * @return 'obj' size [0 if 'obj' is null']
+ */
+ public static int sizedelta (final Object base, final Object obj)
+ {
+ if (obj == null) return 0;
+ if (base == null) throw new IllegalArgumentException ("null input: base");
+
+ final IdentityHashMap visited = new IdentityHashMap ();
+
+ computeSizeof (base, visited, CLASS_METADATA_CACHE);
+ return visited.containsKey (obj) ? 0 : computeSizeof (obj, visited, CLASS_METADATA_CACHE);
+ }
+
+ /**
+ * Creates a spanning tree representation for instance data contained in
+ * 'obj'. The tree is produced using bread-first traversal over the full
+ * object graph implied by non-null instance and array references originating
+ * in 'obj'.
+ *
+ * @see IObjectProfileNode
+ *
+ * @param obj input object instance to be profiled [may not be null]
+ * @return the profile tree root node [never null]
+ */
+ public static IObjectProfileNode profile (final Object obj)
+ {
+ if (obj == null) throw new IllegalArgumentException ("null input: obj");
+
+ final IdentityHashMap visited = new IdentityHashMap ();
+
+ final ObjectProfileNode root = createProfileTree (obj, visited, CLASS_METADATA_CACHE);
+ finishProfileTree (root);
+
+ return root;
+ }
+
+
+ // convenience methods:
+
+ public static String pathName (final IObjectProfileNode [] path)
+ {
+ final StringBuffer s = new StringBuffer ();
+
+ for (int i = 0; i < path.length; ++ i)
+ {
+ if (i != 0) s.append ('/');
+ s.append (path [i].name ());
+ }
+
+ return s.toString ();
+ }
+
+ public static String fieldName (final Field field, final boolean shortClassNames)
+ {
+ return typeName (field.getDeclaringClass (), shortClassNames).concat ("#").concat (field.getName ());
+ }
+
+ public static String typeName (Class cls, final boolean shortClassNames)
+ {
+ int dims = 0;
+ for ( ; cls.isArray (); ++ dims) cls = cls.getComponentType ();
+
+ String clsName = cls.getName ();
+
+ if (shortClassNames)
+ {
+ final int lastDot = clsName.lastIndexOf ('.');
+ if (lastDot >= 0) clsName = clsName.substring (lastDot + 1);
+ }
+ else if (SHORT_COMMON_TYPE_NAMES)
+ {
+ if (clsName.startsWith ("java.lang.")) clsName = clsName.substring (10);
+ else if (clsName.startsWith ("java.util.")) clsName = clsName.substring (10);
+ }
+
+ for (int i = 0; i < dims; ++ i) clsName = clsName.concat ("[]");
+
+ return clsName;
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ static final String INPUT_OBJECT_NAME = "<INPUT>"; // root node name
+
+ // private: ...............................................................
+
+
+ /*
+ * Internal class used to cache class metadata information.
+ */
+ private static final class ClassMetadata
+ {
+ ClassMetadata (final int primitiveFieldCount,
+ final int shellSize,
+ final Field [] refFields)
+ {
+ m_primitiveFieldCount = primitiveFieldCount;
+ m_shellSize = shellSize;
+ m_refFields = refFields;
+ }
+
+ // all fields are inclusive of superclasses:
+
+ final int m_primitiveFieldCount;
+ final int m_shellSize; // class shell size
+ final Field [] m_refFields; // cached non-static fields (made accessible)
+
+ } // end of nested class
+
+
+ private static final class ClassAccessPrivilegedAction
+ implements PrivilegedExceptionAction
+ {
+ public Object run () throws Exception
+ {
+ return m_cls.getDeclaredFields ();
+ }
+
+ void setContext (final Class cls)
+ {
+ m_cls = cls;
+ }
+
+
+ private Class m_cls;
+
+ } // end of nested class
+
+
+ private static final class FieldAccessPrivilegedAction
+ implements PrivilegedExceptionAction
+ {
+ public Object run () throws Exception
+ {
+ m_field.setAccessible (true);
+
+ return null;
+ }
+
+ void setContext (final Field field)
+ {
+ m_field = field;
+ }
+
+
+ private Field m_field;
+
+ } // end of nested class
+
+
+ private ObjectProfiler () {} // this class is not extendible
+
+ /*
+ * The main worker method for sizeof() and sizedelta().
+ */
+ private static int computeSizeof (Object obj,
+ final IdentityHashMap visited,
+ final Map /* Class->ClassMetadata */ metadataMap)
+ {
+ // this uses depth-first traversal; the exact graph traversal algorithm
+ // does not matter for computing the total size and this method could be
+ // easily adjusted to do breadth-first instead (addLast() instead of addFirst()),
+ // however, dfs/bfs require max queue length to be the length of the longest
+ // graph path/width of traversal front correspondingly, so I expect
+ // dfs to use fewer resources than bfs for most Java objects;
+
+ if (obj == null) return 0;
+
+ final LinkedList queue = new LinkedList ();
+
+ visited.put (obj, obj);
+ queue.add (obj);
+
+ int result = 0;
+
+ final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction ();
+ final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction ();
+
+ while (! queue.isEmpty ())
+ {
+ obj = queue.removeFirst ();
+ final Class objClass = obj.getClass ();
+
+ if (objClass.isArray ())
+ {
+ final int arrayLength = Array.getLength (obj);
+ final Class componentType = objClass.getComponentType ();
+
+ result += sizeofArrayShell (arrayLength, componentType);
+
+ if (! componentType.isPrimitive ())
+ {
+ // traverse each array slot:
+ for (int i = 0; i < arrayLength; ++ i)
+ {
+ final Object ref = Array.get (obj, i);
+
+ if ((ref != null) && ! visited.containsKey (ref))
+ {
+ visited.put (ref, ref);
+ queue.addFirst (ref);
+ }
+ }
+ }
+ }
+ else // the object is of a non-array type
+ {
+ final ClassMetadata metadata = getClassMetadata (objClass, metadataMap, caAction, faAction);
+ final Field [] fields = metadata.m_refFields;
+
+ result += metadata.m_shellSize;
+
+ // traverse all non-null ref fields:
+
+ for (int f = 0, fLimit = fields.length; f < fLimit; ++ f)
+ {
+ final Field field = fields [f];
+
+ final Object ref;
+ try // to get the field value:
+ {
+ ref = field.get (obj);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException ("cannot get field [" + field.getName () + "] of class [" + field.getDeclaringClass ().getName () + "]: " + e.toString ());
+ }
+
+ if ((ref != null) && ! visited.containsKey (ref))
+ {
+ visited.put (ref, ref);
+ queue.addFirst (ref);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+
+ /*
+ * Performs phase 1 of profile creation: bread-first traversal and node
+ * creation.
+ */
+ private static ObjectProfileNode createProfileTree (Object obj,
+ final IdentityHashMap visited,
+ final Map /* Class->ClassMetadata */ metadataMap)
+ {
+ final ObjectProfileNode root = new ObjectProfileNode (null, obj, null);
+
+ final LinkedList queue = new LinkedList ();
+
+ queue.addFirst (root);
+ visited.put (obj, root);
+
+ final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction ();
+ final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction ();
+
+ while (! queue.isEmpty ())
+ {
+ final ObjectProfileNode node = (ObjectProfileNode) queue.removeFirst ();
+
+ obj = node.m_obj;
+ final Class objClass = obj.getClass ();
+
+ if (objClass.isArray ())
+ {
+ final int arrayLength = Array.getLength (obj);
+ final Class componentType = objClass.getComponentType ();
+
+ // add shell pseudo-node:
+ final AbstractShellProfileNode shell = new ArrayShellProfileNode (node, objClass, arrayLength);
+ shell.m_size = sizeofArrayShell (arrayLength, componentType);
+
+ node.m_shell = shell;
+ node.addFieldRef (shell);
+
+ if (! componentType.isPrimitive ())
+ {
+ // traverse each array slot:
+ for (int i = 0; i < arrayLength; ++ i)
+ {
+ final Object ref = Array.get (obj, i);
+
+ if (ref != null)
+ {
+ ObjectProfileNode child = (ObjectProfileNode) visited.get (ref);
+ if (child != null)
+ ++ child.m_refcount;
+ else
+ {
+ child = new ObjectProfileNode (node, ref, new ArrayIndexLink (node.m_link, i));
+ node.addFieldRef (child);
+
+ queue.addLast (child);
+ visited.put (ref, child);
+ }
+ }
+ }
+ }
+ }
+ else // the object is of a non-array type
+ {
+ final ClassMetadata metadata = getClassMetadata (objClass, metadataMap, caAction, faAction);
+ final Field [] fields = metadata.m_refFields;
+
+ // add shell pseudo-node:
+ final AbstractShellProfileNode shell = new ObjectShellProfileNode (node, metadata.m_primitiveFieldCount, metadata.m_refFields.length);
+ shell.m_size = metadata.m_shellSize;
+
+ node.m_shell = shell;
+ node.addFieldRef (shell);
+
+ // traverse all non-null ref fields:
+ for (int f = 0, fLimit = fields.length; f < fLimit; ++ f)
+ {
+ final Field field = fields [f];
+
+ final Object ref;
+ try // to get the field value:
+ {
+ ref = field.get (obj);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException ("cannot get field [" + field.getName () + "] of class [" + field.getDeclaringClass ().getName () + "]: " + e.toString ());
+ }
+
+ if (ref != null)
+ {
+ ObjectProfileNode child = (ObjectProfileNode) visited.get (ref);
+ if (child != null)
+ ++ child.m_refcount;
+ else
+ {
+ child = new ObjectProfileNode (node, ref, new ClassFieldLink (field));
+ node.addFieldRef (child);
+
+ queue.addLast (child);
+ visited.put (ref, child);
+ }
+ }
+ }
+ }
+ }
+
+ return root;
+ }
+
+
+ /*
+ * Performs phase 2 of profile creation: totalling of node sizes (via
+ * non-recursive post-order traversal of the tree created in phase 1)
+ * and 'locking down' of profile nodes into their most compact form.
+ */
+ private static void finishProfileTree (ObjectProfileNode node)
+ {
+ final LinkedList queue = new LinkedList ();
+ IObjectProfileNode lastFinished = null;
+
+ while (node != null)
+ {
+ // note that an unfinished non-shell node has its child count
+ // in m_size and m_children[0] is its shell node:
+
+ if ((node.m_size == 1) || (lastFinished == node.m_children [1]))
+ {
+ node.finish ();
+ lastFinished = node;
+ }
+ else
+ {
+ queue.addFirst (node);
+ for (int i = 1; i < node.m_size; ++ i)
+ {
+ final IObjectProfileNode child = node.m_children [i];
+ queue.addFirst (child);
+ }
+ }
+
+ if (queue.isEmpty ())
+ return;
+ else
+ node = (ObjectProfileNode) queue.removeFirst ();
+ }
+ }
+
+ /*
+ * A helper method for manipulating a class metadata cache.
+ */
+ private static ClassMetadata getClassMetadata (final Class cls,
+ final Map /* Class->ClassMetadata */ metadataMap,
+ final ClassAccessPrivilegedAction caAction,
+ final FieldAccessPrivilegedAction faAction)
+ {
+ if (cls == null) return null;
+
+ ClassMetadata result;
+ synchronized (metadataMap)
+ {
+ result = (ClassMetadata) metadataMap.get (cls);
+ }
+ if (result != null) return result;
+
+ int primitiveFieldCount = 0;
+ int shellSize = OBJECT_SHELL_SIZE; // java.lang.Object shell
+ final List /* Field */ refFields = new LinkedList ();
+
+ final Field [] declaredFields;
+ try
+ {
+ caAction.setContext (cls);
+ declaredFields = (Field []) AccessController.doPrivileged (caAction);
+ }
+ catch (PrivilegedActionException pae)
+ {
+ throw new RuntimeException ("could not access declared fields of class " + cls.getName () + ": " + pae.getException ());
+ }
+
+ for (int f = 0; f < declaredFields.length; ++ f)
+ {
+ final Field field = declaredFields [f];
+ if ((Modifier.STATIC & field.getModifiers ()) != 0) continue;
+
+ final Class fieldType = field.getType ();
+ if (fieldType.isPrimitive ())
+ {
+ // memory alignment ignored:
+ shellSize += sizeofPrimitiveType (fieldType);
+ ++ primitiveFieldCount;
+ }
+ else
+ {
+ // prepare for graph traversal later:
+ if (! field.isAccessible ())
+ {
+ try
+ {
+ faAction.setContext (field);
+ AccessController.doPrivileged (faAction);
+ }
+ catch (PrivilegedActionException pae)
+ {
+ throw new RuntimeException ("could not make field " + field + " accessible: " + pae.getException ());
+ }
+ }
+
+ // memory alignment ignored:
+ shellSize += OBJREF_SIZE;
+ refFields.add (field);
+ }
+ }
+
+ // recurse into superclass:
+ final ClassMetadata superMetadata = getClassMetadata (cls.getSuperclass (), metadataMap, caAction, faAction);
+ if (superMetadata != null)
+ {
+ primitiveFieldCount += superMetadata.m_primitiveFieldCount;
+ shellSize += superMetadata.m_shellSize - OBJECT_SHELL_SIZE;
+ refFields.addAll (Arrays.asList (superMetadata.m_refFields));
+ }
+
+ final Field [] _refFields = new Field [refFields.size ()];
+ refFields.toArray (_refFields);
+
+ result = new ClassMetadata (primitiveFieldCount, shellSize, _refFields);
+ synchronized (metadataMap)
+ {
+ metadataMap.put (cls, result);
+ }
+
+ return result;
+ }
+
+
+ /*
+ * Computes the "shallow" size of an array instance.
+ */
+ private static int sizeofArrayShell (final int length, final Class componentType)
+ {
+ // this ignores memory alignment issues by design:
+
+ final int slotSize = componentType.isPrimitive ()
+ ? sizeofPrimitiveType (componentType)
+ : OBJREF_SIZE;
+
+ return OBJECT_SHELL_SIZE + INT_FIELD_SIZE + OBJREF_SIZE + length * slotSize;
+ }
+
+ /*
+ * Returns the JVM-specific size of a primitive type.
+ */
+ private static int sizeofPrimitiveType (final Class type)
+ {
+ if (type == int.class)
+ return INT_FIELD_SIZE;
+ else if (type == long.class)
+ return LONG_FIELD_SIZE;
+ else if (type == short.class)
+ return SHORT_FIELD_SIZE;
+ else if (type == byte.class)
+ return BYTE_FIELD_SIZE;
+ else if (type == boolean.class)
+ return BOOLEAN_FIELD_SIZE;
+ else if (type == char.class)
+ return CHAR_FIELD_SIZE;
+ else if (type == double.class)
+ return DOUBLE_FIELD_SIZE;
+ else if (type == float.class)
+ return FLOAT_FIELD_SIZE;
+ else
+ throw new IllegalArgumentException ("not primitive: " + type);
+ }
+
+
+ // class metadata cache:
+ private static final Map CLASS_METADATA_CACHE = new WeakHashMap (101);
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file
diff --git a/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectShellProfileNode.java b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectShellProfileNode.java
new file mode 100644
index 0000000..1a257ee
--- /dev/null
+++ b/spring-tutorial/src/main/java/com/supwisdom/tutorial/sizeof/ObjectShellProfileNode.java
@@ -0,0 +1,38 @@
+
+package com.supwisdom.tutorial.sizeof;
+
+// ----------------------------------------------------------------------------
+/**
+ * A shell pseudo-node implementation for a non-array class.
+ *
+ * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad Roubtsov</a>, 2003
+ */
+final class ObjectShellProfileNode extends AbstractShellProfileNode
+{
+ // public: ................................................................
+
+ public String name ()
+ {
+ return "<shell: " + m_primitiveFieldCount + " prim/" + m_refFieldCount + " ref fields>";
+ }
+
+ // protected: .............................................................
+
+ // package: ...............................................................
+
+
+ ObjectShellProfileNode (final IObjectProfileNode parent, final int primitiveFieldCount, final int refFieldCount)
+ {
+ super (parent);
+
+ m_primitiveFieldCount = primitiveFieldCount;
+ m_refFieldCount = refFieldCount;
+ }
+
+ // private: ...............................................................
+
+
+ private final int m_primitiveFieldCount, m_refFieldCount;
+
+} // end of class
+// ----------------------------------------------------------------------------
\ No newline at end of file