Allows the user to reflectively inspect an object hierarchy : SuperClass « Reflection « Java






Allows the user to reflectively inspect an object hierarchy

        
//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;
  }
}

   
    
    
    
    
    
    
    
  








Related examples in the same category

1.Getting the Superclass of an Object
2.Superclass of Object is null
3.The superclass of primitive types is always null
4.Although the type of o2 is an interface, getSuperclass() returns the object's superclass
5.Retrieving other information through the class pointer
6.Return true if class a is either equivalent to class b, or if class a is a subclass of class b, i.e. if a either "extends" or "implements" b.