View Javadoc
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  
25  package sun.jvm.hotspot.ui;
26  
27  import java.math.*;
28  import java.awt.*;
29  import java.awt.font.*;
30  import java.awt.geom.*;
31  import java.awt.event.*;
32  import java.io.*;
33  import javax.swing.*;
34  import javax.swing.event.*;
35  import java.util.*;
36  
37  import sun.jvm.hotspot.debugger.*;
38  import sun.jvm.hotspot.debugger.dummy.*;
39  import 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  
46  public 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 }