Core Java Technologies Tech Tips Tips, Techniques, and Sample Code Welcome to the Core Java(tm) Technologies Tech Tips, October 22, 2002. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE(tm)). This issue covers: * Filtering Logged Messages * Controlling Focus Traversal Sequencing These tips were developed using Java 2 SDK, Standard Edition, v 1.4. This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc. (http://www.jzventures.com). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/JDCTechTips/2002/tt1022.html. See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FILTERING LOGGED MESSAGES One of the many new features of the J2SE version 1.4 release is the Java Logging APIs. Found in the java.util.logging package, the Logging framework permits you to do the obvious: log messages. What makes the feature so special is its configurability. Out of the box, the logging classes allow you to send log messages to the console (System.out), to memory, to an output stream, to an XML file, or over a socket connection. You have a number of configurable options pertinent to logging. For example, you can configure what types of messages are logged, as well as where they are logged. You specify the configuration options in a logging.properties file in the /lib directory beneath your Java installation directory. The basic process for using the Logging APIs is: Step 1: Get the Logger. The Logger is in charge of accepting messages to log. Logger logger = Logger.getLogger(""); The string passed to the getLogger method specifies which logger to get. Typically, you get this for whatever package you are working in. So, if you were writing a program for the com.example package, you would ask for the "com.example" logger, as shown here: Logger logger = Logger.getLogger("com.example"); Step 2: Use the Logger. Yes, it is that simple. There are many methods of the Logger class for logging messages. The most obvious method is the log method. However, when discussing that method, it's important to point out that logged messages have a Level. The Level of a log message is the importance assigned to the message being sent. Very important messages should be given a priority level of SEVERE. Typically, most messages are informational. These should have a priority level of INFO. The seven levels rank as follows: o SEVERE o WARNING o INFO o CONFIG o FINE o FINER o FINEST Here's a demonstration of the log method. The following program logs a SEVERE message. Notice that the program simply passes the log level and message to the log method: package com.example; import java.util.logging.*; public class LogSample { private static Logger logger = Logger.getLogger("com.example"); public static void main(String argv[]) { logger.log(Level.SEVERE, "Severe Testing"); } } Compile the program with the directory option: javac -d . LogSample.java Then run it: java com.example.LogSample You should see something like this sent to the console (with the current date and time): Oct 16, 2002 1:08:44 PM com.example.LogSample main SEVERE: Severe Testing While the log method is a nice convenience, there is an even more convenient mechanism available to log messages. Instead of passing the log level to the log method, the Logger permits you to call a method specific to the level of the message to log. The methods are: o severe(String message) o warning(String message) o info(String message) o config(String message) o fine(String message) o finer(String message) o finest(String message) Let's update the demonstration to use one of these methods. Notice that you no longer have to specify the priority level as an argument to the method. package com.example; import java.util.logging.*; public class LogSample { private static Logger logger = Logger.getLogger("com.example"); public static void main(String argv[]) { logger.severe("Severe Testing"); } } In addition to the logging methods for the different severity levels, there are also methods for particular operations such as entering and exiting. There is another important thing to consider when you use the logging APIs: you need to limit what is logged. The logging APIs will log for all levels, unless you turn off logging for specific levels (you do this through .properties file settings), or if you install a Filter. This does mean that you don't have to recompile your code to enable logging. There are three primary ways to limit the output from logging operations. The first mechanism is simply to not have Logger method calls in your code. However this is impractical if you eventually want logging messages. That is, if you don't have the Logger method calls in your code, they'll never happen. In fact, that is what most version 1.3 code does now. The second way to limit the output is to tell the Logger that you only want messages over a certain threshold. For instance, if the threshold is CONFIG, you won't get any FINE, FINER, and FINEST messages. Instead, you get only those messages with a priority level of CONFIG or higher. You can configure the default output level for the Logger by modifying the logging.properties file. Alternatively, you can specify the level to the Logger: logger.setLevel(Level.CONFIG); The third way to limit output is the most flexible. It requires you to install a Filter. The Filter interface defines a single method (isLoggable) that lets you examine the LogRecord before letting the Logger log the message. public boolean isLoggable(LogRecord record) The LogRecord contains both the Level and the text message to log. If either of these pieces of information doesn't match some predefined criteria, the isLoggable method of the Filter can return false. This indicates that the message shouldn't be logged. Returning true means the Logger can log the record. Let's look at a Filter that demonstrates these capabilities. The following Filter does not permit logging of consecutive messages that have the same level. The Filter remembers the last level logged. If the next logging request is for a message with the same level, the Filter rejects the request and returns false. package com.example; import java.util.logging.*; public class DupeFilter implements Filter { private Level last; public boolean isLoggable(LogRecord record) { Level level = record.getLevel(); boolean returnValue = !level.equals(last); last = level; return returnValue; } } To use the Filter, you need to install it into the Logger: logger.setFilter(new DupeFilter()); Only one Filter can be installed per logger. Now let's use the Filter, by running the following test program. package com.example; import java.util.logging.*; public class TestDupe { private static Logger logger = Logger.getLogger("com.example"); public static void main(String argv[]) { logger.setFilter(new DupeFilter()); logger.log(Level.SEVERE, "Severe Testing"); logger.log(Level.SEVERE, "Severe Testing 1"); logger.log(Level.SEVERE, "Severe Testing 2"); logger.log(Level.SEVERE, "Severe Testing 3"); logger.info("Testing"); logger.log(Level.SEVERE, "Severe Testing 4"); } } Executing the program will send the following lines to the console (with the current date and time): Oct 16, 2002 12:58:59 PM com.example.TestDupe main SEVERE: Severe Testing Oct 16, 2002 12:58:59 PM com.example.TestDupe main INFO: Testing Oct 16, 2002 12:58:59 PM com.example.TestDupe main SEVERE: Severe Testing 4 For more information about the Java Logging APIs see, "Java Logging APIs" (http://java.sun.com/j2se/1.4/docs/guide/util/logging/index.html) . - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CONTROLLING FOCUS TRAVERSAL SEQUENCING One of the new features of the J2SE version 1.4 release is the revamped focus management subsystem. Prior versions had bugs, platform inconsistencies and irregularities when heavyweight AWT components and lightweight Swing components were mixed. The new subsystem focuses on a centralized KeyboardFocusManager that oversees and controls active windows and their component for focus. Some of the new features of the revamped focus management subsystem are relatively minor. For instance, the FocusEvent now permits you to find out the opposite component during a focus event. From a component that gains focus, you can find out which component lost focus. Conversely, from a component that lost focus, you can find out which component gained focus. Other changes are a bit more complex. For instance, while the TAB and Shift-TAB are the typical focus traversal keys, if you want to add to or modify the set of keys used for traversal support, you no longer have to attach a KeyListener. In the J2SE 1.4 release, there are four separate sets of keys managed for each container to control focus traversal. If you replace any of these sets with a different set of traversal keys, keyboard focus traversal support changes to the new set of keys. You acquire the initial set of focus traversal keys by asking the container. You need to pass in one of the appropriate constants from the KeyboardFocusManager class: FORWARD_TRAVERSAL_KEYS, BACKWARD_TRAVERSAL_KEYS, UP_CYCLE_TRAVERSAL_KEYS, and DOWN_CYCLE_TRAVERSAL_KEYS. Set set = content.getFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); This initial set of focus traversal keys is read-only. However, you can create a new set with the initial set of keys. Then you can add to the new set: KeyStroke forward = KeyStroke.getKeyStroke("RIGHT"); set = new HashSet(set); set.add(forward); Then, after you complete the set, you put the set back: content.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set); Here's an example. The following program adds left/right arrow key selection to the focus traversal set for moving to the previous and next component, respectively. Try it. Compile and run the program. It displays a row of numbered buttons. Use the left and right arrow keys to move from one button to the next. import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.util.*; public class Move { public static void main(String args[]) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); Container content = frame.getContentPane(); content.setLayout(new FlowLayout()); for (int i=0; i<10; i++) { JButton button = new JButton(""+i); content.add(button); } Set set = content.getFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); KeyStroke forward = KeyStroke.getKeyStroke("RIGHT"); set = new HashSet(set); set.add(forward); content.setFocusTraversalKeys( KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, set); set = content.getFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS); KeyStroke backward = KeyStroke.getKeyStroke("LEFT"); set = new HashSet(set); set.add(backward); content.setFocusTraversalKeys( KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, set); frame.pack(); frame.show(); } } One thing worth mentioning is that hiding, removing, disabling, and making the focus owner non-focusable also affects focus transfer -- not just through focus traversal keys. Suppose you don't want to change the keys used for focus traversal, but instead you want to change the order that different components in a container are visited. To change the visit order, you use the FocusTraversalPolicy. A FocusTraversalPolicy defines five operations: o What component is first in a container o What component is last in a container o What component is the next component after a specific component o What component is the previous component before a specific component o Which component is the default (initial) selection for the container The abstract java.awt.FocusTraversalPolicy class offers several concrete implementations to choose from. (The default is different for AWT and Swing.) There are four predefined policies. o java.awt.ContainerOrderFocusTraversalPolicy o java.awt.DefaultFocusTraversalPolicy (AWT default) o javax.swing.InternalFrameFocusTraversalPolicy o javax.swing.LayoutFocusTraversalPolicy (Swing/mixed default) o javax.swing.SortingFocusTraversalPolicy The ContainerOrderFocusTraversalPolicy and DefaultFocusTraversalPolicy are similar in that they move focus based on the order their components were added to a Container. InternalFrameFocusTraversalPolicy deals with JInternalFrame and its initial component. LayoutFocusTraversalPolicy and SortingFocusTraversalPolicy are the most interesting. With LayoutFocusTraversalPolicy, the policy is based on the physical location of components on the screen. This takes into consideration size, position, and orientation. SortingFocusTraversalPolicy relies on a Comparator to do the ordering for you. Just create the Comparator to order any two components and it controls the focus traversal sequencing order. Here's an example that uses SortingFocusTraversalPolicy. The following Comparator offers an alphabetical focus traversal sequence based on the text label of a button. You combine the Comparator with a SortingFocusTraversalPolicy to get that sequencing. The Comparator could offer support for other types, but for simplicity, it assumes that everything in the container is a JButton. (The container itself isn't -- so an instanceof is needed.) import javax.swing.*; import java.util.*; public class AlphaOrder implements Comparator { public int compare(Object one, Object two) { if (! (one instanceof JButton)) return 1; if (! (two instanceof JButton)) return 1; JButton button1 = (JButton)one; JButton button2 = (JButton)two; String text1 = button1.getText(); String text2 = button2.getText(); return text1.compareTo(text2); } } To make sure the container itself isn't in the focus traversal order, you need to make one additional change. By subclassing SortingFocusTraversalPolicy, you can automatically reject non-JButton components. import java.awt.*; import javax.swing.*; import java.util.*; public class AlphaOrderFocusTraversalPolicy extends SortingFocusTraversalPolicy { AlphaOrderFocusTraversalPolicy() { super(new AlphaOrder()); } protected boolean accept(Component aComp) { if (aComp instanceof JButton) { return super.accept(aComp); } return false; } } The following program demonstrates the Comparator in action with the SortingFocusTraversalPolicy subclass. The program displays a screen that contains a series of buttons in a panel. Traversal of the buttons is controlled by the new focus traversal policy set. Notice as you hit the tab key, focus cycles through the components in alphabetical order (with shift-TAB going in the reverse direction). You can create similar comparators for other orderings. import java.awt.*; import javax.swing.*; public class AlphaPolicy { public static void main(String args[]) { String labels[] = {"Henry VII", "Henry VIII", "Edward VI", "Jane", "Mary I", "Elizabeth I"}; JFrame frame = new JFrame(); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); Container content = frame.getContentPane(); JPanel panel = new JPanel(); panel.setFocusTraversalPolicy( new AlphaOrderFocusTraversalPolicy()); panel.setFocusCycleRoot(true); for (int i=0, n=labels.length; i