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       font = GraphicsUtilities.lookupFont("DejaVu Sans Mono");
 313     }
 314     if (font == null) {
 315       throw new RuntimeException("Error looking up monospace font Courier");
 316     }
 317     getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "PageDown");
 318     getActionMap().put("PageDown", new AbstractAction() {
 319         public void actionPerformed(ActionEvent e) {
 320           scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getBlockIncrementHP()));
 321         }
 322       });
 323     getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "PageUp");
 324     getActionMap().put("PageUp", new AbstractAction() {
 325         public void actionPerformed(ActionEvent e) {
 326           scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getBlockIncrementHP()));
 327         }
 328       });
 329     getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Down");
 330     getActionMap().put("Down", new AbstractAction() {
 331         public void actionPerformed(ActionEvent e) {
 332           scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getUnitIncrementHP()));
 333         }
 334       });
 335     getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Up");
 336     getActionMap().put("Up", new AbstractAction() {
 337         public void actionPerformed(ActionEvent e) {
 338           scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getUnitIncrementHP()));
 339         }
 340       });
 341     setEnabled(true);
 342   }
 343 
 344   private void setupScrollBar(BigInteger value, BigInteger min, BigInteger max) {
 345     scrollBar = new HighPrecisionJScrollBar( Scrollbar.VERTICAL, value, min, max);
 346     if (is64Bit) {
 347       bytesPerLine = 8;
 348       // 64-bit mode
 349       scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
 350         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
 351         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}));
 352       scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
 353         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
 354         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40}));
 355     } else {
 356       // 32-bit mode
 357       bytesPerLine = 4;
 358       scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
 359         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04}));
 360       scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
 361         (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20}));
 362     }
 363     scrollBar.addChangeListener(new ChangeListener() {
 364         public void stateChanged(ChangeEvent e) {
 365           HighPrecisionJScrollBar h = (HighPrecisionJScrollBar) e.getSource();
 366           repaint();
 367         }
 368       });
 369   }
 370 
 371   private static BigInteger defaultMemoryLocation(boolean is64Bit) {
 372     if (is64Bit) {
 373       return new BigInteger(1, new byte[] {
 374                            (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
 375                            (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
 376     } else {
 377       return new BigInteger(1, new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00});
 378     }
 379   }
 380 
 381   private static BigInteger defaultMemoryLow(boolean is64Bit) {
 382     if (is64Bit) {
 383       return new BigInteger(1, new byte[] {
 384                  (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
 385                  (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
 386     } else {
 387       return new BigInteger(1, new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
 388     }
 389   }
 390 
 391   private static BigInteger defaultMemoryHigh(boolean is64Bit) {
 392     if (is64Bit) {
 393       return new BigInteger(1, new byte[] {
 394                  (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
 395                  (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
 396     } else {
 397       return new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
 398     }
 399   }
 400 
 401   private void setupScrollBar() {
 402     setupScrollBar(defaultMemoryLocation(is64Bit),
 403                    defaultMemoryLow(is64Bit),
 404                    defaultMemoryHigh(is64Bit));
 405   }
 406 
 407   private String bigIntToHexString(BigInteger bi) {
 408     StringBuffer buf = new StringBuffer();
 409     buf.append("0x");
 410     String val = bi.toString(16);
 411     for (int i = 0; i < ((2 * addressSize) - val.length()); i++) {
 412       buf.append('0');
 413     }
 414     buf.append(val);
 415     return buf.toString();
 416   }
 417 
 418   private Address bigIntToAddress(BigInteger i) {
 419     String s = bigIntToHexString(i);
 420     return debugger.parseAddress(s);
 421   }
 422 
 423   private BigInteger addressToBigInt(Address a) {
 424     String s = addressToString(a);
 425     if (!s.startsWith("0x")) {
 426       throw new NumberFormatException(s);
 427     }
 428     return new BigInteger(s.substring(2), 16);
 429   }
 430 
 431   private String addressToString(Address a) {
 432     if (a == null) {
 433       if (is64Bit) {
 434         return "0x0000000000000000";
 435       } else {
 436         return "0x00000000";
 437       }
 438     }
 439     return a.toString();
 440   }
 441 
 442   /** Scrolls the visible annotations by the given Y amount */
 443   private void scrollAnnotations(int y) {
 444     for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
 445       Annotation anno = (Annotation) iter.next();
 446       anno.setY(anno.getY() + y);
 447     }
 448   }
 449 
 450   /** Takes the list of currently-visible annotations (in the form of
 451       a List<IntervalNode>) and lays them out given the current
 452       visible position and the already-visible annotations. Does not
 453       perturb the layouts of the currently-visible annotations. */
 454   private void layoutAnnotations(java.util.List va,
 455                                  Graphics g,
 456                                  int x,
 457                                  Address startAddr,
 458                                  int lineHeight) {
 459     // Handle degenerate case early: no visible annotations.
 460     if (va.size() == 0) {
 461       visibleAnnotations.clear();
 462       return;
 463     }
 464 
 465     // We have two ranges of visible annotations: the one from the
 466     // last repaint and the currently visible set. We want to preserve
 467     // the layouts of the previously-visible annotations that are
 468     // currently visible (to avoid color flashing, jumping, etc.)
 469     // while making the coloring of the new annotations fit as well as
 470     // possible. Note that annotations may appear and disappear from
 471     // any point in the previously-visible list, but the ordering of
 472     // the visible annotations is always the same.
 473 
 474     // This is really a constrained graph-coloring problem. This
 475     // simple algorithm only takes into account half of the
 476     // constraints (for example, the layout of the previous
 477     // annotation, where it should be taking into account the layout
 478     // of the previous and next annotations that were in the
 479     // previously-visible list). There are situations where it can
 480     // generate overlapping annotations and adjacent annotations with
 481     // the same color; generally visible when scrolling line-by-line
 482     // rather than page-by-page. In some of these situations, will
 483     // have to move previously laid-out annotations. FIXME: revisit
 484     // this.
 485 
 486     // Index of last annotation which we didn't know how to lay out
 487     int deferredIndex = -1;
 488     // We "lay out after" this one
 489     Annotation constraintAnnotation = null;
 490     // The first constraint annotation
 491     Annotation firstConstraintAnnotation = null;
 492     // The index from which we search forward in the
 493     // visibleAnnotations list. This reduces the amount of work we do.
 494     int searchIndex = 0;
 495     // The new set of annotations
 496     java.util.List newAnnos = new ArrayList();
 497 
 498     for (Iterator iter = va.iterator(); iter.hasNext(); ) {
 499       Annotation anno = (Annotation) ((IntervalNode) iter.next()).getData();
 500 
 501       // Search forward for this one
 502       boolean found = false;
 503       for (int i = searchIndex; i < visibleAnnotations.size(); i++) {
 504         Annotation el = (Annotation) visibleAnnotations.get(i);
 505         // See whether we can abort the search unsuccessfully because
 506         // we went forward too far
 507         if (el.getLowAddress().greaterThan(anno.getLowAddress())) {
 508           break;
 509         }
 510         if (el == anno) {
 511           // Found successfully.
 512           found = true;
 513           searchIndex = i;
 514           constraintAnnotation = anno;
 515           if (firstConstraintAnnotation == null) {
 516             firstConstraintAnnotation = constraintAnnotation;
 517           }
 518           break;
 519         }
 520       }
 521 
 522       if (!found) {
 523         if (constraintAnnotation != null) {
 524           layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
 525           constraintAnnotation = anno;
 526         } else {
 527           // Defer layout of this annotation until later
 528           ++deferredIndex;
 529         }
 530       }
 531 
 532       newAnnos.add(anno);
 533     }
 534 
 535     if (firstConstraintAnnotation != null) {
 536       // Go back and lay out deferred annotations
 537       for (int i = deferredIndex; i >= 0; i--) {
 538         Annotation anno = (Annotation) newAnnos.get(i);
 539         layoutBefore(anno, firstConstraintAnnotation, g, x, startAddr, lineHeight);
 540         firstConstraintAnnotation = anno;
 541       }
 542     } else {
 543       // Didn't find any overlap between the old and new annotations.
 544       // Lay out in a feed-forward fashion.
 545       if (Assert.ASSERTS_ENABLED) {
 546         Assert.that(constraintAnnotation == null, "logic error in layout code");
 547       }
 548       for (Iterator iter = newAnnos.iterator(); iter.hasNext(); ) {
 549         Annotation anno = (Annotation) iter.next();
 550         layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
 551         constraintAnnotation = anno;
 552       }
 553     }
 554 
 555     visibleAnnotations = newAnnos;
 556   }
 557 
 558   /** Lays out the given annotation before the optional constraint
 559       annotation, obeying constraints imposed by that annotation if it
 560       is specified. */
 561   private void layoutBefore(Annotation anno, Annotation constraintAnno,
 562                             Graphics g, int x,
 563                             Address startAddr, int lineHeight) {
 564     anno.computeWidthAndHeight(g);
 565     // Color
 566     if (constraintAnno != null) {
 567       anno.setColor(prevColor(constraintAnno.getColor()));
 568     } else {
 569       anno.setColor(colors[0]);
 570     }
 571     // X
 572     anno.setX(x);
 573     // Tentative Y
 574     anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
 575               (5 * lineHeight / 6));
 576     // See whether Y overlaps with last anno's Y; if so, move this one up
 577     if ((constraintAnno != null) && (anno.getY() + anno.getHeight() > constraintAnno.getY())) {
 578       anno.setY(constraintAnno.getY() - anno.getHeight());
 579     }
 580   }
 581 
 582   /** Lays out the given annotation after the optional constraint
 583       annotation, obeying constraints imposed by that annotation if it
 584       is specified. */
 585   private void layoutAfter(Annotation anno, Annotation constraintAnno,
 586                            Graphics g, int x,
 587                            Address startAddr, int lineHeight) {
 588     anno.computeWidthAndHeight(g);
 589     // Color
 590     if (constraintAnno != null) {
 591       anno.setColor(nextColor(constraintAnno.getColor()));
 592     } else {
 593       anno.setColor(colors[0]);
 594     }
 595     // X
 596     anno.setX(x);
 597     // Tentative Y
 598     anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
 599               (5 * lineHeight / 6));
 600     // See whether Y overlaps with last anno's Y; if so, move this one down
 601     if ((constraintAnno != null) && (anno.getY() < (constraintAnno.getY() + constraintAnno.getHeight()))) {
 602       anno.setY(constraintAnno.getY() + constraintAnno.getHeight());
 603     }
 604   }
 605 
 606   /** Returns previous color in our color palette */
 607   private Color prevColor(Color c) {
 608     int i = findColorIndex(c);
 609     if (i == 0) {
 610       return colors[colors.length - 1];
 611     } else {
 612       return colors[i - 1];
 613     }
 614   }
 615 
 616   /** Returns next color in our color palette */
 617   private Color nextColor(Color c) {
 618     return colors[(findColorIndex(c) + 1) % colors.length];
 619   }
 620 
 621   private int findColorIndex(Color c) {
 622     for (int i = 0; i < colors.length; i++) {
 623       if (colors[i] == c) {
 624         return i;
 625       }
 626     }
 627     throw new IllegalArgumentException();
 628   }
 629 
 630   public static void main(String[] args) {
 631     JFrame frame = new JFrame();
 632     DummyDebugger debugger = new DummyDebugger(new MachineDescriptionIntelX86());
 633     AnnotatedMemoryPanel anno = new AnnotatedMemoryPanel(debugger);
 634     frame.getContentPane().add(anno);
 635     anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000000"),
 636                                       debugger.parseAddress("0x80000040"),
 637                                       "Stack Frame for \"foo\""));
 638     anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000010"),
 639                                       debugger.parseAddress("0x80000020"),
 640                                       "Locals for \"foo\""));
 641     anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000020"),
 642                                       debugger.parseAddress("0x80000030"),
 643                                       "Expression stack for \"foo\""));
 644 
 645     frame.setSize(400, 300);
 646     frame.addWindowListener(new WindowAdapter() {
 647         public void windowClosed(WindowEvent e) {
 648           System.exit(0);
 649         }
 650         public void windowClosing(WindowEvent e) {
 651           System.exit(0);
 652         }
 653       });
 654     frame.setVisible(true);
 655   }
 656 }