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