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 {
  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 
  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 
  96   public HSDB(JVMDebugger d) {
  97     jvmDebugger = d;
  98   }
  99 
 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
 135       public void windowClosing(WindowEvent e) {
 136           workerThread.shutdown();
 137           frame.dispose();
 138       }
 139 
 140   }
 141 
 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
 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
 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
 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 
 712   public void showThreadOopInspector(JavaThread thread) {
 713     showInspector(new OopTreeNodeAdapter(thread.getThreadObj(), null));
 714   }
 715 
 716   public void showInspector(SimpleTreeNode adapter) {
 717     showPanel("Inspector", new Inspector(adapter), 1.0f, 0.65f);
 718   }
 719 
 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 
 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 
 802     StackWalker(JavaVFrame vf, AnnotatedMemoryPanel annoPanel) {
 803       this.vf = vf;
 804       this.annoPanel = annoPanel;
 805     }
 806   }
 807 
 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.
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 */
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 */
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 */
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 
1352   private void detachDebugger() {
1353     if (!attached) {
1354       return;
1355     }
1356     agent.detach();
1357     attached = false;
1358   }
1359 
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 */
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 
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 
1418     ObjectHistogramCleanupThunk(sun.jvm.hotspot.oops.ObjectHistogram histo) {
1419       this.histo = histo;
1420     }
1421 
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 
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 
1459     FindObjectByTypeCleanupThunk(FindObjectByType finder) {
1460       this.finder = finder;
1461     }
1462 
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 
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 
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 
1534   private void showFindByQueryPanel() {
1535     showPanel("Find Object by Query", new FindByQueryPanel());
1536   }
1537 
1538   private void showFindPanel() {
1539     showPanel("Find Pointer", new FindPanel());
1540   }
1541 
1542   private void showFindInHeapPanel() {
1543     showPanel("Find Address In Heap", new FindInHeapPanel());
1544   }
1545 
1546   private void showFindInCodeCachePanel() {
1547     showPanel("Find Address In Code Cache", new FindInCodeCachePanel());
1548   }
1549 
1550   private void showHeapParametersPanel() {
1551     showPanel("Heap Parameters", new HeapParametersPanel());
1552   }
1553 
1554   public void showThreadInfo(final JavaThread thread) {
1555     showPanel("Info for " + thread.getThreadName(), new ThreadInfoPanel(thread));
1556   }
1557 
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 
1564   private void showDeadlockDetectionPanel() {
1565     showPanel("Deadlock Detection", new DeadlockDetectionPanel());
1566   }
1567 
1568   private void showMonitorCacheDumpPanel() {
1569     showPanel("Monitor Cache Dump", new MonitorCacheDumpPanel());
1570   }
1571 
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 
1613   public void showCodeViewer() {
1614     showPanel("Code Viewer", new CodeViewerPanel(), 1.25f, 0.85f);
1615   }
1616 
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 
1628   public void showMemoryViewer() {
1629     showPanel("Memory Viewer", new MemoryViewer(agent.getDebugger(), agent.getTypeDataBase().getAddressSize() == 8));
1630   }
1631 
1632   public void showCommandLineFlags() {
1633     showPanel("Command Line Flags", new VMFlagsPanel());
1634   }
1635 
1636   public void showVMVersion() {
1637     showPanel("VM Version Info", new VMVersionInfoPanel());
1638   }
1639 
1640   public void showSystemProperties() {
1641     showPanel("System Properties", new SysPropsPanel());
1642   }
1643 
1644   private void showPanel(String name, JPanel panel) {
1645     showPanel(name, panel, 5.0f / 3.0f, 0.4f);
1646   }
1647 
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 {
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 
1680     HeapProgress(String windowTitle) {
1681       this(windowTitle, "Percentage of heap visited", null);
1682     }
1683 
1684     HeapProgress(String windowTitle, String progressBarTitle) {
1685       this(windowTitle, progressBarTitle, null);
1686     }
1687 
1688     HeapProgress(String windowTitle, String progressBarTitle, CleanupThunk cleanup) {
1689       this.windowTitle = windowTitle;
1690       this.progressBarTitle = progressBarTitle;
1691       this.cleanup = cleanup;
1692     }
1693 
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 
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 
1741     VisitHeap(HeapVisitor visitor) {
1742       this.visitor = visitor;
1743     }
1744 
1745     public void run() {
1746       VM.getVM().getObjectHeap().iterate(visitor);
1747     }
1748   }
1749 
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 
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
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 
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. */
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 
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 }