Reflection Java

//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
   *           true to display inaccessible fields in
   *           the tree, false to hide them
   * @param showStatic
   *           true to show static fields,
   *           false 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 true 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 fields = new LinkedList();
            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 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;
  }
}