/*
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.
*
*
*/