Return-Path: <env_2765010897199552382@hermes.java.sun.com>
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 <alexp@mit.edu>; 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" <body_2765010897199552382@hermes.java.sun.com>
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
