![]() |
|
|
|||||||
![]() |
Java - Why replaceSelection in JTextPane is not behaving safely? |
|
|
Thread Tools | Search this Thread |
|
|
#1 |
|
Hi all, I am now doing my Java homework as a new Java beginner. I've
found the following code from the internet, which I've modified a bit to meet my need. The purpose is to create a console-like window to display the UI (as Java's console has very very limited functions, which cannot be operated as C or C++). But now I've got a mysterious problem, which happens from time to time and I can never predict when it will happen -- the text pane seems to "freeze" at replaceSelection at method "write" (as I've tested to place some console output before and after this line). The problem is that it seems to work perfectly right but then it hangs. I have no idea what goes wrong, as when I debug it in the Eclipse, all the thread are still in running status. And strangely, I got this exception: "Exception in thread "AWT- EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0 >= 0" occasionally, but the program keeps working. I am pretty annoyed by this, as there seems no tools that I can used to debug the program (such as dump the JVM status). As replaceSelection is stated as "safe" explicitly in APIs, why it's not behaving safely? Thanks a million in advanced! (specially Rene Ghosh -- Class TextConsole.java -- import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.event.KeyEvent; import java.awt.font.FontRenderContext; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.swing.JTextPane; import javax.swing.text.BadLocationException; import javax.swing.text.MutableAttributeSet; import javax.swing.text.StyleConstants; /** * The TextConsole component. An extension of the * JTextPane class for user interaction in text * form. */ class TextConsole extends JTextPane { /** * */ private static final long serialVersionUID = -5329149879890129297L; /** * Default parameters for basic font name and size. */ private static final int DEFAULT_FONT_SIZE = 20; private static final String DEFAULT_FONT_NAME = "Courier New"; private static final int DEFAULT_WIDTH_CHARS = 80; private static final int DEFAULT_HEIGHT_CHARS = 25; private static final Color DEFAULT_BACKGROUND_COLOR = Color.BLACK; private static final Color DEFAULT_FOREGROUND_COLOR = Color.WHITE; private Font font = null; private int lastSubmitKey = -1; private StringBuffer clearBuffer; private String blankLine = null; private int[] submitKeys = new int[0]; private Color backgroundColor, foregroundColor; /** * flag to set to true when the form is submitted */ private volatile boolean finished = false; MutableAttributeSet attrs = getInputAttributes(); int widthChars = DEFAULT_WIDTH_CHARS; int heightChars = DEFAULT_HEIGHT_CHARS; /** * Maximum number of characters that will hold * in the console window == width * height */ int maxLength = pointToInt(widthChars, heightChars); /** * The list of keys that should be processed * by the JTextPane superclass. */ int[] processable = new int[] { KeyEvent.VK_UP, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_HOME, KeyEvent.VK_END}; /** * List of zones that support user input. */ private List formRanges = new ArrayList(); /** * Basic constructor: uses default values for * font size (20) and name (Courier New). Input parameters include * width and height. */ public TextConsole(int width, int height) { this(width, height, DEFAULT_FONT_SIZE, DEFAULT_FONT_NAME, DEFAULT_BACKGROUND_COLOR, DEFAULT_FOREGROUND_COLOR); } /** * Full constructor taking as input the width and height in number * of characters, font size and font name. */ public TextConsole(int width, int height, int fontSize, String fontName) { this(width, height, fontSize, fontName, DEFAULT_BACKGROUND_COLOR, DEFAULT_FOREGROUND_COLOR); } public TextConsole(int width, int height, int fontSize, String fontName, Color backgroundColor, Color foregroundColor) { this.font = new Font(fontName, Font.PLAIN, fontSize); this.widthChars = width; this.heightChars = height; this.backgroundColor = backgroundColor; this.foregroundColor = foregroundColor; this.maxLength = pointToInt(widthChars, heightChars); setFont(font); FontRenderContext fontRenderContext = new FontRenderContext(null, false, true); Rectangle2D stringBounds = font.getStringBounds(new char[] { 'W' }, 0, 1, fontRenderContext); setPreferredSize(new Dimension( (int) (5+ (widthChars + 1) * stringBounds.getWidth()), (int) (5+ (heightChars + 1) * stringBounds.getHeight()))); setForeground(this.foregroundColor); setBackground(this.backgroundColor); setCaretColor(this.foregroundColor); /** * Construct a blank buffer to clear the screen. */ clearBuffer = new StringBuffer(); for (int j = 0; j < heightChars; j++) { for (int i = 0; i < widthChars; i++) { clearBuffer.append(" "); } if (j < heightChars - 1) { clearBuffer.append("\n"); } } fill(); } /** * Rendering method. Overrides the paint() method * on the superclass to stop antialiasing to the output * screen. */ /* public void paint(Graphics g) { Graphics2D g2 = (Graphics2D)g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASIN G, RenderingHints.VALUE_ANTIALIAS_OFF); g2.setRenderingHint(RenderingHints.KEY_FRACTIONALM ETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g2.setRenderingHint(RenderingHints.KEY_COLOR_RENDE RING, RenderingHints.VALUE_COLOR_RENDER_SPEED); super.paint(g); }*/ /** * Method to fill the screen with blank characters (space) */ private void fill() { setText(clearBuffer.toString()); StyleConstants.setBackground(attrs, backgroundColor); StyleConstants.setForeground(attrs, foregroundColor); StyleConstants.setUnderline(attrs, false); getStyledDocument().setCharacterAttributes(0, maxLength, attrs, true); } /** * Convert an X-Y position into a sequential index * of a character in the text pane. * @throws Exception */ private int pointToInt(int i, int j) { if (i < 0 || i > widthChars || j < 0 || j > heightChars) return 0; int ret = ((j -1 ) * (widthChars + 1)) + (i - 1); return ret; } /** * set the cursor position on screen */ public void gotoPosition(int x, int y) { int start = pointToInt(x, y); setCaretPosition(start); } /** * set the cursor position on the first * form field on screen */ public void gotoFirstField() { if (formRanges.size() > 0) { FormRange firstRange = (FormRange)formRanges.get(0); setCaretPosition(firstRange.start); } } /** * Clear the screen */ public void clear() { formRanges.clear(); fill(); reset(); } /** * set the prescribed color to all characters in a given range */ public void color(int i, int j, Color color) { StyleConstants.setForeground(attrs, color); getStyledDocument().setCharacterAttributes(i, j - i, attrs, true); } public int getLastSubmitKey() { int lsk = lastSubmitKey; lastSubmitKey = -1; return lsk; } public void setSubmitKeys(int[] keys) { submitKeys = keys; } /** * Key processing function. Certain key events * are delegated to the superclass. Others submit * the user input to the calling object or react to the input * internally. */ protected void processKeyEvent(KeyEvent e) { char keyChar = e.getKeyChar(); int keyCode = e.getKeyCode(); String text = ""; int start, pos; int id = e.getID(); System.out.println("I am here!"); if (keyCode == KeyEvent.VK_ENTER) { finished = true; lastSubmitKey = KeyEvent.VK_ENTER; return; } if (id == KeyEvent.KEY_PRESSED) { for (int i = 0; i < submitKeys.length; i++) { if (submitKeys[i] == keyCode) { finished = true; lastSubmitKey = keyCode; return; } } } boolean needProcess = false; for (int i = 0; i < processable.length; i++) { if (processable[i] == keyCode) { needProcess = true; break; } } if (!needProcess) { int caretPosition = getCaretPosition(); for (Iterator iter = formRanges.iterator(); iter.hasNext() FormRange range = (FormRange) iter.next(); int checkPosition = caretPosition; if (keyCode == KeyEvent.VK_BACK_SPACE) { checkPosition = caretPosition - 1; } if (range.isInRange(checkPosition)) { try { text = getText(range.start, range.end - range.start); } catch (BadLocationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } if (id == KeyEvent.KEY_PRESSED) { //if (e.getID() == KeyEvent.KEY_RELEASED) { setLocalColor(range.color); if (keyChar == KeyEvent.VK_SPACE) { write(" "); } else if(keyCode == KeyEvent.VK_DELETE) { pos = getCaretPosition(); start = pos - range.start; if (start > 0) text = text.substring(0, start) + text.substring(start + 1) + " "; else text = text.substring(1) + " "; setCaretPosition(range.start); write(text); setCaretPosition(pos); } else if (keyCode == KeyEvent.VK_BACK_SPACE) { pos = getCaretPosition(); start = pos - range.start; if (start > 0) text = text.substring(0, start - 1) + text.substring(start) + " "; else text = text.substring(1) + " "; setCaretPosition(range.start); write(text); setCaretPosition(pos - 1); } else if (Character.isLetterOrDigit(keyChar) || keyChar == '.' || keyChar == '+' || keyChar == '-') { write("" + keyChar); } } } } if ((keyCode == KeyEvent.VK_TAB) && (e.getID() == KeyEvent.KEY_PRESSED)) { boolean found = false; if (e.isShiftDown()) { Collections.reverse(formRanges); } for (Iterator iter = formRanges.iterator(); iter.hasNext() && (!found) FormRange hotRange = (FormRange) iter.next(); start = hotRange.start; if (e.isShiftDown()) { if (start < caretPosition) { setCaretPosition(start); found = true; } } else { if (start > caretPosition) { setCaretPosition(start); found = true; } } } if (e.isShiftDown()) { Collections.sort(formRanges); } if (!found && (formRanges.size() > 0)) { setCaretPosition(((FormRange) formRanges.get(0)).start); } } } if (needProcess) { super.processKeyEvent(e); } } /** * Set the foreground font color to that * of the foreground text under the cursor */ private void setLocalColor(Color color) { setForeground(color); } /** * reset the "finished" flag to false. This flag remains false * so long as the user has not submitted the form and blocks the getValues() * method from returning. */ public void reset() { finished = false; } public void clearLine(int y) { int i = 0; if (y >= 1 && y < heightChars) { //gotoPosition(1, y); if (blankLine == null) { blankLine = ""; for (i = 0; i < widthChars; i++) { blankLine += " "; } } write(blankLine, 0, y); StyleConstants.setBackground(attrs, backgroundColor); StyleConstants.setForeground(attrs, foregroundColor); StyleConstants.setUnderline(attrs, false); getStyledDocument().setCharacterAttributes(((y -1 ) * (widthChars + 1)), widthChars, attrs, true); } } /** * Write text to screen with current foreground color */ public void write(String string) { int caretPosition = getCaretPosition(); if (caretPosition + string.length() > maxLength) { string = string.substring(0, maxLength - caretPosition + 1); } int start = caretPosition; int end = caretPosition + string.length(); setSelectionStart(start); setSelectionEnd(end); color(start, end, getForeground()); replaceSelection(string); setSelectionStart(getCaretPosition()); } /** * Set the foreground to prescribed color and * write text to screen */ public void write(String string, Color color) { setForeground(color); write(string); } /** * Move cursor to prescribed position and write * text to screen */ public void write(String string, int x, int y) { gotoPosition(x, y); write(string); } /** * Move cursor to prescribed position, set foreground color * to prescribed color and write text to screen */ public void write(String string, int x, int y, Color color) { gotoPosition(x, y); setForeground(color); write(string); } /** * Return the values map to the calling object. * Each value in the map is associated to a key added * during the call to addFormField(). */ public Map getValues() { while (!finished) { //do nothing until not finished } Map map = new HashMap(); for (Iterator iter = formRanges.iterator(); iter.hasNext() FormRange range = (FormRange) iter.next(); try { String text = getText(range.start, range.end - range.start); map.put(range.name, text.trim()); } catch (BadLocationException e) { e.printStackTrace(); } } reset(); return map; } /** * Add a form field to the screen with prescribed * name key and in a given width. the getValues() * method will return the value associated to this key in * the input map. */ public void addFormField(String fieldName, int width) { int start = getCaretPosition(); int end = getCaretPosition() + width; addFormRange(fieldName, start, end); } /** * Add a form field in a prescribed color. * @see addFormField() */ public void addFormField(String fieldName, int width, Color color) { setForeground(color); addFormField(fieldName, width); } /** * Move the cursor to a prescribed position on the screen and * add a form field. * @see addFormField() */ public void addFormField(String fieldName, int x, int y, int width) { int start = pointToInt(x, y); int end = pointToInt(x + width, y); addFormRange(fieldName, start, end); } /** * Move the cursor to a prescribed position on the screen, * set the foreground color to the prescribed color and * add a form field. * @see addFormField() */ public void addFormField(String fieldName, int x, int y, int width, Color color) { setForeground(color); int start = pointToInt(x, y); int end = pointToInt(x + width, y); addFormRange(fieldName, start, end); } /** * Add a range to the list of form ranges, from the prescribed * start parameter to the end parameter */ private void addFormRange(String fieldName, int start, int end) { FormRange range = new FormRange(fieldName, start, end, getForeground ()); formRanges.add(range); Collections.sort(formRanges); StyleConstants.setUnderline(attrs, true); color(start, end, getForeground()); getStyledDocument().setCharacterAttributes(start, end - start, attrs, true); } /** * Form range container class. * Contains information about a form range, including * - form field key name * - start of range * - end of range * - color of range * @author Rene Ghosh * 6 oct. 2007 */ class FormRange implements Comparable { private String name; private int start; private int end; private Color color; /** * Constructor using name, start, end and color * to set into the object */ public FormRange(String name, int start, int end, Color color) { this.name = name; this.start = start; this.end = end; this.color = color; } /** * Returns "true" if the given int is in the * [start, end] range */ public boolean isInRange(int i) { return (i >= start) && (i < end); } /** * Method to enable sorting on a list of ranges. */ public int compareTo(Object other) { FormRange otherRange = (FormRange) other; return new Integer(start).compareTo(new Integer(otherRange.start)); } } } -- Program to use the TextConsole -- .... public static void main(String[] args) { // TODO Auto-generated method stub A tcApp = new A(); tcApp.start(); } public void start() { frame = new JFrame("Testing"); console = new TextConsole(80, 25, 15, "Lucida Console", Color.BLACK, Color.WHITE); frame.getContentPane().add(console); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOS E); frame.setResizable(false); frame.pack(); frame.setVisible(true); while (!stop) { opt = showMainScreen(); switch (opt) { case 1: showMainScreen(); break; case 2: .... case 6: stop = true; break; } } frame.dispose(); } .... lenyado |
|
|
|
|
#2 |
|
Posts: n/a
|
In article
<428ed5af-0045-4d80-bbe3->, lenyado <> wrote: > Hi all, I am now doing my Java homework as a new Java beginner. I've > found the following code from the internet, which I've modified a bit > to meet my need. The purpose is to create a console-like window to > display the UI (as Java's console has very very limited functions, > which cannot be operated as C or C++). But now I've got a mysterious > problem, which happens from time to time and I can never predict when > it will happen -- the text pane seems to "freeze" at replaceSelection > at method "write" (as I've tested to place some console output before > and after this line). The problem is that it seems to work perfectly > right but then it hangs. I have no idea what goes wrong, as when I > debug it in the Eclipse, all the thread are still in running status. Without trying to debug your long and incomplete example, I see that you're neglecting to construct your Swing component on the Event Dispatch Thread (EDT), as suggested here and the page following: <http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html> > And strangely, I got this exception: "Exception in thread "AWT- > EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0 >= 0" > occasionally, but the program keeps working. Yes, the EDT is designed to dispatch events continually. > I am pretty annoyed by this, as there seems no tools that I can used > to debug the program (such as dump the JVM status). As > replaceSelection is stated as "safe" explicitly in APIs, why it's not > behaving safely? Such sporadic errors suggest incorrect synchronization outside of replaceSelection(). You don't mention your IDE, but most have useful displays of the JVM's threads and the call stack in each. -- John B. Matthews trashgod at gmail dot com <http://sites.google.com/site/drjohnbmatthews> John B. Matthews |
|
|
|
#3 |
|
Posts: n/a
|
John B. Matthews wrote:
> You don't mention your IDE, He said, > I debug it in the Eclipse, -- Lew Lew |
|
|
|
#4 |
|
Posts: n/a
|
lenyado wrote:
> The problem is that it seems to work perfectly > right but then it hangs. I have no idea what goes wrong, as when I > debug it in the Eclipse, all the thread are still in running status. > -- Class TextConsole.java -- For future reference, don't indent code examples for Usenet with TAB characters. Use a maximum of four spaces for indentation. > import ... imports and comments elided for brevity. > class TextConsole extends JTextPane { It's usually a good idea, especially when first learning Java, to declare classes 'public'. > private static final long serialVersionUID = -5329149879890129297L; > > private static final int DEFAULT_FONT_SIZE = 20; > private static final String DEFAULT_FONT_NAME = "Courier New"; > private static final int DEFAULT_WIDTH_CHARS = 80; > private static final int DEFAULT_HEIGHT_CHARS = 25; > private static final Color DEFAULT_BACKGROUND_COLOR = Color.BLACK; > private static final Color DEFAULT_FOREGROUND_COLOR = Color.WHITE; > > private Font font = null; It isn't necessary to initialize member variables to 'null'; it only makes the same initialization happen twice. > private int lastSubmitKey = -1; > private StringBuffer clearBuffer; > private String blankLine = null; > private int[] submitKeys = new int[0]; > private Color backgroundColor, foregroundColor; > > private volatile boolean finished = false; It isn't necessary to initialize member variables to 'false'; it only makes the same initialization happen twice. > MutableAttributeSet attrs = getInputAttributes(); It is necessary to declare 'getInputAttributes()'. > int widthChars = DEFAULT_WIDTH_CHARS; > int heightChars = DEFAULT_HEIGHT_CHARS; > > /** > * Maximum number of characters that will hold > * in the console window == width * height > */ > int maxLength = pointToInt(widthChars, heightChars); Bear in mind that this method call will use the default values for 'widthChars' and 'heightChars', that is, 'DEFAULT_WIDTH_CHARS' and 'DEFAULT_HEIGHT_CHARS', respectively. It will not use the values that are set later by the constructor. One wonders why you set this value twice, once here and another time in the explicit constructor. > ... > public TextConsole(int width, int height, int fontSize, String > fontName, Color backgroundColor, Color foregroundColor) { > this.font = new Font(fontName, Font.PLAIN, fontSize); > this.widthChars = width; > this.heightChars = height; > this.backgroundColor = backgroundColor; > this.foregroundColor = foregroundColor; > this.maxLength = pointToInt(widthChars, heightChars); > setFont(font); > FontRenderContext fontRenderContext = new FontRenderContext(null, > false, true); > Rectangle2D stringBounds = font.getStringBounds(new char[] { 'W' }, > 0, 1, fontRenderContext); > setPreferredSize(new Dimension( > (int) (5+ (widthChars + 1) * stringBounds.getWidth()), > (int) (5+ (heightChars + 1) * stringBounds.getHeight()))); > setForeground(this.foregroundColor); > setBackground(this.backgroundColor); > setCaretColor(this.foregroundColor); > > clearBuffer = new StringBuffer(); > for (int j = 0; j < heightChars; j++) { > for (int i = 0; i < widthChars; i++) { > clearBuffer.append(" "); > } > if (j < heightChars - 1) { > clearBuffer.append("\n"); > } > } > fill(); > } > > private void fill() { > setText(clearBuffer.toString()); > StyleConstants.setBackground(attrs, backgroundColor); > StyleConstants.setForeground(attrs, foregroundColor); > StyleConstants.setUnderline(attrs, false); > getStyledDocument().setCharacterAttributes(0, maxLength, attrs, > true); > } > > private int pointToInt(int i, int j) { > if (i < 0 || i > widthChars || j < 0 || j > heightChars) > return 0; Aside from the fact that you should have braces around the body part of the 'if', if the width x length were zero then you'd be returning an illegal location in your int, because the position (0) is >= the length (0). Hmm, your error message says that the problem is an "ArrayIndexOutOfBoundsException: 0 >= 0". Coincidence? > int ret = ((j -1 ) * (widthChars + 1)) + (i - 1); > return ret; > } > ... John B. Matthews already pointed out that you need to do all GUI work on the EDT, and where to learn what that means. As to your concern that "there seems [sic] no tools that I can used [sic] to debug the program (such as dump the JVM status)", Eclipse has those tools. It certainly has, as John said, "useful displays of the JVM's threads and the call stack in each." -- Lew Lew |
|
|
|
#5 |
|
Posts: n/a
|
In article <hckqgi$cgu$>, Lew <>
wrote: > John B. Matthews wrote: > > You don't mention your IDE, > > He said, > > I debug it in the Eclipse, Egad, you're right! Thanks for catching this. I use the Eclipse debugger relatively less often, but I see that excellent tutorials are available [1]. As the OP is starting out, he may want to look at Swing's Threading Policy [2]. My experience has been that the thread safety of certain JTextArea methods is a convenience, but not a panacea. As an example, this demonstration program [3] uses SwingWorker [4] to cleanly separate background and foreground processing: [1]<http://www.google.com/search?q=eclipse+debugger+tutorial> [2]<http://java.sun.com/javase/6/docs/api/javax/swing/package-summary.html> [3]<http://sites.google.com/site/drjohnbmatthews/randomdata> [4]<http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html> -- John B. Matthews trashgod at gmail dot com <http://sites.google.com/site/drjohnbmatthews> John B. Matthews |
|
![]() |
| Thread Tools | Search this Thread |
|
|
Similar Threads
|
||||
| Thread | Thread Starter | Forum | Replies | Last Post |
| RE: Safely Remove Hardware Icon Doesn't Appear in Systray (Bottom | Kelly | Windows 64bit | 0 | 04-27-2009 03:33 PM |
| can SAFELY REMOVE HARDWARE be cancelled? | Larry Smith | Computer Support | 12 | 08-14-2008 09:59 PM |
| Re: USB - 'safely remove hardware' - device in use | Jeff Strickland | Computer Information | 11 | 08-14-2008 03:38 AM |
| Safely Remove Hardware | Ken Smith | Computer Support | 5 | 01-23-2005 09:22 PM |
| Safely removing my Mp3 player from my computer | JD | Computer Support | 2 | 01-24-2004 07:39 AM |