/*
* Copyright (c) 2007, 2012, 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 com.sun.max.ins.method;
import java.awt.*;
import java.awt.event.*;
import java.awt.print.*;
import java.text.*;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import com.sun.cri.bytecode.*;
import com.sun.cri.ci.*;
import com.sun.max.ins.*;
import com.sun.max.ins.constant.*;
import com.sun.max.ins.debug.*;
import com.sun.max.ins.gui.*;
import com.sun.max.ins.util.*;
import com.sun.max.tele.*;
import com.sun.max.tele.method.*;
import com.sun.max.tele.object.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.member.*;
/**
* A table-based viewer for an (immutable) block of bytecodes.
* Supports visual effects for execution state, and permits user selection
* of instructions for various purposes (e.g. set breakpoint)
*/
public class JTableBytecodeViewer extends BytecodeViewer {
/** Maximum literal string length displayed directly in operand field. */
public static final int MAX_BYTECODE_OPERAND_DISPLAY = 15;
private final Inspection inspection;
private final InspectorView view;
private final BytecodeTable table;
private final BytecodeTableModel tableModel;
private final BytecodeViewPreferences instanceViewPreferences;
public JTableBytecodeViewer(Inspection inspection, MethodView parent, TeleClassMethodActor teleClassMethodActor, MaxCompilation compilation) {
super(inspection, parent, teleClassMethodActor, compilation);
this.inspection = inspection;
this.view = parent;
tableModel = new BytecodeTableModel(inspection, bytecodeInstructions());
instanceViewPreferences = new BytecodeViewPreferences(BytecodeViewPreferences.globalPreferences(inspection())) {
@Override
public void setIsVisible(BytecodeColumnKind columnKind, boolean visible) {
super.setIsVisible(columnKind, visible);
table.getInspectorTableColumnModel().setColumnVisible(columnKind.ordinal(), visible);
JTableColumnResizer.adjustColumnPreferredWidths(table);
refresh(true);
}
@Override
public void setOperandDisplayMode(PoolConstantLabel.Mode mode) {
super.setOperandDisplayMode(mode);
tableModel.fireTableDataChanged();
}
};
final BytecodeTableColumnModel tableColumnModel = new BytecodeTableColumnModel(instanceViewPreferences);
table = new BytecodeTable(inspection, tableModel, tableColumnModel);
createView();
}
@Override
protected void createView() {
super.createView();
// Set up toolbar
// TODO (mlvdv) implement remaining debugging controls in Bytecodes view
// the disabled ones haven't been adapted for bytecode-based debugging
JButton button = new InspectorButton(inspection, actions().toggleBytecodeBreakpoint());
button.setToolTipText(button.getText());
button.setText(null);
final InspectorStyle style = preference().style();
button.setIcon(style.debugToggleBreakpointbuttonIcon());
button.setEnabled(false);
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugStepOver());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugStepOverButtonIcon());
button.setEnabled(false);
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugSingleStep());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugStepInButtonIcon());
button.setEnabled(false);
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugReturnFromFrame());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugStepOutButtonIcon());
button.setEnabled(haveMachineCodeAddresses());
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugRunToSelectedInstruction());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugRunToCursorButtonIcon());
button.setEnabled(haveMachineCodeAddresses());
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugResume());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugContinueButtonIcon());
toolBar().add(button);
button = new InspectorButton(inspection, actions().debugPause());
button.setToolTipText(button.getText());
button.setText(null);
button.setIcon(style.debugPauseButtonIcon());
toolBar().add(button);
toolBar().add(Box.createHorizontalGlue());
toolBar().add(new TextLabel(inspection(), "Bytecodes"));
toolBar().add(Box.createHorizontalGlue());
addSearchButton();
addActiveRowsButton();
final JButton viewOptionsButton = new InspectorButton(inspection, new AbstractAction("View...") {
public void actionPerformed(ActionEvent actionEvent) {
final BytecodeViewPreferences globalPreferences = BytecodeViewPreferences.globalPreferences(inspection());
new TableColumnVisibilityPreferences.ColumnPreferencesDialog<BytecodeColumnKind>(inspection(), "Bytecodes View Options", instanceViewPreferences, globalPreferences);
}
});
viewOptionsButton.setToolTipText("Bytecodes view options");
viewOptionsButton.setText(null);
viewOptionsButton.setIcon(style.generalPreferencesIcon());
toolBar().add(viewOptionsButton);
toolBar().add(Box.createHorizontalGlue());
addCodeViewCloseButton();
final JScrollPane scrollPane = new InspectorScrollPane(inspection, table);
add(scrollPane, BorderLayout.CENTER);
refresh(true);
JTableColumnResizer.adjustColumnPreferredWidths(table);
}
@Override
protected int getRowCount() {
return table.getRowCount();
}
@Override
protected int getSelectedRow() {
return table.getSelectedRow();
}
@Override
protected void setFocusAtRow(int row) {
final int bci = tableModel.rowToInstruction(row).bci;
focus().setCodeLocation(vm().codeLocations().createBytecodeLocation(teleClassMethodActor(), bci, "bytecode view set focus"), false);
}
@Override
protected RowTextMatcher getRowTextSearcher() {
return new TableRowTextMatcher(inspection, table);
}
/**
* Global code selection has changed.
*/
@Override
public boolean updateCodeFocus(MaxCodeLocation codeLocation) {
return table.updateCodeFocus(codeLocation);
}
@Override
public void refresh(boolean force) {
super.refresh(force);
table.refresh(force);
// updateSize();
}
@Override
public void redisplay() {
super.redisplay();
table.redisplay();
// TODO (mlvdv) code view hack for style changes
table.setRowHeight(preference().style().codeTableRowHeight());
invalidate();
repaint();
}
public InspectorView getView() {
return view;
}
// TODO (mlvdv) Extract the table class from the viewer
private final class BytecodeTable extends InspectorTable {
BytecodeTable(Inspection inspection, InspectorTableModel tableModel, InspectorTableColumnModel tableColumnModel) {
super(inspection, tableModel, tableColumnModel);
setFillsViewportHeight(true);
final InspectorStyle style = preference().style();
setShowHorizontalLines(style.codeTableShowHorizontalLines());
setShowVerticalLines(style.codeTableShowVerticalLines());
setIntercellSpacing(style.codeTableIntercellSpacing());
setRowHeight(style.codeTableRowHeight());
setRowSelectionAllowed(true);
setColumnSelectionAllowed(true);
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
@Override
public void valueChanged(ListSelectionEvent e) {
// The selection in the table has changed; might have happened via user action (click, arrow) or
// as a side effect of a focus change.
super.valueChanged(e);
if (!e.getValueIsAdjusting()) {
final int selectedRow = getSelectedRow();
final BytecodeTableModel bytecodeTableModel = (BytecodeTableModel) getModel();
if (selectedRow >= 0 && selectedRow < bytecodeTableModel.getRowCount()) {
final BytecodeInstruction bytecodeInstruction = bytecodeTableModel.rowToInstruction(selectedRow);
final Address machineCodeFirstAddress = bytecodeInstruction.machineCodeFirstAddress;
final int bci = bytecodeInstruction.bci;
if (machineCodeFirstAddress.isZero()) {
focus().setCodeLocation(vm().codeLocations().createBytecodeLocation(teleClassMethodActor(), bci, "bytecode view"));
} else {
try {
focus().setCodeLocation(vm().codeLocations().createMachineCodeLocation(machineCodeFirstAddress, teleClassMethodActor(), bci, "bytecode view"), true);
} catch (InvalidCodeAddressException e1) {
}
}
}
}
}
/**
* {@inheritDoc}.
* <br>
* color text specially if the IP or a call return site is at this row.
*/
@Override
public Color cellForegroundColor(int row, int col) {
final InspectorStyle style = preference().style();
return isInstructionPointer(row) ? style.debugIPTextColor() : (isCallReturn(row) ? style.debugCallReturnTextColor() : null);
}
/**
* @param col TODO
* @return Color to be used for the background of all row labels; may have special overrides in future, as for compiled code
*/
public Color cellBackgroundColor(int row, int col) {
final int[] searchMatchingRows = getSearchMatchingRows();
if (searchMatchingRows != null) {
for (int matchingRow : searchMatchingRows) {
if (row == matchingRow) {
return preference().style().searchMatchedBackground();
}
}
}
return table.getBackground();
}
public boolean updateCodeFocus(MaxCodeLocation codeLocation) {
final int oldSelectedRow = getSelectedRow();
if (codeLocation != null) {
final BytecodeTableModel model = (BytecodeTableModel) getModel();
int focusRow = -1;
if (codeLocation.hasTeleClassMethodActor()) {
if (codeLocation.teleClassMethodActor().classMethodActor() == teleClassMethodActor().classMethodActor()) {
focusRow = model.findRowAtBCI(codeLocation.bci());
}
} else if (codeLocation.hasMethodKey()) {
// Shouldn't happen, but...
if (codeLocation.methodKey().equals(methodKey())) {
focusRow = model.findRowAtBCI(0);
}
} else if (codeLocation.hasAddress()) {
if (compilation() != null && compilation().contains(codeLocation.address())) {
focusRow = model.findRow(codeLocation.address());
}
}
if (focusRow >= 0) {
// View contains the focus; ensure it is selected and visible
if (focusRow != oldSelectedRow) {
updateSelection(focusRow);
}
scrollToRows(focusRow, focusRow);
return true;
}
}
// View doesn't contain the focus; clear any old selection
if (oldSelectedRow >= 0) {
clearSelection();
}
return false;
}
public InspectorView getView() {
return view;
}
}
private final class BytecodeTableColumnModel extends InspectorTableColumnModel<BytecodeColumnKind> {
BytecodeTableColumnModel(BytecodeViewPreferences instanceViewPreferences) {
super(inspection(), BytecodeColumnKind.values().length, instanceViewPreferences);
addColumnIfSupported(BytecodeColumnKind.TAG, new TagRenderer(inspection), null);
addColumnIfSupported(BytecodeColumnKind.NUMBER, new NumberRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.BCI, new BCIRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.INSTRUCTION, new InstructionRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.OPERAND1, new OperandRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.OPERAND2, new OperandRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.SOURCE_LINE, new SourceLineRenderer(), null);
addColumnIfSupported(BytecodeColumnKind.BYTES, new BytesRenderer(), null);
}
}
private final class BytecodeTableModel extends InspectorTableModel {
private List<BytecodeInstruction> bytecodeInstructions;
public BytecodeTableModel(Inspection inspection, List<BytecodeInstruction> bytecodeInstructions) {
super(inspection);
this.bytecodeInstructions = bytecodeInstructions;
}
public int getColumnCount() {
return BytecodeColumnKind.values().length;
}
public int getRowCount() {
return bytecodeInstructions().size();
}
public Object getValueAt(int row, int col) {
final BytecodeInstruction instruction = rowToInstruction(row);
switch (BytecodeColumnKind.values()[col]) {
case TAG:
return null;
case NUMBER:
return row;
case BCI:
return new Integer(instruction.bci);
case INSTRUCTION:
return instruction.opcode;
case OPERAND1:
return instruction.operand1;
case OPERAND2:
return instruction.operand2;
case SOURCE_LINE:
return new CiCodePos(null, teleClassMethodActor().classMethodActor(), instruction.bci);
case BYTES:
return instruction.instructionBytes;
default:
throw new RuntimeException("Column out of range: " + col);
}
}
@Override
public Class< ? > getColumnClass(int col) {
switch (BytecodeColumnKind.values()[col]) {
case TAG:
return Object.class;
case NUMBER:
return Integer.class;
case BCI:
return Integer.class;
case INSTRUCTION:
return int.class;
case OPERAND1:
case OPERAND2:
case SOURCE_LINE:
return Object.class;
case BYTES:
return byte[].class;
default:
throw new RuntimeException("Column out of range: " + col);
}
}
@Override
public String getRowDescription(int row) {
return "Instruction " + row + " (" + Bytecodes.nameOf(rowToInstruction(row).opcode) + ")";
}
public BytecodeInstruction rowToInstruction(int row) {
return bytecodeInstructions.get(row);
}
/**
* @param bci bytecode index: a position (in bytes) in this block of bytecodes
* @return the row in this block of bytecodes containing an instruction starting at this position, -1 if none
*/
public int findRowAtBCI(int bci) {
for (BytecodeInstruction instruction : bytecodeInstructions) {
if (instruction.bci == bci) {
return instruction.row;
}
}
return -1;
}
/**
* @param address a code address in the VM
* @return the row in this block of bytecodes containing an
* instruction whose associated compiled code starts at the address, -1 if none.
*/
public int findRow(Address address) {
if (haveMachineCodeAddresses()) {
for (BytecodeInstruction instruction : bytecodeInstructions) {
int row = instruction.row;
if (rowContainsAddress(row, address)) {
return row;
}
row++;
}
}
return -1;
}
}
/**
* Sets the background of a cell rendering component, depending on the row context.
* <br>
* Makes the renderer transparent if there is no special background needed.
*/
private void setBackgroundForRow(JComponent component, int row) {
if (isSearchMatchRow(row)) {
component.setOpaque(true);
component.setBackground(preference().style().searchMatchedBackground());
} else {
component.setOpaque(false);
}
}
private final class TagRenderer extends InspectorLabel implements TableCellRenderer, TextSearchable, Prober {
public TagRenderer(Inspection inspection) {
super(inspection);
}
public Component getTableCellRendererComponent(JTable table, Object ignore, boolean isSelected, boolean hasFocus, int row, int col) {
setToolTipPrefix(tableModel.getRowDescription(row));
final StringBuilder toolTipSB = new StringBuilder(100);
final MaxStackFrame stackFrame = stackFrame(row);
final InspectorStyle style = preference().style();
if (stackFrame != null) {
if (stackFrame.position() == 0) {
toolTipSB.append("<br>IP (stack frame 0) in thread ");
} else {
toolTipSB.append("<br>Call return (frame ");
toolTipSB.append(stackFrame.position());
toolTipSB.append(") in thread ");
}
toolTipSB.append(inspection.nameDisplay().longName(stackFrame.stack().thread()));
toolTipSB.append(" points here");
if (stackFrame.isTop()) {
setIcon(style.debugIPTagIcon());
setForeground(style.debugIPTagColor());
} else {
setIcon(style.debugCallReturnTagIcon());
setForeground(style.debugCallReturnTagColor());
}
} else {
setIcon(null);
setForeground(null);
}
setText(rowToTagText(row));
final MaxBreakpoint bytecodeBreakpoint = getBytecodeBreakpointAtRow(row);
final List<MaxBreakpoint> machineCodeBreakpoints = getMachineCodeBreakpointsAtRow(row);
if (bytecodeBreakpoint != null) {
toolTipSB.append("<br>breakpont set @");
toolTipSB.append(bytecodeBreakpoint);
toolTipSB.append("; ");
toolTipSB.append(bytecodeBreakpoint.isEnabled() ? ", enabled" : ", disabled");
if (bytecodeBreakpoint.isEnabled()) {
setBorder(style.debugEnabledBytecodeBreakpointTagBorder());
} else {
setBorder(style.debugDisabledBytecodeBreakpointTagBorder());
}
} else if (machineCodeBreakpoints.size() > 0) {
boolean enabled = false;
for (MaxBreakpoint machineCodeBreakpoint : machineCodeBreakpoints) {
toolTipSB.append(machineCodeBreakpoint);
toolTipSB.append("; ");
enabled = enabled || machineCodeBreakpoint.isEnabled();
}
if (enabled) {
setBorder(style.debugEnabledMachineCodeBreakpointTagBorder());
} else {
setBorder(style.debugDisabledMachineCodeBreakpointTagBorder());
}
} else {
setBorder(null);
}
setWrappedToolTipHtmlText(toolTipSB.toString());
setBackgroundForRow(this, row);
return this;
}
public void redisplay() {
}
public void refresh(boolean force) {
}
}
private final class NumberRenderer extends PlainLabel implements TableCellRenderer {
public NumberRenderer() {
super(inspection, "");
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
final BytecodeTable bytecodeTable = (BytecodeTable) table;
setValue(row);
setToolTipText(tableModel.getRowDescription(row));
setBackgroundForRow(this, row);
setForeground(bytecodeTable.cellForegroundColor(row, col));
return this;
}
}
private final class BCIRenderer extends LocationLabel.AsPosition implements TableCellRenderer {
private int bci;
public BCIRenderer() {
super(inspection, 0);
setToolTipPrefix("Instruction");
bci = 0;
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
final BytecodeTable bytecodeTable = (BytecodeTable) table;
final Integer bci = (Integer) value;
if (bci == null) {
return gui().getUnavailableDataTableCellRenderer();
}
if (this.bci != bci) {
this.bci = bci;
setValue(bci);
}
setToolTipPrefix(tableModel.getRowDescription(row) + " location<br>");
setToolTipSuffix(" bytes from beginning");
setBackgroundForRow(this, row);
setForeground(bytecodeTable.cellForegroundColor(row, col));
return this;
}
}
private final class InstructionRenderer extends BytecodeMnemonicLabel implements TableCellRenderer {
public InstructionRenderer() {
super(inspection, -1);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
final BytecodeTable bytecodeTable = (BytecodeTable) table;
if (value == null) {
return gui().getUnavailableDataTableCellRenderer();
}
final int opcode = (Integer) value;
setToolTipPrefix(tableModel.getRowDescription(row) + "<br>");
setValue(opcode);
setBackgroundForRow(this, row);
setForeground(bytecodeTable.cellForegroundColor(row, col));
return this;
}
}
private final class OperandRenderer implements TableCellRenderer, Prober {
public OperandRenderer() {
}
public Component getTableCellRendererComponent(JTable table, Object tableValue, boolean isSelected, boolean hasFocus, int row, int col) {
final BytecodeTable bytecodeTable = (BytecodeTable) table;
InspectorLabel renderer = null;
if (tableValue instanceof InspectorLabel) {
// BytecodePrinter returns a label component for simple values
renderer = (InspectorLabel) tableValue;
renderer.setToolTipPrefix(tableModel.getRowDescription(row) + " operand:<br>");
renderer.setForeground(bytecodeTable.cellForegroundColor(row, col));
} else if (tableValue instanceof Integer) {
// BytecodePrinter returns index of a constant pool entry, when that's the operand
final int index = ((Integer) tableValue).intValue();
renderer = PoolConstantLabel.make(inspection(), index, localConstantPool(), teleConstantPool(), instanceViewPreferences.operandDisplayMode());
if (renderer.getForeground() == null) {
renderer.setForeground(bytecodeTable.cellForegroundColor(row, col));
}
renderer.setToolTipPrefix(tableModel.getRowDescription(row) + " operand:<br>Constant pool reference = ");
renderer.setFont(preference().style().bytecodeOperandFont());
} else if (tableValue == null) {
return gui().getUnavailableDataTableCellRenderer();
} else {
InspectorError.unexpected("unrecognized table value at row=" + row + ", col=" + col);
}
setBackgroundForRow(renderer, row);
return renderer;
}
public void redisplay() {
}
public void refresh(boolean force) {
}
}
private final class SourceLineRenderer extends PlainLabel implements TableCellRenderer {
private CiCodePos lastCodePos;
SourceLineRenderer() {
super(JTableBytecodeViewer.this.inspection(), null);
addMouseListener(new InspectorMouseClickAdapter(inspection()) {
@Override
public void procedure(final MouseEvent mouseEvent) {
final CiCodePos codePos = lastCodePos;
if (codePos != null) {
inspection().viewSourceExternally(codePos);
}
}
});
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
final CiCodePos codePos = (CiCodePos) value;
if (codePos == null) {
return gui().getUnavailableDataTableCellRenderer();
}
setToolTipPrefix(tableModel.getRowDescription(row) + "<br>");
ClassMethodActor method = (ClassMethodActor) codePos.method;
final String sourceFileName = method.holder().sourceFileName;
final int lineNumber = method.sourceLineNumber(codePos.bci);
if (sourceFileName != null && lineNumber >= 0) {
setText(String.valueOf(lineNumber));
setWrappedToolTipHtmlText("Source location =<br>" + sourceFileName + ":" + lineNumber);
} else {
setText("");
setWrappedToolTipHtmlText("Source line not available");
}
setBackgroundForRow(this, row);
lastCodePos = codePos;
return this;
}
}
private final class BytesRenderer extends DataLabel.ByteArrayAsHex implements TableCellRenderer {
BytesRenderer() {
super(inspection, null);
}
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
setBackgroundForRow(this, row);
final BytecodeTable bytecodeTable = (BytecodeTable) table;
setForeground(bytecodeTable.cellForegroundColor(row, col));
setToolTipPrefix(tableModel.getRowDescription(row) + "<br>as bytes = ");
setValue((byte[]) value);
return this;
}
}
@Override
public void print(String name) {
final MessageFormat header = new MessageFormat(name);
final MessageFormat footer = new MessageFormat(vm().entityName() + ": " + codeViewerKindName() + " Printed: " + new Date() + " -- Page: {0, number, integer}");
try {
table.print(JTable.PrintMode.FIT_WIDTH, header, footer);
} catch (PrinterException printerException) {
gui().errorMessage("Print failed: " + printerException.getMessage());
}
}
}