View Javadoc

1   /*
2   Wotonomy: OpenStep design patterns for pure Java applications.
3   Copyright (C) 2000 Michael Powers
4   
5   This library is free software; you can redistribute it and/or
6   modify it under the terms of the GNU Lesser General Public
7   License as published by the Free Software Foundation; either
8   version 2.1 of the License, or (at your option) any later version.
9   
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  Lesser General Public License for more details.
14  
15  You should have received a copy of the GNU Lesser General Public
16  License along with this library; if not, see http://www.gnu.org
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      // key command to copy contents to clipboard
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 ); // this class is the table model
121         table.addMouseListener( this ); // listen for double-clicks
122 
123         // set up keyboard events for cut-copy: Ctrl-C, Ctrl-X
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     // interface TableModel
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     // interface MouseListener
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     // interface ActionEventListener - for listening to key commands
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     // static methods
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         // create new stream
316         ByteArrayOutputStream os = new ByteArrayOutputStream( 256 );
317         PrintStream newErr = new PrintStream( os );
318         aThrowable.printStackTrace( newErr ); // prints to System.err
319 
320         // convert to string
321         trace = os.toString();
322 
323         List result = new ArrayList();
324 
325         // populate list with parsed trace, starting from top
326         String token;
327         StringTokenizer tokens = new StringTokenizer( trace, "\n" );
328         tokens.nextToken(); // strip off description of throwable
329         while ( tokens.hasMoreTokens() )
330         {
331             token = tokens.nextToken();
332             if ( token.indexOf( StackTraceInspector.class.getName() ) == -1 )
333             { // add only those methods not from this class
334 
335                 // strip whitespace, "at  " from front, and \r from end
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         // strip off parentheses, if any
380         int openParam = aString.indexOf( "(" );
381         if ( openParam != -1 )
382         {
383             aString = aString.substring( 0, openParam );
384         }
385 
386         // separate class name from method name
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         // convert "."s to file separator characters
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; // do nothing
404 
405         String name = url.getFile();
406 
407         // try to launch the document
408         try
409         {
410             // NOTE: This is Windows-dependent!
411             String args[] = new String[] {
412                 "cmd", "/c", "\"start \"\" \"" + name.substring(1) + "\"\"" };
413                 // this translates to: cmd /c "start "" "path""
414                 // apparently an array is more reliable for calling exec().
415                 // all the extra quotes are to handle paths with spaces.
416                 // trims off the first "/" before the drive letter.
417                 // needed a dummy title for the console or it wouldn't work.
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  * $Log$
431  * Revision 1.2  2006/02/18 23:19:05  cgruber
432  * Update imports and maven dependencies.
433  *
434  * Revision 1.1  2006/02/16 13:22:22  cgruber
435  * Check in all sources in eclipse-friendly maven-enabled packages.
436  *
437  * Revision 1.5  2003/08/06 23:07:53  chochos
438  * general code cleanup (mostly, removing unused imports)
439  *
440  * Revision 1.4  2002/11/16 16:33:31  mpowers
441  * Now using platform-specific accelerator key for shortcuts.
442  *
443  * Revision 1.3  2001/07/18 21:53:33  mpowers
444  * Added a string argument for display as a message.
445  *
446  * Revision 1.2  2001/07/17 14:01:43  mpowers
447  * Added short stack trace method.
448  *
449  * Revision 1.1.1.1  2000/12/21 15:51:34  mpowers
450  * Contributing wotonomy.
451  *
452  * Revision 1.5  2000/12/20 16:25:45  michael
453  * Added log to all files.
454  *
455  *
456  */
457