1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.wotonomy.ui.swing.util;
20
21 import java.awt.BorderLayout;
22 import java.awt.Dimension;
23 import java.awt.Insets;
24 import java.awt.Toolkit;
25 import java.awt.datatransfer.Clipboard;
26 import java.awt.datatransfer.StringSelection;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.KeyEvent;
30 import java.awt.event.MouseEvent;
31 import java.awt.event.MouseListener;
32 import java.io.ByteArrayOutputStream;
33 import java.io.File;
34 import java.io.PrintStream;
35 import java.util.ArrayList;
36 import java.util.Iterator;
37 import java.util.List;
38 import java.util.StringTokenizer;
39
40 import javax.swing.JComponent;
41 import javax.swing.JFrame;
42 import javax.swing.JPanel;
43 import javax.swing.JScrollPane;
44 import javax.swing.JTable;
45 import javax.swing.KeyStroke;
46 import javax.swing.border.EmptyBorder;
47 import javax.swing.event.TableModelListener;
48 import javax.swing.table.TableModel;
49
50 import net.wotonomy.ui.swing.components.MultiLineLabel;
51
52 /***
53 * The StackTraceInspector displays a JFrame containing
54 * stack trace information for a Throwable. <br><br>
55 *
56 * There are also a few static methods for obtaining
57 * information about the current stack, which is useful
58 * for determining who's calling you at runtime.
59 *
60 * @author michael@mpowers.net
61 * @version $Revision: 904 $
62 */
63
64 public class StackTraceInspector
65 implements TableModel, MouseListener, ActionListener
66 {
67 protected JTable table = null;
68 protected List tableModelListeners = null;
69 protected List methodNames = new ArrayList();
70
71
72 static public final String COPY = "COPY";
73
74 /***
75 * Displays the current stack trace at the time
76 * of instantiation in a table on a frame.
77 */
78 public StackTraceInspector()
79 {
80 initLayout( parseStackTrace( new RuntimeException() ), null );
81 }
82
83 /***
84 * Displays the current stack trace at the time
85 * of instantiation in a table on a frame
86 * annotated with the specified message.
87 */
88 public StackTraceInspector( String aMessage )
89 {
90 initLayout( parseStackTrace( new RuntimeException() ), aMessage );
91 }
92
93 /***
94 * Displays the stack trace for the given throwable
95 * in a table on a frame.
96 * @param aThrowable A Throwable whose stack will be examined.
97 */
98 public StackTraceInspector( Throwable aThrowable )
99 {
100 initLayout( parseStackTrace( aThrowable ),
101 aThrowable.getClass() + ": " + aThrowable.getMessage() );
102 }
103
104 /***
105 * Simply displays the list items in a dialog.
106 * Presumably (but not necessarily) called from
107 * the other constructors. (I guess if you just
108 * want a frame with strings in table, you can
109 * call this.)
110 * @param aStringList A List containing Strings.
111 */
112 public StackTraceInspector( List aStringList )
113 {
114 initLayout( aStringList, null );
115 }
116
117 protected void initLayout( List items, String message )
118 {
119 methodNames = new ArrayList( items );
120 table = new JTable( this );
121 table.addMouseListener( this );
122
123
124 table.registerKeyboardAction( this, COPY,
125 KeyStroke.getKeyStroke( KeyEvent.VK_C,
126 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ),
127 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
128 table.registerKeyboardAction( this, COPY,
129 KeyStroke.getKeyStroke( KeyEvent.VK_X,
130 Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() ),
131 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT );
132
133 JPanel panel = new JPanel();
134 panel.setBorder( new EmptyBorder( new Insets( 10, 10, 10, 10 ) ) );
135 panel.setLayout( new BorderLayout( 10, 10 ) );
136
137 if ( message != null )
138 {
139 panel.add( new MultiLineLabel( message ), BorderLayout.NORTH );
140 }
141
142 JScrollPane scrollPane = new JScrollPane( table );
143 scrollPane.setPreferredSize( new Dimension( 325, 350 ) );
144 panel.add( scrollPane, BorderLayout.CENTER );
145
146 JFrame window = new JFrame();
147 window.setTitle( "Stack Trace Inspector" );
148 window.getContentPane().add( panel );
149
150 window.pack();
151 WindowUtilities.cascade( window );
152 window.show();
153 }
154
155
156
157 public int getRowCount()
158 {
159 return methodNames.size();
160 }
161
162 public int getColumnCount()
163 {
164 return 1;
165 }
166
167 public String getColumnName(int columnIndex)
168 {
169 switch ( columnIndex )
170 {
171 case 0:
172 return "Methods";
173 case 1:
174 return "Property";
175 }
176 System.out.println( "StackTraceInspector.getColumnName: unknown column: " + columnIndex );
177 return "";
178 }
179
180 public Class getColumnClass(int columnIndex)
181 {
182 switch ( columnIndex )
183 {
184 case 0:
185 return String.class;
186 case 1:
187 return String.class;
188 }
189 System.out.println( "StackTraceInspector.getColumnClass: unknown column: " + columnIndex );
190 return Object.class;
191 }
192
193 public boolean isCellEditable(int rowIndex,
194 int columnIndex)
195 {
196 return false;
197 }
198
199 public Object getValueAt(int rowIndex,
200 int columnIndex)
201 {
202 return methodNames.get( rowIndex );
203 }
204
205 public void setValueAt(Object aValue,
206 int rowIndex,
207 int columnIndex)
208 {
209 }
210
211 public void addTableModelListener(TableModelListener l)
212 {
213 if ( tableModelListeners == null )
214 {
215 tableModelListeners = new ArrayList();
216 }
217 tableModelListeners.add( l );
218 }
219
220 public void removeTableModelListener(TableModelListener l)
221 {
222 if ( tableModelListeners != null )
223 {
224 tableModelListeners.remove( l );
225 }
226 }
227
228
229
230 /***
231 * Double click to call invokeFileFromString.
232 */
233
234 public void mouseClicked(MouseEvent e)
235 {
236 if ( e.getSource() == table )
237 {
238 if ( e.getClickCount() > 1 )
239 {
240 int row = table.rowAtPoint( e.getPoint() );
241 int col = table.columnAtPoint( e.getPoint() );
242
243 if ( ( row == -1 ) || ( col != 0 ) ) return;
244
245 invokeFileFromString( methodNames.get( row ).toString() );
246 }
247 }
248 }
249
250 public void mouseReleased(MouseEvent e) {}
251 public void mousePressed(MouseEvent e) {}
252 public void mouseEntered(MouseEvent e) {}
253 public void mouseExited(MouseEvent e) {}
254
255
256
257
258 public void actionPerformed(ActionEvent evt)
259 {
260 if ( COPY.equals( evt.getActionCommand() ) )
261 {
262 copyToClipboard();
263 return;
264 }
265 }
266
267 /***
268 * Copies the contents of the table to the clipboard as a tab-delimited string.
269 */
270 public void copyToClipboard()
271 {
272 Toolkit toolkit = Toolkit.getDefaultToolkit();
273 Clipboard clipboard = toolkit.getSystemClipboard();
274 StringSelection selection =
275 new StringSelection( getSelectedStackString() );
276 clipboard.setContents( selection, selection );
277 }
278
279 /***
280 * Converts the selected contents of the table to a string.
281 * @return A String containing the text contents of the table.
282 */
283 public String getSelectedStackString()
284 {
285 StringBuffer result = new StringBuffer(64);
286
287 TableModel model = table.getModel();
288
289 Object o;
290 int[] selectedRows = table.getSelectedRows();
291 for ( int i = 0; i < selectedRows.length; i++ )
292 {
293 o = model.getValueAt( selectedRows[i], 0 );
294 if ( o == null ) o = "";
295 result.append( o );
296 result.append( '\n' );
297 }
298
299 return result.toString();
300 }
301
302
303
304
305 /***
306 * Obtains a list of strings representing the stack trace
307 * associated with this throwable starting with the most recent call.
308 * @param aThrowable A Throwable whose stack trace is parsed.
309 * @return a List containing the method names as Strings.
310 */
311 static public List parseStackTrace( Throwable aThrowable )
312 {
313 String trace = null;
314
315
316 ByteArrayOutputStream os = new ByteArrayOutputStream( 256 );
317 PrintStream newErr = new PrintStream( os );
318 aThrowable.printStackTrace( newErr );
319
320
321 trace = os.toString();
322
323 List result = new ArrayList();
324
325
326 String token;
327 StringTokenizer tokens = new StringTokenizer( trace, "\n" );
328 tokens.nextToken();
329 while ( tokens.hasMoreTokens() )
330 {
331 token = tokens.nextToken();
332 if ( token.indexOf( StackTraceInspector.class.getName() ) == -1 )
333 {
334
335
336 token.trim();
337 token = token.substring( 4, token.length() - 1 );
338
339 result.add( token );
340 }
341 }
342
343 return result;
344 }
345
346 /***
347 * Convenience method that obtains a String representing
348 * the caller's caller.
349 * @return a String representing a method in stack trace format.
350 */
351 static public String getMyCaller()
352 {
353 List trace = parseStackTrace( new RuntimeException() );
354 if ( trace.size() > 1 )
355 {
356 return trace.get( 1 ).toString();
357 }
358
359 return null;
360 }
361
362 /***
363 * Prints a stack trace up to the first method whose fully
364 * qualified class name begins with "java" to System.out.
365 */
366 static public void printShortStackTrace()
367 {
368 String s;
369 Iterator i = parseStackTrace( new RuntimeException() ).iterator();
370 while ( i.hasNext() )
371 {
372 System.out.println( " " + ( s = i.next().toString() ) );
373 if ( s.startsWith( "java" ) ) break;
374 }
375 }
376
377 protected void invokeFileFromString( String aString )
378 {
379
380 int openParam = aString.indexOf( "(" );
381 if ( openParam != -1 )
382 {
383 aString = aString.substring( 0, openParam );
384 }
385
386
387 int lastDot = aString.lastIndexOf( "." );
388 if ( lastDot == -1 ) return;
389 String className = aString.substring( 0, lastDot );
390 String methodName = aString.substring( lastDot + 1 );
391
392
393 StringBuffer buf = new StringBuffer();
394 StringTokenizer tokens = new StringTokenizer( className, "." );
395 while ( true )
396 {
397 buf.append( tokens.nextToken() );
398 if ( ! tokens.hasMoreTokens() ) break;
399 buf.append( File.separator );
400 }
401 String path = buf.toString();
402 java.net.URL url = ClassLoader.getSystemResource( path + ".java" );
403 if ( url == null ) return;
404
405 String name = url.getFile();
406
407
408 try
409 {
410
411 String args[] = new String[] {
412 "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" };
413
414
415
416
417
418 Runtime.getRuntime().exec( args );
419 }
420 catch ( Exception exc )
421 {
422 System.out.println( "DocumentLinkPanel.invokeDocument: " + exc );
423 }
424 return;
425 }
426
427 }
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457