1 /*
   2  * Copyright 2000-2003 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any 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 }