Open Source Repository

Home /beanshell/bsh-2.0b4 | Repository Home



bsh/util/ClassBrowser.java
/*****************************************************************************
 *                                                                           *
 *  This file is part of the BeanShell Java Scripting distribution.          *
 *  Documentation and updates may be found at http://www.beanshell.org/      *
 *                                                                           *
 *  Sun Public License Notice:                                               *
 *                                                                           *
 *  The contents of this file are subject to the Sun Public License Version  *
 *  1.0 (the "License"); you may not use this file except in compliance with *
 *  the License. A copy of the License is available at http://www.sun.com    * 
 *                                                                           *
 *  The Original Code is BeanShell. The Initial Developer of the Original    *
 *  Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright     *
 *  (C) 2000.  All Rights Reserved.                                          *
 *                                                                           *
 *  GNU Public License Notice:                                               *
 *                                                                           *
 *  Alternatively, the contents of this file may be used under the terms of  *
 *  the GNU Lesser General Public License (the "LGPL"), in which case the    *
 *  provisions of LGPL are applicable instead of those above. If you wish to *
 *  allow use of your version of this file only under the  terms of the LGPL *
 *  and not to allow others to use your version of this file under the SPL,  *
 *  indicate your decision by deleting the provisions above and replace      *
 *  them with the notice and other provisions required by the LGPL.  If you  *
 *  do not delete the provisions above, a recipient may use your version of  *
 *  this file under either the SPL or the LGPL.                              *
 *                                                                           *
 *  Patrick Niemeyer ([email protected])                                           *
 *  Author of Learning Java, O'Reilly & Associates                           *
 *  http://www.pat.net/~pat/                                                 *
 *                                                                           *
 *****************************************************************************/

package bsh.util;

import java.util.*;
import java.util.zip.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.io.*;
import java.awt.*;
import java.lang.reflect.*;
import java.util.List;

// For string related utils
import bsh.BshClassManager;
import bsh.classpath.BshClassPath;
import bsh.classpath.ClassPathListener;
import bsh.ClassPathException;
import bsh.StringUtil;
import bsh.ConsoleInterface;
import bsh.classpath.ClassManagerImpl;

