/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.ui.swing.util; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Insets; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.PrintStream; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.border.EmptyBorder; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; import net.wotonomy.ui.swing.components.MultiLineLabel; /** * The StackTraceInspector displays a JFrame containing stack trace information * for a Throwable.
*
* * There are also a few static methods for obtaining information about the * current stack, which is useful for determining who's calling you at runtime. * * @author michael@mpowers.net * @version $Revision: 904 $ */ public class StackTraceInspector implements TableModel, MouseListener, ActionListener { protected JTable table = null; protected List tableModelListeners = null; protected List methodNames = new ArrayList(); // key command to copy contents to clipboard static public final String COPY = "COPY"; /** * Displays the current stack trace at the time of instantiation in a table on a * frame. */ public StackTraceInspector() { initLayout(parseStackTrace(new RuntimeException()), null); } /** * Displays the current stack trace at the time of instantiation in a table on a * frame annotated with the specified message. */ public StackTraceInspector(String aMessage) { initLayout(parseStackTrace(new RuntimeException()), aMessage); } /** * Displays the stack trace for the given throwable in a table on a frame. * * @param aThrowable A Throwable whose stack will be examined. */ public StackTraceInspector(Throwable aThrowable) { initLayout(parseStackTrace(aThrowable), aThrowable.getClass() + ": " + aThrowable.getMessage()); } /** * Simply displays the list items in a dialog. Presumably (but not necessarily) * called from the other constructors. (I guess if you just want a frame with * strings in table, you can call this.) * * @param aStringList A List containing Strings. */ public StackTraceInspector(List aStringList) { initLayout(aStringList, null); } protected void initLayout(List items, String message) { methodNames = new ArrayList(items); table = new JTable(this); // this class is the table model table.addMouseListener(this); // listen for double-clicks // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X table.registerKeyboardAction(this, COPY, KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); table.registerKeyboardAction(this, COPY, KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); JPanel panel = new JPanel(); panel.setBorder(new EmptyBorder(new Insets(10, 10, 10, 10))); panel.setLayout(new BorderLayout(10, 10)); if (message != null) { panel.add(new MultiLineLabel(message), BorderLayout.NORTH); } JScrollPane scrollPane = new JScrollPane(table); scrollPane.setPreferredSize(new Dimension(325, 350)); panel.add(scrollPane, BorderLayout.CENTER); JFrame window = new JFrame(); window.setTitle("Stack Trace Inspector"); window.getContentPane().add(panel); window.pack(); WindowUtilities.cascade(window); window.show(); } // interface TableModel public int getRowCount() { return methodNames.size(); } public int getColumnCount() { return 1; } public String getColumnName(int columnIndex) { switch (columnIndex) { case 0: return "Methods"; case 1: return "Property"; } System.out.println("StackTraceInspector.getColumnName: unknown column: " + columnIndex); return ""; } public Class getColumnClass(int columnIndex) { switch (columnIndex) { case 0: return String.class; case 1: return String.class; } System.out.println("StackTraceInspector.getColumnClass: unknown column: " + columnIndex); return Object.class; } public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public Object getValueAt(int rowIndex, int columnIndex) { return methodNames.get(rowIndex); } public void setValueAt(Object aValue, int rowIndex, int columnIndex) { } public void addTableModelListener(TableModelListener l) { if (tableModelListeners == null) { tableModelListeners = new ArrayList(); } tableModelListeners.add(l); } public void removeTableModelListener(TableModelListener l) { if (tableModelListeners != null) { tableModelListeners.remove(l); } } // interface MouseListener /** * Double click to call invokeFileFromString. */ public void mouseClicked(MouseEvent e) { if (e.getSource() == table) { if (e.getClickCount() > 1) { int row = table.rowAtPoint(e.getPoint()); int col = table.columnAtPoint(e.getPoint()); if ((row == -1) || (col != 0)) return; invokeFileFromString(methodNames.get(row).toString()); } } } public void mouseReleased(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } // interface ActionEventListener - for listening to key commands public void actionPerformed(ActionEvent evt) { if (COPY.equals(evt.getActionCommand())) { copyToClipboard(); return; } } /** * Copies the contents of the table to the clipboard as a tab-delimited string. */ public void copyToClipboard() { Toolkit toolkit = Toolkit.getDefaultToolkit(); Clipboard clipboard = toolkit.getSystemClipboard(); StringSelection selection = new StringSelection(getSelectedStackString()); clipboard.setContents(selection, selection); } /** * Converts the selected contents of the table to a string. * * @return A String containing the text contents of the table. */ public String getSelectedStackString() { StringBuffer result = new StringBuffer(64); TableModel model = table.getModel(); Object o; int[] selectedRows = table.getSelectedRows(); for (int i = 0; i < selectedRows.length; i++) { o = model.getValueAt(selectedRows[i], 0); if (o == null) o = ""; result.append(o); result.append('\n'); } return result.toString(); } // static methods /** * Obtains a list of strings representing the stack trace associated with this * throwable starting with the most recent call. * * @param aThrowable A Throwable whose stack trace is parsed. * @return a List containing the method names as Strings. */ static public List parseStackTrace(Throwable aThrowable) { String trace = null; // create new stream ByteArrayOutputStream os = new ByteArrayOutputStream(256); PrintStream newErr = new PrintStream(os); aThrowable.printStackTrace(newErr); // prints to System.err // convert to string trace = os.toString(); List result = new ArrayList(); // populate list with parsed trace, starting from top String token; StringTokenizer tokens = new StringTokenizer(trace, "\n"); tokens.nextToken(); // strip off description of throwable while (tokens.hasMoreTokens()) { token = tokens.nextToken(); if (token.indexOf(StackTraceInspector.class.getName()) == -1) { // add only those methods not from this // class // strip whitespace, "at " from front, and \r from end token.trim(); token = token.substring(4, token.length() - 1); result.add(token); } } return result; } /** * Convenience method that obtains a String representing the caller's caller. * * @return a String representing a method in stack trace format. */ static public String getMyCaller() { List trace = parseStackTrace(new RuntimeException()); if (trace.size() > 1) { return trace.get(1).toString(); } return null; } /** * Prints a stack trace up to the first method whose fully qualified class name * begins with "java" to System.out. */ static public void printShortStackTrace() { String s; Iterator i = parseStackTrace(new RuntimeException()).iterator(); while (i.hasNext()) { System.out.println(" " + (s = i.next().toString())); if (s.startsWith("java")) break; } } protected void invokeFileFromString(String aString) { // strip off parentheses, if any int openParam = aString.indexOf("("); if (openParam != -1) { aString = aString.substring(0, openParam); } // separate class name from method name int lastDot = aString.lastIndexOf("."); if (lastDot == -1) return; String className = aString.substring(0, lastDot); String methodName = aString.substring(lastDot + 1); // convert "."s to file separator characters StringBuffer buf = new StringBuffer(); StringTokenizer tokens = new StringTokenizer(className, "."); while (true) { buf.append(tokens.nextToken()); if (!tokens.hasMoreTokens()) break; buf.append(File.separator); } String path = buf.toString(); java.net.URL url = ClassLoader.getSystemResource(path + ".java"); if (url == null) return; // do nothing String name = url.getFile(); // try to launch the document try { // NOTE: This is Windows-dependent! String args[] = new String[] { "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" }; // this translates to: cmd /c "start "" "path"" // apparently an array is more reliable for calling exec(). // all the extra quotes are to handle paths with spaces. // trims off the first "/" before the drive letter. // needed a dummy title for the console or it wouldn't work. Runtime.getRuntime().exec(args); } catch (Exception exc) { System.out.println("DocumentLinkPanel.invokeDocument: " + exc); } return; } } /* * $Log$ Revision 1.2 2006/02/18 23:19:05 cgruber Update imports and maven * dependencies. * * Revision 1.1 2006/02/16 13:22:22 cgruber Check in all sources in * eclipse-friendly maven-enabled packages. * * Revision 1.5 2003/08/06 23:07:53 chochos general code cleanup (mostly, * removing unused imports) * * Revision 1.4 2002/11/16 16:33:31 mpowers Now using platform-specific * accelerator key for shortcuts. * * Revision 1.3 2001/07/18 21:53:33 mpowers Added a string argument for display * as a message. * * Revision 1.2 2001/07/17 14:01:43 mpowers Added short stack trace method. * * Revision 1.1.1.1 2000/12/21 15:51:34 mpowers Contributing wotonomy. * * Revision 1.5 2000/12/20 16:25:45 michael Added log to all files. * * */