/*
 * 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
/** 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() + ")]"); } }