001: /*****************************************************************************
002: * *
003: * This file is part of the BeanShell Java Scripting distribution. *
004: * Documentation and updates may be found at http://www.beanshell.org/ *
005: * *
006: * Sun Public License Notice: *
007: * *
008: * The contents of this file are subject to the Sun Public License Version *
009: * 1.0 (the "License"); you may not use this file except in compliance with *
010: * the License. A copy of the License is available at http://www.sun.com *
011: * *
012: * The Original Code is BeanShell. The Initial Developer of the Original *
013: * Code is Pat Niemeyer. Portions created by Pat Niemeyer are Copyright *
014: * (C) 2000. All Rights Reserved. *
015: * *
016: * GNU Public License Notice: *
017: * *
018: * Alternatively, the contents of this file may be used under the terms of *
019: * the GNU Lesser General Public License (the "LGPL"), in which case the *
020: * provisions of LGPL are applicable instead of those above. If you wish to *
021: * allow use of your version of this file only under the terms of the LGPL *
022: * and not to allow others to use your version of this file under the SPL, *
023: * indicate your decision by deleting the provisions above and replace *
024: * them with the notice and other provisions required by the LGPL. If you *
025: * do not delete the provisions above, a recipient may use your version of *
026: * this file under either the SPL or the LGPL. *
027: * *
028: * Patrick Niemeyer (pat@pat.net) *
029: * Author of Learning Java, O'Reilly & Associates *
030: * http://www.pat.net/~pat/ *
031: * *
032: *****************************************************************************/package bsh.util;
033:
034: import java.util.*;
035: import java.util.zip.*;
036: import javax.swing.*;
037: import javax.swing.tree.*;
038: import javax.swing.event.*;
039: import javax.swing.border.*;
040: import java.io.*;
041: import java.awt.*;
042: import java.lang.reflect.*;
043: import java.util.List;
044:
045: // For string related utils
046: import bsh.BshClassManager;
047: import bsh.classpath.BshClassPath;
048: import bsh.classpath.ClassPathListener;
049: import bsh.ClassPathException;
050: import bsh.StringUtil;
051: import bsh.ConsoleInterface;
052: import bsh.classpath.ClassManagerImpl;
053:
054: /**
055: A simple class browser for the BeanShell desktop.
056: */
057: public class ClassBrowser extends JSplitPane implements
058: ListSelectionListener, ClassPathListener {
059: BshClassPath classPath;
060: BshClassManager classManager;
061:
062: // GUI
063: JFrame frame;
064: JInternalFrame iframe;
065: JList classlist, conslist, mlist, fieldlist;
066: PackageTree ptree;
067: JTextArea methodLine;
068: JTree tree;
069: // For JList models
070: String[] packagesList;
071: String[] classesList;
072: Constructor[] consList;
073: Method[] methodList;
074: Field[] fieldList;
075:
076: String selectedPackage;
077: Class selectedClass;
078:
079: private static final Color LIGHT_BLUE = new Color(245, 245, 255);
080:
081: public ClassBrowser() {
082: this (BshClassManager.createClassManager(null/*interpreter*/));
083: }
084:
085: public ClassBrowser(BshClassManager classManager) {
086: super (VERTICAL_SPLIT, true);
087: this .classManager = classManager;
088:
089: setBorder(null);
090: javax.swing.plaf.SplitPaneUI ui = getUI();
091: if (ui instanceof javax.swing.plaf.basic.BasicSplitPaneUI) {
092: ((javax.swing.plaf.basic.BasicSplitPaneUI) ui).getDivider()
093: .setBorder(null);
094: }
095: }
096:
097: String[] toSortedStrings(Collection c) {
098: List l = new ArrayList(c);
099: String[] sa = (String[]) (l.toArray(new String[0]));
100: return StringUtil.bubbleSort(sa);
101: }
102:
103: void setClist(String packagename) {
104: this .selectedPackage = packagename;
105:
106: Set set = classPath.getClassesForPackage(packagename);
107: if (set == null)
108: set = new HashSet();
109:
110: // remove inner classes and shorten class names
111: List list = new ArrayList();
112: Iterator it = set.iterator();
113: while (it.hasNext()) {
114: String cname = (String) it.next();
115: if (cname.indexOf("$") == -1)
116: list.add(BshClassPath.splitClassname(cname)[1]);
117: }
118:
119: classesList = toSortedStrings(list);
120: classlist.setListData(classesList);
121: //setMlist( (String)classlist.getModel().getElementAt(0) );
122: }
123:
124: String[] parseConstructors(Constructor[] constructors) {
125: String[] sa = new String[constructors.length];
126: for (int i = 0; i < sa.length; i++) {
127: Constructor con = constructors[i];
128: sa[i] = StringUtil.methodString(con.getName(), con
129: .getParameterTypes());
130: }
131: //return bubbleSort(sa);
132: return sa;
133: }
134:
135: String[] parseMethods(Method[] methods) {
136: String[] sa = new String[methods.length];
137: for (int i = 0; i < sa.length; i++)
138: sa[i] = StringUtil.methodString(methods[i].getName(),
139: methods[i].getParameterTypes());
140: //return bubbleSort(sa);
141: return sa;
142: }
143:
144: String[] parseFields(Field[] fields) {
145: String[] sa = new String[fields.length];
146: for (int i = 0; i < sa.length; i++) {
147: Field f = fields[i];
148: sa[i] = f.getName();
149: }
150: return sa;
151: }
152:
153: Constructor[] getPublicConstructors(Constructor[] constructors) {
154: Vector v = new Vector();
155: for (int i = 0; i < constructors.length; i++)
156: if (Modifier.isPublic(constructors[i].getModifiers()))
157: v.addElement(constructors[i]);
158:
159: Constructor[] ca = new Constructor[v.size()];
160: v.copyInto(ca);
161: return ca;
162: }
163:
164: Method[] getPublicMethods(Method[] methods) {
165: Vector v = new Vector();
166: for (int i = 0; i < methods.length; i++)
167: if (Modifier.isPublic(methods[i].getModifiers()))
168: v.addElement(methods[i]);
169:
170: Method[] ma = new Method[v.size()];
171: v.copyInto(ma);
172: return ma;
173: }
174:
175: Field[] getPublicFields(Field[] fields) {
176: Vector v = new Vector();
177: for (int i = 0; i < fields.length; i++)
178: if (Modifier.isPublic(fields[i].getModifiers()))
179: v.addElement(fields[i]);
180:
181: Field[] fa = new Field[v.size()];
182: v.copyInto(fa);
183: return fa;
184: }
185:
186: void setConslist(Class clas) {
187: if (clas == null) {
188: conslist.setListData(new Object[] {});
189: return;
190: }
191:
192: consList = getPublicConstructors(clas.getDeclaredConstructors());
193: conslist.setListData(parseConstructors(consList));
194: }
195:
196: void setMlist(String classname) {
197: if (classname == null) {
198: mlist.setListData(new Object[] {});
199: setConslist(null);
200: setClassTree(null);
201: return;
202: }
203:
204: Class clas;
205: try {
206: if (selectedPackage.equals("<unpackaged>"))
207: selectedClass = classManager.classForName(classname);
208: else
209: selectedClass = classManager
210: .classForName(selectedPackage + "." + classname);
211: } catch (Exception e) {
212: System.err.println(e);
213: return;
214: }
215: if (selectedClass == null) {
216: // not found?
217: System.err.println("class not found: " + classname);
218: return;
219: }
220: methodList = getPublicMethods(selectedClass
221: .getDeclaredMethods());
222: mlist.setListData(parseMethods(methodList));
223:
224: setClassTree(selectedClass);
225: setConslist(selectedClass);
226: setFieldList(selectedClass);
227: }
228:
229: void setFieldList(Class clas) {
230: if (clas == null) {
231: fieldlist.setListData(new Object[] {});
232: return;
233: }
234:
235: fieldList = getPublicFields(clas.getDeclaredFields());
236: fieldlist.setListData(parseFields(fieldList));
237: }
238:
239: void setMethodLine(Object method) {
240: methodLine.setText(method == null ? "" : method.toString());
241: }
242:
243: void setClassTree(Class clas) {
244: if (clas == null) {
245: tree.setModel(null);
246: return;
247: }
248:
249: MutableTreeNode bottom = null, top = null;
250: DefaultMutableTreeNode up;
251: do {
252: up = new DefaultMutableTreeNode(clas.toString());
253: if (top != null)
254: up.add(top);
255: else
256: bottom = up;
257: top = up;
258: } while ((clas = clas.getSuperclass()) != null);
259: tree.setModel(new DefaultTreeModel(top));
260:
261: TreeNode tn = bottom.getParent();
262: if (tn != null) {
263: TreePath tp = new TreePath(((DefaultTreeModel) tree
264: .getModel()).getPathToRoot(tn));
265: tree.expandPath(tp);
266: }
267: }
268:
269: JPanel labeledPane(JComponent comp, String label) {
270: JPanel jp = new JPanel(new BorderLayout());
271: jp.add("Center", comp);
272: jp.add("North", new JLabel(label, SwingConstants.CENTER));
273: return jp;
274: }
275:
276: public void init() throws ClassPathException {
277: // Currently we have to cast because BshClassPath is not known by
278: // the core.
279: classPath = ((ClassManagerImpl) classManager).getClassPath();
280:
281: // maybe add MappingFeedbackListener here... or let desktop if it has
282: /*
283: classPath.insureInitialized( null
284: // get feedback on mapping...
285: new ConsoleInterface() {
286: public Reader getIn() { return null; }
287: public PrintStream getOut() { return System.out; }
288: public PrintStream getErr() { return System.err; }
289: public void println( String s ) { System.out.println(s); }
290: public void print( String s ) { System.out.print(s); }
291: public void print( String s, Color color ) { print( s ); }
292: public void error( String s ) { print( s ); }
293: }
294: );
295: */
296:
297: classPath.addListener(this );
298:
299: Set pset = classPath.getPackagesSet();
300:
301: ptree = new PackageTree(pset);
302: ptree.addTreeSelectionListener(new TreeSelectionListener() {
303: public void valueChanged(TreeSelectionEvent e) {
304: TreePath tp = e.getPath();
305: Object[] oa = tp.getPath();
306: StringBuffer selectedPackage = new StringBuffer();
307: for (int i = 1; i < oa.length; i++) {
308: selectedPackage.append(oa[i].toString());
309: if (i + 1 < oa.length)
310: selectedPackage.append(".");
311: }
312: setClist(selectedPackage.toString());
313: }
314: });
315:
316: classlist = new JList();
317: classlist.setBackground(LIGHT_BLUE);
318: classlist.addListSelectionListener(this );
319:
320: conslist = new JList();
321: conslist.addListSelectionListener(this );
322:
323: mlist = new JList();
324: mlist.setBackground(LIGHT_BLUE);
325: mlist.addListSelectionListener(this );
326:
327: fieldlist = new JList();
328: fieldlist.addListSelectionListener(this );
329:
330: JSplitPane methodConsPane = splitPane(
331: JSplitPane.VERTICAL_SPLIT, true, labeledPane(
332: new JScrollPane(conslist), "Constructors"),
333: labeledPane(new JScrollPane(mlist), "Methods"));
334:
335: JSplitPane rightPane = splitPane(JSplitPane.VERTICAL_SPLIT,
336: true, methodConsPane, labeledPane(new JScrollPane(
337: fieldlist), "Fields"));
338:
339: JSplitPane sp = splitPane(JSplitPane.HORIZONTAL_SPLIT, true,
340: labeledPane(new JScrollPane(classlist), "Classes"),
341: rightPane);
342: sp = splitPane(JSplitPane.HORIZONTAL_SPLIT, true, labeledPane(
343: new JScrollPane(ptree), "Packages"), sp);
344:
345: JPanel bottompanel = new JPanel(new BorderLayout());
346: methodLine = new JTextArea(1, 60);
347: methodLine.setBackground(LIGHT_BLUE);
348: methodLine.setEditable(false);
349: methodLine.setLineWrap(true);
350: methodLine.setWrapStyleWord(true);
351: methodLine.setFont(new Font("Monospaced", Font.BOLD, 14));
352: methodLine.setMargin(new Insets(5, 5, 5, 5));
353: methodLine.setBorder(new MatteBorder(1, 0, 1, 0, LIGHT_BLUE
354: .darker().darker()));
355: bottompanel.add("North", methodLine);
356: JPanel p = new JPanel(new BorderLayout());
357:
358: tree = new JTree();
359: tree.addTreeSelectionListener(new TreeSelectionListener() {
360: public void valueChanged(TreeSelectionEvent e) {
361: driveToClass(e.getPath().getLastPathComponent()
362: .toString());
363: }
364: });
365:
366: tree.setBorder(BorderFactory.createRaisedBevelBorder());
367: setClassTree(null);
368: p.add("Center", tree);
369: bottompanel.add("Center", p);
370:
371: // give it a preferred height
372: bottompanel.setPreferredSize(new java.awt.Dimension(150, 150));
373:
374: setTopComponent(sp);
375: setBottomComponent(bottompanel);
376: }
377:
378: private JSplitPane splitPane(int orientation, boolean redraw,
379: JComponent c1, JComponent c2) {
380: JSplitPane sp = new JSplitPane(orientation, redraw, c1, c2);
381: sp.setBorder(null);
382: javax.swing.plaf.SplitPaneUI ui = sp.getUI();
383: if (ui instanceof javax.swing.plaf.basic.BasicSplitPaneUI) {
384: ((javax.swing.plaf.basic.BasicSplitPaneUI) ui).getDivider()
385: .setBorder(null);
386: }
387: return sp;
388: }
389:
390: public static void main(String[] args) throws Exception {
391: ClassBrowser cb = new ClassBrowser();
392: cb.init();
393:
394: JFrame f = new JFrame("BeanShell Class Browser v1.0");
395: f.getContentPane().add("Center", cb);
396: cb.setFrame(f);
397: f.pack();
398: f.setVisible(true);
399: }
400:
401: public void setFrame(JFrame frame) {
402: this .frame = frame;
403: }
404:
405: public void setFrame(JInternalFrame frame) {
406: this .iframe = frame;
407: }
408:
409: public void valueChanged(ListSelectionEvent e) {
410: if (e.getSource() == classlist) {
411: String classname = (String) classlist.getSelectedValue();
412: setMlist(classname);
413:
414: // hack
415: // show the class source in the "method" line...
416: String methodLineString;
417: if (classname == null)
418: methodLineString = "Package: " + selectedPackage;
419: else {
420: String fullClassName = selectedPackage
421: .equals("<unpackaged>") ? classname
422: : selectedPackage + "." + classname;
423: methodLineString = fullClassName + " (from "
424: + classPath.getClassSource(fullClassName) + ")";
425: }
426:
427: setMethodLine(methodLineString);
428: } else if (e.getSource() == mlist) {
429: int i = mlist.getSelectedIndex();
430: if (i == -1)
431: setMethodLine(null);
432: else
433: setMethodLine(methodList[i]);
434: } else if (e.getSource() == conslist) {
435: int i = conslist.getSelectedIndex();
436: if (i == -1)
437: setMethodLine(null);
438: else
439: setMethodLine(consList[i]);
440: } else if (e.getSource() == fieldlist) {
441: int i = fieldlist.getSelectedIndex();
442: if (i == -1)
443: setMethodLine(null);
444: else
445: setMethodLine(fieldList[i]);
446: }
447: }
448:
449: // fully qualified classname
450: public void driveToClass(String classname) {
451: String[] sa = BshClassPath.splitClassname(classname);
452: String packn = sa[0];
453: String classn = sa[1];
454:
455: // Do we have the package?
456: if (classPath.getClassesForPackage(packn).size() == 0)
457: return;
458:
459: ptree.setSelectedPackage(packn);
460:
461: for (int i = 0; i < classesList.length; i++) {
462: if (classesList[i].equals(classn)) {
463: classlist.setSelectedIndex(i);
464: classlist.ensureIndexIsVisible(i);
465: break;
466: }
467: }
468: }
469:
470: public void toFront() {
471: if (frame != null)
472: frame.toFront();
473: else if (iframe != null)
474: iframe.toFront();
475: }
476:
477: class PackageTree extends JTree {
478: TreeNode root;
479: DefaultTreeModel treeModel;
480: Map nodeForPackage = new HashMap();
481:
482: PackageTree(Collection packages) {
483: setPackages(packages);
484:
485: setRootVisible(false);
486: setShowsRootHandles(true);
487: setExpandsSelectedPaths(true);
488:
489: // open top level paths
490: /*
491: Enumeration e1=root.children();
492: while( e1.hasMoreElements() ) {
493: TreePath tp = new TreePath(
494: treeModel.getPathToRoot( (TreeNode)e1.nextElement() ) );
495: expandPath( tp );
496: }
497: */
498: }
499:
500: public void setPackages(Collection packages) {
501: treeModel = makeTreeModel(packages);
502: setModel(treeModel);
503: }
504:
505: DefaultTreeModel makeTreeModel(Collection packages) {
506: Map packageTree = new HashMap();
507:
508: Iterator it = packages.iterator();
509: while (it.hasNext()) {
510: String pack = (String) (it.next());
511: String[] sa = StringUtil.split(pack, ".");
512: Map level = packageTree;
513: for (int i = 0; i < sa.length; i++) {
514: String name = sa[i];
515: Map map = (Map) level.get(name);
516:
517: if (map == null) {
518: map = new HashMap();
519: level.put(name, map);
520: }
521: level = map;
522: }
523: }
524:
525: root = makeNode(packageTree, "root");
526: mapNodes(root);
527: return new DefaultTreeModel(root);
528: }
529:
530: MutableTreeNode makeNode(Map map, String nodeName) {
531: DefaultMutableTreeNode root = new DefaultMutableTreeNode(
532: nodeName);
533: Iterator it = map.keySet().iterator();
534: while (it.hasNext()) {
535: String name = (String) it.next();
536: Map val = (Map) map.get(name);
537: if (val.size() == 0) {
538: DefaultMutableTreeNode leaf = new DefaultMutableTreeNode(
539: name);
540: root.add(leaf);
541: } else {
542: MutableTreeNode node = makeNode(val, name);
543: root.add(node);
544: }
545: }
546: return root;
547: }
548:
549: /**
550: Map out the location of the nodes by package name.
551: Seems like we should be able to do this while we build above...
552: I'm tired... just going to do this.
553: */
554: void mapNodes(TreeNode node) {
555: addNodeMap(node);
556:
557: Enumeration e = node.children();
558: while (e.hasMoreElements()) {
559: TreeNode tn = (TreeNode) e.nextElement();
560: mapNodes(tn);
561: }
562: }
563:
564: /**
565: map a single node up to the root
566: */
567: void addNodeMap(TreeNode node) {
568:
569: StringBuffer sb = new StringBuffer();
570: TreeNode tn = node;
571: while (tn != root) {
572: sb.insert(0, tn.toString());
573: if (tn.getParent() != root)
574: sb.insert(0, ".");
575: tn = tn.getParent();
576: }
577: String pack = sb.toString();
578:
579: nodeForPackage.put(pack, node);
580: }
581:
582: void setSelectedPackage(String pack) {
583: DefaultMutableTreeNode node = (DefaultMutableTreeNode) nodeForPackage
584: .get(pack);
585: if (node == null)
586: return;
587:
588: TreePath tp = new TreePath(treeModel.getPathToRoot(node));
589: setSelectionPath(tp);
590: setClist(pack);
591:
592: scrollPathToVisible(tp);
593: }
594:
595: }
596:
597: public void classPathChanged() {
598: Set pset = classPath.getPackagesSet();
599: ptree.setPackages(pset);
600: setClist(null);
601: }
602:
603: }
|