diff --git a/README.md b/README.md index 988f251..1b38d43 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,11 @@ SYMON - A 6502 System Simulator =============================== -**NOTE: THIS IS BETA QUALITY SOFTWARE UNDER ACTIVE DEVELOPMENT. Feedback is -welcome!** +**NOTE: THIS SOFTWARE IS UNDER ACTIVE DEVELOPMENT. Feedback is welcome!** -**Version:** 0.8.4 +**Version:** 0.8.5 -**Last Updated:** 4 March, 2013 +**Last Updated:** 30 March, 2013 Copyright (c) 2008-2013 Seth J. Morabito <web@loomcom.com> @@ -98,7 +97,7 @@ Maven will build Symon, run unit tests, and produce a jar file in the Symon is meant to be invoked directly from the jar file. To run with Java 1.5 or greater, just type: - $ java -jar symon-0.8.4.jar + $ java -jar symon-0.8.5.jar When Symon is running, you should be presented with a simple graphical interface. @@ -140,6 +139,9 @@ running. ## 5.0 Revision History + - **0.8.5:** 30 March, 2013 - ASCII display for memory window. + Allows user to select a step count from a drop-down box. + - **0.8.4:** 4 March, 2013 - Fix for ZPX, ZPY display in the trace log (change contributed by jsissom) @@ -169,7 +171,7 @@ running. - Feedback (in the form of dialogs, status bar, etc). - Better debugging tools from the UI, including breakpoints - and disassebly. + and disassembly. - More accurate timing. diff --git a/pom.xml b/pom.xml index 6fc04f7..c25f237 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.loomcom.symon symon jar - 0.8.4 + 0.8.5 symon http://www.loomcom.com/symon @@ -38,7 +38,7 @@ junit junit - 4.7 + 4.10 test diff --git a/screenshots/console.png b/screenshots/console.png index 833e503..0459149 100644 Binary files a/screenshots/console.png and b/screenshots/console.png differ diff --git a/screenshots/full.jpg b/screenshots/full.jpg index 4362504..d762478 100644 Binary files a/screenshots/full.jpg and b/screenshots/full.jpg differ diff --git a/screenshots/memory_window.png b/screenshots/memory_window.png index 58aac7a..7f0393e 100644 Binary files a/screenshots/memory_window.png and b/screenshots/memory_window.png differ diff --git a/src/main/java/com/loomcom/symon/Preferences.java b/src/main/java/com/loomcom/symon/Preferences.java index 914909f..b4b5e43 100644 --- a/src/main/java/com/loomcom/symon/Preferences.java +++ b/src/main/java/com/loomcom/symon/Preferences.java @@ -29,16 +29,12 @@ public interface Preferences { public static final int DEFAULT_PROGRAM_LOAD_ADDRESS = 0x0300; - public static final int DEFAULT_BORDER_WIDTH = 10; - public static final boolean DEFAULT_HALT_ON_BREAK = true; public JDialog getDialog(); public int getProgramStartAddress(); - public int getBorderWidth(); - public boolean getHaltOnBreak(); public void updateUi(); diff --git a/src/main/java/com/loomcom/symon/Simulator.java b/src/main/java/com/loomcom/symon/Simulator.java index ef71dc3..3f5022d 100644 --- a/src/main/java/com/loomcom/symon/Simulator.java +++ b/src/main/java/com/loomcom/symon/Simulator.java @@ -37,6 +37,8 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.io.*; import java.util.Observable; import java.util.Observer; @@ -51,7 +53,7 @@ import java.util.logging.Logger; * and a simulated ACIA for serial I/O. The ACIA is attached to a dumb terminal * with a basic 80x25 character display. */ -public class Simulator implements Observer { +public class Simulator { // Constants used by the simulated system. These define the memory map. private static final int BUS_BOTTOM = 0x0000; @@ -71,8 +73,10 @@ public class Simulator implements Observer { private static final int ROM_BASE = 0xC000; private static final int ROM_SIZE = 0x4000; - private static final int DEFAULT_FONT_SIZE = 12; - private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE); + // UI constants + private static final int DEFAULT_FONT_SIZE = 12; + private static final Font DEFAULT_FONT = new Font(Font.MONOSPACED, Font.PLAIN, DEFAULT_FONT_SIZE); + private static final int CONSOLE_BORDER_WIDTH = 10; // Since it is very expensive to update the UI with Swing's Event Dispatch Thread, we can't afford // to refresh the status view on every simulated clock cycle. Instead, we will only refresh the status view @@ -99,6 +103,9 @@ public class Simulator implements Observer { // requested private int stepsSinceLastUpdate = 0; + // The number of steps to run per click of the "Step" button + private int stepsPerClick = 1; + /** * The Main Window is the primary control point for the simulator. * It is in charge of the menu, and sub-windows. It also shows the @@ -130,10 +137,16 @@ public class Simulator implements Observer { private JButton runStopButton; private JButton stepButton; private JButton resetButton; + private JComboBox stepCountBox; private JFileChooser fileChooser; private PreferencesDialog preferences; + /** + * The list of step counts that will appear in the "Step" drop-down. + */ + private static final String[] STEPS = {"1", "5", "10", "20", "50", "100"}; + public Simulator() throws MemoryRangeException, IOException { this.acia = new Acia(ACIA_BASE); this.via = new Via(VIA_BASE); @@ -172,10 +185,11 @@ public class Simulator implements Observer { this.console = new com.loomcom.symon.ui.Console(80, 25, DEFAULT_FONT); this.statusPane = new StatusPanel(); + console.setBorderWidth(CONSOLE_BORDER_WIDTH); + // File Chooser fileChooser = new JFileChooser(System.getProperty("user.dir")); preferences = new PreferencesDialog(mainWindow, true); - preferences.addObserver(this); // Panel for Console and Buttons JPanel consoleContainer = new JPanel(); @@ -189,8 +203,22 @@ public class Simulator implements Observer { stepButton = new JButton("Step"); resetButton = new JButton("Reset"); + stepCountBox = new JComboBox(STEPS); + stepCountBox.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + try { + JComboBox cb = (JComboBox) actionEvent.getSource(); + stepsPerClick = Integer.parseInt((String) cb.getSelectedItem()); + } catch (NumberFormatException ex) { + stepsPerClick = 1; + stepCountBox.setSelectedIndex(0); + } + } + }); + buttonContainer.add(runStopButton); buttonContainer.add(stepButton); + buttonContainer.add(stepCountBox); buttonContainer.add(resetButton); // Left side - console @@ -215,7 +243,7 @@ public class Simulator implements Observer { stepButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { - handleStep(); + handleStep(stepsPerClick); } }); @@ -290,11 +318,13 @@ public class Simulator implements Observer { } /** - * Step once, and immediately refresh the UI. + * Step the requested number of times, and immediately refresh the UI. */ - private void handleStep() { + private void handleStep(int numSteps) { try { - step(); + for (int i = 0; i < numSteps; i++) { + step(); + } SwingUtilities.invokeLater(new Runnable() { public void run() { if (traceLog.isVisible()) { @@ -378,24 +408,6 @@ public class Simulator implements Observer { }); } - /** - * The configuration has changed. Re-load. - * - * @param observable - * @param o - */ - public void update(Observable observable, Object o) { - // Instance equality should work here, there is only one instance. - if (observable == preferences) { - int oldBorderWidth = console.getBorderWidth(); - if (oldBorderWidth != preferences.getBorderWidth()) { - // Resize the main window if the border width has changed. - console.setBorderWidth(preferences.getBorderWidth()); - mainWindow.pack(); - } - } - } - /** * Main entry point to the simulator. Creates a simulator and shows the main * window. @@ -441,6 +453,7 @@ public class Simulator implements Observer { public void run() { // Don't allow step while the simulator is running stepButton.setEnabled(false); + stepCountBox.setEnabled(false); menuBar.simulatorDidStart(); // Toggle the state of the run button runStopButton.setText("Stop"); @@ -461,6 +474,7 @@ public class Simulator implements Observer { statusPane.updateState(cpu); runStopButton.setText("Run"); stepButton.setEnabled(true); + stepCountBox.setEnabled(true); if (traceLog.isVisible()) { traceLog.refresh(); } diff --git a/src/main/java/com/loomcom/symon/ui/MemoryWindow.java b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java index 0bfe027..0050cc7 100644 --- a/src/main/java/com/loomcom/symon/ui/MemoryWindow.java +++ b/src/main/java/com/loomcom/symon/ui/MemoryWindow.java @@ -28,11 +28,19 @@ import com.loomcom.symon.exceptions.MemoryAccessException; import com.loomcom.symon.util.HexUtil; import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; +import javax.swing.border.MatteBorder; import javax.swing.table.AbstractTableModel; +import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableModel; +import javax.swing.text.JTextComponent; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.EventObject; import java.util.logging.Level; import java.util.logging.Logger; @@ -45,8 +53,22 @@ public class MemoryWindow extends JFrame implements ActionListener { private MemoryTableModel memoryTableModel; private JTable memoryTable; private JTextField pageNumberTextField; + private JButton previousPageButton; + private JButton nextPageButton; - private static final Dimension MINIMUM_SIZE = new Dimension(380, 400); + private static final Dimension MINIMUM_SIZE = new Dimension(320, 600); + + // The width of column 0 (address), in pixels + private static final int ADDR_COL_WIDTH = 48; + + private static final int HEX_COL_WIDTH = 32; + + // The width of the ASCII cells, in pixels + private static final int ASCII_COL_WIDTH = 8; + + // The start/end columns of the ASCII view + private static final int ASCII_COL_START = 9; + private static final int ASCII_COL_END = 16; /** * Initialize a new MemoryWindow frame with the specified Bus. @@ -81,8 +103,12 @@ public class MemoryWindow extends JFrame implements ActionListener { * Set the contents of the page number text field with the current * page number, in hex. */ - private void updatePageNumberTextField() { - pageNumberTextField.setText(HexUtil.byteToHex(getPageNumber())); + private void updateControls() { + int pageNumber = getPageNumber(); + + previousPageButton.setEnabled(pageNumber > 0x00); + nextPageButton.setEnabled(pageNumber < 0xff); + pageNumberTextField.setText(HexUtil.byteToHex(pageNumber)); } /** @@ -90,35 +116,54 @@ public class MemoryWindow extends JFrame implements ActionListener { */ private void createUi() { setTitle("Memory Contents"); - this.memoryTable = new JTable(memoryTableModel); + this.memoryTable = new MemoryTable(memoryTableModel); memoryTable.setDragEnabled(false); memoryTable.setCellSelectionEnabled(false); - memoryTable.setShowGrid(true); - memoryTable.setShowHorizontalLines(true); - memoryTable.setShowVerticalLines(true); - memoryTable.setGridColor(Color.LIGHT_GRAY); + memoryTable.setIntercellSpacing(new Dimension(0, 0)); memoryTable.getTableHeader().setReorderingAllowed(false); memoryTable.getTableHeader().setResizingAllowed(false); memoryTable.getTableHeader().setVisible(false); + memoryTable.getColumnModel().getColumn(0).setMaxWidth(ADDR_COL_WIDTH); + + for (int i = 1; i < ASCII_COL_START; i++) { + memoryTable.getColumnModel().getColumn(i).setMaxWidth(HEX_COL_WIDTH); + } + + for (int i = ASCII_COL_START; i <= ASCII_COL_END; i++) { + memoryTable.getColumnModel().getColumn(i).setMaxWidth(ASCII_COL_WIDTH); + } + + MemoryTableCellRenderer memoryTableCellRenderer = new MemoryTableCellRenderer(); + + memoryTableCellRenderer.setHorizontalAlignment(JLabel.CENTER); + memoryTable.setDefaultRenderer(String.class, memoryTableCellRenderer); + // Turn off tool-tips for the table. ToolTipManager.sharedInstance().unregisterComponent(memoryTable); ToolTipManager.sharedInstance().unregisterComponent(memoryTable.getTableHeader()); - JLabel pageNumberLabel = new JLabel("Page Number"); + JLabel pageNumberLabel = new JLabel("Page"); pageNumberTextField = new JTextField(8); pageNumberTextField.addActionListener(this); - updatePageNumberTextField(); + nextPageButton = new JButton("Next >>"); + previousPageButton = new JButton("<< Prev"); + nextPageButton.addActionListener(this); + previousPageButton.addActionListener(this); + + updateControls(); JPanel controlPanel = new JPanel(); JPanel memoryPanel = new JPanel(); memoryPanel.setLayout(new BorderLayout()); memoryPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); + controlPanel.add(previousPageButton); controlPanel.add(pageNumberLabel); controlPanel.add(pageNumberTextField); + controlPanel.add(nextPageButton); JScrollPane scrollPane = new JScrollPane(memoryTable); scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); @@ -131,7 +176,8 @@ public class MemoryWindow extends JFrame implements ActionListener { getContentPane().add(memoryPanel, BorderLayout.CENTER); setMinimumSize(MINIMUM_SIZE); - setPreferredSize(MINIMUM_SIZE); + memoryPanel.setPreferredSize(memoryTable.getPreferredSize()); + setPreferredSize(memoryPanel.getPreferredSize()); pack(); } @@ -142,8 +188,21 @@ public class MemoryWindow extends JFrame implements ActionListener { * @param e The action event */ public void actionPerformed(ActionEvent e) { - if (e.getSource() == pageNumberTextField) { - + if (e.getSource() == previousPageButton) { + int currentPage = getPageNumber(); + if (currentPage > 0x00) { + setPageNumber(currentPage - 1); + updateControls(); + memoryTable.updateUI(); + } + } else if (e.getSource() == nextPageButton) { + int currentPage = getPageNumber(); + if (currentPage < 0xff) { + setPageNumber(currentPage + 1); + updateControls(); + memoryTable.updateUI(); + } + } else if (e.getSource() == pageNumberTextField) { String pageNumberInput = pageNumberTextField.getText(); try { // Try to parse a hex value out of the pageNumber. @@ -156,7 +215,50 @@ public class MemoryWindow extends JFrame implements ActionListener { Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "Can't parse page number " + pageNumberInput); } - updatePageNumberTextField(); + updateControls(); + } + } + + /** + * A JTable that will automatically select all text in a cell + * being edited. + */ + private class MemoryTable extends JTable { + + public MemoryTable(TableModel tableModel) { + super(tableModel); + } + + @Override + public boolean editCellAt(int row, int col, EventObject e) { + boolean result = super.editCellAt(row, col, e); + + final Component editor = getEditorComponent(); + + if (editor != null && editor instanceof JTextComponent) { + ((JTextComponent) editor).selectAll(); + } + + return result; + } + } + + private class MemoryTableCellRenderer extends DefaultTableCellRenderer { + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, + int row, int col) { + final Component cell = super.getTableCellRendererComponent(table, value, + isSelected, hasFocus, + row, col); + + if (isSelected) { + cell.setBackground(Color.LIGHT_GRAY); + cell.setForeground(Color.BLACK); + } + + return cell; } } @@ -168,7 +270,7 @@ public class MemoryWindow extends JFrame implements ActionListener { private Bus bus; private int pageNumber; - private static final int COLUMN_COUNT = 9; + private static final int COLUMN_COUNT = 17; private static final int ROW_COUNT = 32; public MemoryTableModel(Bus bus) { @@ -213,15 +315,19 @@ public class MemoryWindow extends JFrame implements ActionListener { @Override public boolean isCellEditable(int row, int column) { - return column > 0; + return (column > 0 && column < ASCII_COL_START); } public Object getValueAt(int row, int column) { try { if (column == 0) { return HexUtil.wordToHex(fullAddress(row, 1)); - } else { + } else if (column < 9) { + // Display hex value of the data return HexUtil.byteToHex(bus.read(fullAddress(row, column))); + } else { + // Display the ASCII equivalent (if printable) + return HexUtil.byteToAscii(bus.read(fullAddress(row, column - 8))); } } catch (MemoryAccessException ex) { return "??"; @@ -234,7 +340,7 @@ public class MemoryWindow extends JFrame implements ActionListener { try { String hexValue = (String)o; int fullAddress = fullAddress(row, column); - int newValue = Integer.parseInt(hexValue, 16); + int newValue = Integer.parseInt(hexValue, 16) & 0xff; bus.write(fullAddress, newValue); } catch (MemoryAccessException ex) { ; diff --git a/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java index 0651ccd..6fda5c4 100644 --- a/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java +++ b/src/main/java/com/loomcom/symon/ui/PreferencesDialog.java @@ -40,10 +40,8 @@ public class PreferencesDialog extends Observable implements Preferences { private JCheckBox haltOnBreakCheckBox; private JTextField programLoadAddressField; - private JTextField borderWidthField; private int programLoadAddress = DEFAULT_PROGRAM_LOAD_ADDRESS; - private int borderWidth = DEFAULT_BORDER_WIDTH; private boolean haltOnBreak = DEFAULT_HALT_ON_BREAK; public PreferencesDialog(Frame parent, boolean modal) { @@ -72,14 +70,11 @@ public class PreferencesDialog extends Observable implements Preferences { final JLabel haltOnBreakLabel = new JLabel("Halt on BRK"); final JLabel programLoadAddressLabel = new JLabel("Program Load Address"); - final JLabel borderWidthLabel = new JLabel("Console Border Width"); haltOnBreakCheckBox = new JCheckBox(); programLoadAddressField = new JTextField(8); - borderWidthField = new JTextField(8); programLoadAddressLabel.setLabelFor(programLoadAddressField); - borderWidthLabel.setLabelFor(borderWidthField); GridBagConstraints constraints = new GridBagConstraints(); @@ -99,17 +94,9 @@ public class PreferencesDialog extends Observable implements Preferences { constraints.gridx = 1; settingsContainer.add(programLoadAddressField, constraints); - constraints.gridy = 2; - constraints.gridx = 0; - settingsContainer.add(borderWidthLabel, constraints); - - constraints.gridx = 1; - settingsContainer.add(borderWidthField, constraints); - JButton applyButton = new JButton("Apply"); JButton cancelButton = new JButton("Cancel"); - cancelButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent actionEvent) { updateUi(); @@ -121,7 +108,6 @@ public class PreferencesDialog extends Observable implements Preferences { public void actionPerformed(ActionEvent actionEvent) { haltOnBreak = haltOnBreakCheckBox.isSelected(); programLoadAddress = hexToInt(programLoadAddressField.getText()); - borderWidth = Integer.parseInt(borderWidthField.getText()); updateUi(); // TODO: Actually check to see if values have changed, don't assume. setChanged(); @@ -143,13 +129,6 @@ public class PreferencesDialog extends Observable implements Preferences { return programLoadAddress; } - /** - * @return The width of the console border, in pixels. - */ - public int getBorderWidth() { - return borderWidth; - } - /** * @return True if 'halt on break' is desired, false otherwise. */ @@ -160,7 +139,6 @@ public class PreferencesDialog extends Observable implements Preferences { public void updateUi() { haltOnBreakCheckBox.setSelected(haltOnBreak); programLoadAddressField.setText(intToHex(programLoadAddress)); - borderWidthField.setText(Integer.toString(borderWidth)); } private String intToHex(int i) { diff --git a/src/main/java/com/loomcom/symon/util/HexUtil.java b/src/main/java/com/loomcom/symon/util/HexUtil.java index 74c94b0..5004276 100644 --- a/src/main/java/com/loomcom/symon/util/HexUtil.java +++ b/src/main/java/com/loomcom/symon/util/HexUtil.java @@ -42,38 +42,67 @@ package com.loomcom.symon.util; * */ public class HexUtil { - private static final String[] HEX_CONSTANTS = {"00", "01", "02", "03", "04", "05", "06", "07", - "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", - "10", "11", "12", "13", "14", "15", "16", "17", - "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", - "20", "21", "22", "23", "24", "25", "26", "27", - "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", - "30", "31", "32", "33", "34", "35", "36", "37", - "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", - "40", "41", "42", "43", "44", "45", "46", "47", - "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", - "50", "51", "52", "53", "54", "55", "56", "57", - "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", - "60", "61", "62", "63", "64", "65", "66", "67", - "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", - "70", "71", "72", "73", "74", "75", "76", "77", - "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", - "80", "81", "82", "83", "84", "85", "86", "87", - "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", - "90", "91", "92", "93", "94", "95", "96", "97", - "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", - "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", - "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", - "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", - "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", - "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", - "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", - "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", - "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", - "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", - "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", - "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", - "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"}; + + static final String NON_PRINTABLE = "."; + + static final String[] ASCII_CONSTANTS = {" ", "!", "\"", "#", "$", "%", "&", "'", + "(", ")", "*", "+", ",", "-", ".", "/", + "0", "1", "2", "3", "4", "5", "6", "7", + "8", "9", ":", ";", "<", "=", ">", "?", + "@", "A", "B", "C", "D", "E", "F", "G", + "H", "I", "J", "K", "L", "M", "N", "O", + "P", "Q", "R", "S", "T", "U", "V", "W", + "X", "Y", "Z", "[", "\\", "]", "^", "_", + "`", "a", "b", "c", "d", "e", "f", "g", + "h", "i", "j", "k", "l", "m", "n", "o", + "p", "q", "r", "s", "t", "u", "v", "w", + "x", "y", "z", "{", "|", "}", "~"}; + + static final String[] HEX_CONSTANTS = {"00", "01", "02", "03", "04", "05", "06", "07", + "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", + "10", "11", "12", "13", "14", "15", "16", "17", + "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", + "20", "21", "22", "23", "24", "25", "26", "27", + "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", + "30", "31", "32", "33", "34", "35", "36", "37", + "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", + "40", "41", "42", "43", "44", "45", "46", "47", + "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", + "50", "51", "52", "53", "54", "55", "56", "57", + "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", + "60", "61", "62", "63", "64", "65", "66", "67", + "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", + "70", "71", "72", "73", "74", "75", "76", "77", + "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", + "80", "81", "82", "83", "84", "85", "86", "87", + "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", + "90", "91", "92", "93", "94", "95", "96", "97", + "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", + "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", + "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", + "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", + "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", + "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", + "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", + "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", + "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", + "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", + "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", + "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", + "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"}; + + /** + * Very fast 8-bit int to ASCII conversion. + * @param val The value of an ASCII character. + * @return A string representing the ASCII character. + */ + public static String byteToAscii(int val) { + if (val >= 32 && val <= 126) { + return ASCII_CONSTANTS[val - 32]; + } else { + return NON_PRINTABLE; + } + } /** * Very fast 8-bit int to hex conversion, with zero-padded output.