/**
  A simple class browser for the BeanShell desktop.
*/
public class ClassBrowser extends JSplitPane 
  implements ListSelectionListener, ClassPathListener
{
  BshClassPath classPath;
  BshClassManager classManager;

  // GUI
  JFrame frame;
  JInternalFrame iframe;
  JList classlist, conslist, mlist, fieldlist;
  PackageTree ptree;
  JTextArea methodLine;
  JTree tree;
  // For JList models
  String [] packagesList;
  String [] classesList;
  Constructor [] consList;
  Method [] methodList;
  Field [] fieldList;

  String selectedPackage;
  Class selectedClass;

  private static final Color LIGHT_BLUE = new Color(245,245,255);
  
  public ClassBrowser() {
    this( BshClassManager.createClassManager( null/*interpreter*/ ) );
  }

  public ClassBrowser( BshClassManager classManager ) {
    super( VERTICAL_SPLIT, true );
    this.classManager = classManager;
    
    setBorder(null);
    javax.swing.plaf.SplitPaneUI ui = getUI();
    if(ui instanceof javax.swing.plaf.basic.BasicSplitPaneUI) {
      ((javax.swing.plaf.basic.BasicSplitPaneUI)ui).getDivider()
        .setBorder(null);
    }  
  }

  String [] toSortedStrings ( Collection c ) {
    List l = new ArrayList( c );
    String [] sa = (String[])(l.toArray( new String[0] ));
    return StringUtil.bubbleSort(sa);
  }

  void setClist( String packagename ) {
    this.selectedPackage = packagename;

    Set set = classPath.getClassesForPackage( packagename );
    if ( set == null )
      set = new HashSet();

    // remove inner classes and shorten class names
    List list = new ArrayList();
    Iterator it = set.iterator();
    while (it.hasNext()) {
      String cname = (String)it.next();
      if ( cname.indexOf("$") == -1 )
        list.add( BshClassPath.splitClassname( cname )[1] );
    }

    classesList = toSortedStrings(list);
    classlist.setListData( classesList );
    //setMlist( (String)classlist.getModel().getElementAt(0) );
  }

  String [] parseConstructors( Constructor [] constructors ) {
    String [] sa = new String [ constructors.length ] ;
    for(int i=0; i< sa.length; i++) {
      Constructor con = constructors[i];
      sa[i] = StringUtil.methodString( 
        con.getName(), con.getParameterTypes() );
    }
    //return bubbleSort(sa);
    return sa;
  }  
  
  String [] parseMethods( Method [] methods ) {
    String [] sa = new String [ methods.length ] ;
    for(int i=0; i< sa.length; i++)
      sa[i] = StringUtil.methodString( 
        methods[i].getName(), methods[i].getParameterTypes() );
    //return bubbleSort(sa);
    return sa;
  }

  String [] parseFields( Field[] fields ) {
    String [] sa = new String [ fields.length ] ;
    for(int i=0; i< sa.length; i++) {
      Field f = fields[i];
      sa[i] = f.getName();
    }
    return sa;
  }
  
  Constructor [] getPublicConstructors( Constructor [] constructors ) {
    Vector v = new Vector();
    for(int i=0; i< constructors.length; i++)
      if ( Modifier.isPublic(constructors[i].getModifiers()) )
        v.addElement( constructors[i] );

    Constructor [] ca = new Constructor [ v.size() ];
    v.copyInto( ca );
    return ca;
  }
  
  Method [] getPublicMethods( Method [] methods ) {
    Vector v = new Vector();
    for(int i=0; i< methods.length; i++)
      if ( Modifier.isPublic(methods[i].getModifiers()) )
        v.addElement( methods[i] );

    Method [] ma = new Method [ v.size() ];
    v.copyInto( ma );
    return ma;
  }
  
  Field[] getPublicFields( Field [] fields ) {
    Vector v = new Vector();
    for(int i=0; i< fields.length; i++)
      if ( Modifier.isPublic(fields[i].getModifiers()) )
        v.addElement( fields[i] );

    Field [] fa = new Field [ v.size() ];
    v.copyInto( fa );
    return fa;    
  }

  void setConslist( Class clas ) {
    if ( clas == null ) {
      conslist.setListData( new Object [] { } );
      return;
    }

    consList = getPublicConstructors( clas.getDeclaredConstructors() );
    conslist.setListData( parseConstructors(consList) );
  }  
  
  void setMlist( String classname ) 
  {
    if ( classname == null ) 
    {
      mlist.setListData( new Object [] { } );
      setConslist( null );
      setClassTree( null );
      return;
    }

    Class clas;
    try {
      if ( selectedPackage.equals("<unpackaged>") )
        selectedClass = classManager.classForName( classname );
      else
        selectedClass = classManager.classForName( 
          selectedPackage + "." + classname );
    } catch ( Exception e ) { 
      System.err.println(e);
      return;
    }
    if ( selectedClass == null ) {
      // not found?
      System.err.println("class not found: "+classname);
      return;
    }
    methodList = getPublicMethods( selectedClass.getDeclaredMethods() );
    mlist.setListData( parseMethods(methodList) );
    
    setClassTree( selectedClass );
    setConslist( selectedClass );
    setFieldList( selectedClass );
  }
  
  void setFieldList( Class clas ) {
    if ( clas == null ) {
      fieldlist.setListData( new Object [] { } );
      return;
    }

    fieldList = getPublicFields(clas.getDeclaredFields());
    fieldlist.setListData( parseFields(fieldList) );
  }    

  void setMethodLine( Object method ) {
    methodLine.setText( method==null ? "" : method.toString() );
  }

  void setClassTree( Class clas ) {
    if ( clas == null ) {
      tree.setModel( null );
      return;
    }
      
    MutableTreeNode bottom = null, top = null;
    DefaultMutableTreeNode up;
    do {
      up= new DefaultMutableTreeNode( clas.toString() );
      if ( top != null )
        up.add( top );
      else
        bottom = up;
      top = up;
    } while ( (clas = clas.getSuperclass()) != null );
    tree.setModel( new DefaultTreeModel(top) );

    TreeNode tn = bottom.getParent();
    if ( tn != null ) {
      TreePath tp =  new TreePath (
        ((DefaultTreeModel)tree.getModel()).getPathToRoot( tn ) );
      tree.expandPath( tp );
    }
  }

  JPanel labeledPane( JComponent comp, String label ) {
    JPanel jp = new JPanel( new BorderLayout() );
    jp.add( "Center", comp );
    jp.add( "North", new JLabel(label, SwingConstants.CENTER) );
    return jp;
  }

  public void init() throws ClassPathException 
  {
    // Currently we have to cast because BshClassPath is not known by
    // the core.
    classPath = ((ClassManagerImpl)classManager).getClassPath();

  // maybe add MappingFeedbackListener here... or let desktop if it has
  /*
    classPath.insureInitialized( null 
      // get feedback on mapping...
      new ConsoleInterface() {
        public Reader getIn() { return null; }
        public PrintStream getOut() { return System.out; }
        public PrintStream getErr() { return System.err; }
        public void println( String s ) { System.out.println(s); }
        public void print( String s ) { System.out.print(s); }
        public void print( String s, Color color ) { print( s ); }
        public void error( String s ) { print( s ); }
      }
    );
  */

    classPath.addListener( this );

    Set pset = classPath.getPackagesSet();

    ptree = new PackageTree( pset );
    ptree.addTreeSelectionListener( new TreeSelectionListener() {
      public void valueChanged(TreeSelectionEvent e) {
        TreePath tp = e.getPath();
        Object [] oa = tp.getPath();
        StringBuffer selectedPackage = new StringBuffer();
        for(int i=1; i<oa.length; i++) {
          selectedPackage.append( oa[i].toString() );
          if ( i+1 < oa.length )
            selectedPackage.append(".");
        }
        setClist( selectedPackage.toString() );
      }
    } );

    classlist=new JList();
    classlist.setBackground(LIGHT_BLUE);
    classlist.addListSelectionListener(this);

    conslist = new JList();
    conslist.addListSelectionListener(this);    
    
    mlist = new JList();
    mlist.setBackground(LIGHT_BLUE);
    mlist.addListSelectionListener(this);

    fieldlist = new JList();
    fieldlist.addListSelectionListener(this);

    JSplitPane methodConsPane = splitPane(
      JSplitPane.VERTICAL_SPLIT, true, 
      labeledPane(new JScrollPane(conslist), "Constructors"),
      labeledPane(new JScrollPane(mlist), "Methods")
      );
    
    JSplitPane rightPane = splitPane(JSplitPane.VERTICAL_SPLIT, true,
      methodConsPane,
      labeledPane(new JScrollPane(fieldlist), "Fields")
      );
      
    JSplitPane sp = splitPane( 
      JSplitPane.HORIZONTAL_SPLIT, true, 
      labeledPane(new JScrollPane(classlist), "Classes"),
      rightPane );
    sp = splitPane( 
      JSplitPane.HORIZONTAL_SPLIT, true, 
        labeledPane(new JScrollPane(ptree), "Packages"), sp);

    JPanel bottompanel = new JPanel( new BorderLayout() );
    methodLine = new JTextArea(1,60);
    methodLine.setBackground(LIGHT_BLUE);
    methodLine.setEditable(false);
    methodLine.setLineWrap(true);
    methodLine.setWrapStyleWord(true);
    methodLine.setFont( new Font("Monospaced", Font.BOLD, 14) );
    methodLine.setMargin( new Insets(5,5,5,5) );
    methodLine.setBorder( new MatteBorder(1,0,1,0,
      LIGHT_BLUE.darker().darker()) );
    bottompanel.add("North", methodLine);
    JPanel p = new JPanel( new BorderLayout() );

    tree = new JTree();
    tree.addTreeSelectionListener( new TreeSelectionListener() {
      public void valueChanged(TreeSelectionEvent e) {
        driveToClass( e.getPath().getLastPathComponent().toString() );
      }
    } );

    tree.setBorder( BorderFactory.createRaisedBevelBorder() );
    setClassTree(null);
    p.add( "Center", tree );
    bottompanel.add("Center", p );

    // give it a preferred height
    bottompanel.setPreferredSize(new java.awt.Dimension(150,150));
    
    setTopComponent( sp );
    setBottomComponent( bottompanel );
  }
  
  private JSplitPane splitPane(
    int orientation,
    boolean redraw,
    JComponent c1,
    JComponent c2
  ) {
    JSplitPane sp = new JSplitPane(orientation, redraw, c1, c2);
    sp.setBorder(null);
    javax.swing.plaf.SplitPaneUI ui = sp.getUI();
    if(ui instanceof javax.swing.plaf.basic.BasicSplitPaneUI) {
      ((javax.swing.plaf.basic.BasicSplitPaneUI)ui).getDivider()
        .setBorder(null);
    }
    return sp;
  }

  public static void main( String [] args ) 
    throws Exception
  {
    ClassBrowser cb = new ClassBrowser();
    cb.init();

    JFrame f=new JFrame("BeanShell Class Browser v1.0");
    f.getContentPane().add( "Center", cb );
    cb.setFrame( f );
    f.pack();
    f.setVisible(true);
  }

  public void setFrame( JFrame frame ) {
    this.frame = frame;
  }
  public void setFrame( JInternalFrame frame ) {
    this.iframe = frame;
  }

  public void valueChanged(ListSelectionEvent e) 
  {
    if ( e.getSource() == classlist ) 
    {
      String classname = (String)classlist.getSelectedValue();
      setMlist( classname );

      // hack
      // show the class source in the "method" line...
      String methodLineString;
      if ( classname == null )
        methodLineString = "Package: "+selectedPackage;
      else
      {
        String fullClassName = 
          selectedPackage.equals("<unpackaged>") ?  
            classname : selectedPackage+"."+classname;
        methodLineString = 
          fullClassName
          +" (from "+ classPath.getClassSource( fullClassName ) +")";
      }

      setMethodLine( methodLineString );
    } 
    else 
    if ( e.getSource() == mlist ) 
    {
      int i = mlist.getSelectedIndex();
      if ( i == -1 )
        setMethodLine( null );
      else
        setMethodLine( methodList[i] );
    } 
    else
    if ( e.getSource() == conslist ) 
    {
      int i = conslist.getSelectedIndex();
      if ( i == -1 )
        setMethodLine( null );
      else
        setMethodLine( consList[i] );
    }
    else
    if ( e.getSource() == fieldlist ) 
    {
      int i = fieldlist.getSelectedIndex();
      if ( i == -1 )
        setMethodLine( null );
      else
        setMethodLine( fieldList[i] );
    } 
  }

  // fully qualified classname
  public void driveToClass( String classname ) {
    String [] sa = BshClassPath.splitClassname( classname );
    String packn = sa[0];
    String classn = sa[1];

    // Do we have the package?
    if ( classPath.getClassesForPackage(packn).size()==0 )
      return;

    ptree.setSelectedPackage( packn );

    for(int i=0; i< classesList.length; i++) {
      if ( classesList[i].equals(classn) ) {
        classlist.setSelectedIndex(i);
        classlist.ensureIndexIsVisible(i);
        break;
      }
    }
  }

  public void toFront() {
    if ( frame != null )
      frame.toFront();    
    else
    if ( iframe != null )
      iframe.toFront();    
  }

  class PackageTree extends JTree 
  {
    TreeNode root;
    DefaultTreeModel treeModel;
    Map nodeForPackage = new HashMap();

    PackageTree( Collection packages ) {
      setPackages( packages );

      setRootVisible(false);
      setShowsRootHandles(true);
      setExpandsSelectedPaths(true);

      // open top level paths
      /*
      Enumeration e1=root.children();
      while( e1.hasMoreElements() ) {
        TreePath tp = new TreePath( 
          treeModel.getPathToRoot( (TreeNode)e1.nextElement() ) );
        expandPath( tp );
      }
      */
    }

    public void setPackages( Collection packages ) {
      treeModel = makeTreeModel(packages);
      setModel( treeModel );
    }
    
    DefaultTreeModel makeTreeModel( Collection packages ) 
    {
      Map packageTree = new HashMap();

      Iterator it=packages.iterator();
      while( it.hasNext() ) {
        String pack = (String)(it.next());
        String [] sa = StringUtil.split( pack, "." );
        Map level=packageTree;
        for (int i=0; i< sa.length; i++ ) {
          String name = sa[i];
          Map map=(Map)level.get( name );

          if ( map == null ) {
            map=new HashMap();
            level.put( name, map );
          } 
          level = map;
        }
      }

      root = makeNode( packageTree, "root" );
      mapNodes(root);
      return new DefaultTreeModel( root );
    }


    MutableTreeNode makeNode( Map map, String nodeName ) 
    {
      DefaultMutableTreeNode root = 
        new DefaultMutableTreeNode( nodeName );
      Iterator it=map.keySet().iterator();
      while(it.hasNext() ) {
        String name = (String)it.next();
        Map val = (Map)map.get(name);
        if ( val.size() == 0 ) {
          DefaultMutableTreeNode leaf = 
            new DefaultMutableTreeNode( name );
          root.add( leaf );
        } else {
          MutableTreeNode node = makeNode( val, name );
          root.add( node );
        }
      }
      return root;
    }

    /**
      Map out the location of the nodes by package name.
      Seems like we should be able to do this while we build above...
      I'm tired... just going to do this.
    */
    void mapNodes( TreeNode node ) {
      addNodeMap( node );

      Enumeration e = node.children();
      while(e.hasMoreElements()) {
        TreeNode tn = (TreeNode)e.nextElement();
        mapNodes( tn );
      }
    }

    /**
      map a single node up to the root
    */
    void addNodeMap( TreeNode node ) {

      StringBuffer sb = new StringBuffer();
      TreeNode tn = node;
      while( tn != root ) {
        sb.insert(0, tn.toString() );
        if ( tn.getParent() != root )
          sb.insert(0, "." );
        tn = tn.getParent();
      }
      String pack = sb.toString();

      nodeForPackage.put( pack, node );
    }

    void setSelectedPackage( String pack ) {
      DefaultMutableTreeNode node = 
        (DefaultMutableTreeNode)nodeForPackage.get(pack);
      if ( node == null )
        return;

      TreePath tp = new TreePath(treeModel.getPathToRoot( node ));
      setSelectionPath( tp );
      setClist( pack );

      scrollPathToVisible( tp );
    }

  }

  public void classPathChanged() {
    Set pset = classPath.getPackagesSet();
    ptree.setPackages( pset );
    setClist(null);
  }

}