/*
* Copyright (c) 2001, 2002, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*
*/
package sun.jvm.hotspot.ui;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.IOException;
import java.math.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import sun.jvm.hotspot.debugger.*;
import sun.jvm.hotspot.ui.*;
public class MemoryPanel extends JPanel {
private boolean is64Bit;
private Debugger debugger;
private int addressSize;
private String unmappedAddrString;
private HighPrecisionJScrollBar scrollBar;
private AbstractTableModel model;
private JTable table;
private BigInteger startVal;
// Includes any partially-visible row at the bottom
private int numVisibleRows;
// Frequently-used subexpression
private int numUsableRows;
// Multi-row (and multi-column) selection. Have to duplicate state
// from UI so this can work as we scroll off the screen.
private boolean haveAnchor;
private int rowAnchorIndex;
private int colAnchorIndex;
private boolean haveLead;
private int rowLeadIndex;
private int colLeadIndex;
abstract class ActionWrapper extends AbstractAction {
private Action parent;
ActionWrapper() {
}
void setParent(Action parent) {
this.parent = parent;
}
Action getParent() {
return parent;
}
public void actionPerformed(ActionEvent e) {
if (getParent() != null) {
getParent().actionPerformed(e);
}
}
}
public MemoryPanel(final Debugger debugger, boolean is64Bit) {
super();
this.debugger = debugger;
this.is64Bit = is64Bit;
if (is64Bit) {
addressSize = 8;
unmappedAddrString = "??????????????????";
} else {
addressSize = 4;
unmappedAddrString = "??????????";
}
setLayout(new BorderLayout());
setupScrollBar();
add(scrollBar, BorderLayout.EAST);
model = new AbstractTableModel() {
public int getRowCount() {
return numVisibleRows;
}
public int getColumnCount() {
return 2;
}
public Object getValueAt(int row, int column) {
switch (column) {
case 0: return bigIntToHexString(startVal.add(new BigInteger(Integer.toString((row * addressSize)))));
case 1: {
try {
Address addr = bigIntToAddress(startVal.add(new BigInteger(Integer.toString((row * addressSize)))));
if (addr != null) {
return addressToString(addr.getAddressAt(0));
}
return unmappedAddrString;
} catch (UnmappedAddressException e) {
return unmappedAddrString;
}
}
default: throw new RuntimeException("Column " + column + " out of bounds");
}
}
public boolean isCellEditable(int row, int col) {
return false;
}
};
// View with JTable with no header
table = new JTable(model);
table.setTableHeader(null);
table.setShowGrid(false);
table.setIntercellSpacing(new Dimension(0, 0));
table.setCellSelectionEnabled(true);
table.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
table.setDragEnabled(true);
Font font = GraphicsUtilities.lookupFont("Courier");
if (font == null) {
throw new RuntimeException("Error looking up monospace font Courier");
}
table.setFont(font);
// Export proper data.
// We need to keep our own notion of the selection in order to
// properly export data, since the selection can go beyond the
// visible area on the screen (and since the table's model doesn't
// back all of those slots).
// Code thanks to Shannon.Hickey@sfbay
table.setTransferHandler(new TransferHandler() {
protected Transferable createTransferable(JComponent c) {
JTable table = (JTable)c;
if (haveSelection()) {
StringBuffer buf = new StringBuffer();
int iDir = (getRowAnchor() < getRowLead() ? 1 : -1);
int jDir = (getColAnchor() < getColLead() ? 1 : -1);
for (int i = getRowAnchor(); i != getRowLead() + iDir; i += iDir) {
for (int j = getColAnchor(); j != getColLead() + jDir; j += jDir) {
Object val = model.getValueAt(i, j);
buf.append(val == null ? "" : val.toString());
if (j != getColLead()) {
buf.append("\t");
}
}
if (i != getRowLead()) {
buf.append("\n");
}
}
return new StringTransferable(buf.toString());
}
return null;
}
public int getSourceActions(JComponent c) {
return COPY;
}
public boolean importData(JComponent c, Transferable t) {
if (canImport(c, t.getTransferDataFlavors())) {
try {
String str = (String)t.getTransferData(DataFlavor.stringFlavor);
handleImport(c, str);
return true;
} catch (UnsupportedFlavorException ufe) {
} catch (IOException ioe) {
}
}
return false;
}
public boolean canImport(JComponent c, DataFlavor[] flavors) {
for (int i = 0; i < flavors.length; i++) {
if (DataFlavor.stringFlavor.equals(flavors[i])) {
return true;
}
}
return false;
}
private void handleImport(JComponent c, String str) {
// do whatever you want with the string here
try {
makeVisible(debugger.parseAddress(str));
clearSelection();
table.clearSelection();
} catch (NumberFormatException e) {
System.err.println("Unable to parse address \"" + str + "\"");
}
}
});
// Supporting keyboard scrolling
// See src/share/classes/javax/swing/plaf/metal/MetalLookAndFeel.java,
// search for Table.AncestorInputMap
// Actions to override:
// selectPreviousRow, selectNextRow,
// scrollUpChangeSelection, scrollDownChangeSelection,
// selectPreviousRowExtendSelection, selectNextRowExtendSelection,
// scrollDownExtendSelection, scrollUpExtendSelection (Shift-PgDn/PgUp)
ActionMap map = table.getActionMap();
// Up arrow
installActionWrapper(map, "selectPreviousRow", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
clearSelection();
if (table.getSelectedRow() == 0) {
scrollBar.scrollUpOrLeft();
table.setRowSelectionInterval(0, 0);
} else {
super.actionPerformed(e);
}
maybeGrabSelection();
endUpdate();
}
});
// Down arrow
installActionWrapper(map, "selectNextRow", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
clearSelection();
int row = table.getSelectedRow();
if (row >= numUsableRows) {
scrollBar.scrollDownOrRight();
table.setRowSelectionInterval(row, row);
} else {
super.actionPerformed(e);
}
maybeGrabSelection();
endUpdate();
}
});
// Page up
installActionWrapper(map, "scrollUpChangeSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
clearSelection();
int row = table.getSelectedRow();
scrollBar.pageUpOrLeft();
if (row >= 0) {
table.setRowSelectionInterval(row, row);
}
maybeGrabSelection();
endUpdate();
}
});
// Page down
installActionWrapper(map, "scrollDownChangeSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
clearSelection();
int row = table.getSelectedRow();
scrollBar.pageDownOrRight();
if (row >= 0) {
table.setRowSelectionInterval(row, row);
}
maybeGrabSelection();
endUpdate();
}
});
// Shift + Up arrow
installActionWrapper(map, "selectPreviousRowExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
// setAnchor(table.getSelectedRow());
// setLead(table.getSelectedRow());
}
int newLead = getRowLead() - 1;
int newAnchor = getRowAnchor();
if (newLead < 0) {
scrollBar.scrollUpOrLeft();
++newLead;
++newAnchor;
}
setSelection(newAnchor, newLead, getColAnchor(), getColLead());
// printSelection();
endUpdate();
}
});
// Shift + Left arrow
installActionWrapper(map, "selectPreviousColumnExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
}
int newLead = Math.max(0, getColLead() - 1);
setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead);
// printSelection();
endUpdate();
}
});
// Shift + Down arrow
installActionWrapper(map, "selectNextRowExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
// setAnchor(table.getSelectedRow());
// setLead(table.getSelectedRow());
}
int newLead = getRowLead() + 1;
int newAnchor = getRowAnchor();
if (newLead > numUsableRows) {
scrollBar.scrollDownOrRight();
--newLead;
--newAnchor;
}
setSelection(newAnchor, newLead, getColAnchor(), getColLead());
// printSelection();
endUpdate();
}
});
// Shift + Right arrow
installActionWrapper(map, "selectNextColumnExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
}
int newLead = Math.min(model.getColumnCount() - 1, getColLead() + 1);
setSelection(getRowAnchor(), getRowLead(), getColAnchor(), newLead);
// printSelection();
endUpdate();
}
});
// Shift + Page up
installActionWrapper(map, "scrollUpExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
// setAnchor(table.getSelectedRow());
// setLead(table.getSelectedRow());
}
int newLead = getRowLead() - numUsableRows;
int newAnchor = getRowAnchor();
if (newLead < 0) {
scrollBar.pageUpOrLeft();
newLead += numUsableRows;
newAnchor += numUsableRows;
}
setSelection(newAnchor, newLead, getColAnchor(), getColLead());
// printSelection();
endUpdate();
}
});
// Shift + Page down
installActionWrapper(map, "scrollDownExtendSelection", new ActionWrapper() {
public void actionPerformed(ActionEvent e) {
beginUpdate();
if (!haveAnchor()) {
setAnchorFromTable();
setLeadFromTable();
// setAnchor(table.getSelectedRow());
// setLead(table.getSelectedRow());
}
int newLead = getRowLead() + numUsableRows;
int newAnchor = getRowAnchor();
if (newLead > numUsableRows) {
scrollBar.pageDownOrRight();
newLead -= numUsableRows;
newAnchor -= numUsableRows;
}
setSelection(newAnchor, newLead, getColAnchor(), getColLead());
// printSelection();
endUpdate();
}
});
// Clear our notion of selection upon mouse press
table.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (shouldIgnore(e)) {
return;
}
// Make shift-clicking work properly
if (e.isShiftDown()) {
maybeGrabSelection();
return;
}
// System.err.println(" Clearing selection on mouse press");
clearSelection();
}
});
// Watch for mouse going out of bounds
table.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
if (shouldIgnore(e)) {
// System.err.println(" (Ignoring consumed mouse event)");
return;
}
// Look for drag events outside table and scroll if necessary
Point p = e.getPoint();
if (table.rowAtPoint(p) == -1) {
// See whether we are above or below the table
Rectangle rect = new Rectangle();
getBounds(rect);
beginUpdate();
if (p.y < rect.y) {
// System.err.println(" Scrolling up due to mouse event");
// Scroll up
scrollBar.scrollUpOrLeft();
setSelection(getRowAnchor(), 0, getColAnchor(), getColLead());
} else {
// System.err.println(" Scrolling down due to mouse event");
// Scroll down
scrollBar.scrollDownOrRight();
setSelection(getRowAnchor(), numUsableRows, getColAnchor(), getColLead());
}
// printSelection();
endUpdate();
} else {
maybeGrabSelection();
}
}
});
add(table, BorderLayout.CENTER);
// Make sure we recompute number of visible rows
addComponentListener(new ComponentAdapter() {
public void componentResized(ComponentEvent e) {
recomputeNumVisibleRows();
constrain();
}
});
addHierarchyListener(new HierarchyListener() {
public void hierarchyChanged(HierarchyEvent e) {
recomputeNumVisibleRows();
constrain();
}
});
updateFromScrollBar();
}
/** Makes the given address visible somewhere in the window */
public void makeVisible(Address addr) {
BigInteger bi = addressToBigInt(addr);
scrollBar.setValueHP(bi);
}
//----------------------------------------------------------------------
// Internals only below this point
//
private void setupScrollBar() {
if (is64Bit) {
// 64-bit mode
scrollBar =
new HighPrecisionJScrollBar(
Scrollbar.VERTICAL,
new BigInteger(1, new byte[] {
(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
new BigInteger(1, new byte[] {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC}));
scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}));
scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40}));
} else {
// 32-bit mode
scrollBar=
new HighPrecisionJScrollBar(
Scrollbar.VERTICAL,
new BigInteger(1, new byte[] {
(byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00}),
new BigInteger(1, new byte[] {
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC}));
scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04}));
scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
(byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20}));
}
scrollBar.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
updateFromScrollBar();
}
});
}
private void updateFromScrollBar() {
beginUpdate();
BigInteger oldStartVal = startVal;
startVal = scrollBar.getValueHP();
constrain();
model.fireTableDataChanged();
if (oldStartVal != null) {
modifySelection(oldStartVal.subtract(startVal).intValue() / addressSize);
}
endUpdate();
}
private void constrain() {
BigInteger offset = new BigInteger(Integer.toString(addressSize * (numUsableRows)));
BigInteger endVal = startVal.add(offset);
if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) {
startVal = scrollBar.getMaximumHP().subtract(offset);
endVal = scrollBar.getMaximumHP();
scrollBar.setValueHP(startVal);
model.fireTableDataChanged();
}
}
private void recomputeNumVisibleRows() {
Rectangle rect = new Rectangle();
getBounds(rect);
int h = table.getRowHeight();
numVisibleRows = (rect.height + (h - 1)) / h;
numUsableRows = numVisibleRows - 2;
scrollBar.setBlockIncrementHP(new BigInteger(Integer.toString(addressSize * (numUsableRows))));
model.fireTableDataChanged();
// FIXME: refresh selection
}
private String bigIntToHexString(BigInteger bi) {
StringBuffer buf = new StringBuffer();
buf.append("0x");
String val = bi.toString(16);
for (int i = 0; i < ((2 * addressSize) - val.length()); i++) {
buf.append('0');
}
buf.append(val);
return buf.toString();
}
private Address bigIntToAddress(BigInteger i) {
String s = bigIntToHexString(i);
return debugger.parseAddress(s);
}
private BigInteger addressToBigInt(Address a) {
String s = addressToString(a);
if (!s.startsWith("0x")) {
throw new NumberFormatException(s);
}
return new BigInteger(s.substring(2), 16);
}
private String addressToString(Address a) {
if (a == null) {
if (is64Bit) {
return "0x0000000000000000";
} else {
return "0x00000000";
}
}
return a.toString();
}
private static void installActionWrapper(ActionMap map,
String actionName,
ActionWrapper wrapper) {
wrapper.setParent(map.get(actionName));
map.put(actionName, wrapper);
}
private boolean shouldIgnore(MouseEvent e) {
return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled()));
}
private void clearSelection() {
haveAnchor = false;
haveLead = false;
}
private int updateLevel;
private boolean updating() { return updateLevel > 0; }
private void beginUpdate() { ++updateLevel; }
private void endUpdate() { --updateLevel; }
private boolean haveAnchor() { return haveAnchor; }
private boolean haveLead() { return haveLead; }
private boolean haveSelection() { return haveAnchor() && haveLead(); }
private int getRowAnchor() { return rowAnchorIndex; }
private int getColAnchor() { return colAnchorIndex; }
private int getRowLead() { return rowLeadIndex; }
private int getColLead() { return colLeadIndex; }
private void setAnchorFromTable() {
setAnchor(table.getSelectionModel().getAnchorSelectionIndex(),
table.getColumnModel().getSelectionModel().getAnchorSelectionIndex());
}
private void setLeadFromTable() {
setLead(table.getSelectionModel().getAnchorSelectionIndex(),
table.getColumnModel().getSelectionModel().getAnchorSelectionIndex());
}
private void setAnchor(int row, int col) {
rowAnchorIndex = row;
colAnchorIndex = col;
haveAnchor = true;
}
private void setLead(int row, int col) {
rowLeadIndex = row;
colLeadIndex = col;
haveLead = true;
}
private int clamp(int val, int min, int max) {
return Math.max(Math.min(val, max), min);
}
private void maybeGrabSelection() {
if (table.getSelectedRow() != -1) {
// Grab selection
ListSelectionModel rowSel = table.getSelectionModel();
ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
if (!haveAnchor()) {
// System.err.println("Updating from table's selection");
setSelection(rowSel.getAnchorSelectionIndex(), rowSel.getLeadSelectionIndex(),
colSel.getAnchorSelectionIndex(), colSel.getLeadSelectionIndex());
} else {
// System.err.println("Updating lead from table's selection");
setSelection(getRowAnchor(), rowSel.getLeadSelectionIndex(),
getColAnchor(), colSel.getLeadSelectionIndex());
}
// printSelection();
}
}
private void setSelection(int rowAnchor, int rowLead, int colAnchor, int colLead) {
setAnchor(rowAnchor, colAnchor);
setLead(rowLead, colLead);
table.setRowSelectionInterval(clamp(rowAnchor, 0, numUsableRows),
clamp(rowLead, 0, numUsableRows));
table.setColumnSelectionInterval(colAnchor, colLead);
}
private void modifySelection(int amount) {
if (haveSelection()) {
setSelection(getRowAnchor() + amount, getRowLead() + amount,
getColAnchor(), getColLead());
}
}
private void printSelection() {
System.err.println("Selection updated to (" +
model.getValueAt(getRowAnchor(), getColAnchor()) +
", " +
model.getValueAt(getRowLead(), getColLead()) + ") [(" +
getRowAnchor() + ", " + getColAnchor() + "), (" +
getRowLead() + ", " + getColLead() + ")]");
}
}