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