Return-Path: Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id TAA05023; Fri, 18 May 2001 19:46:50 -0400 (EDT) Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85]) by fort-point-station.mit.edu (8.9.2/8.9.2) with SMTP id TAA25104 for ; Fri, 18 May 2001 19:46:49 -0400 (EDT) Message-Id: <200105182346.TAA25104@fort-point-station.mit.edu> Date: Fri, 18 May 2001 16:46:49 PDT From: "JDC Tech Tips" To: alexp@mit.edu Subject: JDC Tech Tips May 18, 2001 Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, May 18, 2001. This issue covers: * Using the PushbackReader Class * Optimizing StringBuffer Usage * Handling Keyboard Focus These tips were developed using Java(tm) 2 SDK, Standard Edition, v 1.3. You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2001/tt0518.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING THE PUSHBACKREADER CLASS PushbackReader is an I/O class that allows you to "unread" or "push back" characters into the input. When you create a PushbackReader object, you can specify the number of characters that you are allowed to push back; the default is one character. Here's a simple example that shows how this class is used: import java.io.*; public class PushDemo1 { public static void main(String args[]) throws Exception { char buf[] = {'t', 'e', 's', 't'}; // set up reader to read from char array CharArrayReader car = new CharArrayReader(buf); PushbackReader pr = new PushbackReader(car, 1); char c; // read a character c = (char)pr.read(); System.out.println(c); // read another character c = (char)pr.read(); System.out.println(c); // unread the character pr.unread(c); // read it again c = (char)pr.read(); System.out.println(c); } } The PushDemo1 program reads and displays two characters from the PushbackReader, which is backed by a char array. The two characters are "t" and "e". Then the program pushes back the second character ("e"), and reads it again. The output of this program is: t e e If you try to push back more characters than you specified when the PushbackReader object was constructed, you'll get an exception. Here's an example: import java.io.*; public class PushDemo2 { public static void main(String args[]) throws Exception { char buf[] = {'t', 'e', 's', 't'}; CharArrayReader car = new CharArrayReader(buf); PushbackReader pr = new PushbackReader(car, 1); char c1, c2; // read two characters c1 = (char)pr.read(); System.out.println(c1); c2 = (char)pr.read(); System.out.println(c2); // unread both of them pr.unread(c2); pr.unread(c1); c1 = (char)pr.read(); System.out.println(c1); } } When you run this program, the result is an IOException. You get an exception because two characters are pushed back, and the PushbackReader object supports only one. What would you use a PushbackReader for in the real world? One example is an application that needs to tokenize some input, that is, split up the input into meaningful chunks such as words and numbers. Suppose that you have input that looks like this: testing123abc and you'd like to split this stream of characters into: testing 123 abc How can you do this? The tokenizing method scans through the characters, accumulating words and numbers. After the method reads the "g" in "testing", it cannot know that it's at the end of a token, until it reads the "1" in "123". But at this point, it would be nice to somehow unread the "1" so that it can be read again the next time the method is called. PushbackReader can be used in this situation. Here's an example of an application that tokenizes input: import java.io.*; class ParseInput { private PushbackReader pr; // types of tokens private static final int NONE = 0; private static final int LETTER = 1; private static final int DIGIT = 2; // set up PushbackReader for a file public ParseInput(String fn) throws IOException { FileReader fr = new FileReader(fn); BufferedReader br = new BufferedReader(fr); pr = new PushbackReader(br, 1); } // get next token from file public String getToken() throws IOException { int c; char ch; int type = NONE; // find starting character do { c = pr.read(); // end of file? if (c == -1) { return null; } ch = (char)c; // classify character as letter or digit if (Character.isLetter(ch)) { type = LETTER; } else if (Character.isDigit(ch)) { type = DIGIT; } } while (type == NONE); // have at least one character, set up // string buffer to accumulate string StringBuffer sb = new StringBuffer(); sb.append(ch); for (;;) { c = pr.read(); if (c == -1) { break; } ch = (char)c; // see if current character of same // type as first character if ((type == LETTER && Character.isLetter(ch)) || (type == DIGIT && Character.isDigit(ch))) { sb.append(ch); } else { pr.unread(c); break; } } return sb.toString(); } public void close() throws IOException { pr.close(); } } public class PushDemo3 { public static void main(String args[]) throws IOException { // check for missing argument if (args.length != 1) { System.err.println("Missing input file"); System.exit(1); } ParseInput pi = new ParseInput(args[0]); String token; // loop over tokens in input file while ((token = pi.getToken()) != null) { System.out.println(token); } pi.close(); } } PushDemo3 looks for a file as an input argument. The file should contain the string you want tokenized. The program calls getToken. Each time getToken is called, it scans for a starting letter or digit. If it can't find a starting character, getToken returns null, indicating the end of the file. getToken then records the type of the first character (letter or digit), sets up a StringBuffer, accumulates the rest of the token, and ultimately returns everything as a string. If getToken finds a character of the wrong type, it unreads the character, and terminates the loop. If you create a file containing this line: testing123this4is56a78test9 the output of running the program on this file will be: testing 123 this 4 is 56 a 78 test 9 The Java library contains a StreamTokenizer class, whose operation is somewhat like the PushDemo3 program. StreamTokenizer operates on byte streams, and is not suitable for situations where input tokens consist of arbitrary Unicode characters. Note that PushbackReader is used for character streams. There is also a PushbackInputStream class for byte streams. Note also that using a PushbackReader as illustrated above is not particularly efficient for large input. For large input, you might want to find a way to read more than one character at a time. For more information about the PushbackReader class, see Section 15.4.11, Pushback Streams, and Section 15.4.12, StreamTokenizer, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - OPTIMIZING STRINGBUFFER USAGE The April 10, 2001 issue of the JDC Tech Tips included a tip about optimizing performance when converting from object lists to strings. The tip included a code example that illustrated how to optimize these kind of conversions. There's a further improvement you can make in the example by changing the line where the StringBuffer is allocated to read as follows: StringBuffer sb = new StringBuffer(list.size() * 4 + 1); A StringBuffer starts at a default size, and grows as needed when new characters or strings are appended to it. For the Java 2 SDK version 1.3 implementation, the starting size is 16; the size doubles each time the buffer needs to grow. Each time this happens, the contents of the buffer must be copied to a bigger buffer, which requires extra processing time. Each list item in the original example consists of one or two digits as a string. Each item is delimited by a comma and space, so the space requirement in the buffer is about four characters for each item. If you change the line where the StringBuffer is allocated as indicated above, it should make the optimized list-to-string conversion run about three times as fast as the original, instead of twice as fast (the performance improvement produced by the original example). Here's a short demo that illustrates this issue: public class GrowDemo { static final int N = 1000000; static final String str = "testing"; public static void main(String args[]) { StringBuffer sb = new StringBuffer(); //StringBuffer sb = new StringBuffer(N * str.length()); long start = System.currentTimeMillis(); for (int i = 1; i <= N; i++) { sb.append(str); } long elapsed = System.currentTimeMillis() - start; System.out.println(elapsed); } } In this example, N strings are appended to the buffer. Because you know the length of each string, you can calculate the ultimate size of the buffer, and in this way avoid the repeated copying and reallocation as the buffer grows. This program runs about twice as fast if you replace the line that allocates the StringBuffer with the line that allocates a StringBuffer of a specific size. There's a "downside" to this technique. If you don't know how big the buffer will get, and you allocate a lot more space than you need, you end up wasting space. This can be quite important if you're dealing with large buffer sizes. The same technique is worth considering for other situations. This is especially true where you know what the final size of a data structure will be, and you're using classes for which you can specify this size to the class constructor. For more information about optimizing StringBuffer usage, see Section 9.8.3, Capacity Management, in "The Java(tm) Programming Language Third Edition" by Arnold, Gosling, and Holmes http://java.sun.com/docs/books/javaprog/thirdedition/. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - HANDLING KEYBOARD FOCUS One of the desirable properties of a user interface is that it supports mouseless operation. This means that you can use the keyboard to navigate between interface components. If you're using Java(tm) Foundation Classes Swing components, you can move the input focus to the next component by using the Tab key. You can also use the Shift-Tab key to move the focus back to the previous component. There are several mechanisms that you can use to control focus. This tip illustrates some of them in a series of examples. The first example creates a custom component called LocalCanvas. A number of instances of this component are added to a panel, along with a button. Here's what the example looks like: import java.awt.*; import javax.swing.*; import java.awt.event.*; // local class that extends JPanel class LocalCanvas extends JPanel { private boolean can_traverse; public LocalCanvas(boolean b) { can_traverse = b; // add listener for focus gained/lost events addFocusListener(new FocusListener() { public void focusGained(FocusEvent e) { repaint(); } public void focusLost(FocusEvent e) { repaint(); } }); // add listener for mouse events addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { requestFocus(); } }); } // paint the canvas public void paintComponent(Graphics g) { super.paintComponent(g); // highlight if canvas has the focus if (hasFocus()) { g.setColor(Color.red); } else { g.setColor(Color.blue); } Dimension d = getSize(); g.draw3DRect(1, 1, d.width - 3, d.height - 3, true); } // set preferred size of 50 x 50 for the canvas public Dimension getPreferredSize() { return new Dimension(50, 50); } // return true if can traverse this component using Tab public boolean isFocusTraversable() { return can_traverse; } } public class FocusDemo1 { public static void main(String args[]) { JFrame frame = new JFrame("Focus Demo 1"); // handle window close frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // set up button and canvases JButton button1 = new JButton("Standard Button"); JComponent canvas1 = new LocalCanvas(true); JComponent canvas2 = new LocalCanvas(false); // add to panel JPanel panel = new JPanel(); panel.add(button1); panel.add(canvas1); panel.add(canvas2); // display frame.getContentPane().add(panel); frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); } } The constructor for LocalCanvas takes a boolean argument that specifies whether the component supports a traversable focus. This simply means that you can switch the focus to the component by successively pressing Tab on the keyboard. If you run this program, you can press Tab to move the focus from the button to the first LocalCanvas component (a square box). If you then press Tab again, the focus moves back to the button, and the second LocalCanvas component is skipped. It is skipped because it is not focus traversable. If a component is not focus traversable it can still receive focus. However the focus cannot be gained by pressing the Tab key. If you use the mouse to click on the second LocalCanvas component, it still receives focus. There's another point to make about this example. If you write a custom component, and it receives focus, it's desirable to indicate this in some visible way. For example, you might want the component to change color. The program above sets up focus gained and lost listeners, and repaints the object in red instead of blue when the focus is gained. What sort of components are not traversable? An example is a scrollbar, as illustrated in the following example: import java.awt.*; import javax.swing.*; import java.awt.event.*; public class FocusDemo2 { public static void main(String args[]) { JFrame frame = new JFrame("Focus Demo 2"); // handle window closing frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // set up a panel and two buttons and a scrollbar JPanel panel = new JPanel(); JButton button1 = new JButton("Button1"); JScrollBar jsb = new JScrollBar(); JButton button2 = new JButton("Button2"); panel.add(button1); panel.add(jsb); panel.add(button2); // display frame.getContentPane().add(panel); frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); } } This demo sets up two buttons with a scrollbar between them. If you press the Tab key, the focus goes back and forth between the buttons. You can manipulate the scrollbar as usual, but it is ignored during keyboard traversal. A final aspect of focus management concerns the default focus manager. Swing provides a focus manager that is used to transfer focus from one component to another. You can override this focus manager with your own focus manager. For example, suppose you want to change the order of component traversal. The default is left-to-right and top-to-bottom, and you'd like it to be top-to-bottom and left-to-right. Here's an example of how you can do that: import java.awt.*; import javax.swing.*; import java.awt.event.*; // local version of focus manager class LocalFocusManager extends DefaultFocusManager { public boolean compareTabOrder(Component c1, Component c2) { Point loc1 = c1.getLocation(); Point loc2 = c2.getLocation(); // sort the two components if (loc1.x != loc2.x) { return loc1.x < loc2.x; } return loc1.y < loc2.y; } } public class FocusDemo3 { public static void main(String args[]) { JFrame frame = new JFrame("Focus Demo 3"); // handle window closing frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // install a new focus manager FocusManager.setCurrentManager(new LocalFocusManager()); // add a 5 x 5 matrix of buttons to the panel JPanel panel = new JPanel(); final int N = 5; panel.setLayout(new GridLayout(N, N)); for (int i = 1; i <= N; i++) { for (int j = 1; j <= N; j++) { JButton b = new JButton(i + "," + j); panel.add(b); } } // display frame.getContentPane().add(panel); frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); } } Run this program, and then repeatedly press the Tab key. Notice that the focus goes down the first column of buttons instead of going across the first row. If you comment the line that installs the custom focus manager, "FocusManager.setCurrentManager...", the behavior reverts to the default, that is, focus goes across the row. The compareTabOrder method returns true if the first component should be ranked ahead of the second for purposes of traversal. Changing the order of traversal might be important if you're trying to support a specialized type of application. For example, you might have an application that works with a language other than English. Or you might have a specialized financial application involving columns of data. For more information about handling keyboard focus, see the section "Focus Management" in chapter 4 of "Graphic Java: Mastering the JFC 3rd Edition Volume II" by David Geary. . . . . . . . . . . . . . . . . . . . . . . . - NOTE Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun Microsystems(tm) purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page http://developer.java.sun.com/subscription/ uncheck the appropriate checkbox, and click the Update button. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page http://developer.java.sun.com/subscription/ choose the newsletters you want to subscribe to, and click Update. - FEEDBACK Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com - ARCHIVES You'll find the JDC Tech Tips archives at: http://java.sun.com/jdc/TechTips/index.html - COPYRIGHT Copyright 2001 Sun Microsystems, Inc. All rights reserved. 901 San Antonio Road, Palo Alto, California 94303 USA. This document is protected by copyright. For more information, see: http://java.sun.com/jdc/copyright.html - LINKS TO NON-SUN SITES The JDC Tech Tips may provide, or third parties may provide, links to other Internet sites or resources. Because Sun has no control over such sites and resources, You acknowledge and agree that Sun is not responsible for the availability of such external sites or resources, and does not endorse and is not responsible or liable for any Content, advertising, products, or other materials on or available from such sites or resources. Sun will not be responsible or liable, directly or indirectly, for any damage or loss caused or alleged to be caused by or in connection with use of or reliance on any such Content, goods or services available on or through any such site or resource. This issue of the JDC Tech Tips is written by Glen McCluskey. JDC Tech Tips May 18, 2001 Sun, Sun Microsystems, Java, and Java Developer Connection are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. To unsubscribe from this mailing list, select the following URL: http://hermes.java.sun.com/unsubscribe?2765010897199552382