Welcome to the Core Java 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).
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.
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".
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:
- What component is first in a container
- What component is last in a container
- What component is the next component after a specific component
- What component is the previous component before a specific component
- 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<n; i++) {
JButton button = new JButton(labels[i]);
panel.add(button);
}
content.add(panel, BorderLayout.CENTER);
frame.pack();
frame.show();
panel.getComponent(0).requestFocusInWindow();
}
}
|
For additional information on the changes to the AWT Focus
Subsystem, see "The AWT Focus Subsystem".
IMPORTANT: Please read our Terms of Use, Privacy, and Licensing
policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html
Comments? Send your feedback on the Core Java Technologies Tech Tips to: jdc-webmaster@sun.com
Subscribe to other Java developer Tech Tips:
- Enterprise Java Technologies Tech Tips. Get tips on using
enterprise Java technologies and APIs, such as those in the
Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless
Java technologies and APIs, such as those in the Java 2
Platform, Micro Edition (J2ME).
To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page,
choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".
ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright 2002 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
Sun, Sun Microsystems, Java, Java Developer Connection, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun
Microsystems, Inc. in the United States and other countries.
|