1/*
2 * Copyright (c) 2000, 2008, 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.math.*;
28import java.awt.*;
29import java.awt.font.*;
30import java.awt.geom.*;
31import java.awt.event.*;
32import java.io.*;
33import javax.swing.*;
34import javax.swing.event.*;
35import java.util.*;
36
37import sun.jvm.hotspot.debugger.*;
38import sun.jvm.hotspot.debugger.dummy.*;
39import sun.jvm.hotspot.utilities.*;
40
41/** A subclass of JPanel which displays a hex dump of memory along
42    with annotations describing the significance of various
43    pieces. This can be used to implement either a stack or heap
44    inspector. */
45
46public class AnnotatedMemoryPanel extends JPanel {
47  private boolean is64Bit;
48  private Debugger debugger;
49  private long    addressSize;
50  private HighPrecisionJScrollBar scrollBar;
51  private Font font;
52  private int bytesPerLine;
53  private int paintCount;
54  private String unmappedAddrString;
55  // Type of this is an IntervalTree indexed by Interval<Address> and
56  // with user data of type Annotation
57  private IntervalTree annotations =
58    new IntervalTree(new Comparator() {
59        public int compare(Object o1, Object o2) {
60          Address a1 = (Address) o1;
61          Address a2 = (Address) o2;
62
63          if ((a1 == null) && (a2 == null)) {
64            return 0;
65          } else if (a1 == null) {
66            return -1;
67          } else if (a2 == null) {
68            return 1;
69          }
70
71          if (a1.equals(a2)) {
72            return 0;
73          } else if (a1.lessThan(a2)) {
74            return -1;
75          }
76          return 1;
77        }
78      });
79  // Keep track of the last start address at which we painted, so we
80  // can scroll annotations
81  private Address lastStartAddr;
82  // This contains the list of currently-visible IntervalNodes, in
83  // sorted order by their low endpoint, in the form of a
84  // List<Annotation>. These annotations have already been laid out.
85  private java.util.List visibleAnnotations;
86  // Darker colors than defaults for better readability
87  private static Color[] colors = {
88    new Color(0.0f, 0.0f, 0.6f), // blue
89    new Color(0.6f, 0.0f, 0.6f), // magenta
90    new Color(0.0f, 0.8f, 0.0f), // green
91    new Color(0.8f, 0.3f, 0.0f), // orange
92    new Color(0.0f, 0.6f, 0.8f), // cyan
93    new Color(0.2f, 0.2f, 0.2f), // dark gray
94  };
95
96  /** Default is 32-bit mode */
97  public AnnotatedMemoryPanel(Debugger debugger) {
98    this(debugger, false);
99  }
100
101  public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit, Address addrValue, Address addrLow, Address addrHigh) {
102    super();
103    init(debugger, is64Bit, addressToBigInt(addrValue), addressToBigInt(addrLow), addressToBigInt(addrHigh));
104  }
105
106  public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit ) {
107    super();
108    init(debugger, is64Bit, defaultMemoryLocation(is64Bit), defaultMemoryLow(is64Bit), defaultMemoryHigh(is64Bit));
109  }
110
111  static class AnnoX {
112    int     lineX;
113    Address highBound;
114
115    public AnnoX(int lineX, Address highBound) {
116      this.lineX = lineX;
117      this.highBound = highBound;
118    }
119  }
120
121  public synchronized void paintComponent(Graphics g) {
122    //    System.err.println("AnnotatedMemoryPanel.paintComponent() " + ++paintCount);
123    super.paintComponent(g);
124
125    // Clone the Graphics so we don't screw up its state for Swing
126    // drawing (as this code otherwise does)
127    g = g.create();
128
129    g.setFont(font);
130    g.setColor(Color.black);
131    Rectangle rect = new Rectangle();
132    getBounds(rect);
133    String firstAddressString = null;
134    int lineHeight;
135    int addrWidth;
136    {
137      Rectangle2D bounds = GraphicsUtilities.getStringBounds(unmappedAddrString, g);
138      lineHeight = (int) bounds.getHeight();
139      addrWidth  = (int) bounds.getWidth();
140    }
141    int addrX = (int) (0.25 * addrWidth);
142    int dataX = (int) (addrX + (1.5 * addrWidth));
143    int lineStartX = dataX + addrWidth + 5;
144    int annoStartX = (int) (lineStartX + (0.75 * addrWidth));
145
146    int numLines = rect.height / lineHeight;
147
148    BigInteger startVal  = scrollBar.getValueHP();
149    BigInteger perLine = new BigInteger(Integer.toString((int) addressSize));
150    // lineCount and maxLines are both 1 less than expected
151    BigInteger lineCount = new BigInteger(Integer.toString((int) (numLines - 1)));
152    BigInteger maxLines = scrollBar.getMaximumHP().subtract(scrollBar.getMinimumHP()).divide(perLine);
153    if (lineCount.compareTo(maxLines) > 0) {
154      lineCount = maxLines;
155    }
156    BigInteger offsetVal = lineCount.multiply(perLine);
157    BigInteger endVal    = startVal.add(offsetVal);
158    if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) {
159      startVal = scrollBar.getMaximumHP().subtract(offsetVal);
160      endVal   = scrollBar.getMaximumHP();
161      // Sure seems like this call will cause us to immediately redraw...
162      scrollBar.setValueHP(startVal);
163    }
164    scrollBar.setVisibleAmountHP(offsetVal.add(perLine));
165    scrollBar.setBlockIncrementHP(offsetVal);
166
167    Address startAddr = bigIntToAddress(startVal);
168    Address endAddr   = bigIntToAddress(endVal);
169
170    // Scroll last-known annotations
171    int scrollOffset = 0;
172    if (lastStartAddr != null) {
173      scrollOffset = (int) lastStartAddr.minus(startAddr);
174    } else {
175      if (startAddr != null) {
176        scrollOffset = (int) (-1 * startAddr.minus(lastStartAddr));
177      }
178    }
179    scrollOffset = scrollOffset * lineHeight / (int) addressSize;
180    scrollAnnotations(scrollOffset);
181    lastStartAddr = startAddr;
182
183    int curY = lineHeight;
184    Address curAddr = startAddr;
185    for (int i = 0; i < numLines; i++) {
186      String s = bigIntToHexString(startVal);
187      g.drawString(s, addrX, curY);
188      try {
189        s = addressToString(startAddr.getAddressAt(i * addressSize));
190      }
191      catch (UnmappedAddressException e) {
192        s = unmappedAddrString;
193      }
194      g.drawString(s, dataX, curY);
195      curY += lineHeight;
196      startVal = startVal.add(perLine);
197    }
198
199    // Query for visible annotations (little slop to ensure we get the
200    // top and bottom)
201    // FIXME: it would be nice to have a more static layout; that is,
202    // if something scrolls off the bottom of the screen, other
203    // annotations still visible shouldn't change position
204    java.util.List va =
205      annotations.findAllNodesIntersecting(new Interval(startAddr.addOffsetTo(-addressSize),
206                                                        endAddr.addOffsetTo(2 * addressSize)));
207
208    // Render them
209    int curLineX = lineStartX;
210    int curTextX = annoStartX;
211    int curColorIdx = 0;
212    if (g instanceof Graphics2D) {
213      Stroke stroke = new BasicStroke(3.0f);
214      ((Graphics2D) g).setStroke(stroke);
215    }
216
217    Stack drawStack = new Stack();
218
219    layoutAnnotations(va, g, curTextX, startAddr, lineHeight);
220
221    for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
222      Annotation anno   = (Annotation) iter.next();
223      Interval interval = anno.getInterval();
224
225      if (!drawStack.empty()) {
226        // See whether we can pop any items off the stack
227        boolean shouldContinue = true;
228        do {
229          AnnoX annoX = (AnnoX) drawStack.peek();
230          if (annoX.highBound.lessThanOrEqual((Address) interval.getLowEndpoint())) {
231            curLineX = annoX.lineX;
232            drawStack.pop();
233            shouldContinue = !drawStack.empty();
234          } else {
235            shouldContinue = false;
236          }
237        } while (shouldContinue);
238      }
239
240      // Draw a line covering the interval
241      Address lineStartAddr = (Address) interval.getLowEndpoint();
242      // Give it a little slop
243      int lineStartY = (int) (lineStartAddr.minus(startAddr) * lineHeight / addressSize) +
244        (lineHeight / 3);
245      Address lineEndAddr = (Address) interval.getHighEndpoint();
246      drawStack.push(new AnnoX(curLineX, lineEndAddr));
247      int lineEndY = (int) (lineEndAddr.minus(startAddr) * lineHeight / addressSize);
248      g.setColor(anno.getColor());
249      g.drawLine(curLineX, lineStartY, curLineX, lineEndY);
250      // Draw line to text
251      g.drawLine(curLineX, lineStartY, curTextX - 10, anno.getY() - (lineHeight / 2));
252      curLineX += 8;
253      anno.draw(g);
254      ++curColorIdx;
255    }
256  }
257
258  /** Add an annotation covering the address range [annotation.lowAddress,
259      annotation.highAddress); that is, it includes the low address and does not
260      include the high address. */
261  public synchronized void addAnnotation(Annotation annotation) {
262    annotations.insert(annotation.getInterval(), annotation);
263  }
264
265  /** Makes the given address visible somewhere in the window */
266  public synchronized void makeVisible(Address addr) {
267    BigInteger bi = addressToBigInt(addr);
268    scrollBar.setValueHP(bi);
269  }
270
271  public void print() {
272    printOn(System.out);
273  }
274
275  public void printOn(PrintStream tty) {
276    annotations.printOn(tty);
277  }
278
279  //----------------------------------------------------------------------
280  // Internals only below this point
281  //
282
283  private void init(Debugger debugger, boolean is64Bit, BigInteger addrValue, BigInteger addrLow, BigInteger addrHigh) {
284    this.is64Bit = is64Bit;
285    this.debugger = debugger;
286    if (is64Bit) {
287      addressSize = 8;
288      unmappedAddrString = "??????????????????";
289    } else {
290      addressSize = 4;
291      unmappedAddrString = "??????????";
292    }
293    setLayout(new BorderLayout());
294    setupScrollBar(addrValue, addrLow, addrHigh);
295    add(scrollBar, BorderLayout.EAST);
296    visibleAnnotations = new ArrayList();
297    setBackground(Color.white);
298    addHierarchyBoundsListener(new HierarchyBoundsListener() {
299        public void ancestorMoved(HierarchyEvent e) {
300        }
301
302        public void ancestorResized(HierarchyEvent e) {
303          // FIXME: should perform incremental layout
304          //          System.err.println("Ancestor resized");
305        }
306      });
307
308    if (font == null) {
309      font = GraphicsUtilities.lookupFont("Courier");
310    }
311    if (font == null) {
312      throw new RuntimeException("Error looking up monospace font Courier");
313    }
314    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "PageDown");
315    getActionMap().put("PageDown", new AbstractAction() {
316        public void actionPerformed(ActionEvent e) {
317          scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getBlockIncrementHP()));
318        }
319      });
320    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "PageUp");
321    getActionMap().put("PageUp", new AbstractAction() {
322        public void actionPerformed(ActionEvent e) {
323          scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getBlockIncrementHP()));
324        }
325      });
326    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Down");
327    getActionMap().put("Down", new AbstractAction() {
328        public void actionPerformed(ActionEvent e) {
329          scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getUnitIncrementHP()));
330        }
331      });
332    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Up");
333    getActionMap().put("Up", new AbstractAction() {
334        public void actionPerformed(ActionEvent e) {
335          scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getUnitIncrementHP()));
336        }
337      });
338    setEnabled(true);
339  }
340
341  private void setupScrollBar(BigInteger value, BigInteger min, BigInteger max) {
342    scrollBar = new HighPrecisionJScrollBar( Scrollbar.VERTICAL, value, min, max);
343    if (is64Bit) {
344      bytesPerLine = 8;
345      // 64-bit mode
346      scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
347        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
348        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}));
349      scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
350        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
351        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40}));
352    } else {
353      // 32-bit mode
354      bytesPerLine = 4;
355      scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
356        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04}));
357      scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
358        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20}));
359    }
360    scrollBar.addChangeListener(new ChangeListener() {
361        public void stateChanged(ChangeEvent e) {
362          HighPrecisionJScrollBar h = (HighPrecisionJScrollBar) e.getSource();
363          repaint();
364        }
365      });
366  }
367
368  private static BigInteger defaultMemoryLocation(boolean is64Bit) {
369    if (is64Bit) {
370      return new BigInteger(1, new byte[] {
371                           (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
372                           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
373    } else {
374      return new BigInteger(1, new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00});
375    }
376  }
377
378  private static BigInteger defaultMemoryLow(boolean is64Bit) {
379    if (is64Bit) {
380      return new BigInteger(1, new byte[] {
381                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
382                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
383    } else {
384      return new BigInteger(1, new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
385    }
386  }
387
388  private static BigInteger defaultMemoryHigh(boolean is64Bit) {
389    if (is64Bit) {
390      return new BigInteger(1, new byte[] {
391                 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
392                 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
393    } else {
394      return new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
395    }
396  }
397
398  private void setupScrollBar() {
399    setupScrollBar(defaultMemoryLocation(is64Bit),
400                   defaultMemoryLow(is64Bit),
401                   defaultMemoryHigh(is64Bit));
402  }
403
404  private String bigIntToHexString(BigInteger bi) {
405    StringBuffer buf = new StringBuffer();
406    buf.append("0x");
407    String val = bi.toString(16);
408    for (int i = 0; i < ((2 * addressSize) - val.length()); i++) {
409      buf.append('0');
410    }
411    buf.append(val);
412    return buf.toString();
413  }
414
415  private Address bigIntToAddress(BigInteger i) {
416    String s = bigIntToHexString(i);
417    return debugger.parseAddress(s);
418  }
419
420  private BigInteger addressToBigInt(Address a) {
421    String s = addressToString(a);
422    if (!s.startsWith("0x")) {
423      throw new NumberFormatException(s);
424    }
425    return new BigInteger(s.substring(2), 16);
426  }
427
428  private String addressToString(Address a) {
429    if (a == null) {
430      if (is64Bit) {
431        return "0x0000000000000000";
432      } else {
433        return "0x00000000";
434      }
435    }
436    return a.toString();
437  }
438
439  /** Scrolls the visible annotations by the given Y amount */
440  private void scrollAnnotations(int y) {
441    for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
442      Annotation anno = (Annotation) iter.next();
443      anno.setY(anno.getY() + y);
444    }
445  }
446
447  /** Takes the list of currently-visible annotations (in the form of
448      a List<IntervalNode>) and lays them out given the current
449      visible position and the already-visible annotations. Does not
450      perturb the layouts of the currently-visible annotations. */
451  private void layoutAnnotations(java.util.List va,
452                                 Graphics g,
453                                 int x,
454                                 Address startAddr,
455                                 int lineHeight) {
456    // Handle degenerate case early: no visible annotations.
457    if (va.size() == 0) {
458      visibleAnnotations.clear();
459      return;
460    }
461
462    // We have two ranges of visible annotations: the one from the
463    // last repaint and the currently visible set. We want to preserve
464    // the layouts of the previously-visible annotations that are
465    // currently visible (to avoid color flashing, jumping, etc.)
466    // while making the coloring of the new annotations fit as well as
467    // possible. Note that annotations may appear and disappear from
468    // any point in the previously-visible list, but the ordering of
469    // the visible annotations is always the same.
470
471    // This is really a constrained graph-coloring problem. This
472    // simple algorithm only takes into account half of the
473    // constraints (for example, the layout of the previous
474    // annotation, where it should be taking into account the layout
475    // of the previous and next annotations that were in the
476    // previously-visible list). There are situations where it can
477    // generate overlapping annotations and adjacent annotations with
478    // the same color; generally visible when scrolling line-by-line
479    // rather than page-by-page. In some of these situations, will
480    // have to move previously laid-out annotations. FIXME: revisit
481    // this.
482
483    // Index of last annotation which we didn't know how to lay out
484    int deferredIndex = -1;
485    // We "lay out after" this one
486    Annotation constraintAnnotation = null;
487    // The first constraint annotation
488    Annotation firstConstraintAnnotation = null;
489    // The index from which we search forward in the
490    // visibleAnnotations list. This reduces the amount of work we do.
491    int searchIndex = 0;
492    // The new set of annotations
493    java.util.List newAnnos = new ArrayList();
494
495    for (Iterator iter = va.iterator(); iter.hasNext(); ) {
496      Annotation anno = (Annotation) ((IntervalNode) iter.next()).getData();
497
498      // Search forward for this one
499      boolean found = false;
500      for (int i = searchIndex; i < visibleAnnotations.size(); i++) {
501        Annotation el = (Annotation) visibleAnnotations.get(i);
502        // See whether we can abort the search unsuccessfully because
503        // we went forward too far
504        if (el.getLowAddress().greaterThan(anno.getLowAddress())) {
505          break;
506        }
507        if (el == anno) {
508          // Found successfully.
509          found = true;
510          searchIndex = i;
511          constraintAnnotation = anno;
512          if (firstConstraintAnnotation == null) {
513            firstConstraintAnnotation = constraintAnnotation;
514          }
515          break;
516        }
517      }
518
519      if (!found) {
520        if (constraintAnnotation != null) {
521          layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
522          constraintAnnotation = anno;
523        } else {
524          // Defer layout of this annotation until later
525          ++deferredIndex;
526        }
527      }
528
529      newAnnos.add(anno);
530    }
531
532    if (firstConstraintAnnotation != null) {
533      // Go back and lay out deferred annotations
534      for (int i = deferredIndex; i >= 0; i--) {
535        Annotation anno = (Annotation) newAnnos.get(i);
536        layoutBefore(anno, firstConstraintAnnotation, g, x, startAddr, lineHeight);
537        firstConstraintAnnotation = anno;
538      }
539    } else {
540      // Didn't find any overlap between the old and new annotations.
541      // Lay out in a feed-forward fashion.
542      if (Assert.ASSERTS_ENABLED) {
543        Assert.that(constraintAnnotation == null, "logic error in layout code");
544      }
545      for (Iterator iter = newAnnos.iterator(); iter.hasNext(); ) {
546        Annotation anno = (Annotation) iter.next();
547        layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
548        constraintAnnotation = anno;
549      }
550    }
551
552    visibleAnnotations = newAnnos;
553  }
554
555  /** Lays out the given annotation before the optional constraint
556      annotation, obeying constraints imposed by that annotation if it
557      is specified. */
558  private void layoutBefore(Annotation anno, Annotation constraintAnno,
559                            Graphics g, int x,
560                            Address startAddr, int lineHeight) {
561    anno.computeWidthAndHeight(g);
562    // Color
563    if (constraintAnno != null) {
564      anno.setColor(prevColor(constraintAnno.getColor()));
565    } else {
566      anno.setColor(colors[0]);
567    }
568    // X
569    anno.setX(x);
570    // Tentative Y
571    anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
572              (5 * lineHeight / 6));
573    // See whether Y overlaps with last anno's Y; if so, move this one up
574    if ((constraintAnno != null) && (anno.getY() + anno.getHeight() > constraintAnno.getY())) {
575      anno.setY(constraintAnno.getY() - anno.getHeight());
576    }
577  }
578
579  /** Lays out the given annotation after the optional constraint
580      annotation, obeying constraints imposed by that annotation if it
581      is specified. */
582  private void layoutAfter(Annotation anno, Annotation constraintAnno,
583                           Graphics g, int x,
584                           Address startAddr, int lineHeight) {
585    anno.computeWidthAndHeight(g);
586    // Color
587    if (constraintAnno != null) {
588      anno.setColor(nextColor(constraintAnno.getColor()));
589    } else {
590      anno.setColor(colors[0]);
591    }
592    // X
593    anno.setX(x);
594    // Tentative Y
595    anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
596              (5 * lineHeight / 6));
597    // See whether Y overlaps with last anno's Y; if so, move this one down
598    if ((constraintAnno != null) && (anno.getY() < (constraintAnno.getY() + constraintAnno.getHeight()))) {
599      anno.setY(constraintAnno.getY() + constraintAnno.getHeight());
600    }
601  }
602
603  /** Returns previous color in our color palette */
604  private Color prevColor(Color c) {
605    int i = findColorIndex(c);
606    if (i == 0) {
607      return colors[colors.length - 1];
608    } else {
609      return colors[i - 1];
610    }
611  }
612
613  /** Returns next color in our color palette */
614  private Color nextColor(Color c) {
615    return colors[(findColorIndex(c) + 1) % colors.length];
616  }
617
618  private int findColorIndex(Color c) {
619    for (int i = 0; i < colors.length; i++) {
620      if (colors[i] == c) {
621        return i;
622      }
623    }
624    throw new IllegalArgumentException();
625  }
626
627  public static void main(String[] args) {
628    JFrame frame = new JFrame();
629    DummyDebugger debugger = new DummyDebugger(new MachineDescriptionIntelX86());
630    AnnotatedMemoryPanel anno = new AnnotatedMemoryPanel(debugger);
631    frame.getContentPane().add(anno);
632    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000000"),
633                                      debugger.parseAddress("0x80000040"),
634                                      "Stack Frame for \"foo\""));
635    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000010"),
636                                      debugger.parseAddress("0x80000020"),
637                                      "Locals for \"foo\""));
638    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000020"),
639                                      debugger.parseAddress("0x80000030"),
640                                      "Expression stack for \"foo\""));
641
642    frame.setSize(400, 300);
643    frame.addWindowListener(new WindowAdapter() {
644        public void windowClosed(WindowEvent e) {
645          System.exit(0);
646        }
647        public void windowClosing(WindowEvent e) {
648          System.exit(0);
649        }
650      });
651    frame.setVisible(true);
652  }
653}
654