diff agent/src/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java @ 0:a61af66fc99e jdk7-b24

Initial load
author duke
date Sat, 01 Dec 2007 00:00:00 +0000
parents
children c70a245cad3a
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/agent/src/share/classes/sun/jvm/hotspot/ui/AnnotatedMemoryPanel.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,653 @@
+/*
+ * Copyright 2000-2003 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ *
+ */
+
+package sun.jvm.hotspot.ui;
+
+import java.math.*;
+import java.awt.*;
+import java.awt.font.*;
+import java.awt.geom.*;
+import java.awt.event.*;
+import java.io.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import java.util.*;
+
+import sun.jvm.hotspot.debugger.*;
+import sun.jvm.hotspot.debugger.dummy.*;
+import sun.jvm.hotspot.utilities.*;
+
+/** A subclass of JPanel which displays a hex dump of memory along
+    with annotations describing the significance of various
+    pieces. This can be used to implement either a stack or heap
+    inspector. */
+
+public class AnnotatedMemoryPanel extends JPanel {
+  private boolean is64Bit;
+  private Debugger debugger;
+  private long    addressSize;
+  private HighPrecisionJScrollBar scrollBar;
+  private Font font;
+  private int bytesPerLine;
+  private int paintCount;
+  private String unmappedAddrString;
+  // Type of this is an IntervalTree indexed by Interval<Address> and
+  // with user data of type Annotation
+  private IntervalTree annotations =
+    new IntervalTree(new Comparator() {
+        public int compare(Object o1, Object o2) {
+          Address a1 = (Address) o1;
+          Address a2 = (Address) o2;
+
+          if ((a1 == null) && (a2 == null)) {
+            return 0;
+          } else if (a1 == null) {
+            return -1;
+          } else if (a2 == null) {
+            return 1;
+          }
+
+          if (a1.equals(a2)) {
+            return 0;
+          } else if (a1.lessThan(a2)) {
+            return -1;
+          }
+          return 1;
+        }
+      });
+  // Keep track of the last start address at which we painted, so we
+  // can scroll annotations
+  private Address lastStartAddr;
+  // This contains the list of currently-visible IntervalNodes, in
+  // sorted order by their low endpoint, in the form of a
+  // List<Annotation>. These annotations have already been laid out.
+  private java.util.List visibleAnnotations;
+  // Darker colors than defaults for better readability
+  private static Color[] colors = {
+    new Color(0.0f, 0.0f, 0.6f), // blue
+    new Color(0.6f, 0.0f, 0.6f), // magenta
+    new Color(0.0f, 0.8f, 0.0f), // green
+    new Color(0.8f, 0.3f, 0.0f), // orange
+    new Color(0.0f, 0.6f, 0.8f), // cyan
+    new Color(0.2f, 0.2f, 0.2f), // dark gray
+  };
+
+  /** Default is 32-bit mode */
+  public AnnotatedMemoryPanel(Debugger debugger) {
+    this(debugger, false);
+  }
+
+  public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit, Address addrValue, Address addrLow, Address addrHigh) {
+    super();
+    init(debugger, is64Bit, addressToBigInt(addrValue), addressToBigInt(addrLow), addressToBigInt(addrHigh));
+  }
+
+  public AnnotatedMemoryPanel(Debugger debugger, boolean is64Bit ) {
+    super();
+    init(debugger, is64Bit, defaultMemoryLocation(is64Bit), defaultMemoryLow(is64Bit), defaultMemoryHigh(is64Bit));
+  }
+
+  static class AnnoX {
+    int     lineX;
+    Address highBound;
+
+    public AnnoX(int lineX, Address highBound) {
+      this.lineX = lineX;
+      this.highBound = highBound;
+    }
+  }
+
+  public synchronized void paintComponent(Graphics g) {
+    //    System.err.println("AnnotatedMemoryPanel.paintComponent() " + ++paintCount);
+    super.paintComponent(g);
+
+    // Clone the Graphics so we don't screw up its state for Swing
+    // drawing (as this code otherwise does)
+    g = g.create();
+
+    g.setFont(font);
+    g.setColor(Color.black);
+    Rectangle rect = new Rectangle();
+    getBounds(rect);
+    String firstAddressString = null;
+    int lineHeight;
+    int addrWidth;
+    {
+      Rectangle2D bounds = GraphicsUtilities.getStringBounds(unmappedAddrString, g);
+      lineHeight = (int) bounds.getHeight();
+      addrWidth  = (int) bounds.getWidth();
+    }
+    int addrX = (int) (0.25 * addrWidth);
+    int dataX = (int) (addrX + (1.5 * addrWidth));
+    int lineStartX = dataX + addrWidth + 5;
+    int annoStartX = (int) (lineStartX + (0.75 * addrWidth));
+
+    int numLines = rect.height / lineHeight;
+
+    BigInteger startVal  = scrollBar.getValueHP();
+    BigInteger perLine = new BigInteger(Integer.toString((int) addressSize));
+    // lineCount and maxLines are both 1 less than expected
+    BigInteger lineCount = new BigInteger(Integer.toString((int) (numLines - 1)));
+    BigInteger maxLines = scrollBar.getMaximumHP().subtract(scrollBar.getMinimumHP()).divide(perLine);
+    if (lineCount.compareTo(maxLines) > 0) {
+      lineCount = maxLines;
+    }
+    BigInteger offsetVal = lineCount.multiply(perLine);
+    BigInteger endVal    = startVal.add(offsetVal);
+    if (endVal.compareTo(scrollBar.getMaximumHP()) > 0) {
+      startVal = scrollBar.getMaximumHP().subtract(offsetVal);
+      endVal   = scrollBar.getMaximumHP();
+      // Sure seems like this call will cause us to immediately redraw...
+      scrollBar.setValueHP(startVal);
+    }
+    scrollBar.setVisibleAmountHP(offsetVal.add(perLine));
+    scrollBar.setBlockIncrementHP(offsetVal);
+
+    Address startAddr = bigIntToAddress(startVal);
+    Address endAddr   = bigIntToAddress(endVal);
+
+    // Scroll last-known annotations
+    int scrollOffset = 0;
+    if (lastStartAddr != null) {
+      scrollOffset = (int) lastStartAddr.minus(startAddr);
+    } else {
+      if (startAddr != null) {
+        scrollOffset = (int) (-1 * startAddr.minus(lastStartAddr));
+      }
+    }
+    scrollOffset = scrollOffset * lineHeight / (int) addressSize;
+    scrollAnnotations(scrollOffset);
+    lastStartAddr = startAddr;
+
+    int curY = lineHeight;
+    Address curAddr = startAddr;
+    for (int i = 0; i < numLines; i++) {
+      String s = bigIntToHexString(startVal);
+      g.drawString(s, addrX, curY);
+      try {
+        s = addressToString(startAddr.getAddressAt(i * addressSize));
+      }
+      catch (UnmappedAddressException e) {
+        s = unmappedAddrString;
+      }
+      g.drawString(s, dataX, curY);
+      curY += lineHeight;
+      startVal = startVal.add(perLine);
+    }
+
+    // Query for visible annotations (little slop to ensure we get the
+    // top and bottom)
+    // FIXME: it would be nice to have a more static layout; that is,
+    // if something scrolls off the bottom of the screen, other
+    // annotations still visible shouldn't change position
+    java.util.List va =
+      annotations.findAllNodesIntersecting(new Interval(startAddr.addOffsetTo(-addressSize),
+                                                        endAddr.addOffsetTo(2 * addressSize)));
+
+    // Render them
+    int curLineX = lineStartX;
+    int curTextX = annoStartX;
+    int curColorIdx = 0;
+    if (g instanceof Graphics2D) {
+      Stroke stroke = new BasicStroke(3.0f);
+      ((Graphics2D) g).setStroke(stroke);
+    }
+
+    Stack drawStack = new Stack();
+
+    layoutAnnotations(va, g, curTextX, startAddr, lineHeight);
+
+    for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
+      Annotation anno   = (Annotation) iter.next();
+      Interval interval = anno.getInterval();
+
+      if (!drawStack.empty()) {
+        // See whether we can pop any items off the stack
+        boolean shouldContinue = true;
+        do {
+          AnnoX annoX = (AnnoX) drawStack.peek();
+          if (annoX.highBound.lessThanOrEqual((Address) interval.getLowEndpoint())) {
+            curLineX = annoX.lineX;
+            drawStack.pop();
+            shouldContinue = !drawStack.empty();
+          } else {
+            shouldContinue = false;
+          }
+        } while (shouldContinue);
+      }
+
+      // Draw a line covering the interval
+      Address lineStartAddr = (Address) interval.getLowEndpoint();
+      // Give it a little slop
+      int lineStartY = (int) (lineStartAddr.minus(startAddr) * lineHeight / addressSize) +
+        (lineHeight / 3);
+      Address lineEndAddr = (Address) interval.getHighEndpoint();
+      drawStack.push(new AnnoX(curLineX, lineEndAddr));
+      int lineEndY = (int) (lineEndAddr.minus(startAddr) * lineHeight / addressSize);
+      g.setColor(anno.getColor());
+      g.drawLine(curLineX, lineStartY, curLineX, lineEndY);
+      // Draw line to text
+      g.drawLine(curLineX, lineStartY, curTextX - 10, anno.getY() - (lineHeight / 2));
+      curLineX += 8;
+      anno.draw(g);
+      ++curColorIdx;
+    }
+  }
+
+  /** Add an annotation covering the address range [annotation.lowAddress,
+      annotation.highAddress); that is, it includes the low address and does not
+      include the high address. */
+  public synchronized void addAnnotation(Annotation annotation) {
+    annotations.insert(annotation.getInterval(), annotation);
+  }
+
+  /** Makes the given address visible somewhere in the window */
+  public synchronized void makeVisible(Address addr) {
+    BigInteger bi = addressToBigInt(addr);
+    scrollBar.setValueHP(bi);
+  }
+
+  public void print() {
+    printOn(System.out);
+  }
+
+  public void printOn(PrintStream tty) {
+    annotations.printOn(tty);
+  }
+
+  //----------------------------------------------------------------------
+  // Internals only below this point
+  //
+
+  private void init(Debugger debugger, boolean is64Bit, BigInteger addrValue, BigInteger addrLow, BigInteger addrHigh) {
+    this.is64Bit = is64Bit;
+    this.debugger = debugger;
+    if (is64Bit) {
+      addressSize = 8;
+      unmappedAddrString = "??????????????????";
+    } else {
+      addressSize = 4;
+      unmappedAddrString = "??????????";
+    }
+    setLayout(new BorderLayout());
+    setupScrollBar(addrValue, addrLow, addrHigh);
+    add(scrollBar, BorderLayout.EAST);
+    visibleAnnotations = new ArrayList();
+    setBackground(Color.white);
+    addHierarchyBoundsListener(new HierarchyBoundsListener() {
+        public void ancestorMoved(HierarchyEvent e) {
+        }
+
+        public void ancestorResized(HierarchyEvent e) {
+          // FIXME: should perform incremental layout
+          //          System.err.println("Ancestor resized");
+        }
+      });
+
+    if (font == null) {
+      font = GraphicsUtilities.lookupFont("Courier");
+    }
+    if (font == null) {
+      throw new RuntimeException("Error looking up monospace font Courier");
+    }
+    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0), "PageDown");
+    getActionMap().put("PageDown", new AbstractAction() {
+        public void actionPerformed(ActionEvent e) {
+          scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getBlockIncrementHP()));
+        }
+      });
+    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0), "PageUp");
+    getActionMap().put("PageUp", new AbstractAction() {
+        public void actionPerformed(ActionEvent e) {
+          scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getBlockIncrementHP()));
+        }
+      });
+    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), "Down");
+    getActionMap().put("Down", new AbstractAction() {
+        public void actionPerformed(ActionEvent e) {
+          scrollBar.setValueHP(scrollBar.getValueHP().add(scrollBar.getUnitIncrementHP()));
+        }
+      });
+    getInputMap(WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), "Up");
+    getActionMap().put("Up", new AbstractAction() {
+        public void actionPerformed(ActionEvent e) {
+          scrollBar.setValueHP(scrollBar.getValueHP().subtract(scrollBar.getUnitIncrementHP()));
+        }
+      });
+    setEnabled(true);
+  }
+
+  private void setupScrollBar(BigInteger value, BigInteger min, BigInteger max) {
+    scrollBar = new HighPrecisionJScrollBar( Scrollbar.VERTICAL, value, min, max);
+    if (is64Bit) {
+      bytesPerLine = 8;
+      // 64-bit mode
+      scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x08}));
+      scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x40}));
+    } else {
+      // 32-bit mode
+      bytesPerLine = 4;
+      scrollBar.setUnitIncrementHP(new BigInteger(1, new byte[] {
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x04}));
+      scrollBar.setBlockIncrementHP(new BigInteger(1, new byte[] {
+        (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x20}));
+    }
+    scrollBar.addChangeListener(new ChangeListener() {
+        public void stateChanged(ChangeEvent e) {
+          HighPrecisionJScrollBar h = (HighPrecisionJScrollBar) e.getSource();
+          repaint();
+        }
+      });
+  }
+
+  private static BigInteger defaultMemoryLocation(boolean is64Bit) {
+    if (is64Bit) {
+      return new BigInteger(1, new byte[] {
+                           (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                           (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+    } else {
+      return new BigInteger(1, new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+    }
+  }
+
+  private static BigInteger defaultMemoryLow(boolean is64Bit) {
+    if (is64Bit) {
+      return new BigInteger(1, new byte[] {
+                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                 (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+    } else {
+      return new BigInteger(1, new byte[] { (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00});
+    }
+  }
+
+  private static BigInteger defaultMemoryHigh(boolean is64Bit) {
+    if (is64Bit) {
+      return new BigInteger(1, new byte[] {
+                 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
+                 (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
+    } else {
+      return new BigInteger(1, new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFC});
+    }
+  }
+
+  private void setupScrollBar() {
+    setupScrollBar(defaultMemoryLocation(is64Bit),
+                   defaultMemoryLow(is64Bit),
+                   defaultMemoryHigh(is64Bit));
+  }
+
+  private String bigIntToHexString(BigInteger bi) {
+    StringBuffer buf = new StringBuffer();
+    buf.append("0x");
+    String val = bi.toString(16);
+    for (int i = 0; i < ((2 * addressSize) - val.length()); i++) {
+      buf.append('0');
+    }
+    buf.append(val);
+    return buf.toString();
+  }
+
+  private Address bigIntToAddress(BigInteger i) {
+    String s = bigIntToHexString(i);
+    return debugger.parseAddress(s);
+  }
+
+  private BigInteger addressToBigInt(Address a) {
+    String s = addressToString(a);
+    if (!s.startsWith("0x")) {
+      throw new NumberFormatException(s);
+    }
+    return new BigInteger(s.substring(2), 16);
+  }
+
+  private String addressToString(Address a) {
+    if (a == null) {
+      if (is64Bit) {
+        return "0x0000000000000000";
+      } else {
+        return "0x00000000";
+      }
+    }
+    return a.toString();
+  }
+
+  /** Scrolls the visible annotations by the given Y amount */
+  private void scrollAnnotations(int y) {
+    for (Iterator iter = visibleAnnotations.iterator(); iter.hasNext(); ) {
+      Annotation anno = (Annotation) iter.next();
+      anno.setY(anno.getY() + y);
+    }
+  }
+
+  /** Takes the list of currently-visible annotations (in the form of
+      a List<IntervalNode>) and lays them out given the current
+      visible position and the already-visible annotations. Does not
+      perturb the layouts of the currently-visible annotations. */
+  private void layoutAnnotations(java.util.List va,
+                                 Graphics g,
+                                 int x,
+                                 Address startAddr,
+                                 int lineHeight) {
+    // Handle degenerate case early: no visible annotations.
+    if (va.size() == 0) {
+      visibleAnnotations.clear();
+      return;
+    }
+
+    // We have two ranges of visible annotations: the one from the
+    // last repaint and the currently visible set. We want to preserve
+    // the layouts of the previously-visible annotations that are
+    // currently visible (to avoid color flashing, jumping, etc.)
+    // while making the coloring of the new annotations fit as well as
+    // possible. Note that annotations may appear and disappear from
+    // any point in the previously-visible list, but the ordering of
+    // the visible annotations is always the same.
+
+    // This is really a constrained graph-coloring problem. This
+    // simple algorithm only takes into account half of the
+    // constraints (for example, the layout of the previous
+    // annotation, where it should be taking into account the layout
+    // of the previous and next annotations that were in the
+    // previously-visible list). There are situations where it can
+    // generate overlapping annotations and adjacent annotations with
+    // the same color; generally visible when scrolling line-by-line
+    // rather than page-by-page. In some of these situations, will
+    // have to move previously laid-out annotations. FIXME: revisit
+    // this.
+
+    // Index of last annotation which we didn't know how to lay out
+    int deferredIndex = -1;
+    // We "lay out after" this one
+    Annotation constraintAnnotation = null;
+    // The first constraint annotation
+    Annotation firstConstraintAnnotation = null;
+    // The index from which we search forward in the
+    // visibleAnnotations list. This reduces the amount of work we do.
+    int searchIndex = 0;
+    // The new set of annotations
+    java.util.List newAnnos = new ArrayList();
+
+    for (Iterator iter = va.iterator(); iter.hasNext(); ) {
+      Annotation anno = (Annotation) ((IntervalNode) iter.next()).getData();
+
+      // Search forward for this one
+      boolean found = false;
+      for (int i = searchIndex; i < visibleAnnotations.size(); i++) {
+        Annotation el = (Annotation) visibleAnnotations.get(i);
+        // See whether we can abort the search unsuccessfully because
+        // we went forward too far
+        if (el.getLowAddress().greaterThan(anno.getLowAddress())) {
+          break;
+        }
+        if (el == anno) {
+          // Found successfully.
+          found = true;
+          searchIndex = i;
+          constraintAnnotation = anno;
+          if (firstConstraintAnnotation == null) {
+            firstConstraintAnnotation = constraintAnnotation;
+          }
+          break;
+        }
+      }
+
+      if (!found) {
+        if (constraintAnnotation != null) {
+          layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
+          constraintAnnotation = anno;
+        } else {
+          // Defer layout of this annotation until later
+          ++deferredIndex;
+        }
+      }
+
+      newAnnos.add(anno);
+    }
+
+    if (firstConstraintAnnotation != null) {
+      // Go back and lay out deferred annotations
+      for (int i = deferredIndex; i >= 0; i--) {
+        Annotation anno = (Annotation) newAnnos.get(i);
+        layoutBefore(anno, firstConstraintAnnotation, g, x, startAddr, lineHeight);
+        firstConstraintAnnotation = anno;
+      }
+    } else {
+      // Didn't find any overlap between the old and new annotations.
+      // Lay out in a feed-forward fashion.
+      if (Assert.ASSERTS_ENABLED) {
+        Assert.that(constraintAnnotation == null, "logic error in layout code");
+      }
+      for (Iterator iter = newAnnos.iterator(); iter.hasNext(); ) {
+        Annotation anno = (Annotation) iter.next();
+        layoutAfter(anno, constraintAnnotation, g, x, startAddr, lineHeight);
+        constraintAnnotation = anno;
+      }
+    }
+
+    visibleAnnotations = newAnnos;
+  }
+
+  /** Lays out the given annotation before the optional constraint
+      annotation, obeying constraints imposed by that annotation if it
+      is specified. */
+  private void layoutBefore(Annotation anno, Annotation constraintAnno,
+                            Graphics g, int x,
+                            Address startAddr, int lineHeight) {
+    anno.computeWidthAndHeight(g);
+    // Color
+    if (constraintAnno != null) {
+      anno.setColor(prevColor(constraintAnno.getColor()));
+    } else {
+      anno.setColor(colors[0]);
+    }
+    // X
+    anno.setX(x);
+    // Tentative Y
+    anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
+              (5 * lineHeight / 6));
+    // See whether Y overlaps with last anno's Y; if so, move this one up
+    if ((constraintAnno != null) && (anno.getY() + anno.getHeight() > constraintAnno.getY())) {
+      anno.setY(constraintAnno.getY() - anno.getHeight());
+    }
+  }
+
+  /** Lays out the given annotation after the optional constraint
+      annotation, obeying constraints imposed by that annotation if it
+      is specified. */
+  private void layoutAfter(Annotation anno, Annotation constraintAnno,
+                           Graphics g, int x,
+                           Address startAddr, int lineHeight) {
+    anno.computeWidthAndHeight(g);
+    // Color
+    if (constraintAnno != null) {
+      anno.setColor(nextColor(constraintAnno.getColor()));
+    } else {
+      anno.setColor(colors[0]);
+    }
+    // X
+    anno.setX(x);
+    // Tentative Y
+    anno.setY((int) (((Address) anno.getInterval().getLowEndpoint()).minus(startAddr) * lineHeight / addressSize) +
+              (5 * lineHeight / 6));
+    // See whether Y overlaps with last anno's Y; if so, move this one down
+    if ((constraintAnno != null) && (anno.getY() < (constraintAnno.getY() + constraintAnno.getHeight()))) {
+      anno.setY(constraintAnno.getY() + constraintAnno.getHeight());
+    }
+  }
+
+  /** Returns previous color in our color palette */
+  private Color prevColor(Color c) {
+    int i = findColorIndex(c);
+    if (i == 0) {
+      return colors[colors.length - 1];
+    } else {
+      return colors[i - 1];
+    }
+  }
+
+  /** Returns next color in our color palette */
+  private Color nextColor(Color c) {
+    return colors[(findColorIndex(c) + 1) % colors.length];
+  }
+
+  private int findColorIndex(Color c) {
+    for (int i = 0; i < colors.length; i++) {
+      if (colors[i] == c) {
+        return i;
+      }
+    }
+    throw new IllegalArgumentException();
+  }
+
+  public static void main(String[] args) {
+    JFrame frame = new JFrame();
+    DummyDebugger debugger = new DummyDebugger(new MachineDescriptionIntelX86());
+    AnnotatedMemoryPanel anno = new AnnotatedMemoryPanel(debugger);
+    frame.getContentPane().add(anno);
+    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000000"),
+                                      debugger.parseAddress("0x80000040"),
+                                      "Stack Frame for \"foo\""));
+    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000010"),
+                                      debugger.parseAddress("0x80000020"),
+                                      "Locals for \"foo\""));
+    anno.addAnnotation(new Annotation(debugger.parseAddress("0x80000020"),
+                                      debugger.parseAddress("0x80000030"),
+                                      "Expression stack for \"foo\""));
+
+    frame.setSize(400, 300);
+    frame.addWindowListener(new WindowAdapter() {
+        public void windowClosed(WindowEvent e) {
+          System.exit(0);
+        }
+        public void windowClosing(WindowEvent e) {
+          System.exit(0);
+        }
+      });
+    frame.show();
+  }
+}