1 /*
2  * Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.
8  *
9  * This code is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12  * version 2 for more details (a copy is included in the LICENSE file that
13  * accompanied this code).
14  *
15  * You should have received a copy of the GNU General Public License version
16  * 2 along with this work; if not, write to the Free Software Foundation,
17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20  * or visit www.oracle.com if you need additional information or have any
21  * questions.
22  *
23  */
24 
25 package sun.jvm.hotspot;
26 
27 import java.io.*;
28 import java.awt.*;
29 import java.awt.event.*;
30 import javax.swing.*;
31 import java.util.*;
32 
33 import sun.jvm.hotspot.code.*;
34 import sun.jvm.hotspot.compiler.*;
35 import sun.jvm.hotspot.debugger.*;
36 import sun.jvm.hotspot.gc.epsilon.*;
37 import sun.jvm.hotspot.gc.parallel.*;
38 import sun.jvm.hotspot.gc.shared.*;
39 import sun.jvm.hotspot.gc.shenandoah.*;
40 import sun.jvm.hotspot.gc.g1.*;
41 import sun.jvm.hotspot.gc.z.*;
42 import sun.jvm.hotspot.interpreter.*;
43 import sun.jvm.hotspot.memory.*;
44 import sun.jvm.hotspot.oops.*;
45 import sun.jvm.hotspot.runtime.*;
46 import sun.jvm.hotspot.ui.*;
47 import sun.jvm.hotspot.ui.tree.*;
48 import sun.jvm.hotspot.ui.classbrowser.*;
49 import sun.jvm.hotspot.utilities.*;
50 
51 /** The top-level HotSpot Debugger. FIXME: make this an embeddable
52     component! (Among other things, figure out what to do with the
53     menu bar...) */
54 
55 public class HSDB implements ObjectHistogramPanel.Listener, SAListener {
main(String[] args)56   public static void main(String[] args) {
57     new HSDB(args).run();
58   }
59 
60   //--------------------------------------------------------------------------------
61   // Internals only below this point
62   //
63   private HotSpotAgent agent;
64   private JVMDebugger jvmDebugger;
65   private JDesktopPane desktop;
66   private boolean      attached;
67   private boolean      argError;
68   private JFrame frame;
69   /** List <JMenuItem> */
70   private java.util.List attachMenuItems;
71   /** List <JMenuItem> */
72   private java.util.List detachMenuItems;
73   private JMenu toolsMenu;
74   private JMenuItem showDbgConsoleMenuItem;
75   private JMenuItem computeRevPtrsMenuItem;
76   private JInternalFrame attachWaitDialog;
77   private JInternalFrame threadsFrame;
78   private JInternalFrame consoleFrame;
79   private WorkerThread workerThread;
80   // These had to be made data members because they are referenced in inner classes.
81   private String pidText;
82   private int pid;
83   private String execPath;
84   private String coreFilename;
85 
doUsage()86   private void doUsage() {
87     System.out.println("Usage:  java HSDB [[pid] | [path-to-java-executable [path-to-corefile]] | help ]");
88     System.out.println("           pid:                     attach to the process whose id is 'pid'");
89     System.out.println("           path-to-java-executable: Debug a core file produced by this program");
90     System.out.println("           path-to-corefile:        Debug this corefile.  The default is 'core'");
91     System.out.println("        If no arguments are specified, you can select what to do from the GUI.\n");
92     HotSpotAgent.showUsage();
93     argError = true;
94   }
95 
HSDB(JVMDebugger d)96   public HSDB(JVMDebugger d) {
97     jvmDebugger = d;
98   }
99 
HSDB(String[] args)100   private HSDB(String[] args) {
101     switch (args.length) {
102     case (0):
103       break;
104 
105     case (1):
106       if (args[0].equals("help") || args[0].equals("-help")) {
107         doUsage();
108       }
109       // If all numbers, it is a PID to attach to
110       // Else, it is a pathname to a .../bin/java for a core file.
111       try {
112         int unused = Integer.parseInt(args[0]);
113         // If we get here, we have a PID and not a core file name
114         pidText = args[0];
115       } catch (NumberFormatException e) {
116         execPath = args[0];
117         coreFilename = "core";
118       }
119       break;
120 
121     case (2):
122       execPath = args[0];
123       coreFilename = args[1];
124       break;
125 
126     default:
127       System.out.println("HSDB Error: Too many options specified");
128       doUsage();
129     }
130   }
131 
132   private class CloseUI extends WindowAdapter {
133 
134       @Override
windowClosing(WindowEvent e)135       public void windowClosing(WindowEvent e) {
136           workerThread.shutdown();
137           frame.dispose();
138       }
139 
140   }
141 
run()142   public void run() {
143     // Don't start the UI if there were bad arguments.
144     if (argError) {
145         return;
146     }
147 
148     // Create frame first, to catch any GUI creation issues
149     // before we initialize agent
150 
151     frame = new JFrame("HSDB - HotSpot Debugger");
152     frame.setSize(800, 600);
153     frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
154     frame.addWindowListener(new CloseUI());
155 
156     agent = new HotSpotAgent();
157     workerThread = new WorkerThread();
158     attachMenuItems = new java.util.ArrayList();
159     detachMenuItems = new java.util.ArrayList();
160 
161 
162     JMenuBar menuBar = new JMenuBar();
163 
164     //
165     // File menu
166     //
167 
168     JMenu menu = new JMenu("File");
169     menu.setMnemonic(KeyEvent.VK_F);
170     JMenuItem item;
171     item = createMenuItem("Attach to HotSpot process...",
172                           new ActionListener() {
173                               public void actionPerformed(ActionEvent e) {
174                                 showAttachDialog();
175                               }
176                             });
177     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, ActionEvent.ALT_MASK));
178     item.setMnemonic(KeyEvent.VK_A);
179     menu.add(item);
180     attachMenuItems.add(item);
181 
182     item = createMenuItem("Open HotSpot core file...",
183                           new ActionListener() {
184                               public void actionPerformed(ActionEvent e) {
185                                 showOpenCoreFileDialog();
186                               }
187                             });
188     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, ActionEvent.ALT_MASK));
189     item.setMnemonic(KeyEvent.VK_O);
190     menu.add(item);
191     attachMenuItems.add(item);
192 
193     item = createMenuItem("Connect to debug server...",
194                           new ActionListener() {
195                               public void actionPerformed(ActionEvent e) {
196                                 showConnectDialog();
197                               }
198                             });
199     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.ALT_MASK));
200     item.setMnemonic(KeyEvent.VK_S);
201     menu.add(item);
202     attachMenuItems.add(item);
203 
204     item = createMenuItem("Detach",
205                           new ActionListener() {
206                               public void actionPerformed(ActionEvent e) {
207                                 detach();
208                               }
209                             });
210     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, ActionEvent.ALT_MASK));
211     item.setMnemonic(KeyEvent.VK_S);
212     menu.add(item);
213     detachMenuItems.add(item);
214 
215     // Disable detach menu items at first
216     setMenuItemsEnabled(detachMenuItems, false);
217 
218     menu.addSeparator();
219 
220     item = createMenuItem("Exit",
221                             new ActionListener() {
222                                 public void actionPerformed(ActionEvent e) {
223                                   workerThread.shutdown();
224                                   frame.dispose();
225                                 }
226                               });
227     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.ALT_MASK));
228     item.setMnemonic(KeyEvent.VK_X);
229     menu.add(item);
230     menuBar.add(menu);
231 
232     //
233     // Tools menu
234     //
235 
236     toolsMenu = new JMenu("Tools");
237     toolsMenu.setMnemonic(KeyEvent.VK_T);
238 
239     item = createMenuItem("Class Browser",
240                           new ActionListener() {
241                              public void actionPerformed(ActionEvent e) {
242                                 showClassBrowser();
243                              }
244                           });
245     item.setMnemonic(KeyEvent.VK_B);
246 
247     toolsMenu.add(item);
248 
249     item = createMenuItem("Code Viewer",
250                           new ActionListener() {
251                              public void actionPerformed(ActionEvent e) {
252                                 showCodeViewer();
253                              }
254                           });
255     item.setMnemonic(KeyEvent.VK_C);
256 
257     toolsMenu.add(item);
258 
259 
260     item = createMenuItem("Compute Reverse Ptrs",
261                           new ActionListener() {
262                               public void actionPerformed(ActionEvent e) {
263                                 fireComputeReversePtrs();
264                               }
265                             });
266     computeRevPtrsMenuItem = item;
267     item.setMnemonic(KeyEvent.VK_M);
268     toolsMenu.add(item);
269 
270     item = createMenuItem("Deadlock Detection",
271                           new ActionListener() {
272                               public void actionPerformed(ActionEvent e) {
273                                 showDeadlockDetectionPanel();
274                               }
275                             });
276     item.setMnemonic(KeyEvent.VK_D);
277     toolsMenu.add(item);
278 
279     item = createMenuItem("Find Object by Query",
280                           new ActionListener() {
281                               public void actionPerformed(ActionEvent e) {
282                                 showFindByQueryPanel();
283                               }
284                             });
285     item.setMnemonic(KeyEvent.VK_Q);
286     toolsMenu.add(item);
287 
288 
289     item = createMenuItem("Find Pointer",
290                           new ActionListener() {
291                               public void actionPerformed(ActionEvent e) {
292                                 showFindPanel();
293                               }
294                             });
295     item.setMnemonic(KeyEvent.VK_P);
296     toolsMenu.add(item);
297 
298     item = createMenuItem("Find Value In Heap",
299                           new ActionListener() {
300                               public void actionPerformed(ActionEvent e) {
301                                 showFindInHeapPanel();
302                               }
303                             });
304     item.setMnemonic(KeyEvent.VK_V);
305     toolsMenu.add(item);
306 
307     item = createMenuItem("Find Value In Code Cache",
308                           new ActionListener() {
309                               public void actionPerformed(ActionEvent e) {
310                                 showFindInCodeCachePanel();
311                               }
312                             });
313     item.setMnemonic(KeyEvent.VK_A);
314     toolsMenu.add(item);
315 
316     item = createMenuItem("Heap Parameters",
317                           new ActionListener() {
318                               public void actionPerformed(ActionEvent e) {
319                                 showHeapParametersPanel();
320                               }
321                             });
322     item.setMnemonic(KeyEvent.VK_H);
323     toolsMenu.add(item);
324 
325     item = createMenuItem("Inspector",
326                           new ActionListener() {
327                               public void actionPerformed(ActionEvent e) {
328                                 showInspector(null);
329                               }
330                             });
331     item.setMnemonic(KeyEvent.VK_R);
332     item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.ALT_MASK));
333     toolsMenu.add(item);
334 
335     item = createMenuItem("Memory Viewer",
336                           new ActionListener() {
337                              public void actionPerformed(ActionEvent e) {
338                                 showMemoryViewer();
339                              }
340                           });
341     item.setMnemonic(KeyEvent.VK_M);
342     toolsMenu.add(item);
343 
344     item = createMenuItem("Monitor Cache Dump",
345                           new ActionListener() {
346                               public void actionPerformed(ActionEvent e) {
347                                 showMonitorCacheDumpPanel();
348                               }
349                             });
350     item.setMnemonic(KeyEvent.VK_D);
351     toolsMenu.add(item);
352 
353     item = createMenuItem("Object Histogram",
354                           new ActionListener() {
355                               public void actionPerformed(ActionEvent e) {
356                                 showObjectHistogram();
357                               }
358                             });
359     item.setMnemonic(KeyEvent.VK_O);
360     toolsMenu.add(item);
361 
362     item = createMenuItem("Show System Properties",
363                           new ActionListener() {
364                              public void actionPerformed(ActionEvent e) {
365                                 showSystemProperties();
366                              }
367                           });
368     item.setMnemonic(KeyEvent.VK_S);
369     toolsMenu.add(item);
370 
371     item = createMenuItem("Show VM Version",
372                           new ActionListener() {
373                              public void actionPerformed(ActionEvent e) {
374                                 showVMVersion();
375                              }
376                           });
377     item.setMnemonic(KeyEvent.VK_M);
378     toolsMenu.add(item);
379 
380     item = createMenuItem("Show -XX flags",
381                           new ActionListener() {
382                              public void actionPerformed(ActionEvent e) {
383                                 showCommandLineFlags();
384                              }
385                           });
386     item.setMnemonic(KeyEvent.VK_X);
387     toolsMenu.add(item);
388 
389     toolsMenu.setEnabled(false);
390     menuBar.add(toolsMenu);
391 
392     //
393     // Windows menu
394     //
395 
396     JMenu windowsMenu = new JMenu("Windows");
397     windowsMenu.setMnemonic(KeyEvent.VK_W);
398     item = createMenuItem("Console",
399                           new ActionListener() {
400                              public void actionPerformed(ActionEvent e) {
401                                  showConsole();
402                              }
403                           });
404     item.setMnemonic(KeyEvent.VK_C);
405     windowsMenu.add(item);
406     showDbgConsoleMenuItem = createMenuItem("Debugger Console",
407                                          new ActionListener() {
408                                              public void actionPerformed(ActionEvent e) {
409                                                showDebuggerConsole();
410                                              }
411                                            });
412     showDbgConsoleMenuItem.setMnemonic(KeyEvent.VK_D);
413     windowsMenu.add(showDbgConsoleMenuItem);
414     showDbgConsoleMenuItem.setEnabled(false);
415 
416     menuBar.add(windowsMenu);
417 
418 
419     frame.setJMenuBar(menuBar);
420 
421     desktop = new JDesktopPane();
422     frame.getContentPane().add(desktop);
423     GraphicsUtilities.reshapeToAspectRatio(frame, 4.0f/3.0f, 0.75f, Toolkit.getDefaultToolkit().getScreenSize());
424     GraphicsUtilities.centerInContainer(frame, Toolkit.getDefaultToolkit().getScreenSize());
425     frame.setVisible(true);
426 
427     Runtime.getRuntime().addShutdownHook(new java.lang.Thread() {
428         public void run() {
429           detachDebugger();
430         }
431       });
432 
433     // If jvmDebugger is already set, we have been given a JVMDebugger.
434     // Otherwise, if pidText != null we are supposed to attach to it.
435     // Finally, if execPath != null, it is the path of a jdk/bin/java
436     // and coreFilename is the pathname of a core file we are
437     // supposed to attach to.
438 
439     if (jvmDebugger != null) {
440       attach(jvmDebugger);
441     } else if (pidText != null) {
442       attach(pidText);
443     } else if (execPath != null) {
444       attach(execPath, coreFilename);
445     }
446   }
447 
448   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
showAttachDialog()449   private void showAttachDialog() {
450     // FIXME: create filtered text field which only accepts numbers
451     setMenuItemsEnabled(attachMenuItems, false);
452     final JInternalFrame attachDialog = new JInternalFrame("Attach to HotSpot process");
453     attachDialog.getContentPane().setLayout(new BorderLayout());
454 
455     JPanel panel = new JPanel();
456     panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
457     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
458     attachDialog.setBackground(panel.getBackground());
459 
460     panel.add(new JLabel("Enter process ID:"));
461     final JTextField pidTextField = new JTextField(10);
462     ActionListener attacher = new ActionListener() {
463         public void actionPerformed(ActionEvent e) {
464           attachDialog.setVisible(false);
465           desktop.remove(attachDialog);
466           workerThread.invokeLater(new Runnable() {
467               public void run() {
468                 attach(pidTextField.getText());
469               }
470             });
471         }
472       };
473 
474     pidTextField.addActionListener(attacher);
475     panel.add(pidTextField);
476     attachDialog.getContentPane().add(panel, BorderLayout.NORTH);
477 
478     Box vbox = Box.createVerticalBox();
479     panel = new JPanel();
480     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
481     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
482     JTextArea ta = new JTextArea(
483                                  "Enter the process ID of a currently-running HotSpot process. On " +
484                                  "Solaris and most Unix operating systems, this can be determined by " +
485                                  "typing \"ps -u <your username> | grep java\"; the process ID is the " +
486                                  "first number which appears on the resulting line. On Windows, the " +
487                                  "process ID is present in the Task Manager, which can be brought up " +
488                                  "while logged on to the desktop by pressing Ctrl-Alt-Delete.");
489     ta.setLineWrap(true);
490     ta.setWrapStyleWord(true);
491     ta.setEditable(false);
492     ta.setBackground(panel.getBackground());
493     panel.add(ta);
494     vbox.add(panel);
495 
496     Box hbox = Box.createHorizontalBox();
497     hbox.add(Box.createGlue());
498     JButton button = new JButton("OK");
499     button.addActionListener(attacher);
500     hbox.add(button);
501     hbox.add(Box.createHorizontalStrut(20));
502     button = new JButton("Cancel");
503     button.addActionListener(new ActionListener() {
504         public void actionPerformed(ActionEvent e) {
505           attachDialog.setVisible(false);
506           desktop.remove(attachDialog);
507           setMenuItemsEnabled(attachMenuItems, true);
508         }
509       });
510     hbox.add(button);
511     hbox.add(Box.createGlue());
512     panel = new JPanel();
513     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
514     panel.add(hbox);
515     vbox.add(panel);
516 
517     attachDialog.getContentPane().add(vbox, BorderLayout.SOUTH);
518 
519     desktop.add(attachDialog);
520     attachDialog.setSize(400, 300);
521     GraphicsUtilities.centerInContainer(attachDialog);
522     attachDialog.show();
523     pidTextField.requestFocus();
524   }
525 
526   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
showOpenCoreFileDialog()527   private void showOpenCoreFileDialog() {
528     setMenuItemsEnabled(attachMenuItems, false);
529     final JInternalFrame dialog = new JInternalFrame("Open Core File");
530     dialog.getContentPane().setLayout(new BorderLayout());
531 
532     JPanel panel = new JPanel();
533     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
534     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
535     dialog.setBackground(panel.getBackground());
536 
537     Box hbox = Box.createHorizontalBox();
538     Box vbox = Box.createVerticalBox();
539     vbox.add(new JLabel("Path to core file:"));
540     vbox.add(new JLabel("Path to Java executable:"));
541     hbox.add(vbox);
542 
543     vbox = Box.createVerticalBox();
544     final JTextField corePathField = new JTextField(40);
545     final JTextField execPathField = new JTextField(40);
546     vbox.add(corePathField);
547     vbox.add(execPathField);
548     hbox.add(vbox);
549 
550     final JButton browseCorePath = new JButton("Browse ..");
551     final JButton browseExecPath = new JButton("Browse ..");
552     browseCorePath.addActionListener(new ActionListener() {
553                                         public void actionPerformed(ActionEvent e) {
554                                            JFileChooser fileChooser = new JFileChooser(new File("."));
555                                            int retVal = fileChooser.showOpenDialog(dialog);
556                                            if (retVal == JFileChooser.APPROVE_OPTION) {
557                                               corePathField.setText(fileChooser.getSelectedFile().getPath());
558                                            }
559                                         }
560                                      });
561     browseExecPath.addActionListener(new ActionListener() {
562                                         public void actionPerformed(ActionEvent e) {
563                                            JFileChooser fileChooser = new JFileChooser(new File("."));
564                                            int retVal = fileChooser.showOpenDialog(dialog);
565                                            if (retVal == JFileChooser.APPROVE_OPTION) {
566                                               execPathField.setText(fileChooser.getSelectedFile().getPath());
567                                            }
568                                         }
569                                      });
570     vbox = Box.createVerticalBox();
571     vbox.add(browseCorePath);
572     vbox.add(browseExecPath);
573     hbox.add(vbox);
574 
575     panel.add(hbox);
576     dialog.getContentPane().add(panel, BorderLayout.NORTH);
577 
578     ActionListener attacher = new ActionListener() {
579         public void actionPerformed(ActionEvent e) {
580           dialog.setVisible(false);
581           desktop.remove(dialog);
582           workerThread.invokeLater(new Runnable() {
583               public void run() {
584                 attach(execPathField.getText(), corePathField.getText());
585               }
586             });
587         }
588       };
589     corePathField.addActionListener(attacher);
590     execPathField.addActionListener(attacher);
591 
592     vbox = Box.createVerticalBox();
593     panel = new JPanel();
594     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
595     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
596     JTextArea ta = new JTextArea(
597                                  "Enter the full path names to the core file from a HotSpot process " +
598                                  "and the Java executable from which it came. The latter is typically " +
599                                  "located in the JDK/JRE directory under the directory " +
600                                  "jre/bin/<arch>/native_threads.");
601     ta.setLineWrap(true);
602     ta.setWrapStyleWord(true);
603     ta.setEditable(false);
604     ta.setBackground(panel.getBackground());
605     panel.add(ta);
606     vbox.add(panel);
607 
608     hbox = Box.createHorizontalBox();
609     hbox.add(Box.createGlue());
610     JButton button = new JButton("OK");
611     button.addActionListener(attacher);
612     hbox.add(button);
613     hbox.add(Box.createHorizontalStrut(20));
614     button = new JButton("Cancel");
615     button.addActionListener(new ActionListener() {
616         public void actionPerformed(ActionEvent e) {
617           dialog.setVisible(false);
618           desktop.remove(dialog);
619           setMenuItemsEnabled(attachMenuItems, true);
620         }
621       });
622     hbox.add(button);
623     hbox.add(Box.createGlue());
624     panel = new JPanel();
625     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
626     panel.add(hbox);
627     vbox.add(panel);
628 
629     dialog.getContentPane().add(vbox, BorderLayout.SOUTH);
630 
631     desktop.add(dialog);
632     dialog.setSize(500, 300);
633     GraphicsUtilities.centerInContainer(dialog);
634     dialog.show();
635     corePathField.requestFocus();
636   }
637 
638   // FIXME: merge showAttachDialog, showOpenCoreFileDialog, showConnectDialog
showConnectDialog()639   private void showConnectDialog() {
640     // FIXME: create filtered text field which only accepts numbers
641     setMenuItemsEnabled(attachMenuItems, false);
642     final JInternalFrame dialog = new JInternalFrame("Connect to HotSpot Debug Server");
643     dialog.getContentPane().setLayout(new BorderLayout());
644 
645     JPanel panel = new JPanel();
646     panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
647     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
648     dialog.setBackground(panel.getBackground());
649 
650     panel.add(new JLabel("Enter machine name:"));
651     final JTextField pidTextField = new JTextField(40);
652     ActionListener attacher = new ActionListener() {
653         public void actionPerformed(ActionEvent e) {
654           dialog.setVisible(false);
655           desktop.remove(dialog);
656           workerThread.invokeLater(new Runnable() {
657               public void run() {
658                 connect(pidTextField.getText());
659               }
660             });
661         }
662       };
663 
664     pidTextField.addActionListener(attacher);
665     panel.add(pidTextField);
666     dialog.getContentPane().add(panel, BorderLayout.NORTH);
667 
668     Box vbox = Box.createVerticalBox();
669     panel = new JPanel();
670     panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
671     panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
672     JTextArea ta = new JTextArea(
673                                  "Enter the name of a machine on which the HotSpot \"Debug Server\" is " +
674                                  "running and is attached to a process or core file.");
675     ta.setLineWrap(true);
676     ta.setWrapStyleWord(true);
677     ta.setEditable(false);
678     ta.setBackground(panel.getBackground());
679     panel.add(ta);
680     vbox.add(panel);
681 
682     Box hbox = Box.createHorizontalBox();
683     hbox.add(Box.createGlue());
684     JButton button = new JButton("OK");
685     button.addActionListener(attacher);
686     hbox.add(button);
687     hbox.add(Box.createHorizontalStrut(20));
688     button = new JButton("Cancel");
689     button.addActionListener(new ActionListener() {
690         public void actionPerformed(ActionEvent e) {
691           dialog.setVisible(false);
692           desktop.remove(dialog);
693           setMenuItemsEnabled(attachMenuItems, true);
694         }
695       });
696     hbox.add(button);
697     hbox.add(Box.createGlue());
698     panel = new JPanel();
699     panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
700     panel.add(hbox);
701     vbox.add(panel);
702 
703     dialog.getContentPane().add(vbox, BorderLayout.SOUTH);
704 
705     desktop.add(dialog);
706     dialog.setSize(400, 300);
707     GraphicsUtilities.centerInContainer(dialog);
708     dialog.show();
709     pidTextField.requestFocus();
710   }
711 
showThreadOopInspector(JavaThread thread)712   public void showThreadOopInspector(JavaThread thread) {
713     showInspector(new OopTreeNodeAdapter(thread.getThreadObj(), null));
714   }
715 
showInspector(SimpleTreeNode adapter)716   public void showInspector(SimpleTreeNode adapter) {
717     showPanel("Inspector", new Inspector(adapter), 1.0f, 0.65f);
718   }
719 
showLiveness(Oop oop, LivenessPathList liveness)720   public void showLiveness(Oop oop, LivenessPathList liveness) {
721     ByteArrayOutputStream bos = new ByteArrayOutputStream();
722     PrintStream tty = new PrintStream(bos);
723     int numPaths = liveness.size();
724     for (int i = 0; i < numPaths; i++) {
725       tty.println("Path " + (i + 1) + " of " + numPaths + ":");
726       liveness.get(i).printOn(tty);
727     }
728     JTextArea ta = new JTextArea(bos.toString());
729     ta.setLineWrap(true);
730     ta.setWrapStyleWord(true);
731     ta.setEditable(false);
732 
733     JPanel panel = new JPanel();
734     panel.setLayout(new BorderLayout());
735 
736     JScrollPane scroller = new JScrollPane();
737     scroller.getViewport().add(ta);
738 
739     panel.add(scroller, BorderLayout.CENTER);
740 
741     bos = new ByteArrayOutputStream();
742     tty = new PrintStream(bos);
743     tty.print("Liveness result for ");
744     Oop.printOopValueOn(oop, tty);
745 
746     JInternalFrame frame = new JInternalFrame(bos.toString());
747     frame.setResizable(true);
748     frame.setClosable(true);
749     frame.setIconifiable(true);
750     frame.getContentPane().setLayout(new BorderLayout());
751     frame.getContentPane().add(panel, BorderLayout.CENTER);
752     frame.pack();
753     desktop.add(frame);
754     GraphicsUtilities.reshapeToAspectRatio(frame, 0.5f / 0.2f, 0.5f, frame.getParent().getSize());
755     frame.show();
756   }
757 
fireComputeReversePtrs()758   private void fireComputeReversePtrs() {
759     // Possible this might have been computed elsewhere
760     if (VM.getVM().getRevPtrs() != null) {
761       computeRevPtrsMenuItem.setEnabled(false);
762       return;
763     }
764 
765     workerThread.invokeLater(new Runnable() {
766         public void run() {
767           HeapProgress progress = new HeapProgress("Reverse Pointers Analysis");
768           try {
769             ReversePtrsAnalysis analysis = new ReversePtrsAnalysis();
770             analysis.setHeapProgressThunk(progress);
771             analysis.run();
772             computeRevPtrsMenuItem.setEnabled(false);
773           } catch (OutOfMemoryError e) {
774             final String errMsg = formatMessage(e.toString(), 80);
775             SwingUtilities.invokeLater(new Runnable() {
776                 public void run() {
777                   JOptionPane.showInternalMessageDialog(desktop,
778                                                         "Error computing reverse pointers:" + errMsg,
779                                                         "Error",
780                                                         JOptionPane.WARNING_MESSAGE);
781                 }
782               });
783           } finally {
784             // make sure the progress bar goes away
785             progress.heapIterationComplete();
786           }
787         }
788       });
789   }
790 
791   // Simple struct containing signal information
792   class SignalInfo {
793     public int sigNum;
794     public String sigName;
795   }
796 
797   // Need to have mutable vframe as well as visible memory panel
798   abstract class StackWalker implements Runnable {
799     protected JavaVFrame vf;
800     protected AnnotatedMemoryPanel annoPanel;
801 
StackWalker(JavaVFrame vf, AnnotatedMemoryPanel annoPanel)802     StackWalker(JavaVFrame vf, AnnotatedMemoryPanel annoPanel) {
803       this.vf = vf;
804       this.annoPanel = annoPanel;
805     }
806   }
807 
showThreadStackMemory(final JavaThread thread)808   public void showThreadStackMemory(final JavaThread thread) {
809     // dumpStack(thread);
810     JavaVFrame vframe = getLastJavaVFrame(thread);
811     if (vframe == null) {
812       JOptionPane.showInternalMessageDialog(desktop,
813                                             "Thread \"" + thread.getThreadName() +
814                                             "\" has no Java frames on its stack",
815                                             "Show Stack Memory",
816                                             JOptionPane.INFORMATION_MESSAGE);
817       return;
818     }
819 
820     JInternalFrame stackFrame = new JInternalFrame("Stack Memory for " + thread.getThreadName());
821     stackFrame.getContentPane().setLayout(new BorderLayout());
822     stackFrame.setResizable(true);
823     stackFrame.setClosable(true);
824     stackFrame.setIconifiable(true);
825     final long addressSize = agent.getTypeDataBase().getAddressSize();
826     boolean is64Bit = (addressSize == 8);
827     // This is somewhat of a  hack to guess a thread's stack limits since the
828     // JavaThread doesn't support this functionality. However it is nice in that
829     // it locks us into the active region of the thread's stack and not its
830     // theoretical limits.
831     //
832     sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess();
833     Address sp = tmpFrame.getSP();
834     Address starting = sp;
835     Address maxSP = starting;
836     Address minSP = starting;
837     RegisterMap tmpMap = thread.newRegisterMap(false);
838     while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) {
839         tmpFrame = tmpFrame.sender(tmpMap);
840         if (tmpFrame != null) {
841           sp = tmpFrame.getSP();
842           if (sp != null) {
843             maxSP = AddressOps.max(maxSP, sp);
844             minSP = AddressOps.min(minSP, sp);
845           }
846         }
847 
848     }
849     // It is useful to be able to see say +/- 8K on the current stack range
850     AnnotatedMemoryPanel annoMemPanel = new AnnotatedMemoryPanel(agent.getDebugger(), is64Bit, starting,
851                                                                  minSP.addOffsetTo(-8192),
852                                                                  maxSP.addOffsetTo( 8192));
853 
854     stackFrame.getContentPane().add(annoMemPanel, BorderLayout.CENTER);
855     desktop.add(stackFrame);
856     GraphicsUtilities.reshapeToAspectRatio(stackFrame, 4.0f / 3.0f, 0.85f, stackFrame.getParent().getSize());
857     stackFrame.show();
858 
859     // Stackmap computation for interpreted frames is expensive; do
860     // all stackwalking work in another thread for better GUI
861     // responsiveness
862     workerThread.invokeLater(new StackWalker(vframe, annoMemPanel) {
863         public void run() {
864           Address startAddr = null;
865 
866           // As this is a debugger, we want to provide potential crash
867           // information to the user, i.e., by marking signal handler frames
868           // on the stack. Since this system is currently targeted at
869           // annotating the Java frames (interpreted or compiled) on the
870           // stack and not, for example, "external" frames (note the current
871           // absence of a PC-to-symbol lookup mechanism at the Debugger
872           // level), we want to mark any Java frames which were interrupted
873           // by a signal. We do this by making two passes over the stack,
874           // one which finds signal handler frames and puts the parent
875           // frames in a table and one which finds Java frames and if they
876           // are in the table indicates that they were interrupted by a signal.
877 
878           Map interruptedFrameMap = new HashMap();
879           {
880             sun.jvm.hotspot.runtime.Frame tmpFrame = thread.getCurrentFrameGuess();
881             RegisterMap tmpMap = thread.newRegisterMap(false);
882             while ((tmpFrame != null) && (!tmpFrame.isFirstFrame())) {
883               if (tmpFrame.isSignalHandlerFrameDbg()) {
884                 // Add some information to the map that we can extract later
885                 sun.jvm.hotspot.runtime.Frame interruptedFrame = tmpFrame.sender(tmpMap);
886                 SignalInfo info = new SignalInfo();
887                 info.sigNum  = tmpFrame.getSignalNumberDbg();
888                 info.sigName = tmpFrame.getSignalNameDbg();
889                 interruptedFrameMap.put(interruptedFrame, info);
890               }
891               tmpFrame = tmpFrame.sender(tmpMap);
892             }
893           }
894 
895           while (vf != null) {
896             String anno = null;
897             JavaVFrame curVFrame = vf;
898             sun.jvm.hotspot.runtime.Frame curFrame = curVFrame.getFrame();
899             Method interpreterFrameMethod = null;
900 
901             if (curVFrame.isInterpretedFrame()) {
902               anno = "Interpreted frame";
903             } else {
904               anno = "Compiled frame";
905               if (curVFrame.isDeoptimized()) {
906                 anno += " (deoptimized)";
907               }
908             }
909             if (curVFrame.mayBeImpreciseDbg()) {
910               anno += "; information may be imprecise";
911             }
912 
913             if (curVFrame.isInterpretedFrame()) {
914               // Find the codelet
915               InterpreterCodelet codelet = VM.getVM().getInterpreter().getCodeletContaining(curFrame.getPC());
916               String description = null;
917               if (codelet != null) {
918                 description = codelet.getDescription();
919               }
920               if (description == null) {
921                 anno += "\n(Unknown interpreter codelet)";
922               } else {
923                 anno += "\nExecuting in codelet \"" + description + "\" at PC = " + curFrame.getPC();
924               }
925             } else if (curVFrame.isCompiledFrame()) {
926               anno += "\nExecuting at PC = " + curFrame.getPC();
927             }
928 
929             if (startAddr == null) {
930               startAddr = curFrame.getSP();
931             }
932 
933             // FIXME: some compiled frames with empty oop map sets have been
934             // found (for example, Vector's inner Enumeration class, method
935             // "hasMoreElements"). Not sure yet why these cases are showing
936             // up -- should be possible (though unlikely) for safepoint code
937             // to patch the return instruction of these methods and then
938             // later attempt to get an oop map for that instruction. For
939             // now, we warn if we find such a method.
940             boolean shouldSkipOopMaps = false;
941             if (curVFrame.isCompiledFrame()) {
942               CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC());
943               ImmutableOopMapSet maps = cb.getOopMaps();
944               if ((maps == null) || (maps.getCount() == 0)) {
945                 shouldSkipOopMaps = true;
946               }
947             }
948 
949             // Add signal information to annotation if necessary
950             SignalInfo sigInfo = (SignalInfo) interruptedFrameMap.get(curFrame);
951             if (sigInfo != null) {
952               // This frame took a signal and we need to report it.
953               anno = (anno + "\n*** INTERRUPTED BY SIGNAL " + Integer.toString(sigInfo.sigNum) +
954                       " (" + sigInfo.sigName + ")");
955             }
956 
957             JavaVFrame nextVFrame = curVFrame;
958             sun.jvm.hotspot.runtime.Frame nextFrame = curFrame;
959             do {
960               curVFrame = nextVFrame;
961               curFrame = nextFrame;
962 
963               try {
964                 Method method = curVFrame.getMethod();
965                 if (interpreterFrameMethod == null && curVFrame.isInterpretedFrame()) {
966                   interpreterFrameMethod = method;
967                 }
968                 int bci = curVFrame.getBCI();
969                 String lineNumberAnno = "";
970                 if (method.hasLineNumberTable()) {
971                   if ((bci == DebugInformationRecorder.SYNCHRONIZATION_ENTRY_BCI) ||
972                       (bci >= 0 && bci < method.getCodeSize())) {
973                     lineNumberAnno = ", line " + method.getLineNumberFromBCI(bci);
974                   } else {
975                     lineNumberAnno = " (INVALID BCI)";
976                   }
977                 }
978                 anno += "\n" + method.getMethodHolder().getName().asString() + "." +
979                                method.getName().asString() + method.getSignature().asString() +
980                                "\n@bci " + bci + lineNumberAnno;
981               } catch (Exception e) {
982                 anno += "\n(ERROR while iterating vframes for frame " + curFrame + ")";
983               }
984 
985               nextVFrame = curVFrame.javaSender();
986               if (nextVFrame != null) {
987                 nextFrame = nextVFrame.getFrame();
988               }
989             } while (nextVFrame != null && nextFrame.equals(curFrame));
990 
991             if (shouldSkipOopMaps) {
992               anno = anno + "\nNOTE: null or empty ImmutableOopMapSet found for this CodeBlob";
993             }
994 
995             if (curFrame.getFP() != null) {
996               annoPanel.addAnnotation(new Annotation(curFrame.getSP(),
997                                                      curFrame.getFP(),
998                                                      anno));
999             } else {
1000               // For C2, which has null frame pointers on x86/amd64/aarch64
1001               CodeBlob cb = VM.getVM().getCodeCache().findBlob(curFrame.getPC());
1002               Address sp = curFrame.getSP();
1003               if (Assert.ASSERTS_ENABLED) {
1004                 Assert.that(cb.getFrameSize() > 0, "CodeBlob must have non-zero frame size");
1005               }
1006               annoPanel.addAnnotation(new Annotation(sp,
1007                                                      sp.addOffsetTo(cb.getFrameSize()),
1008                                                      anno));
1009             }
1010 
1011             // Add interpreter frame annotations
1012             if (curFrame.isInterpretedFrame()) {
1013               annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameExpressionStack(),
1014                                                      curFrame.addressOfInterpreterFrameTOS(),
1015                                                      "Interpreter expression stack"));
1016               Address monBegin = curFrame.interpreterFrameMonitorBegin().address();
1017               Address monEnd = curFrame.interpreterFrameMonitorEnd().address();
1018               if (!monBegin.equals(monEnd)) {
1019                   annoPanel.addAnnotation(new Annotation(monBegin, monEnd,
1020                                                          "BasicObjectLocks"));
1021               }
1022               if (interpreterFrameMethod != null) {
1023                 // The offset is just to get the right stack slots highlighted in the output
1024                 int offset = 1;
1025                 annoPanel.addAnnotation(new Annotation(curFrame.addressOfInterpreterFrameLocal(offset),
1026                                                        curFrame.addressOfInterpreterFrameLocal((int) interpreterFrameMethod.getMaxLocals() + offset),
1027                                                        "Interpreter locals area for frame with SP = " + curFrame.getSP()));
1028               }
1029               String methodAnno = "Interpreter frame Method*";
1030               if (interpreterFrameMethod == null) {
1031                 methodAnno += " (BAD OOP)";
1032               }
1033               Address a = curFrame.addressOfInterpreterFrameMethod();
1034               annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), methodAnno));
1035               a = curFrame.addressOfInterpreterFrameCPCache();
1036               annoPanel.addAnnotation(new Annotation(a, a.addOffsetTo(addressSize), "Interpreter constant pool cache"));
1037             }
1038 
1039             RegisterMap rm = (RegisterMap) vf.getRegisterMap().clone();
1040             if (!shouldSkipOopMaps) {
1041               try {
1042                 curFrame.oopsDo(new AddressVisitor() {
1043                     public void visitAddress(Address addr) {
1044                       if (Assert.ASSERTS_ENABLED) {
1045                         Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null,
1046                                     "Address " + addr + "should have been aligned");
1047                       }
1048                       OopHandle handle = addr.getOopHandleAt(0);
1049                       addAnnotation(addr, handle);
1050                     }
1051 
1052                     public void visitCompOopAddress(Address addr) {
1053                       if (Assert.ASSERTS_ENABLED) {
1054                         Assert.that(addr.andWithMask(VM.getVM().getAddressSize() - 1) == null,
1055                                     "Address " + addr + "should have been aligned");
1056                       }
1057                       OopHandle handle = addr.getCompOopHandleAt(0);
1058                       addAnnotation(addr, handle);
1059                     }
1060 
1061                     public void addAnnotation(Address addr, OopHandle handle) {
1062                       // Check contents
1063                       String anno = "null oop";
1064                       if (handle != null) {
1065                         // Find location
1066                         CollectedHeap collHeap = VM.getVM().getUniverse().heap();
1067                         boolean bad = true;
1068                         anno = "BAD OOP";
1069                         if (collHeap instanceof GenCollectedHeap) {
1070                           GenCollectedHeap heap = (GenCollectedHeap) collHeap;
1071                           for (int i = 0; i < heap.nGens(); i++) {
1072                             if (heap.getGen(i).isIn(handle)) {
1073                               if (i == 0) {
1074                                 anno = "NewGen ";
1075                               } else if (i == 1) {
1076                                 anno = "OldGen ";
1077                               } else {
1078                                 anno = "Gen " + i + " ";
1079                               }
1080                               bad = false;
1081                               break;
1082                             }
1083                           }
1084 
1085                         } else if (collHeap instanceof G1CollectedHeap) {
1086                           G1CollectedHeap heap = (G1CollectedHeap)collHeap;
1087                           HeapRegion region = heap.hrm().getByAddress(handle);
1088 
1089                           if (region.isFree()) {
1090                             anno = "Free ";
1091                             bad = false;
1092                           } else if (region.isYoung()) {
1093                             anno = "Young ";
1094                             bad = false;
1095                           } else if (region.isHumongous()) {
1096                             anno = "Humongous ";
1097                             bad = false;
1098                           } else if (region.isPinned()) {
1099                             anno = "Pinned ";
1100                             bad = false;
1101                           } else if (region.isOld()) {
1102                             anno = "Old ";
1103                             bad = false;
1104                           }
1105                         } else if (collHeap instanceof ParallelScavengeHeap) {
1106                           ParallelScavengeHeap heap = (ParallelScavengeHeap) collHeap;
1107                           if (heap.youngGen().isIn(handle)) {
1108                             anno = "PSYoungGen ";
1109                             bad = false;
1110                           } else if (heap.oldGen().isIn(handle)) {
1111                             anno = "PSOldGen ";
1112                             bad = false;
1113                           }
1114                         } else if (collHeap instanceof EpsilonHeap) {
1115                           anno = "Epsilon ";
1116                           bad = false;
1117                         } else if (collHeap instanceof ShenandoahHeap) {
1118                           ShenandoahHeap heap = (ShenandoahHeap) collHeap;
1119                           anno = "ShenandoahHeap ";
1120                           bad = false;
1121                         } else if (collHeap instanceof ZCollectedHeap) {
1122                           ZCollectedHeap heap = (ZCollectedHeap) collHeap;
1123                           anno = "ZHeap ";
1124                           bad = false;
1125                         } else {
1126                           // Optimistically assume the oop isn't bad
1127                           anno = "[Unknown generation] ";
1128                           bad = false;
1129                         }
1130 
1131                         if (!bad) {
1132                           try {
1133                             Oop oop = VM.getVM().getObjectHeap().newOop(handle);
1134                             if (oop instanceof Instance) {
1135                                 // Java-level objects always have workable names
1136                               anno = anno + oop.getKlass().getName().asString();
1137                             } else {
1138                               ByteArrayOutputStream bos = new ByteArrayOutputStream();
1139                               Oop.printOopValueOn(oop, new PrintStream(bos));
1140                               anno = anno + bos.toString();
1141                             }
1142                           }
1143                           catch (AddressException e) {
1144                             anno += "CORRUPT OOP";
1145                           }
1146                           catch (NullPointerException e) {
1147                             anno += "CORRUPT OOP (null pointer)";
1148                           }
1149                         }
1150                       }
1151 
1152                       annoPanel.addAnnotation(new Annotation(addr, addr.addOffsetTo(addressSize), anno));
1153                     }
1154                   }, rm);
1155               } catch (Exception e) {
1156                 System.err.println("Error while performing oopsDo for frame " + curFrame);
1157                 e.printStackTrace();
1158               }
1159             }
1160 
1161             vf = nextVFrame;
1162           }
1163 
1164           // This used to paint as we walked the frames. This caused the display to be refreshed
1165           // enough to be annoying on remote displays. It also would cause the annotations to
1166           // be displayed in varying order which caused some annotations to overwrite others
1167           // depending on the races between painting and adding annotations. This latter problem
1168           // still exists to some degree but moving this code here definitely seems to reduce it
1169           annoPanel.makeVisible(startAddr);
1170           annoPanel.repaint();
1171         }
1172       });
1173   }
1174 
1175   // Attach to existing JVMDebugger, which should be already attached to a core/process.
attach(JVMDebugger d)1176   private void attach(JVMDebugger d) {
1177     attached = true;
1178     showThreadsDialog();
1179   }
1180 
1181   /** NOTE we are in a different thread here than either the main
1182       thread or the Swing/AWT event handler thread, so we must be very
1183       careful when creating or removing widgets */
attach(String pidText)1184   private void attach(String pidText) {
1185       try {
1186       this.pidText = pidText;
1187       pid = Integer.parseInt(pidText);
1188     }
1189     catch (NumberFormatException e) {
1190       SwingUtilities.invokeLater(new Runnable() {
1191           public void run() {
1192             setMenuItemsEnabled(attachMenuItems, true);
1193             JOptionPane.showInternalMessageDialog(desktop,
1194                                                   "Unable to parse process ID \"" + HSDB.this.pidText + "\".\nPlease enter a number.",
1195                                                   "Parse error",
1196                                                   JOptionPane.WARNING_MESSAGE);
1197           }
1198         });
1199       return;
1200     }
1201 
1202     // Try to attach to this process
1203     Runnable remover = new Runnable() {
1204           public void run() {
1205             attachWaitDialog.setVisible(false);
1206             desktop.remove(attachWaitDialog);
1207             attachWaitDialog = null;
1208           }
1209       };
1210 
1211     try {
1212       SwingUtilities.invokeLater(new Runnable() {
1213           public void run() {
1214             JOptionPane pane = new JOptionPane("Attaching to process " + pid + ", please wait...", JOptionPane.INFORMATION_MESSAGE);
1215             pane.setOptions(new Object[] {});
1216             attachWaitDialog = pane.createInternalFrame(desktop, "Attaching to Process");
1217             attachWaitDialog.show();
1218           }
1219         });
1220 
1221       // FIXME: display exec'd debugger's output messages during this
1222       // lengthy call
1223       agent.attach(pid);
1224       if (agent.getDebugger().hasConsole()) {
1225         showDbgConsoleMenuItem.setEnabled(true);
1226       }
1227       attached = true;
1228       SwingUtilities.invokeLater(remover);
1229     }
1230     catch (DebuggerException e) {
1231       SwingUtilities.invokeLater(remover);
1232       final String errMsg = formatMessage(e.getMessage(), 80);
1233       SwingUtilities.invokeLater(new Runnable() {
1234           public void run() {
1235             setMenuItemsEnabled(attachMenuItems, true);
1236             JOptionPane.showInternalMessageDialog(desktop,
1237                                                   "Unable to connect to process ID " + pid + ":\n\n" + errMsg,
1238                                                   "Unable to Connect",
1239                                                   JOptionPane.WARNING_MESSAGE);
1240           }
1241         });
1242       agent.detach();
1243       return;
1244     }
1245 
1246     // OK, the VM should be available. Create the Threads dialog.
1247     showThreadsDialog();
1248   }
1249 
1250   /** NOTE we are in a different thread here than either the main
1251       thread or the Swing/AWT event handler thread, so we must be very
1252       careful when creating or removing widgets */
attach(final String executablePath, final String corePath)1253   private void attach(final String executablePath, final String corePath) {
1254     // Try to open this core file
1255     Runnable remover = new Runnable() {
1256           public void run() {
1257             attachWaitDialog.setVisible(false);
1258             desktop.remove(attachWaitDialog);
1259             attachWaitDialog = null;
1260           }
1261       };
1262 
1263     try {
1264       SwingUtilities.invokeLater(new Runnable() {
1265           public void run() {
1266             JOptionPane pane = new JOptionPane("Opening core file, please wait...", JOptionPane.INFORMATION_MESSAGE);
1267             pane.setOptions(new Object[] {});
1268             attachWaitDialog = pane.createInternalFrame(desktop, "Opening Core File");
1269             attachWaitDialog.show();
1270           }
1271         });
1272 
1273       // FIXME: display exec'd debugger's output messages during this
1274       // lengthy call
1275       agent.attach(executablePath, corePath);
1276       if (agent.getDebugger().hasConsole()) {
1277         showDbgConsoleMenuItem.setEnabled(true);
1278       }
1279       attached = true;
1280       SwingUtilities.invokeLater(remover);
1281     }
1282     catch (DebuggerException e) {
1283       SwingUtilities.invokeLater(remover);
1284       final String errMsg = formatMessage(e.getMessage(), 80);
1285       SwingUtilities.invokeLater(new Runnable() {
1286           public void run() {
1287             setMenuItemsEnabled(attachMenuItems, true);
1288             JOptionPane.showInternalMessageDialog(desktop,
1289                                                   "Unable to open core file\n" + corePath + ":\n\n" + errMsg,
1290                                                   "Unable to Open Core File",
1291                                                   JOptionPane.WARNING_MESSAGE);
1292           }
1293         });
1294       agent.detach();
1295       return;
1296     }
1297 
1298     // OK, the VM should be available. Create the Threads dialog.
1299     showThreadsDialog();
1300   }
1301 
1302   /** NOTE we are in a different thread here than either the main
1303       thread or the Swing/AWT event handler thread, so we must be very
1304       careful when creating or removing widgets */
connect(final String remoteMachineName)1305   private void connect(final String remoteMachineName) {
1306     // Try to open this core file
1307     Runnable remover = new Runnable() {
1308           public void run() {
1309             attachWaitDialog.setVisible(false);
1310             desktop.remove(attachWaitDialog);
1311             attachWaitDialog = null;
1312           }
1313       };
1314 
1315     try {
1316       SwingUtilities.invokeLater(new Runnable() {
1317           public void run() {
1318             JOptionPane pane = new JOptionPane("Connecting to debug server, please wait...", JOptionPane.INFORMATION_MESSAGE);
1319             pane.setOptions(new Object[] {});
1320             attachWaitDialog = pane.createInternalFrame(desktop, "Connecting to Debug Server");
1321             attachWaitDialog.show();
1322           }
1323         });
1324 
1325       agent.attach(remoteMachineName);
1326       if (agent.getDebugger().hasConsole()) {
1327         showDbgConsoleMenuItem.setEnabled(true);
1328       }
1329       attached = true;
1330       SwingUtilities.invokeLater(remover);
1331     }
1332     catch (DebuggerException e) {
1333       SwingUtilities.invokeLater(remover);
1334       final String errMsg = formatMessage(e.getMessage(), 80);
1335       SwingUtilities.invokeLater(new Runnable() {
1336           public void run() {
1337             setMenuItemsEnabled(attachMenuItems, true);
1338             JOptionPane.showInternalMessageDialog(desktop,
1339                                                   "Unable to connect to machine \"" + remoteMachineName + "\":\n\n" + errMsg,
1340                                                   "Unable to Connect",
1341                                                   JOptionPane.WARNING_MESSAGE);
1342           }
1343         });
1344       agent.detach();
1345       return;
1346     }
1347 
1348     // OK, the VM should be available. Create the Threads dialog.
1349     showThreadsDialog();
1350   }
1351 
detachDebugger()1352   private void detachDebugger() {
1353     if (!attached) {
1354       return;
1355     }
1356     agent.detach();
1357     attached = false;
1358   }
1359 
detach()1360   private void detach() {
1361     detachDebugger();
1362     attachWaitDialog = null;
1363     threadsFrame = null;
1364     consoleFrame = null;
1365     setMenuItemsEnabled(attachMenuItems, true);
1366     setMenuItemsEnabled(detachMenuItems, false);
1367     toolsMenu.setEnabled(false);
1368     showDbgConsoleMenuItem.setEnabled(false);
1369     // FIXME: is this sufficient, or will I have to do anything else
1370     // to the components to kill them off? What about WorkerThreads?
1371     desktop.removeAll();
1372     desktop.invalidate();
1373     desktop.validate();
1374     desktop.repaint();
1375   }
1376 
1377   /** NOTE that this is called from another thread than the main or
1378       Swing thread and we have to be careful about synchronization */
showThreadsDialog()1379   private void showThreadsDialog() {
1380     SwingUtilities.invokeLater(new Runnable() {
1381         public void run() {
1382           threadsFrame = new JInternalFrame("Java Threads");
1383           threadsFrame.setResizable(true);
1384           threadsFrame.setIconifiable(true);
1385           JavaThreadsPanel threadsPanel = new JavaThreadsPanel();
1386           threadsPanel.addPanelListener(HSDB.this);
1387           threadsFrame.getContentPane().add(threadsPanel);
1388           threadsFrame.setSize(500, 300);
1389           threadsFrame.pack();
1390           desktop.add(threadsFrame);
1391           GraphicsUtilities.moveToInContainer(threadsFrame, 0.75f, 0.25f, 0, 20);
1392           threadsFrame.show();
1393           setMenuItemsEnabled(attachMenuItems, false);
1394           setMenuItemsEnabled(detachMenuItems, true);
1395           toolsMenu.setEnabled(true);
1396           VM.registerVMInitializedObserver(new Observer() {
1397               public void update(Observable o, Object data) {
1398                 computeRevPtrsMenuItem.setEnabled(true);
1399               }
1400             });
1401         }
1402       });
1403   }
1404 
showObjectHistogram()1405   private void showObjectHistogram() {
1406     sun.jvm.hotspot.oops.ObjectHistogram histo = new sun.jvm.hotspot.oops.ObjectHistogram();
1407     ObjectHistogramCleanupThunk cleanup =
1408       new ObjectHistogramCleanupThunk(histo);
1409     doHeapIteration("Object Histogram",
1410                     "Generating histogram...",
1411                     histo,
1412                     cleanup);
1413   }
1414 
1415   class ObjectHistogramCleanupThunk implements CleanupThunk {
1416     sun.jvm.hotspot.oops.ObjectHistogram histo;
1417 
ObjectHistogramCleanupThunk(sun.jvm.hotspot.oops.ObjectHistogram histo)1418     ObjectHistogramCleanupThunk(sun.jvm.hotspot.oops.ObjectHistogram histo) {
1419       this.histo = histo;
1420     }
1421 
heapIterationComplete()1422     public void heapIterationComplete() {
1423       SwingUtilities.invokeLater(new Runnable() {
1424           public void run() {
1425             JInternalFrame histoFrame = new JInternalFrame("Object Histogram");
1426             histoFrame.setResizable(true);
1427             histoFrame.setClosable(true);
1428             histoFrame.setIconifiable(true);
1429             histoFrame.getContentPane().setLayout(new BorderLayout());
1430             ObjectHistogramPanel panel = new ObjectHistogramPanel(histo);
1431             panel.addPanelListener(HSDB.this);
1432             histoFrame.getContentPane().add(panel);
1433             desktop.add(histoFrame);
1434             GraphicsUtilities.reshapeToAspectRatio(histoFrame, 4.0f / 3.0f, 0.6f,
1435                                        histoFrame.getParent().getSize());
1436             GraphicsUtilities.centerInContainer(histoFrame);
1437             histoFrame.show();
1438           }
1439         });
1440     }
1441   }
1442 
showObjectsOfType(Klass type)1443   public void showObjectsOfType(Klass type) {
1444     FindObjectByType finder = new FindObjectByType(type);
1445     FindObjectByTypeCleanupThunk cleanup =
1446       new FindObjectByTypeCleanupThunk(finder);
1447     ByteArrayOutputStream bos = new ByteArrayOutputStream();
1448     type.printValueOn(new PrintStream(bos));
1449     String typeName = bos.toString();
1450     doHeapIteration("Show Objects Of Type",
1451                     "Finding instances of \"" + typeName + "\"",
1452                     finder,
1453                     cleanup);
1454   }
1455 
1456   class FindObjectByTypeCleanupThunk implements CleanupThunk {
1457     FindObjectByType finder;
1458 
FindObjectByTypeCleanupThunk(FindObjectByType finder)1459     FindObjectByTypeCleanupThunk(FindObjectByType finder) {
1460       this.finder = finder;
1461     }
1462 
heapIterationComplete()1463     public void heapIterationComplete() {
1464       SwingUtilities.invokeLater(new Runnable() {
1465           public void run() {
1466             JInternalFrame finderFrame = new JInternalFrame("Show Objects of Type");
1467             finderFrame.getContentPane().setLayout(new BorderLayout());
1468             finderFrame.setResizable(true);
1469             finderFrame.setClosable(true);
1470             finderFrame.setIconifiable(true);
1471             ObjectListPanel panel = new ObjectListPanel(finder.getResults(),
1472                                                         new HeapProgress("Reverse Pointers Analysis"));
1473             panel.addPanelListener(HSDB.this);
1474             finderFrame.getContentPane().add(panel);
1475             desktop.add(finderFrame);
1476             GraphicsUtilities.reshapeToAspectRatio(finderFrame, 4.0f / 3.0f, 0.6f,
1477                                        finderFrame.getParent().getSize());
1478             GraphicsUtilities.centerInContainer(finderFrame);
1479             finderFrame.show();
1480           }
1481         });
1482     }
1483   }
1484 
showDebuggerConsole()1485   private void showDebuggerConsole() {
1486     if (consoleFrame == null) {
1487       consoleFrame = new JInternalFrame("Debugger Console");
1488       consoleFrame.setResizable(true);
1489       consoleFrame.setClosable(true);
1490       consoleFrame.setIconifiable(true);
1491       consoleFrame.getContentPane().setLayout(new BorderLayout());
1492       consoleFrame.getContentPane().add(new DebuggerConsolePanel(agent.getDebugger()), BorderLayout.CENTER);
1493       GraphicsUtilities.reshapeToAspectRatio(consoleFrame, 5.0f, 0.9f, desktop.getSize());
1494     }
1495     if (consoleFrame.getParent() == null) {
1496       desktop.add(consoleFrame);
1497     }
1498     consoleFrame.setVisible(true);
1499     consoleFrame.show();
1500     consoleFrame.getContentPane().getComponent(0).requestFocus();
1501   }
1502 
showConsole()1503   private void showConsole() {
1504       CommandProcessor.DebuggerInterface di = new CommandProcessor.DebuggerInterface() {
1505               public HotSpotAgent getAgent() {
1506                   return agent;
1507               }
1508               public boolean isAttached() {
1509                   return attached;
1510               }
1511               public void attach(String pid) {
1512                   HSDB.this.attach(pid);
1513               }
1514               public void attach(String java, String core) {
1515               }
1516               public void detach() {
1517                   detachDebugger();
1518               }
1519               public void reattach() {
1520                   if (attached) {
1521                       detachDebugger();
1522                   }
1523                   if (pidText != null) {
1524                       attach(pidText);
1525                   } else {
1526                       attach(execPath, coreFilename);
1527                   }
1528               }
1529           };
1530 
1531       showPanel("Command Line", new CommandProcessorPanel(new CommandProcessor(di, null, null, null)));
1532   }
1533 
showFindByQueryPanel()1534   private void showFindByQueryPanel() {
1535     showPanel("Find Object by Query", new FindByQueryPanel());
1536   }
1537 
showFindPanel()1538   private void showFindPanel() {
1539     showPanel("Find Pointer", new FindPanel());
1540   }
1541 
showFindInHeapPanel()1542   private void showFindInHeapPanel() {
1543     showPanel("Find Address In Heap", new FindInHeapPanel());
1544   }
1545 
showFindInCodeCachePanel()1546   private void showFindInCodeCachePanel() {
1547     showPanel("Find Address In Code Cache", new FindInCodeCachePanel());
1548   }
1549 
showHeapParametersPanel()1550   private void showHeapParametersPanel() {
1551     showPanel("Heap Parameters", new HeapParametersPanel());
1552   }
1553 
showThreadInfo(final JavaThread thread)1554   public void showThreadInfo(final JavaThread thread) {
1555     showPanel("Info for " + thread.getThreadName(), new ThreadInfoPanel(thread));
1556   }
1557 
showJavaStackTrace(final JavaThread thread)1558   public void showJavaStackTrace(final JavaThread thread) {
1559     JavaStackTracePanel jstp = new JavaStackTracePanel();
1560     showPanel("Java stack trace for " + thread.getThreadName(), jstp);
1561     jstp.setJavaThread(thread);
1562   }
1563 
showDeadlockDetectionPanel()1564   private void showDeadlockDetectionPanel() {
1565     showPanel("Deadlock Detection", new DeadlockDetectionPanel());
1566   }
1567 
showMonitorCacheDumpPanel()1568   private void showMonitorCacheDumpPanel() {
1569     showPanel("Monitor Cache Dump", new MonitorCacheDumpPanel());
1570   }
1571 
showClassBrowser()1572   public void showClassBrowser() {
1573     final JInternalFrame progressFrame = new JInternalFrame("Class Browser");
1574     progressFrame.setResizable(true);
1575     progressFrame.setClosable(true);
1576     progressFrame.setIconifiable(true);
1577     progressFrame.getContentPane().setLayout(new BorderLayout());
1578     final ProgressBarPanel bar = new ProgressBarPanel("Generating class list ..");
1579     bar.setIndeterminate(true);
1580     progressFrame.getContentPane().add(bar, BorderLayout.CENTER);
1581     desktop.add(progressFrame);
1582     progressFrame.pack();
1583     GraphicsUtilities.centerInContainer(progressFrame);
1584     progressFrame.show();
1585 
1586     workerThread.invokeLater(new Runnable() {
1587                                 public void run() {
1588                                    HTMLGenerator htmlGen = new HTMLGenerator();
1589                                    InstanceKlass[] klasses = SystemDictionaryHelper.getAllInstanceKlasses();
1590                                    final String htmlText = htmlGen.genHTMLForKlassNames(klasses);
1591                                    SwingUtilities.invokeLater(new Runnable() {
1592                                       public void run() {
1593                                          JInternalFrame cbFrame = new JInternalFrame("Class Browser");
1594                                          cbFrame.getContentPane().setLayout(new BorderLayout());
1595                                          cbFrame.setResizable(true);
1596                                          cbFrame.setClosable(true);
1597                                          cbFrame.setIconifiable(true);
1598                                          ClassBrowserPanel cbPanel = new ClassBrowserPanel();
1599                                          cbFrame.getContentPane().add(cbPanel, BorderLayout.CENTER);
1600                                          desktop.remove(progressFrame);
1601                                          desktop.repaint();
1602                                          desktop.add(cbFrame);
1603                                          GraphicsUtilities.reshapeToAspectRatio(cbFrame, 1.25f, 0.85f,
1604                                                                       cbFrame.getParent().getSize());
1605                                          cbFrame.show();
1606                                          cbPanel.setClassesText(htmlText);
1607                                       }
1608                                    });
1609                                 }
1610                              });
1611   }
1612 
showCodeViewer()1613   public void showCodeViewer() {
1614     showPanel("Code Viewer", new CodeViewerPanel(), 1.25f, 0.85f);
1615   }
1616 
showCodeViewer(final Address address)1617   public void showCodeViewer(final Address address) {
1618     final CodeViewerPanel panel = new CodeViewerPanel();
1619     showPanel("Code Viewer", panel, 1.25f, 0.85f);
1620     SwingUtilities.invokeLater(new Runnable() {
1621         public void run() {
1622           panel.viewAddress(address);
1623         }
1624       });
1625 
1626   }
1627 
showMemoryViewer()1628   public void showMemoryViewer() {
1629     showPanel("Memory Viewer", new MemoryViewer(agent.getDebugger(), agent.getTypeDataBase().getAddressSize() == 8));
1630   }
1631 
showCommandLineFlags()1632   public void showCommandLineFlags() {
1633     showPanel("Command Line Flags", new VMFlagsPanel());
1634   }
1635 
showVMVersion()1636   public void showVMVersion() {
1637     showPanel("VM Version Info", new VMVersionInfoPanel());
1638   }
1639 
showSystemProperties()1640   public void showSystemProperties() {
1641     showPanel("System Properties", new SysPropsPanel());
1642   }
1643 
showPanel(String name, JPanel panel)1644   private void showPanel(String name, JPanel panel) {
1645     showPanel(name, panel, 5.0f / 3.0f, 0.4f);
1646   }
1647 
showPanel(String name, JPanel panel, float aspectRatio, float fillRatio)1648   private void showPanel(String name, JPanel panel, float aspectRatio, float fillRatio) {
1649     JInternalFrame frame = new JInternalFrame(name);
1650     frame.getContentPane().setLayout(new BorderLayout());
1651     frame.setResizable(true);
1652     frame.setClosable(true);
1653     frame.setIconifiable(true);
1654     frame.setMaximizable(true);
1655     frame.getContentPane().add(panel, BorderLayout.CENTER);
1656     desktop.add(frame);
1657     GraphicsUtilities.reshapeToAspectRatio(frame, aspectRatio, fillRatio, frame.getParent().getSize());
1658     GraphicsUtilities.randomLocation(frame);
1659     frame.show();
1660     if (panel instanceof SAPanel) {
1661       ((SAPanel)panel).addPanelListener(this);
1662     }
1663   }
1664 
1665   //--------------------------------------------------------------------------------
1666   // Framework for heap iteration with progress bar
1667   //
1668 
1669   interface CleanupThunk {
heapIterationComplete()1670     public void heapIterationComplete();
1671   }
1672 
1673   class HeapProgress implements HeapProgressThunk {
1674     private JInternalFrame frame;
1675     private ProgressBarPanel bar;
1676     private String windowTitle;
1677     private String progressBarTitle;
1678     private CleanupThunk cleanup;
1679 
HeapProgress(String windowTitle)1680     HeapProgress(String windowTitle) {
1681       this(windowTitle, "Percentage of heap visited", null);
1682     }
1683 
HeapProgress(String windowTitle, String progressBarTitle)1684     HeapProgress(String windowTitle, String progressBarTitle) {
1685       this(windowTitle, progressBarTitle, null);
1686     }
1687 
HeapProgress(String windowTitle, String progressBarTitle, CleanupThunk cleanup)1688     HeapProgress(String windowTitle, String progressBarTitle, CleanupThunk cleanup) {
1689       this.windowTitle = windowTitle;
1690       this.progressBarTitle = progressBarTitle;
1691       this.cleanup = cleanup;
1692     }
1693 
heapIterationFractionUpdate(final double fractionOfHeapVisited)1694     public void heapIterationFractionUpdate(final double fractionOfHeapVisited) {
1695       if (frame == null) {
1696         SwingUtilities.invokeLater(new Runnable() {
1697             public void run() {
1698               frame = new JInternalFrame(windowTitle);
1699               frame.setResizable(true);
1700               frame.setIconifiable(true);
1701               frame.getContentPane().setLayout(new BorderLayout());
1702               bar = new ProgressBarPanel(progressBarTitle);
1703               frame.getContentPane().add(bar, BorderLayout.CENTER);
1704               desktop.add(frame);
1705               frame.pack();
1706               GraphicsUtilities.constrainToSize(frame, frame.getParent().getSize());
1707               GraphicsUtilities.centerInContainer(frame);
1708               frame.show();
1709             }
1710           });
1711       }
1712 
1713       SwingUtilities.invokeLater(new Runnable() {
1714           public void run() {
1715             bar.setValue(fractionOfHeapVisited);
1716           }
1717         });
1718     }
1719 
heapIterationComplete()1720     public void heapIterationComplete() {
1721       SwingUtilities.invokeLater(new Runnable() {
1722           public void run() {
1723             desktop.remove(frame);
1724             desktop.repaint();
1725             if (VM.getVM().getRevPtrs() != null) {
1726               // Ended up computing reverse pointers as a side-effect
1727               computeRevPtrsMenuItem.setEnabled(false);
1728             }
1729           }
1730         });
1731 
1732       if (cleanup != null) {
1733         cleanup.heapIterationComplete();
1734       }
1735     }
1736   }
1737 
1738   class VisitHeap implements Runnable {
1739     HeapVisitor visitor;
1740 
VisitHeap(HeapVisitor visitor)1741     VisitHeap(HeapVisitor visitor) {
1742       this.visitor = visitor;
1743     }
1744 
run()1745     public void run() {
1746       VM.getVM().getObjectHeap().iterate(visitor);
1747     }
1748   }
1749 
doHeapIteration(String frameTitle, String progressBarText, HeapVisitor visitor, CleanupThunk cleanup)1750   private void doHeapIteration(String frameTitle,
1751                                String progressBarText,
1752                                HeapVisitor visitor,
1753                                CleanupThunk cleanup) {
1754     sun.jvm.hotspot.oops.ObjectHistogram histo = new sun.jvm.hotspot.oops.ObjectHistogram();
1755     HeapProgress progress = new HeapProgress(frameTitle,
1756                                              progressBarText,
1757                                              cleanup);
1758     HeapVisitor progVisitor = new ProgressiveHeapVisitor(visitor, progress);
1759     workerThread.invokeLater(new VisitHeap(progVisitor));
1760   }
1761 
1762   //--------------------------------------------------------------------------------
1763   // Stack trace helper
1764   //
1765 
getLastJavaVFrame(JavaThread cur)1766   private static JavaVFrame getLastJavaVFrame(JavaThread cur) {
1767     RegisterMap regMap = cur.newRegisterMap(true);
1768     sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess();
1769     if (f == null) return null;
1770     boolean imprecise = true;
1771     if (f.isInterpretedFrame() && !f.isInterpretedFrameValid()) {
1772       System.err.println("Correcting for invalid interpreter frame");
1773       f = f.sender(regMap);
1774       imprecise = false;
1775     }
1776     VFrame vf = VFrame.newVFrame(f, regMap, cur, true, imprecise);
1777     if (vf == null) {
1778       System.err.println(" (Unable to create vframe for topmost frame guess)");
1779       return null;
1780     }
1781     if (vf.isJavaFrame()) {
1782       return (JavaVFrame) vf;
1783     }
1784     return (JavaVFrame) vf.javaSender();
1785   }
1786 
1787   // Internal routine for debugging
dumpStack(JavaThread cur)1788   private static void dumpStack(JavaThread cur) {
1789     RegisterMap regMap = cur.newRegisterMap(true);
1790     sun.jvm.hotspot.runtime.Frame f = cur.getCurrentFrameGuess();
1791     PrintStream tty = System.err;
1792     while (f != null) {
1793       tty.print("Found ");
1794            if (f.isInterpretedFrame()) { tty.print("interpreted"); }
1795       else if (f.isCompiledFrame())    { tty.print("compiled"); }
1796       else if (f.isEntryFrame())       { tty.print("entry"); }
1797       else if (f.isNativeFrame())      { tty.print("native"); }
1798       else if (f.isRuntimeFrame())     { tty.print("runtime"); }
1799       else { tty.print("external"); }
1800       tty.print(" frame with PC = " + f.getPC() + ", SP = " + f.getSP() + ", FP = " + f.getFP());
1801       if (f.isSignalHandlerFrameDbg()) {
1802         tty.print(" (SIGNAL HANDLER)");
1803       }
1804       tty.println();
1805 
1806       if (!f.isFirstFrame()) {
1807         f = f.sender(regMap);
1808       } else {
1809         f = null;
1810       }
1811     }
1812   }
1813 
1814   //--------------------------------------------------------------------------------
1815   // Component utilities
1816   //
1817 
createMenuItem(String name, ActionListener l)1818   private static JMenuItem createMenuItem(String name, ActionListener l) {
1819     JMenuItem item = new JMenuItem(name);
1820     item.addActionListener(l);
1821     return item;
1822   }
1823 
1824   /** Punctuates the given string with \n's where necessary to not
1825       exceed the given number of characters per line. Strips
1826       extraneous whitespace. */
formatMessage(String message, int charsPerLine)1827   private String formatMessage(String message, int charsPerLine) {
1828     StringBuffer buf = new StringBuffer(message.length());
1829     StringTokenizer tokenizer = new StringTokenizer(message);
1830     int curLineLength = 0;
1831     while (tokenizer.hasMoreTokens()) {
1832       String tok = tokenizer.nextToken();
1833       if (curLineLength + tok.length() > charsPerLine) {
1834         buf.append('\n');
1835         curLineLength = 0;
1836       } else {
1837         if (curLineLength != 0) {
1838           buf.append(' ');
1839           ++curLineLength;
1840         }
1841       }
1842       buf.append(tok);
1843       curLineLength += tok.length();
1844     }
1845     return buf.toString();
1846   }
1847 
setMenuItemsEnabled(java.util.List items, boolean enabled)1848   private void setMenuItemsEnabled(java.util.List items, boolean enabled) {
1849     for (Iterator iter = items.iterator(); iter.hasNext(); ) {
1850       ((JMenuItem) iter.next()).setEnabled(enabled);
1851     }
1852   }
1853 }
1854