001: package jdepend.swingui;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import java.io.*;
006: import java.util.*;
007: import javax.swing.*;
008: import javax.swing.border.*;
009: import javax.swing.event.*;
010: import javax.swing.tree.*;
011:
012: import jdepend.framework.JavaClass;
013: import jdepend.framework.JavaPackage;
014: import jdepend.framework.PackageComparator;
015: import jdepend.framework.PackageFilter;
016: import jdepend.framework.ParserListener;
017:
018: /**
019: * The <code>JDepend</code> class analyzes directories of Java class files,
020: * generates metrics for each Java package, and reports the metrics in a Swing
021: * tree.
022: *
023: * @author <b>Mike Clark</b>
024: * @author Clarkware Consulting, Inc.
025: */
026:
027: public class JDepend implements ParserListener {
028:
029: private jdepend.framework.JDepend analyzer;
030:
031: private JFrame frame;
032:
033: private StatusPanel statusPanel;
034:
035: private JTextField statusField;
036:
037: private JProgressBar progressBar;
038:
039: private DependTree afferentTree;
040:
041: private DependTree efferentTree;
042:
043: private Hashtable resourceStrings;
044:
045: private Hashtable actions;
046:
047: private static Font BOLD_FONT = new Font("dialog", Font.BOLD, 12);
048:
049: /**
050: * Constructs a <code>JDepend</code> instance.
051: */
052: public JDepend() {
053:
054: analyzer = new jdepend.framework.JDepend();
055:
056: analyzer.addParseListener(this );
057:
058: //
059: // Force the cross platform L&F.
060: //
061: try {
062: UIManager.setLookAndFeel(UIManager
063: .getCrossPlatformLookAndFeelClassName());
064: //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
065: } catch (Exception e) {
066: e.printStackTrace();
067: }
068:
069: //
070: // Install the resource string table.
071: //
072: resourceStrings = new Hashtable();
073: resourceStrings.put("menubar", "File");
074: resourceStrings.put("File", "About Exit");
075:
076: //
077: // Install the action table.
078: //
079: actions = new Hashtable();
080: actions.put("About", new AboutAction());
081: actions.put("Exit", new ExitAction());
082: }
083:
084: /**
085: * Adds the specified directory name to the collection of directories to be
086: * analyzed.
087: *
088: * @param name Directory name.
089: * @throws IOException If the directory does not exist.
090: */
091: public void addDirectory(String name) throws IOException {
092: analyzer.addDirectory(name);
093: }
094:
095: /**
096: * Sets the package filter.
097: *
098: * @param filter Package filter.
099: */
100: public void setFilter(PackageFilter filter) {
101: analyzer.setFilter(filter);
102: }
103:
104: /**
105: * Sets the comma-separated list of components.
106: */
107: public void setComponents(String components) {
108: analyzer.setComponents(components);
109: }
110:
111: /**
112: * Analyzes the registered directories, generates metrics for each Java
113: * package, and reports the metrics in a graphical format.
114: */
115: public void analyze() {
116:
117: display();
118:
119: startProgressMonitor(analyzer.countClasses());
120:
121: ArrayList packages = new ArrayList(analyzer.analyze());
122:
123: Collections.sort(packages, new PackageComparator(
124: PackageComparator.byName()));
125:
126: stopProgressMonitor();
127:
128: updateTree(packages);
129: }
130:
131: /**
132: * Called whenever a Java source file is parsed into the specified
133: * <code>JavaClass</code> instance.
134: *
135: * @param jClass Parsed Java class.
136: */
137: public void onParsedJavaClass(final JavaClass jClass) {
138: SwingUtilities.invokeLater(new Runnable() {
139:
140: public void run() {
141: getProgressBar().setValue(
142: getProgressBar().getValue() + 1);
143: }
144: });
145: }
146:
147: private void display() {
148: frame = createUI();
149: frame.setVisible(true);
150: }
151:
152: private void updateTree(ArrayList packages) {
153:
154: JavaPackage jPackage = new JavaPackage("root");
155: jPackage.setAfferents(packages);
156: jPackage.setEfferents(packages);
157:
158: AfferentNode ah = new AfferentNode(null, jPackage);
159: getAfferentTree().setModel(new DependTreeModel(ah));
160:
161: EfferentNode eh = new EfferentNode(null, jPackage);
162: getEfferentTree().setModel(new DependTreeModel(eh));
163: }
164:
165: private void startProgressMonitor(final int maxValue) {
166: SwingUtilities.invokeLater(new Runnable() {
167:
168: public void run() {
169: getProgressBar().setMinimum(0);
170: getProgressBar().setMaximum(maxValue);
171: getStatusPanel().setStatusComponent(getProgressBar());
172: }
173: });
174: }
175:
176: private void stopProgressMonitor() {
177: SwingUtilities.invokeLater(new Runnable() {
178:
179: public void run() {
180: getStatusPanel().setStatusComponent(getStatusField());
181: int classCount = analyzer.countClasses();
182: int packageCount = analyzer.countPackages();
183: showStatusMessage("Analyzed " + packageCount
184: + " packages (" + classCount + " classes).");
185: }
186: });
187: }
188:
189: private JFrame createUI() {
190:
191: JFrame frame = createFrame("JDepend");
192:
193: JMenuBar menuBar = createMenubar();
194: frame.setJMenuBar(menuBar);
195:
196: JPanel treePanel = createTreePanel();
197: StatusPanel statusPanel = getStatusPanel();
198:
199: frame.getContentPane().add("Center", treePanel);
200: frame.getContentPane().add("South", statusPanel);
201: frame.pack();
202:
203: Dimension screenSize = Toolkit.getDefaultToolkit()
204: .getScreenSize();
205: int width = 700;
206: int height = 500;
207: int x = (screenSize.width - width) / 2;
208: int y = (screenSize.height - height) / 2;
209: frame.setBounds(x, y, width, height);
210: frame.setSize(width, height);
211:
212: return frame;
213: }
214:
215: private JFrame createFrame(String title) {
216:
217: JFrame frame = new JFrame(title);
218:
219: frame.getContentPane().setLayout(new BorderLayout());
220: frame.setBackground(SystemColor.control);
221:
222: frame.addWindowListener(new WindowAdapter() {
223:
224: public void windowClosing(WindowEvent e) {
225: new ExitAction().actionPerformed(null);
226: }
227: });
228:
229: return frame;
230: }
231:
232: private JPanel createTreePanel() {
233:
234: JPanel panel = new JPanel();
235:
236: panel.setLayout(new GridLayout(2, 1));
237: panel.add(getEfferentTree());
238: panel.add(getAfferentTree());
239:
240: /*
241: * panel.setLayout(new GridLayout(1,1)); JSplitPane splitPane = new
242: * JSplitPane(JSplitPane.VERTICAL_SPLIT);
243: * splitPane.setOneTouchExpandable(true);
244: * splitPane.setTopComponent(getEfferentTree());
245: * splitPane.setBottomComponent(getAfferentTree());
246: * panel.add(splitPane);
247: */
248:
249: return panel;
250: }
251:
252: private StatusPanel createStatusPanel() {
253: StatusPanel panel = new StatusPanel();
254: panel.setStatusComponent(getStatusField());
255:
256: return panel;
257: }
258:
259: private JProgressBar createProgressBar() {
260: JProgressBar bar = new JProgressBar();
261: bar.setStringPainted(true);
262:
263: return bar;
264: }
265:
266: private JTextField createStatusField() {
267: JTextField statusField = new JTextField();
268: statusField.setFont(BOLD_FONT);
269: statusField.setEditable(false);
270: statusField.setForeground(Color.black);
271: statusField.setBorder(BorderFactory
272: .createBevelBorder(BevelBorder.LOWERED));
273:
274: Insets insets = new Insets(5, 5, 5, 5);
275: statusField.setMargin(insets);
276:
277: return statusField;
278: }
279:
280: private JMenuBar createMenubar() {
281:
282: JMenuBar menuBar = new JMenuBar();
283:
284: String[] menuKeys = tokenize((String) resourceStrings
285: .get("menubar"));
286: for (int i = 0; i < menuKeys.length; i++) {
287: JMenu m = createMenu(menuKeys[i]);
288: if (m != null) {
289: menuBar.add(m);
290: }
291: }
292:
293: return menuBar;
294: }
295:
296: private JMenu createMenu(String key) {
297:
298: String[] itemKeys = tokenize((String) resourceStrings.get(key));
299: JMenu menu = new JMenu(key);
300: for (int i = 0; i < itemKeys.length; i++) {
301: if (itemKeys[i].equals("-")) {
302: menu.addSeparator();
303: } else {
304: JMenuItem mi = createMenuItem(itemKeys[i]);
305: menu.add(mi);
306: }
307: }
308:
309: char mnemonic = key.charAt(0);
310: menu.setMnemonic(mnemonic);
311:
312: return menu;
313: }
314:
315: private JMenuItem createMenuItem(String key) {
316:
317: JMenuItem mi = new JMenuItem(key);
318:
319: char mnemonic = key.charAt(0);
320: mi.setMnemonic(mnemonic);
321:
322: char accelerator = key.charAt(0);
323: mi.setAccelerator(KeyStroke.getKeyStroke(accelerator,
324: java.awt.Event.CTRL_MASK));
325:
326: String actionString = key;
327: mi.setActionCommand(actionString);
328:
329: Action a = getActionForCommand(actionString);
330: if (a != null) {
331: mi.addActionListener(a);
332: mi.setEnabled(a.isEnabled());
333: } else {
334: mi.setEnabled(false);
335: }
336:
337: return mi;
338: }
339:
340: private void showStatusMessage(final String message) {
341: getStatusField().setFont(BOLD_FONT);
342: getStatusField().setForeground(Color.black);
343: getStatusField().setText(" " + message);
344: }
345:
346: private void showStatusError(final String message) {
347: getStatusField().setFont(BOLD_FONT);
348: getStatusField().setForeground(Color.red);
349: getStatusField().setText(" " + message);
350: }
351:
352: private DependTree getAfferentTree() {
353: if (afferentTree == null) {
354: afferentTree = new DependTree();
355: afferentTree.addTreeSelectionListener(new TreeListener());
356: }
357:
358: return afferentTree;
359: }
360:
361: private DependTree getEfferentTree() {
362: if (efferentTree == null) {
363: efferentTree = new DependTree();
364: efferentTree.addTreeSelectionListener(new TreeListener());
365: }
366:
367: return efferentTree;
368: }
369:
370: private StatusPanel getStatusPanel() {
371: if (statusPanel == null) {
372: statusPanel = createStatusPanel();
373: }
374: return statusPanel;
375: }
376:
377: private JProgressBar getProgressBar() {
378: if (progressBar == null) {
379: progressBar = createProgressBar();
380: }
381:
382: return progressBar;
383: }
384:
385: private JTextField getStatusField() {
386: if (statusField == null) {
387: statusField = createStatusField();
388: }
389: return statusField;
390: }
391:
392: private Action getActionForCommand(String command) {
393: return (Action) actions.get(command);
394: }
395:
396: /*
397: * Parses the specified string into an array of strings on whitespace
398: * boundaries. @param input String to tokenize. @return Strings.
399: */
400: private String[] tokenize(String input) {
401:
402: Vector v = new Vector();
403: StringTokenizer t = new StringTokenizer(input);
404:
405: while (t.hasMoreTokens()) {
406: v.addElement(t.nextToken());
407: }
408:
409: String cmd[] = new String[v.size()];
410: for (int i = 0; i < cmd.length; i++) {
411: cmd[i] = (String) v.elementAt(i);
412: }
413:
414: return cmd;
415: }
416:
417: private void postStatusMessage(final String message) {
418: SwingUtilities.invokeLater(new Runnable() {
419:
420: public void run() {
421: showStatusMessage(message);
422: }
423: });
424: }
425:
426: private void postStatusError(final String message) {
427: SwingUtilities.invokeLater(new Runnable() {
428:
429: public void run() {
430: showStatusError(message);
431: }
432: });
433: }
434:
435: //
436: // Tree selection handler.
437: //
438: private class TreeListener implements TreeSelectionListener {
439:
440: /**
441: * Constructs a <code>TreeListener</code> instance.
442: */
443: TreeListener() {
444: }
445:
446: /**
447: * Callback method triggered whenever the value of the tree selection
448: * changes.
449: *
450: * @param te Event that characterizes the change.
451: */
452: public void valueChanged(TreeSelectionEvent te) {
453:
454: TreePath path = te.getNewLeadSelectionPath();
455:
456: if (path != null) {
457: PackageNode node = (PackageNode) path
458: .getLastPathComponent();
459: showStatusMessage(node.toMetricsString());
460: }
461: }
462: }
463:
464: //
465: // About action handler.
466: //
467: private class AboutAction extends AbstractAction {
468:
469: /**
470: * Constructs an <code>AboutAction</code> instance.
471: */
472: AboutAction() {
473: super ("About");
474: }
475:
476: /**
477: * Handles the action.
478: */
479: public void actionPerformed(ActionEvent e) {
480: AboutDialog d = new AboutDialog(frame);
481: d.setModal(true);
482: d.setLocation(300, 300);
483: d.show();
484: }
485: }
486:
487: //
488: // Exit action handler.
489: //
490: private class ExitAction extends AbstractAction {
491:
492: /**
493: * Constructs an <code>ExitAction</code> instance.
494: */
495: ExitAction() {
496: super ("Exit");
497: }
498:
499: /**
500: * Handles the action.
501: */
502: public void actionPerformed(ActionEvent e) {
503: frame.dispose();
504: System.exit(0);
505: }
506: }
507:
508: private void usage(String message) {
509: if (message != null) {
510: System.err.println("\n" + message);
511: }
512:
513: String baseUsage = "\nJDepend ";
514:
515: System.err.println("");
516: System.err.println("usage: ");
517: System.err.println(baseUsage + "-components <components> "
518: + "<directory> [directory2 [directory 3] ...]");
519: System.exit(1);
520: }
521:
522: private void instanceMain(String[] args) {
523:
524: if (args.length < 1) {
525: usage("Must specify at least one directory.");
526: }
527:
528: int directoryCount = 0;
529:
530: for (int i = 0; i < args.length; i++) {
531: if (args[i].startsWith("-")) {
532: if (args[i].equalsIgnoreCase("-components")) {
533: if (args.length <= i + 1) {
534: usage("Components not specified.");
535: }
536: setComponents(args[++i]);
537: } else {
538: usage("Invalid argument: " + args[i]);
539: }
540: } else {
541: try {
542: addDirectory(args[i]);
543: directoryCount++;
544: } catch (IOException ioe) {
545: usage("Directory does not exist: " + args[i]);
546: }
547: }
548: }
549:
550: if (directoryCount == 0) {
551: usage("Must specify at least one directory.");
552: }
553:
554: analyze();
555:
556: for (int i = 0; i < args.length; i++) {
557:
558: if (args[i].startsWith("-")) {
559: usage("Invalid argument: " + args[i]);
560: }
561:
562: try {
563:
564: addDirectory(args[i]);
565: directoryCount++;
566:
567: } catch (IOException ioe) {
568: usage("Directory does not exist: " + args[i]);
569: }
570: }
571:
572: if (directoryCount == 0) {
573: usage("Must specify at least one directory.");
574: }
575:
576: analyze();
577: }
578:
579: public static void main(String[] args) {
580: new JDepend().instanceMain(args);
581: }
582: }
|