1/* 2 * Copyright (c) 2001, 2002, 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 25package sun.jvm.hotspot.ui; 26 27import java.awt.*; 28import java.awt.datatransfer.*; 29import java.awt.event.*; 30import java.io.IOException; 31import java.math.*; 32import java.util.*; 33import javax.swing.*; 34import javax.swing.event.*; 35import javax.swing.table.*; 36import sun.jvm.hotspot.debugger.*; 37import sun.jvm.hotspot.ui.*; 38 39public class MemoryPanel extends JPanel { 40 private boolean is64Bit; 41 private Debugger debugger; 42 private int addressSize; 43 private String unmappedAddrString; 44 private HighPrecisionJScrollBar scrollBar; 45 private AbstractTableModel model; 46 private JTable table; 47 private BigInteger startVal; 48 // Includes any partially-visible row at the bottom 49 private int numVisibleRows; 50 // Frequently-used subexpression 51 private int numUsableRows; 52 // Multi-row (and multi-column) selection. Have to duplicate state 53 // from UI so this can work as we scroll off the screen. 54 private boolean haveAnchor; 55 private int rowAnchorIndex; 56 private int colAnchorIndex; 57 private boolean haveLead; 58 private int rowLeadIndex; 59 private int colLeadIndex; 60 61 abstract class ActionWrapper extends AbstractAction { 62 private Action parent; 63 ActionWrapper() { 64 } 65 66 void setParent(Action parent) { 67 this.parent = parent; 68 } 69 70 Action getParent() { 71 return parent; 72 } 73 74 public void actionPerformed(ActionEvent e) { 75 if (getParent() != null) { 76 getParent().actionPerformed(e); 77 } 78 } 79 } 80 81 public MemoryPanel(final Debugger debugger, boolean is64Bit) { 82 super(); 83 this.debugger = debugger; 84 this.is64Bit = is64Bit; 85 if (is64Bit) { 86 addressSize = 8; 87 unmappedAddrString = "??????????????????"; 88 } else { 89 addressSize = 4; 90 unmappedAddrString = "??????????"; 91 } 92 setLayout(new BorderLayout()); 93 setupScrollBar(); 94 add(scrollBar, BorderLayout.EAST); 95 96 model = new AbstractTableModel() { 97 public int getRowCount() { 98 return numVisibleRows; 99 } 100 public int getColumnCount() { 101 return 2; 102 } 103 public Object getValueAt(int row, int column) { 104 switch (column) { 105 case 0: return bigIntToHexString(startVal.add(new BigInteger(Integer.toString((row * addressSize))))); 106 case 1: { 107 try { 108 Address addr = bigIntToAddress(startVal.add(new BigInteger(Integer.toString((row * addressSize))))); 109 if (addr != null) { 110 return addressToString(addr.getAddressAt(0)); 111 } 112 return unmappedAddrString; 113 } catch (UnmappedAddressException e) { 114 return unmappedAddrString; 115 } 116 } 117 default: throw new RuntimeException("Column " + column + " out of bounds"); 118 } 119 } 120 public boolean isCellEditable(int row, int col) { 121 return false; 122 } 123 }; 124 125 // View with JTable with no header 126 table = new JTable(model); 127 table.setTableHeader(null); 128 table.setShowGrid(false); 129 table.setIntercellSpacing(new Dimension(0, 0)); 130 table.setCellSelectionEnabled(true); 131 table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); 132 table.setDragEnabled(true); 133 Font font = GraphicsUtilities.lookupFont("Courier"); 134 if (font == null) { 135 throw new RuntimeException("Error looking up monospace font Courier"); 136 } 137 table.setFont(font); 138 139 // Export proper data. 140 // We need to keep our own notion of the selection in order to 141 // properly export data, since the selection can go beyond the 142 // visible area on the screen (and since the table's model doesn't 143 // back all of those slots). 144 // Code thanks to Shannon.Hickey@sfbay 145 table.setTransferHandler(new TransferHandler() { 146 protected Transferable createTransferable(JComponent c) { 147 JTable table = (JTable)c; 148 if (haveSelection()) { 149 StringBuffer buf = new StringBuffer(); 150 int iDir = (getRowAnchor() < getRowLead() ? 1 : -1); 151 int jDir = (getColAnchor() < getColLead() ? 1 : -1); 152 153 for (int i = getRowAnchor(); i != getRowLead() + iDir; i += iDir) { 154 for (int j = getColAnchor(); j != getColLead() + jDir; j += jDir) { 155 Object val = model.getValueAt(i, j); 156 buf.append(val == null ? "" : val.toString()); 157 if (j != getColLead()) { 158 buf.append("\t"); 159 } 160 } 161 if (i != getRowLead()) { 162 buf.append("\n"); 163 } 164 } 165 166 return new StringTransferable(buf.toString()); 167 } 168 return null; 169 } 170 171 public int getSourceActions(JComponent c) { 172 return COPY; 173 } 174 175 public boolean importData(JComponent c, Transferable t) { 176 if (canImport(c, t.getTransferDataFlavors())) { 177 try { 178 String str = (String)t.getTransferData(DataFlavor.stringFlavor); 179 handleImport(c, str); 180 return true; 181 } catch (UnsupportedFlavorException ufe) { 182 } catch (IOException ioe) { 183 } 184 } 185 186 return false; 187 } 188 189 public boolean canImport(JComponent c, DataFlavor[] flavors) { 190 for (int i = 0; i < flavors.length; i++) { 191 if (DataFlavor.stringFlavor.equals(flavors[i])) { 192 return true; 193 } 194 } 195 return false; 196 } 197 198 private void handleImport(JComponent c, String str) { 199 // do whatever you want with the string here 200 try { 201 makeVisible(debugger.parseAddress(str)); 202 clearSelection(); 203 table.clearSelection(); 204 } catch (NumberFormatException e) { 205 System.err.println("Unable to parse address \"" + str + "\""); 206 } 207 } 208 }); 209 210 // Supporting keyboard scrolling 211 // See src/share/classes/javax/swing/plaf/metal/MetalLookAndFeel.java, 212 // search for Table.AncestorInputMap 213 214 // Actions to override: 215 // selectPreviousRow, selectNextRow, 216 // scrollUpChangeSelection, scrollDownChangeSelection, 217 // selectPreviousRowExtendSelection, selectNextRowExtendSelection, 218 // scrollDownExtendSelection, scrollUpExtendSelection (Shift-PgDn/PgUp) 219 220 ActionMap map = table.getActionMap(); 221 222 // Up arrow 223 installActionWrapper(map, "selectPreviousRow", new ActionWrapper() { 224 public void actionPerformed(ActionEvent e) { 225 beginUpdate(); 226 clearSelection(); 227 if (table.getSelectedRow() == 0) { 228 scrollBar.scrollUpOrLeft(); 229 table.setRowSelectionInterval(0, 0); 230 } else { 231 super.actionPerformed(e); 232 } 233 maybeGrabSelection(); 234 endUpdate(); 235 } 236 }); 237 // Down arrow 238 installActionWrapper(map, "selectNextRow", new ActionWrapper() { 239 public void actionPerformed(ActionEvent e) { 240 beginUpdate(); 241 clearSelection(); 242 int row = table.getSelectedRow(); 243 if (row >= numUsableRows) { 244 scrollBar.scrollDownOrRight(); 245 table.setRowSelectionInterval(row, row); 246 } else { 247 super.actionPerformed(e); 248 } 249 maybeGrabSelection(); 250 endUpdate(); 251 } 252 }); 253 // Page up 254 installActionWrapper(map, "scrollUpChangeSelection", new ActionWrapper() { 255 public void actionPerformed(ActionEvent e) { 256 beginUpdate(); 257 clearSelection(); 258 int row = table.getSelectedRow(); 259 scrollBar.pageUpOrLeft(); 260 if (row >= 0) { 261 table.setRowSelectionInterval(row, row); 262 } 263 maybeGrabSelection(); 264 endUpdate(); 265 } 266 }); 267 // Page down 268 installActionWrapper(map, "scrollDownChangeSelection", new ActionWrapper() { 269 public void actionPerformed(ActionEvent e) { 270 beginUpdate(); 271 clearSelection(); 272 int row = table.getSelectedRow(); 273 scrollBar.pageDownOrRight(); 274 if (row >= 0) { 275 table.setRowSelectionInterval(row, row); 276 } 277 maybeGrabSelection(); 278 endUpdate(); 279 } 280 }); 281 // Shift + Up arrow 282 installActionWrapper(map, "selectPreviousRowExtendSelection", new ActionWrapper() { 283 public void actionPerformed(ActionEvent e) { 284 beginUpdate(); 285 if (!haveAnchor()) { 286 setAnchorFromTable(); 287 setLeadFromTable(); 288 // setAnchor(table.getSelectedRow()); 289 // setLead(table.getSelectedRow()); 290 } 291 int newLead = getRowLead() - 1; 292 int newAnchor = getRowAnchor(); 293 if (newLead < 0) { 294 scrollBar.scrollUpOrLeft(); 295 ++newLead; 296 ++newAnchor; 297 } 298 setSelection(newAnchor, newLead, getColAnchor(), getColLead()); 299 // printSelection(); 300 endUpdate(); 301 } 302 }); 303 // Shift + Left arrow 304 installActionWrapper(map, "selectPreviousColumnExtendSelection", new ActionWrapper() { 305 public void actionPerformed(ActionEvent e) { 306 beginUpdate(); 307 if (!haveAnchor()) { 308 setAnchorFromTable(); 309 setLeadFromTable(); 310 } 311 int newLead = Math.max(0, getColLead() - 1); 312 setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead); 313 // printSelection(); 314 endUpdate(); 315 } 316 }); 317 // Shift + Down arrow 318 installActionWrapper(map, "selectNextRowExtendSelection", new ActionWrapper() { 319 public void actionPerformed(ActionEvent e) { 320 beginUpdate(); 321 if (!haveAnchor()) { 322 setAnchorFromTable(); 323 setLeadFromTable(); 324 // setAnchor(table.getSelectedRow()); 325 // setLead(table.getSelectedRow()); 326 } 327 int newLead = getRowLead() + 1; 328 int newAnchor = getRowAnchor(); 329 if (newLead > numUsableRows) { 330 scrollBar.scrollDownOrRight(); 331 --newLead; 332 --newAnchor; 333 } 334 setSelection(newAnchor, newLead, getColAnchor(), getColLead()); 335 // printSelection(); 336 endUpdate(); 337 } 338 }); 339 // Shift + Right arrow 340 installActionWrapper(map, "selectNextColumnExtendSelection", new ActionWrapper() { 341 public void actionPerformed(ActionEvent e) { 342 beginUpdate(); 343 if (!haveAnchor()) { 344 setAnchorFromTable(); 345 setLeadFromTable(); 346 } 347 int newLead = Math.min(model.getColumnCount() - 1, getColLead() + 1); 348 setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead); 349 // printSelection(); 350 endUpdate(); 351 } 352 }); 353 // Shift + Page up 354 installActionWrapper(map, "scrollUpExtendSelection", new ActionWrapper() { 355 public void actionPerformed(ActionEvent e) { 356 beginUpdate(); 357 if (!haveAnchor()) { 358 setAnchorFromTable(); 359 setLeadFromTable(); 360 // setAnchor(table.getSelectedRow()); 361 // setLead(table.getSelectedRow()); 362 } 363 int newLead = getRowLead() - numUsableRows; 364 int newAnchor = getRowAnchor(); 365 if (newLead < 0) { 366 scrollBar.pageUpOrLeft(); 367 newLead += numUsableRows; 368 newAnchor += numUsableRows; 369 } 370 setSelection(newAnchor, newLead, getColAnchor(), getColLead()); 371 // printSelection(); 372 endUpdate(); 373 } 374 }); 375 // Shift + Page down 376 installActionWrapper(map, "scrollDownExtendSelection", new ActionWrapper() { 377 public void actionPerformed(ActionEvent e) { 378 beginUpdate(); 379 if (!haveAnchor()) { 380 setAnchorFromTable(); 381 setLeadFromTable(); 382 // setAnchor(table.getSelectedRow()); 383 // setLead(table.getSelectedRow()); 384 } 385 int newLead = getRowLead() + numUsableRows; 386 int newAnchor = getRowAnchor(); 387 if (newLead > numUsableRows) { 388 scrollBar.pageDownOrRight(); 389 newLead -= numUsableRows; 390 newAnchor -= numUsableRows; 391 } 392 setSelection(newAnchor, newLead, getColAnchor(), getColLead()); 393 // printSelection(); 394 endUpdate(); 395 } 396 }); 397 398 // Clear our notion of selection upon mouse press 399 table.addMouseListener(new MouseAdapter() { 400 public void mousePressed(MouseEvent e) { 401 if (shouldIgnore(e)) { 402 return; 403 } 404 // Make shift-clicking work properly 405 if (e.isShiftDown()) { 406 maybeGrabSelection(); 407 return; 408 } 409 // System.err.println(" Clearing selection on mouse press"); 410 clearSelection(); 411 } 412 }); 413 414 // Watch for mouse going out of bounds 415 table.addMouseMotionListener(new MouseMotionAdapter() { 416 public void mouseDragged(MouseEvent e) { 417 if (shouldIgnore(e)) { 418 // System.err.println(" (Ignoring consumed mouse event)"); 419 return; 420 } 421 422 // Look for drag events outside table and scroll if necessary 423 Point p = e.getPoint(); 424 if (table.rowAtPoint(p) == -1) { 425 // See whether we are above or below the table 426 Rectangle rect = new Rectangle(); 427 getBounds(rect); 428 beginUpdate(); 429 if (p.y < rect.y) { 430 // System.err.println(" Scrolling up due to mouse event"); 431 // Scroll up 432 scrollBar.scrollUpOrLeft(); 433 setSelection(getRowAnchor(), 0, getColAnchor(), getColLead()); 434 } else { 435 // System.err.println(" Scrolling down due to mouse event"); 436 // Scroll down 437 scrollBar.scrollDownOrRight(); 438 setSelection(getRowAnchor(), numUsableRows, getColAnchor(), getColLead()); 439 } 440 // printSelection(); 441 endUpdate(); 442 } else { 443 maybeGrabSelection(); 444 } 445 } 446 }); 447 448 449 add(table, BorderLayout.CENTER); 450 451 // Make sure we recompute number of visible rows 452 addComponentListener(new ComponentAdapter() { 453 public void componentResized(ComponentEvent e) { 454 recomputeNumVisibleRows(); 455 constrain(); 456 } 457 }); 458 addHierarchyListener(new HierarchyListener() { 459 public void hierarchyChanged(HierarchyEvent e) { 460 recomputeNumVisibleRows(); 461 constrain(); 462 } 463 }); 464 updateFromScrollBar(); 465 } 466 467 /** Makes the given address visible somewhere in the window */ 468 public void makeVisible(Address addr) { 469 BigInteger bi = addressToBigInt(addr); 470 scrollBar.setValueHP(bi); 471 } 472 473 //---------------------------------------------------------------------- 474 // Internals only below this point 475 // 476 477 private void setupScrollBar() { 478 if (is64Bit) { 479 // 64-bit mode 480 scrollBar = 481 new HighPrecisionJScrollBar( 482 Scrollbar.VERTICAL, 483 new BigInteger(1, new byte[] { 484 (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00, 485 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), 486 new BigInteger(1, new byte[] { 487 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 488 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), 489 new BigInteger(1, new byte[] { 490 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 491 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC})); 492 scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] { 493 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 494 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08})); 495 scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] { 496 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, 497 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40})); 498 } else { 499 // 32-bit mode 500 scrollBar= 501 new HighPrecisionJScrollBar( 502 Scrollbar.VERTICAL, 503 new BigInteger(1, new byte[] { 504 (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}), 505 new BigInteger(1, new byte[] { 506 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}), 507 new BigInteger(1, new byte[] { 508 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC})); 509 scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] { 510 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04})); 511 scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] { 512 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20})); 513 } 514 scrollBar.addChangeListener(new ChangeListener() { 515 public void stateChanged(ChangeEvent e) { 516 updateFromScrollBar(); 517 } 518 }); 519 } 520 521 private void updateFromScrollBar() { 522 beginUpdate(); 523 BigInteger oldStartVal = startVal; 524 startVal = scrollBar.getValueHP(); 525 constrain(); 526 model.fireTableDataChanged(); 527 if (oldStartVal != null) { 528 modifySelection(oldStartVal.subtract(startVal).intValue() / addressSize); 529 } 530 endUpdate(); 531 } 532 533 private void constrain() { 534 BigInteger offset = new BigInteger(Integer.toString(addressSize * (numUsableRows))); 535 BigInteger endVal = startVal.add(offset); 536 if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) { 537 startVal = scrollBar.getMaximumHP().subtract(offset); 538 endVal = scrollBar.getMaximumHP(); 539 scrollBar.setValueHP(startVal); 540 model.fireTableDataChanged(); 541 } 542 } 543 544 private void recomputeNumVisibleRows() { 545 Rectangle rect = new Rectangle(); 546 getBounds(rect); 547 int h = table.getRowHeight(); 548 numVisibleRows = (rect.height + (h - 1)) / h; 549 numUsableRows = numVisibleRows - 2; 550 scrollBar.setBlockIncrementHP(new BigInteger(Integer.toString(addressSize * (numUsableRows)))); 551 model.fireTableDataChanged(); 552 // FIXME: refresh selection 553 } 554 555 private String bigIntToHexString(BigInteger bi) { 556 StringBuffer buf = new StringBuffer(); 557 buf.append("0x"); 558 String val = bi.toString(16); 559 for (int i = 0; i < ((2 * addressSize) - val.length()); i++) { 560 buf.append('0'); 561 } 562 buf.append(val); 563 return buf.toString(); 564 } 565 566 private Address bigIntToAddress(BigInteger i) { 567 String s = bigIntToHexString(i); 568 return debugger.parseAddress(s); 569 } 570 571 private BigInteger addressToBigInt(Address a) { 572 String s = addressToString(a); 573 if (!s.startsWith("0x")) { 574 throw new NumberFormatException(s); 575 } 576 return new BigInteger(s.substring(2), 16); 577 } 578 579 private String addressToString(Address a) { 580 if (a == null) { 581 if (is64Bit) { 582 return "0x0000000000000000"; 583 } else { 584 return "0x00000000"; 585 } 586 } 587 return a.toString(); 588 } 589 590 private static void installActionWrapper(ActionMap map, 591 String actionName, 592 ActionWrapper wrapper) { 593 wrapper.setParent(map.get(actionName)); 594 map.put(actionName, wrapper); 595 } 596 597 private boolean shouldIgnore(MouseEvent e) { 598 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled())); 599 } 600 601 private void clearSelection() { 602 haveAnchor = false; 603 haveLead = false; 604 } 605 606 private int updateLevel; 607 private boolean updating() { return updateLevel > 0; } 608 private void beginUpdate() { ++updateLevel; } 609 private void endUpdate() { --updateLevel; } 610 611 private boolean haveAnchor() { return haveAnchor; } 612 private boolean haveLead() { return haveLead; } 613 private boolean haveSelection() { return haveAnchor() && haveLead(); } 614 private int getRowAnchor() { return rowAnchorIndex; } 615 private int getColAnchor() { return colAnchorIndex; } 616 private int getRowLead() { return rowLeadIndex; } 617 private int getColLead() { return colLeadIndex; } 618 619 private void setAnchorFromTable() { 620 setAnchor(table.getSelectionModel().getAnchorSelectionIndex(), 621 table.getColumnModel().getSelectionModel().getAnchorSelectionIndex()); 622 } 623 private void setLeadFromTable() { 624 setLead(table.getSelectionModel().getAnchorSelectionIndex(), 625 table.getColumnModel().getSelectionModel().getAnchorSelectionIndex()); 626 } 627 private void setAnchor(int row, int col) { 628 rowAnchorIndex = row; 629 colAnchorIndex = col; 630 haveAnchor = true; 631 } 632 private void setLead(int row, int col) { 633 rowLeadIndex = row; 634 colLeadIndex = col; 635 haveLead = true; 636 } 637 private int clamp(int val, int min, int max) { 638 return Math.max(Math.min(val, max), min); 639 } 640 private void maybeGrabSelection() { 641 if (table.getSelectedRow() != -1) { 642 // Grab selection 643 ListSelectionModel rowSel = table.getSelectionModel(); 644 ListSelectionModel colSel = table.getColumnModel().getSelectionModel(); 645 if (!haveAnchor()) { 646 // System.err.println("Updating from table's selection"); 647 setSelection(rowSel.getAnchorSelectionIndex(), rowSel.getLeadSelectionIndex(), 648 colSel.getAnchorSelectionIndex(), colSel.getLeadSelectionIndex()); 649 } else { 650 // System.err.println("Updating lead from table's selection"); 651 setSelection(getRowAnchor(), rowSel.getLeadSelectionIndex(), 652 getColAnchor(), colSel.getLeadSelectionIndex()); 653 } 654 // printSelection(); 655 } 656 } 657 private void setSelection(int rowAnchor, int rowLead, int colAnchor, int colLead) { 658 setAnchor(rowAnchor, colAnchor); 659 setLead(rowLead, colLead); 660 table.setRowSelectionInterval(clamp(rowAnchor, 0, numUsableRows), 661 clamp(rowLead, 0, numUsableRows)); 662 table.setColumnSelectionInterval(colAnchor, colLead); 663 } 664 private void modifySelection(int amount) { 665 if (haveSelection()) { 666 setSelection(getRowAnchor() + amount, getRowLead() + amount, 667 getColAnchor(), getColLead()); 668 } 669 } 670 private void printSelection() { 671 System.err.println("Selection updated to (" + 672 model.getValueAt(getRowAnchor(), getColAnchor()) + 673 ", " + 674 model.getValueAt(getRowLead(), getColLead()) + ") [(" + 675 getRowAnchor() + ", " + getColAnchor() + "), (" + 676 getRowLead() + ", " + getColLead() + ")]"); 677 } 678} 679