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