package mars.tools; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import java.awt.*; import java.awt.event.*; import java.util.*; import mars.tools.*; import mars.mips.hardware.*; /* Copyright (c) 2003-2006, Pete Sanderson and Kenneth Vollmar Developed by Pete Sanderson (psanderson@otterbein.edu) and Kenneth Vollmar (kenvollmar@missouristate.edu) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. (MIT license, http://www.opensource.org/licenses/mit-license.html) */ /** * Memory reference visualization. It can be run either as a stand-alone Java application having * access to the mars package, or through MARS as an item in its Tools menu. It makes * maximum use of methods inherited from its abstract superclass AbstractMarsToolAndApplication. * Pete Sanderson, verison 1.0, 14 November 2006. */ public class MemoryReferenceVisualization extends AbstractMarsToolAndApplication { private static String version = "Version 1.0"; private static String heading = "Visualizing memory reference patterns"; // Major GUI components private JComboBox wordsPerUnitSelector, visualizationUnitPixelWidthSelector, visualizationUnitPixelHeightSelector, visualizationPixelWidthSelector, visualizationPixelHeightSelector, displayBaseAddressSelector; private JCheckBox drawHashMarksSelector; private Graphics drawingArea; private JPanel canvas; private JPanel results; // Some GUI settings private EmptyBorder emptyBorder = new EmptyBorder(4,4,4,4); private Font countFonts = new Font("Times", Font.BOLD,12); private Color backgroundColor = Color.WHITE; // Values for Combo Boxes private final String[] wordsPerUnitChoices = {"1","2","4","8","16","32","64","128","256","512","1024","2048"}; private final int defaultWordsPerUnitIndex = 0; private final String[] visualizationUnitPixelWidthChoices = {"1","2","4","8","16","32"}; private final int defaultVisualizationUnitPixelWidthIndex = 4; private final String[] visualizationUnitPixelHeightChoices = {"1","2","4","8","16","32"}; private final int defaultVisualizationUnitPixelHeightIndex = 4; private final String[] displayAreaPixelWidthChoices = {"64","128","256","512","1024"}; private final int defaultDisplayWidthIndex = 2; private final String[] displayAreaPixelHeightChoices = {"64","128","256","512","1024"}; private final int defaultDisplayHeightIndex = 2; private final boolean defaultDrawHashMarks = true; // Values for display canvas. Note their initialization uses the identifiers just above. private int unitPixelWidth = Integer.parseInt(visualizationUnitPixelWidthChoices[defaultVisualizationUnitPixelWidthIndex]); private int unitPixelHeight = Integer.parseInt(visualizationUnitPixelHeightChoices[defaultVisualizationUnitPixelHeightIndex]); private int wordsPerUnit = Integer.parseInt(wordsPerUnitChoices[defaultWordsPerUnitIndex]); private int visualizationAreaWidthInPixels = Integer.parseInt(displayAreaPixelWidthChoices[defaultDisplayWidthIndex]); private int visualizationAreaHeightInPixels = Integer.parseInt(displayAreaPixelHeightChoices[defaultDisplayHeightIndex]); //`Values for mapping of reference counts to colors for display. // This array of (count,color) pairs must be kept sorted! count is low end of subrange. // This array will grow if user adds colors at additional counter points (see below). private CounterColor[] defaultCounterColors = { new CounterColor(0, Color.black), new CounterColor(1, Color.blue), new CounterColor(2, Color.green), new CounterColor(3, Color.yellow), new CounterColor(5, Color.orange), new CounterColor(10, Color.red) }; /* Values for reference count color slider. These are all possible counter values for which * colors can be assigned. As you can see just above, not all these values are assigned * a default color. */ private int[] countTable = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // 0-10 20, 30, 40, 50, 100, 200, 300, 400, 500, 1000, // 11-20 2000, 3000, 4000, 5000, 10000, 50000, 100000, 500000, 1000000 // 21-29 }; private final int COUNT_INDEX_INIT = 10; // array element #10, arbitrary starting point // The next four are initialized dynamically in initializeDisplayBaseChoices() private String[] displayBaseAddressChoices; private int[] displayBaseAddresses; private int defaultBaseAddressIndex; private int baseAddress; private Grid theGrid; private CounterColorScale counterColorScale; /** * Simple constructor, likely used to run a stand-alone memory reference visualizer. * @param title String containing title for title bar * @param heading String containing text for heading shown in upper part of window. */ public MemoryReferenceVisualization(String title, String heading) { super(title,heading); } /** * Simple constructor, likely used by the MARS Tools menu mechanism */ public MemoryReferenceVisualization() { super ("Memory Reference Visualization, "+version, heading); } /** * Main provided for pure stand-alone use. Recommended stand-alone use is to write a * driver program that instantiates a MemoryReferenceVisualization object then invokes its go() method. * "stand-alone" means it is not invoked from the MARS Tools menu. "Pure" means there * is no driver program to invoke the application. */ public static void main(String[] args) { new MemoryReferenceVisualization("Memory Reference Visualization stand-alone, "+version,heading).go(); } /** * Required MarsTool method to return Tool name. * @return Tool name. MARS will display this in menu item. */ public String getName() { return "Memory Reference Visualization"; } /** * Override the inherited method, which registers us as an Observer over the static data segment * (starting address 0x10010000) only. This version will register us as observer over the * the memory range as selected by the base address combo box and capacity of the visualization display * (number of visualization elements times the number of memory words each one represents). * It does so by calling the inherited 2-parameter overload of this method. * If you use the inherited GUI buttons, this * method is invoked when you click "Connect" button on MarsTool or the * "Assemble and Run" button on a Mars-based app. */ protected void addAsObserver() { int highAddress = baseAddress+theGrid.getRows()*theGrid.getColumns()*Memory.WORD_LENGTH_BYTES*wordsPerUnit; // Special case: baseAddress<0 means we're in kernel memory (0x80000000 and up) and most likely // in memory map address space (0xffff0000 and up). In this case, we need to make sure the high address // does not drop off the high end of 32 bit address space. Highest allowable word address is 0xfffffffc, // which is interpreted in Java int as -4. if (baseAddress < 0 && highAddress > -4) { highAddress = -4; } addAsObserver(baseAddress, highAddress); } /** * Method that constructs the main display area. It is organized vertically * into two major components: the display configuration which an be modified * using combo boxes, and the visualization display which is updated as the * attached MIPS program executes. * @return the GUI component containing these two areas */ protected JComponent buildMainDisplayArea() { results = new JPanel(); results.add(buildOrganizationArea()); results.add(buildVisualizationArea()); return results; } ////////////////////////////////////////////////////////////////////////////////////// // Rest of the protected methods. These override do-nothing methods inherited from // the abstract superclass. ////////////////////////////////////////////////////////////////////////////////////// /** * Update display when connected MIPS program accesses (data) memory. * @param memory the attached memory * @param accessNotice information provided by memory in MemoryAccessNotice object */ protected void processMIPSUpdate(Observable memory, AccessNotice accessNotice) { incrementReferenceCountForAddress(((MemoryAccessNotice)accessNotice).getAddress()); updateDisplay(); } /** * Initialize all JComboBox choice structures not already initialized at declaration. * Overrides inherited method that does nothing. */ protected void initializePreGUI() { initializeDisplayBaseChoices(); counterColorScale = new CounterColorScale(defaultCounterColors); // NOTE: Can't call "createNewGrid()" here because it uses settings from // several combo boxes that have not been created yet. But a default grid // needs to be allocated for initial canvas display. theGrid = new Grid(visualizationAreaHeightInPixels/unitPixelHeight, visualizationAreaWidthInPixels/unitPixelWidth); } /** * The only post-GUI initialization is to create the initial Grid object based on the default settings * of the various combo boxes. Overrides inherited method that does nothing. */ protected void initializePostGUI() { wordsPerUnit = getIntComboBoxSelection(wordsPerUnitSelector); theGrid = createNewGrid(); updateBaseAddress(); } /** * Method to reset counters and display when the Reset button selected. * Overrides inherited method that does nothing. */ protected void reset() { resetCounts(); updateDisplay(); } /** * Updates display immediately after each update (AccessNotice) is processed, after * display configuration changes as needed, and after each execution step when Mars * is running in timed mode. Overrides inherited method that does nothing. */ protected void updateDisplay() { canvas.repaint(); } /** * Overrides default method, to provide a Help button for this tool/app. */ protected JComponent getHelpComponent() { final String helpContent = "Use this program to visualize dynamic memory reference\n"+ "patterns in MIPS assembly programs. It may be run either\n"+ "from MARS' Tools menu or as a stand-alone application. For\n"+ "the latter, simply write a small driver to instantiate a\n"+ "MemoryReferenceVisualization object and invoke its go() method.\n"+ "\n"+ "You can easily learn to use this small program by playing with\n"+ "it! For the best animation, set the MIPS program to run in\n"+ "timed mode using the Run Speed slider. Each rectangular unit\n"+ "on the display represents one or more memory words (default 1)\n"+ "and each time a memory word is accessed by the MIPS program,\n"+ "its reference count is incremented then rendered in the color\n"+ "assigned to the count value. You can change the count-color\n"+ "assignments using the count slider and color patch. Select a\n"+ "counter value then click on the color patch to change the color.\n"+ "This color will apply beginning at the selected count and\n"+ "extending up to the next slider-provided count.\n"+ "\n"+ "Contact Pete Sanderson at psanderson@otterbein.edu with\n"+ "questions or comments.\n"; JButton help = new JButton("Help"); help.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(theWindow, helpContent); } }); return help; } ////////////////////////////////////////////////////////////////////////////////////// // Private methods defined to support the above. ////////////////////////////////////////////////////////////////////////////////////// // UI components and layout for left half of GUI, where settings are specified. private JComponent buildOrganizationArea() { JPanel organization = new JPanel(new GridLayout(9,1)); drawHashMarksSelector = new JCheckBox(); drawHashMarksSelector.setSelected(defaultDrawHashMarks); drawHashMarksSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { updateDisplay(); } }); wordsPerUnitSelector = new JComboBox(wordsPerUnitChoices); wordsPerUnitSelector.setEditable(false); wordsPerUnitSelector.setBackground(backgroundColor); wordsPerUnitSelector.setSelectedIndex(defaultWordsPerUnitIndex); wordsPerUnitSelector.setToolTipText("Number of memory words represented by one visualization element (rectangle)"); wordsPerUnitSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { wordsPerUnit = getIntComboBoxSelection(wordsPerUnitSelector); reset(); } }); visualizationUnitPixelWidthSelector = new JComboBox(visualizationUnitPixelWidthChoices); visualizationUnitPixelWidthSelector.setEditable(false); visualizationUnitPixelWidthSelector.setBackground(backgroundColor); visualizationUnitPixelWidthSelector.setSelectedIndex(defaultVisualizationUnitPixelWidthIndex); visualizationUnitPixelWidthSelector.setToolTipText("Width in pixels of rectangle representing memory access"); visualizationUnitPixelWidthSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { unitPixelWidth = getIntComboBoxSelection(visualizationUnitPixelWidthSelector); theGrid = createNewGrid(); updateDisplay(); } }); visualizationUnitPixelHeightSelector = new JComboBox(visualizationUnitPixelHeightChoices); visualizationUnitPixelHeightSelector.setEditable(false); visualizationUnitPixelHeightSelector.setBackground(backgroundColor); visualizationUnitPixelHeightSelector.setSelectedIndex(defaultVisualizationUnitPixelHeightIndex); visualizationUnitPixelHeightSelector.setToolTipText("Height in pixels of rectangle representing memory access"); visualizationUnitPixelHeightSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { unitPixelHeight = getIntComboBoxSelection(visualizationUnitPixelHeightSelector); theGrid = createNewGrid(); updateDisplay(); } }); visualizationPixelWidthSelector = new JComboBox(displayAreaPixelWidthChoices); visualizationPixelWidthSelector.setEditable(false); visualizationPixelWidthSelector.setBackground(backgroundColor); visualizationPixelWidthSelector.setSelectedIndex(defaultDisplayWidthIndex); visualizationPixelWidthSelector.setToolTipText("Total width in pixels of visualization area"); visualizationPixelWidthSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { visualizationAreaWidthInPixels = getIntComboBoxSelection(visualizationPixelWidthSelector); canvas.setPreferredSize(getDisplayAreaDimension()); canvas.setSize(getDisplayAreaDimension()); theGrid = createNewGrid(); canvas.repaint(); updateDisplay(); } }); visualizationPixelHeightSelector = new JComboBox(displayAreaPixelHeightChoices); visualizationPixelHeightSelector.setEditable(false); visualizationPixelHeightSelector.setBackground(backgroundColor); visualizationPixelHeightSelector.setSelectedIndex(defaultDisplayHeightIndex); visualizationPixelHeightSelector.setToolTipText("Total height in pixels of visualization area"); visualizationPixelHeightSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { visualizationAreaHeightInPixels = getIntComboBoxSelection(visualizationPixelHeightSelector); canvas.setPreferredSize(getDisplayAreaDimension()); canvas.setSize(getDisplayAreaDimension()); theGrid = createNewGrid(); canvas.repaint(); updateDisplay(); } }); displayBaseAddressSelector = new JComboBox(displayBaseAddressChoices); displayBaseAddressSelector.setEditable(false); displayBaseAddressSelector.setBackground(backgroundColor); displayBaseAddressSelector.setSelectedIndex(defaultBaseAddressIndex); displayBaseAddressSelector.setToolTipText("Base address for visualization area (upper left corner)"); displayBaseAddressSelector.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { // This may also affect what address range we should be registered as an Observer // for. The default (inherited) address range is the MIPS static data segment // starting at 0x10010000. To change this requires override of // AbstractMarsToolAndApplication.addAsObserver(). The no-argument version of // that method is called automatically when "Connect" button is clicked for MarsTool // and when "Assemble and Run" button is clicked for Mars application. updateBaseAddress(); // If display base address is changed while connected to MIPS (this can only occur // when being used as a MarsTool), we have to delete ourselves as an observer and re-register. if (connectButton != null && connectButton.isConnected()) { deleteAsObserver(); addAsObserver(); } theGrid = createNewGrid(); updateDisplay(); } }); // ALL COMPONENTS FOR "ORGANIZATION" SECTION JPanel hashMarksRow = getPanelWithBorderLayout(); hashMarksRow.setBorder(emptyBorder); hashMarksRow.add(new JLabel("Show unit boundaries (grid marks)"), BorderLayout.WEST); hashMarksRow.add(drawHashMarksSelector, BorderLayout.EAST); JPanel wordsPerUnitRow = getPanelWithBorderLayout(); wordsPerUnitRow.setBorder(emptyBorder); wordsPerUnitRow.add(new JLabel("Memory Words per Unit "),BorderLayout.WEST); wordsPerUnitRow.add(wordsPerUnitSelector,BorderLayout.EAST); JPanel unitWidthInPixelsRow = getPanelWithBorderLayout(); unitWidthInPixelsRow.setBorder(emptyBorder); unitWidthInPixelsRow.add(new JLabel("Unit Width in Pixels "),BorderLayout.WEST); unitWidthInPixelsRow.add(visualizationUnitPixelWidthSelector, BorderLayout.EAST); JPanel unitHeightInPixelsRow = getPanelWithBorderLayout(); unitHeightInPixelsRow.setBorder(emptyBorder); unitHeightInPixelsRow.add(new JLabel("Unit Height in Pixels "),BorderLayout.WEST); unitHeightInPixelsRow.add(visualizationUnitPixelHeightSelector,BorderLayout.EAST); JPanel widthInPixelsRow = getPanelWithBorderLayout(); widthInPixelsRow.setBorder(emptyBorder); widthInPixelsRow.add(new JLabel("Display Width in Pixels "),BorderLayout.WEST); widthInPixelsRow.add(visualizationPixelWidthSelector, BorderLayout.EAST); JPanel heightInPixelsRow = getPanelWithBorderLayout(); heightInPixelsRow.setBorder(emptyBorder); heightInPixelsRow.add(new JLabel("Display Height in Pixels "),BorderLayout.WEST); heightInPixelsRow.add(visualizationPixelHeightSelector,BorderLayout.EAST); JPanel baseAddressRow = getPanelWithBorderLayout(); baseAddressRow.setBorder(emptyBorder); baseAddressRow.add(new JLabel("Base address for display "),BorderLayout.WEST); baseAddressRow.add(displayBaseAddressSelector,BorderLayout.EAST); ColorChooserControls colorChooserControls = new ColorChooserControls(); // Lay 'em out in the grid... organization.add(hashMarksRow); organization.add(wordsPerUnitRow); organization.add(unitWidthInPixelsRow); organization.add(unitHeightInPixelsRow); organization.add(widthInPixelsRow); organization.add(heightInPixelsRow); organization.add(baseAddressRow); organization.add(colorChooserControls.colorChooserRow); organization.add(colorChooserControls.countDisplayRow); return organization; } // UI components and layout for right half of GUI, the visualization display area. private JComponent buildVisualizationArea() { canvas = new GraphicsPanel(); canvas.setPreferredSize(getDisplayAreaDimension()); canvas.setToolTipText("Memory reference count visualization area"); return canvas; } // For greatest flexibility, initialize the display base choices directly from // the constants defined in the Memory class. This method called prior to // building the GUI. Here are current values from Memory.java: //textBaseAddress=0x00400000, dataSegmentBaseAddress=0x10000000, globalPointer=0x10008000 //dataBaseAddress=0x10010000, heapBaseAddress=0x10040000, memoryMapBaseAddress=0xffff0000 private void initializeDisplayBaseChoices() { int[] displayBaseAddressArray = {Memory.textBaseAddress, Memory.dataSegmentBaseAddress, Memory.globalPointer, Memory.dataBaseAddress, Memory.heapBaseAddress, Memory.memoryMapBaseAddress }; // Must agree with above in number and order... String[] descriptions = { " (text)", " (global data)", " ($gp)", " (static data)", " (heap)", " (memory map)" }; displayBaseAddresses = displayBaseAddressArray; displayBaseAddressChoices = new String[displayBaseAddressArray.length]; for (int i=0; i 10 characters long, slice off the first 10 and apply Integer.parseInt() to it to get custom base address. */ } // Returns Dimension object with current width and height of display area as determined // by current settings of respective combo boxes. private Dimension getDisplayAreaDimension() { return new Dimension(visualizationAreaWidthInPixels, visualizationAreaHeightInPixels); } // reset all counters in the Grid. private void resetCounts() { theGrid.reset(); } // Will return int equivalent of specified combo box's current selection. // The selection must be a String that parses to an int. private int getIntComboBoxSelection(JComboBox comboBox) { try { return Integer.parseInt((String)comboBox.getSelectedItem()); } catch (NumberFormatException nfe) { // Can occur only if initialization list contains badly formatted numbers. This // is a developer's error, not a user error, and better be caught before release. return 1; } } // Use this for consistent results. private JPanel getPanelWithBorderLayout() { return new JPanel(new BorderLayout(2,2)); } // Method to determine grid dimensions based on durrent control settings. // Each grid element corresponds to one visualization unit. private Grid createNewGrid() { int rows = visualizationAreaHeightInPixels/unitPixelHeight; int columns = visualizationAreaWidthInPixels/unitPixelWidth; return new Grid(rows,columns); } // Given memory address, increment the counter for the corresponding grid element. // Need to consider words per unit (number of memory words that each visual element represents). // If address maps to invalid grid element (e.g. is outside the current bounds based on all // display settings) then nothing happens. private void incrementReferenceCountForAddress(int address) { int offset = (address - baseAddress)/Memory.WORD_LENGTH_BYTES/wordsPerUnit; // If you care to do anything with it, the following will return -1 if the address // maps outside the dimensions of the grid (e.g. below the base address or beyond end). theGrid.incrementElement(offset / theGrid.getColumns(), offset % theGrid.getColumns()); } ////////////////////////////////////////////////////////////////////////////////////// // Specialized inner classes for modeling and animation. ////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////// // Class that represents the panel for visualizing and animating memory reference // patterns. private class GraphicsPanel extends JPanel { // override default paint method to assure visualized reference pattern is produced every time // the panel is repainted. public void paint(Graphics g) { paintGrid(g, theGrid); if (drawHashMarksSelector.isSelected()) { paintHashMarks(g, theGrid); } } // Paint (ash marks on the grid. Their color is chosef to be in // "contrast" to the current color for reference count of zero. private void paintHashMarks(Graphics g, Grid grid) { g.setColor(getContrastingColor(counterColorScale.getColor(0))); int leftX=0; int rightX=visualizationAreaWidthInPixels; int upperY=0; int lowerY=visualizationAreaHeightInPixels; // draw vertical hash marks for (int j=0; j= 10) { spaces = " "; } else if (value >=100) { spaces = ""; } return "Counter value "+spaces+value; } // Listener that both revises label as user slides and updates current index when sliding stops. private class ColorChooserListener implements ChangeListener { public void stateChanged(ChangeEvent e) { JSlider source = (JSlider)e.getSource(); if (!source.getValueIsAdjusting()) { counterIndex = (int)source.getValue(); } else { int count = countTable[(int)source.getValue()]; sliderLabel.setText(setLabel(count)); currentColorButton.setBackground(counterColorScale.getColor(count)); } } } } //////////////////////////////////////////////////////////////////////////////// // Object that represents mapping from counter value to color it is displayed as. // private class CounterColorScale { CounterColor[] counterColors; CounterColorScale(CounterColor[] colors) { counterColors = colors; } // return color associated with specified counter value private Color getColor(int count) { Color result = counterColors[0].associatedColor; int index=0; while (index < counterColors.length && count >= counterColors[index].colorRangeStart) { result = counterColors[index].associatedColor; index++; } return result; } // For a given counter value, return the counter value at the high end of the range of // counter values having the same color. private int getHighEndOfRange(int count) { int highEnd = Integer.MAX_VALUE; if (count < counterColors[counterColors.length-1].colorRangeStart) { int index=0; while (index < counterColors.length-1 && count >= counterColors[index].colorRangeStart) { highEnd = counterColors[index+1].colorRangeStart - 1; index++; } } return highEnd; } // The given entry should either be inserted into the the scale or replace an existing // element. The latter occurs if the new CounterColor has same starting counter value // as an existing one. private void insertOrReplace(CounterColor newColor) { int index = Arrays.binarySearch(counterColors, newColor); if (index >= 0) { // found, so replace counterColors[index] = newColor; } else { // not found, so insert int insertIndex = -index-1; CounterColor[] newSortedArray = new CounterColor[counterColors.length+1]; System.arraycopy(counterColors, 0, newSortedArray, 0, insertIndex); System.arraycopy(counterColors, insertIndex, newSortedArray, insertIndex+1, counterColors.length-insertIndex); newSortedArray[insertIndex] = newColor; counterColors = newSortedArray; } } } /////////////////////////////////////////////////////////////////////////////////////// // Each object represents beginning of a counter value range (non-negative integer) and // color for rendering the range. High end of the range is defined as low end of the // next range minus 1. For last range, high end is Integer.MAX_VALUE. private class CounterColor implements Comparable { private int colorRangeStart; private Color associatedColor; public CounterColor(int start, Color color) { this.colorRangeStart = start; this.associatedColor = color; } // Necessary for sorting in ascending order of range low end. public int compareTo(Object other) { if (other instanceof CounterColor) { return this.colorRangeStart - ((CounterColor)other).colorRangeStart; } else { throw new ClassCastException(); } } } //////////////////////////////////////////////////////////////////////// // Represents grid of memory access counts private class Grid { int[][] grid; int rows, columns; private Grid(int rows, int columns) { grid = new int[rows][columns]; this.rows = rows; this.columns = columns; // automatically initialized to 0, so I won't bother to.... } private int getRows() { return rows; } private int getColumns() { return columns; } // Returns value in given grid element; -1 if row or column is out of range. private int getElement(int row, int column) { return (row>=0 && row<=rows && column>=0 && column<=columns) ? grid[row][column] : -1; } // Returns value in given grid element without doing any row/column index checking. // Is faster than getElement but will throw array index out of bounds exception if // parameter values are outside the bounds of the grid. private int getElementFast(int row, int column) { return grid[row][column]; } // Increment the given grid element and return incremented value. // Returns -1 if row or column is out of range. private int incrementElement(int row, int column) { return (row>=0 && row<=rows && column>=0 && column<=columns) ? ++grid[row][column] : -1; } // Just set all grid elements to 0. private void reset() { for (int i=0; i