/*
* 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.util.*;
import java.util.List;
import javax.swing.*;
import com.sun.max.ins.*;
import com.sun.max.ins.gui.*;
import com.sun.max.program.*;
import com.sun.max.tele.*;
/**
* Base class for panels that show a row-oriented view of a method in a MethodInspector framework.
* Not intended for use outside a {@link JavaMethodView}, so not undockable;
* Includes machinery for some common operations, based on abstract "rows"
* - maintaining a cache that maps row->stackFrame for the thread of current focus
* - tracking which rows are "active", i.e. have some frame at that location for the thread of current focus
* - an action, attached to a toolbar button, that scrolls to the next active row
* - a "search" function that causes a separate toolbar to appear that permits regexp row-based searching.
*/
public abstract class CodeViewer extends InspectorPanel {
private static final int TRACE_VALUE = 2;
private final MethodView parent;
private JPanel toolBarPanel;
private JToolBar toolBar;
private RowTextSearchToolBar searchToolBar;
private final JButton searchButton;
private final JButton activeRowsButton;
private JButton viewCloseButton;
public MethodView parent() {
return parent;
}
protected JToolBar toolBar() {
return toolBar;
}
public abstract MethodCodeKind codeKind();
public abstract String codeViewerKindName();
public abstract void print(String name);
public abstract boolean updateCodeFocus(MaxCodeLocation codeLocation);
public void updateThreadFocus(MaxThread thread) {
updateCaches(false);
}
public CodeViewer(Inspection inspection, MethodView parent) {
super(inspection, new BorderLayout());
this.parent = parent;
searchButton = new InspectorButton(inspection, new AbstractAction("Search...") {
public void actionPerformed(ActionEvent actionEvent) {
addSearchToolBar();
}
});
searchButton.setText(null);
final InspectorStyle style = preference().style();
searchButton.setIcon(style.generalFindIcon());
searchButton.setToolTipText("Open toolbar for searching");
activeRowsButton = new InspectorButton(inspection, new AbstractAction(null, style.navigationForwardIcon()) {
public void actionPerformed(ActionEvent actionEvent) {
int nextActiveRow = nextActiveRow();
if (nextActiveRow >= 0) {
if (nextActiveRow == getSelectedRow()) {
// If already at an active row, go to the next one, if it exists.
nextActiveRow = nextActiveRow();
}
setFocusAtRow(nextActiveRow);
}
}
});
activeRowsButton.setText(null);
activeRowsButton.setForeground(style.debugIPTagColor());
activeRowsButton.setToolTipText("Scroll to next line with IP or Call Return");
activeRowsButton.setEnabled(false);
viewCloseButton =
new InspectorButton(inspection(), "", "Close " + codeViewerKindName());
viewCloseButton.setAction(new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
parent().closeCodeViewer(CodeViewer.this);
}
});
viewCloseButton.setIcon(style.codeViewCloseIcon());
}
protected void createView() {
toolBarPanel = new InspectorPanel(inspection(), new GridLayout(0, 1));
toolBar = new InspectorToolBar(inspection());
toolBar.setBorder(preference().style().defaultPaneBorder());
toolBar.setFloatable(false);
toolBar.setRollover(true);
toolBarPanel.add(toolBar);
add(toolBarPanel, BorderLayout.NORTH);
}
private int[] searchMatchingRows = null;
/**
* @return the rows that match a current search session; null if no search session active.
*/
protected final int[] getSearchMatchingRows() {
return searchMatchingRows;
}
private final RowMatchNavigationListener rowMatchListener = new RowMatchNavigationListener() {
public void setSearchResult(int[] result) {
searchMatchingRows = result;
// go to next matching row from current selection
if (searchMatchingRows != null) {
Trace.line(TRACE_VALUE, "search: matches " + searchMatchingRows.length + " = " + searchMatchingRows);
}
repaint();
}
public void selectNextResult() {
setFocusAtNextSearchMatch();
}
public void selectPreviousResult() {
setFocusAtPreviousSearchMatch();
}
public void closeRequested() {
CodeViewer.this.closeSearch();
}
};
private void addSearchToolBar() {
if (searchToolBar == null) {
searchToolBar = new RowTextSearchToolBar(inspection(), rowMatchListener, getRowTextSearcher());
toolBarPanel.add(searchToolBar);
parent().validate();
searchToolBar.getFocus();
}
}
private void closeSearch() {
Trace.line(TRACE_VALUE, "search: closing");
toolBarPanel.remove(searchToolBar);
parent().validate();
searchToolBar = null;
searchMatchingRows = null;
}
private void setFocusAtNextSearchMatch() {
Trace.line(TRACE_VALUE, "search: next match");
if (searchMatchingRows.length > 0) {
int currentRow = getSelectedRow();
for (int row : searchMatchingRows) {
if (row > currentRow) {
setFocusAtRow(row);
return;
}
}
// wrap, could be optional, or dialog choice
currentRow = -1;
for (int row : searchMatchingRows) {
if (row > currentRow) {
setFocusAtRow(row);
return;
}
}
} else {
flash();
}
}
private void setFocusAtPreviousSearchMatch() {
Trace.line(TRACE_VALUE, "search: previous match");
if (searchMatchingRows.length > 0) {
int currentRow = getSelectedRow();
for (int index = searchMatchingRows.length - 1; index >= 0; index--) {
final Integer matchingRow = searchMatchingRows[index];
if (matchingRow < currentRow) {
setFocusAtRow(matchingRow);
return;
}
}
// wrap, could be optional, or dialog choice
currentRow = getRowCount();
for (int index = searchMatchingRows.length - 1; index >= 0; index--) {
final Integer matchingRow = searchMatchingRows[index];
if (matchingRow < currentRow) {
setFocusAtRow(matchingRow);
return;
}
}
} else {
flash();
}
}
/**
* @return a searcher for locating rows with a textual regexp.
*/
protected abstract RowTextMatcher getRowTextSearcher();
/**
* @return how man rows are in the view.
*/
protected abstract int getRowCount();
/**
* @return the row in a code display that is currently selected (at code focus); -1 if no selection
*/
protected abstract int getSelectedRow();
/**
* Sets the global focus of code location at the code being displayed in the row.
*/
protected abstract void setFocusAtRow(int row);
/**
* Adds a button to the view's tool bar that enables textual search.
*/
protected void addSearchButton() {
toolBar().add(searchButton);
}
/**
* Adds a button to the view's tool bar that enables navigation among "active" rows, those that correspond to
* stack locations in the current thread.
*/
protected void addActiveRowsButton() {
toolBar().add(activeRowsButton);
}
/**
* Adds a button to the view's tool bar that closes this view.
*/
protected void addCodeViewCloseButton() {
toolBar.add(viewCloseButton);
}
@Override
public void refresh(boolean force) {
updateCaches(force);
}
protected void updateSize() {
for (int index = 0; index < getComponentCount(); index++) {
final Component component = getComponent(index);
if (component instanceof JScrollPane) {
final JScrollPane scrollPane = (JScrollPane) component;
final Dimension size = scrollPane.getViewport().getPreferredSize();
setMaximumSize(new Dimension(size.width + 40, size.height + 40));
}
}
}
protected void flash() {
parent.flash();
}
// Cached stack information, relative to this method, derived from the thread of current focus.
// TODO (mlvdv) Generalize to account for the possibility of multiple stack frames associated with a single row.
protected MaxStackFrame[] rowToStackFrame;
/**
* Rebuild the data in the cached stack information for the code view.
*/
protected abstract void updateStackCache();
// The thread from which the stack cache was last built.
private MaxThread threadForCache = null;
private MaxVMState lastRefreshedState = null;
private void updateCaches(boolean force) {
final MaxThread thread = focus().thread();
if (thread != threadForCache || vm().state().newerThan(lastRefreshedState) || force) {
lastRefreshedState = vm().state();
updateStackCache();
// Active rows depend on the stack cache.
updateActiveRows();
threadForCache = thread;
}
}
/**
* Returns stack frame, if any, associated with the row.
*/
protected MaxStackFrame stackFrame(int row) {
return rowToStackFrame[row];
}
/**
* Is the machine code address at the row an instruction pointer
* for a non-top frame of the stack of the thread that is the current focus?
*/
protected boolean isCallReturn(int row) {
final MaxStackFrame stackFrame = rowToStackFrame[row];
return stackFrame != null && !stackFrame.isTop();
}
/**
* Is the machine code address at the row an instruction pointer
* for the top frame of the stack of the thread that is the current focus?
*/
protected boolean isInstructionPointer(int row) {
final MaxStackFrame stackFrame = rowToStackFrame[row];
return stackFrame != null && stackFrame.isTop();
}
// Active rows are those for which there is an associated stack frame
private List<Integer> activeRows = new ArrayList<Integer>(3);
private int currentActiveRowIndex = -1;
private void updateActiveRows() {
activeRows.clear();
for (int row = 0; row < rowToStackFrame.length; row++) {
if (rowToStackFrame[row] != null) {
activeRows.add(row);
}
}
currentActiveRowIndex = -1;
activeRowsButton.setEnabled(hasActiveRows());
}
/**
* Does the method have any rows that are either the current instruction pointer or call return lines marked.
*/
protected boolean hasActiveRows() {
return activeRows.size() > 0;
}
/**
* Cycles through the rows in the method that are either the current instruction pointer or call return lines marked.
* Resets to the first after each refresh.
*/
protected int nextActiveRow() {
if (hasActiveRows()) {
currentActiveRowIndex = (currentActiveRowIndex + 1) % activeRows.size();
return activeRows.get(currentActiveRowIndex);
}
return -1;
}
/**
* Is there a currently active search that matches the specified row?
*/
protected boolean isSearchMatchRow(int row) {
final int[] searchMatchingRows = getSearchMatchingRows();
if (searchMatchingRows != null) {
for (int matchingRow : searchMatchingRows) {
if (row == matchingRow) {
return true;
}
}
}
return false;
}
}