Java tutorial
//package com.ryanm.util.swing; import java.awt.event.MouseEvent; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.LinkedList; import javax.swing.JTree; import javax.swing.ToolTipManager; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.ExpandVetoException; import javax.swing.tree.TreePath; /** * Allows the user to reflectively inspect an object hierarchy * * @author ryanm */ public class ObjectInspector extends JTree { private boolean showInaccessibleFields = true; private boolean showStaticFields = true; private ObjectNode treeRoot = new ObjectNode(null, true); private DefaultTreeModel treeModel = new DefaultTreeModel(treeRoot); private TreeWillExpandListener expansionListener = new TreeWillExpandListener() { @Override public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException { Object obj = event.getPath().getLastPathComponent(); if (obj instanceof ObjectNode) { ObjectNode on = (ObjectNode) obj; assert !on.root; on.expanded = false; on.refreshValue(on.inspectedObject); } } @Override public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException { Object obj = event.getPath().getLastPathComponent(); if (obj instanceof ObjectNode) { ObjectNode on = (ObjectNode) obj; on.expanded = true; on.buildChildren(); on.refreshTree(on.inspectedObject); treeModel.reload(on); } } }; /** * Builds a new {@link ObjectInspector} * * @param o * The object to inspect * @param showInaccessible * <code>true</code> to display inaccessible fields in * the tree, <code>false</code> to hide them * @param showStatic * <code>true</code> to show static fields, * <code>false</code> to hide them */ public ObjectInspector(Object o, boolean showInaccessible, boolean showStatic) { setModel(treeModel); showInaccessibleFields = showInaccessible; showStaticFields = showStatic; setEditable(false); addTreeWillExpandListener(expansionListener); treeRoot.refreshTree(o); ToolTipManager.sharedInstance().registerComponent(this); } /** * Inspects an object * * @param o * The object to inspect */ public void inspect(Object o) { treeRoot.refreshTree(o); } @Override public String getToolTipText(MouseEvent me) { TreePath pathForLocation = getPathForLocation(me.getX(), me.getY()); if (pathForLocation != null) { Object lastPathComponent = pathForLocation.getLastPathComponent(); if (lastPathComponent instanceof ObjectNode) { ObjectNode on = (ObjectNode) lastPathComponent; return on.tooltip; } } return null; } private class ObjectNode extends DefaultMutableTreeNode { private Object inspectedObject = null; private Field inspectedField = null; private final boolean root; private final boolean accessible; private final boolean primitive; private boolean array = false; private boolean childrenBuilt = false; private TreePath path; private final DefaultMutableTreeNode dummyNode = new DefaultMutableTreeNode("Inspecting..."); private String tooltip; private boolean expanded = false; private ObjectNode(Object inspectedObject, boolean root) { this.root = root; this.inspectedObject = inspectedObject; accessible = true; primitive = false; if (root) { buildChildren(); expanded = true; } } private ObjectNode(Field inspectedField) { root = false; setUserObject(inspectedField.getType().getSimpleName() + " : " + inspectedField.getName()); this.inspectedField = inspectedField; primitive = inspectedField.getType().isPrimitive(); boolean a = false; try { inspectedField.setAccessible(true); a = true; } catch (SecurityException se) { a = false; } accessible = a; if (!primitive && accessible) { insert(dummyNode, 0); } if (!accessible) { setUserObject(inspectedField.getName() + " : Inaccessible"); } tooltip = inspectedField.getType().toString(); } private void refreshTree(Object o) { if (objectTypeChanged(o)) { /* * the object class has changed, we need to change the * tree */ removeAllChildren(); childrenBuilt = false; inspectedObject = o; if (inspectedObject != null) { array = o.getClass().isArray(); if (!primitive && accessible) { insert(dummyNode, getChildCount()); } if (expanded) { buildChildren(); } } else { childrenBuilt = true; } treeModel.nodeStructureChanged(this); } else if (array) { // need to check if the array length has changed int oldCount = getChildCount(); int desiredCount = Array.getLength(o); // may need to add or remove children while (getChildCount() < desiredCount) { ObjectNode on = new ObjectNode(null, false); insert(on, getChildCount()); } while (getChildCount() > desiredCount) { remove(getChildCount() - 1); } if (oldCount != desiredCount) { treeModel.nodeStructureChanged(this); } assert getChildCount() == desiredCount; } inspectedObject = o; if (!root && getChildCount() == 0) { expanded = false; } if (expanded && getChildCount() > 0) { int index = 0; for (Object child : children) { assert child != dummyNode; ObjectNode on = (ObjectNode) child; if (array) { on.refreshTree(Array.get(inspectedObject, index)); } else if (on.accessible) { try { on.refreshTree(on.inspectedField.get(inspectedObject)); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } index++; } } refreshValue(o); } /** * Updates the value of this node * * @param o */ private void refreshValue(Object o) { StringBuilder buff = new StringBuilder(); if (inspectedField != null) { buff.append(inspectedField.getName()); buff.append(" : "); buff.append(inspectedField.getType().getSimpleName()); } else { assert inspectedField == null; if (o != null) { buff.append(o.getClass().getSimpleName()); } else { buff.append("null"); } } if (primitive) { buff.append(" : "); buff.append(o); } else if (!expanded) { buff.append(" : "); buff.append(buildString(o)); } setUserObject(buff.toString()); if (path != null) { path = new TreePath(getPath()); } if (path == null) { path = new TreePath(getPath()); } treeModel.valueForPathChanged(path, getUserObject()); if (o != null) { if (!primitive) { tooltip = o.getClass().getName(); } else { tooltip = inspectedField.getType().getName(); } } else { tooltip = "null"; } } /** * Determines if the object type has changed * * @param o * the new object * @return <code>true</code> if the tree needs to be changed, * false otherwise */ private boolean objectTypeChanged(Object o) { if (inspectedObject == null && o == null) { return false; } else if (inspectedObject == null != (o == null)) { return true; } else if (inspectedObject != null && o != null && !inspectedObject.getClass().equals(o.getClass())) { return true; } return false; } private void buildChildren() { if (!childrenBuilt) { if (children != null && children.contains(dummyNode)) { remove(dummyNode); } if (inspectedObject != null) { if (array) { for (int i = 0; i < Array.getLength(inspectedObject); i++) { ObjectNode on = new ObjectNode(inspectedObject, false); insert(on, getChildCount()); } } else { Collection<Field> fields = new LinkedList<Field>(); getFields(fields, inspectedObject.getClass()); for (Field f : fields) { ObjectNode on = new ObjectNode(f); if ((showInaccessibleFields || on.accessible) && (showStaticFields || !Modifier.isStatic(f.getModifiers()))) { insert(on, getChildCount()); } } } treeModel.nodeStructureChanged(this); } else { setUserObject("null"); } childrenBuilt = true; } } } /** * Recurses up the inheritance chain and collects all the fields * * @param fields * The collection of fields found so far * @param c * The class to get fields from */ private static void getFields(Collection<Field> fields, Class c) { for (Field f : c.getDeclaredFields()) { fields.add(f); } if (c.getSuperclass() != null) { getFields(fields, c.getSuperclass()); } } /** * Attempts to build a nicer looking string than the basic * {@link Object}.toString() * * @param o * The object to build from * @return A descriptive string */ private static String buildString(Object o) { if (o == null) { return "null"; } // first see if there is a version of toString more specific // than that supplied by Object... try { Method m = o.getClass().getMethod("toString"); if (!m.getDeclaringClass().equals(Object.class)) { return o.toString(); } } catch (SecurityException e) { } catch (NoSuchMethodException e) { } // then see if it is an array... if (o.getClass().isArray()) { StringBuilder buff = new StringBuilder(" [ "); for (int i = 0; i < Array.getLength(o); i++) { /* * this could recurse infinitely, but only if the user is * trying to be malicious, like so - Object[] array = new * Object[ 1 ]; array[ 0 ] = array; - which, I'm sure * we'll agree, is and odd thing to do. I say let the * StackOverflowException catch it. */ buff.append(buildString(Array.get(o, i))); buff.append(", "); } if (Array.getLength(o) > 0) { buff.delete(buff.length() - 2, buff.length()); } buff.append(" ]"); return buff.toString(); } return getObjectPosition(o); } /** * Returns a String of an object's position in memory * * @param o * @return The object's memory position */ private static String getObjectPosition(Object o) { String s = o.toString(); s = s.substring(s.lastIndexOf("@")); return s; } }