|
Welcome to the Core Java Technologies Tech Tips for October 19, 2004. 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:
Queues and Delayed Processing
Getting to Know Synth
These tips were developed using the Java 2 Platform Standard Edition Development Kit 5.0 (JDK 5.0). Download JDK 5.0.
This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.
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.
For more Java technology content, visit these sites:
java.sun.com - The latest Java platform releases, tutorials, and newsletters.
java.net - A web forum for collaborating and building solutions together.
java.com - The marketplace for Java technology, applications and services.
QUEUES AND DELAYED PROCESSING
The J2SE 5.0 release adds a new top-level Queue interface to the Collections Framework, to go along with the existing Map, List, and Set interfaces. Typically, queues are a first in, first out data structure, though for some implementations such as priority queues, elements might not be added at the end of a queue. You can think of a queue and its first in, first out structure much like the line at a bank. A bank teller checks to see if there are customers in the line. The teller then handles the first customer in the line, processing any transactions for that person. In doing so, the teller removes the customer from the waiting line.
In addition to the new Queue interface in J2SE 5.0,there are some new queue implementations. One such implementation is a DelayQueue. In a DelayQueue, the items in the queue can't be processed until a delay has happened. In this tip, you'll learn about the new Queue interface and the DelayQueue implementation.
First, let's examine the Queue interface. This interface extends Collection and adds its own series of five methods:
E element()
boolean offer(E o)
E peek()
E poll()
E remove()
As a result of the new support in J2SE 5.0 for generics, E here can be any data type, as specified by the type of element defined when the Queue got created.
Although you can certainly use the Collection interface methods for adding and removing elements from a Queue, it is recommended that you limit yourself to those in the Queue interface. That's because the methods enforce additional behavioral requirements. For instance, instead of adding an element to the queue with the Collection.add method, you offer it to the queue with the offer method. Why the difference? An add operation can fail -- one example is if a queue is bounded in size (using the bank line analogy, there can't be more than 10 people waiting.) With the add method of Collection, add fails by throwing an exception. By comparison, the offer method "fails" by returning false. So by using the offer method, you can save exception handling for truly exceptional cases (especially since the exception thrown is an unchecked runtime exception).
The other four methods of Queue can be described in pairs: remove/poll and element/peek. Both the remove and poll methods of Queue are used to remove the first element, or "head", of the queue. The remove method throws an exception when it's called with an empty collection. The poll method simply returns null in this case. Instead of removing the head element, you can just look at the element. That's where the element and peek methods are used. Here, the element method throws an exception on an empty queue, and peek returns null. Because queues are typically used for processing tasks, having an empty queue isn't necessarily an exceptional condition. As a result, the poll/peek model tends to be the more appropriate model to follow.
Queue is typically used in a way similar to the following:
class Producer implements Runnable {
private final Queue queue;
Producer(Queue q) { queue = q; }
public void run() {
try {
while(true) { queue.offer(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable {
private final Queue queue;
Consumer(Queue q) { queue = q; }
public void run() {
try {
Object o;
while((o = queue.poll()) != null) { consume(o); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
One question you might have is what happens when the queue is full (for the producer) or empty (for the consumer)? That question is a good reason to start the discussion of another new queue interface: BlockingQueue. Instead of using Queue to blindly add elements (with offer) and remove them (with poll), you can add elements with the put method of BlockingQueue and remove them with the take method. Both put and take cause the calling thread to block if a certain condition is true. For put, the blocking condition is a full queue. For take, the blocking condition is an empty queue.
The typical usage pattern for BlockingQueue is:
class Producer implements Runnable {
private final BlockingQueue queue;
Producer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { queue.put(produce()); }
} catch (InterruptedException ex) { ... handle ...}
}
Object produce() { ... }
}
class Consumer implements Runnable {
private final BlockingQueue queue;
Consumer(BlockingQueue q) { queue = q; }
public void run() {
try {
while(true) { consume(queue.take()); }
} catch (InterruptedException ex) { ... handle ...}
}
void consume(Object x) { ... }
}
Each created producer waits if put tries to add a newly produced item to a full queue, and take waits until there is something to take. The while(true) condition will never change if the queue is empty.
DelayQueue is a concrete implementation of the BlockingQueue interface. Items added to a DelayQueue must implement the new Delayed interface, which has a single method: long getDelay(TimeUnit unit). DelayQueue works as a time-based scheduling queue that is backed by a priority heap data structure. In other words, when you add an element to the queue, you specify how long the queue must wait before the element can be processed.
To demonstrate, the following program, DelayTest, provides an implementation of the Delayed interface that works in seconds. The key things to know are (1) a nanosecond is a billionth of a second, and (2) there is a new method of System called nanoTime that allows you to work in nanosecond units. Working in nanoseconds is important because the getDelay method wants the number of nanoseconds returned.
import java.util.Random;
import java.util.concurrent.Delayed;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;
public class DelayTest {
public static long BILLION = 1000000000;
static class SecondsDelayed implements Delayed {
long trigger;
String name;
SecondsDelayed(String name, long i) {
this.name = name;
trigger = System.nanoTime() + (i * BILLION);
}
public int compareTo(Delayed d) {
long i = trigger;
long j = ((SecondsDelayed)d).trigger;
int returnValue;
if (i < j) {
returnValue = -1;
} else if (i > j) {
returnValue = 1;
} else {
returnValue = 0;
}
return returnValue;
}
public boolean equals(Object other) {
return ((SecondsDelayed)other).trigger == trigger;
}
public long getDelay(TimeUnit unit) {
long n = trigger - System.nanoTime();
return unit.convert(n, TimeUnit.NANOSECONDS);
}
public long getTriggerTime() {
return trigger;
}
public String getName() {
return name;
}
public String toString() {
return name + " / " + String.valueOf(trigger);
}
}
public static void main(String args[])
throws InterruptedException {
Random random = new Random();
DelayQueue<SecondsDelayed> queue =
new DelayQueue<SecondsDelayed>();
for (int i=0; i < 10; i++) {
int delay = random.nextInt(10);
System.out.println("Delaying: " +
delay + " for loop " + i);
queue.add(new SecondsDelayed("loop " + i, delay));
}
long last = 0;
for (int i=0; i < 10; i++) {
SecondsDelayed delay = (SecondsDelayed)(queue.take());
String name = delay.getName();
long tt = delay.getTriggerTime();
if (i != 0) {
System.out.println("Delta: " +
(tt - last) / (double)BILLION);
}
System.out.println(name + " / Trigger time: " + tt);
last = tt;
}
}
}
The DelayTest program places ten elements in the DelayQueue before processing them.
Here is the output for one run of DelayQueue:
Delaying: 8 for loop 0
Delaying: 7 for loop 1
Delaying: 2 for loop 2
Delaying: 4 for loop 3
Delaying: 0 for loop 4
Delaying: 9 for loop 5
Delaying: 3 for loop 6
Delaying: 4 for loop 7
Delaying: 6 for loop 8
Delaying: 2 for loop 9
loop 4 / Trigger time: 1883173869520000
Delta: 1.9995545
loop 2 / Trigger time: 1883175869074500
Delta: 0.0012475
loop 9 / Trigger time: 1883175870322000
Delta: 0.9995177
loop 6 / Trigger time: 1883176869839700
Delta: 0.9995187
loop 3 / Trigger time: 1883177869358400
Delta: 6.408E-4
loop 7 / Trigger time: 1883177869999200
Delta: 2.0001667
loop 8 / Trigger time: 1883179870165900
Delta: 0.9986953
loop 1 / Trigger time: 1883180868861200
Delta: 0.9995595
loop 0 / Trigger time: 1883181868420700
Delta: 1.001262
loop 5 / Trigger time: 1883182869682700
The output indicates that the item for loop 4 is set to start at time 0, that is, without delay:
Delaying: 0 for loop 4
So it runs first:
loop 4 / Trigger time: 1883173869520000
Loop 2 has a 2 second delay:
Delaying: 2 for loop 2
So it comes in next:
loop 2 / Trigger time: 1883175869074500
The delta here is 1.9995545, which is roughly 2 seconds:
Delta: 1.9995545
Similar deltas are there for the other loops.
For a more real-world example, instead of just printing out something when pulled from the DelayQueue, items in the queue might implement Runnable and you would call their run method.
For additional information about Queue, DelayQueue, and other changes to the Collections Framework with J2SE 5.0, see the document Collection Framework Enhancements.
GETTING TO KNOW SYNTH
Synth is a new look and feel added to project Swing in J2SE 5.0. Synth is a skinnable (that is, a customizable) look and feel, where the "skin" (that is, the user interface) is controlled by an XML file. Instead of customizing the look and feel by providing default properties to the UIManager in a Properties table, you load in an XML file with component definitions. That means you can create a custom look without writing code.
To demonstrate, here's an example of using the Synth look and feel:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.io.InputStream;
import java.text.ParseException;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JFrame;
import static javax.swing.JFrame.*;
import javax.swing.UIManager;
import javax.swing.plaf.synth.SynthLookAndFeel;
public class HelloSynth {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
SynthLookAndFeel synth = new SynthLookAndFeel();
try {
Class aClass = HelloSynth.class;
InputStream is =
aClass.getResourceAsStream("synth.xml");
if (is == null) {
System.err.println("Missing configuration file");
System.exit(-1);
}
synth.load(is, aClass);
} catch (ParseException e) {
System.err.println("Bad configuration file");
System.exit(-2);
}
try {
UIManager.setLookAndFeel(synth);
} catch
(javax.swing.UnsupportedLookAndFeelException e) {
System.err.println("Cannot change to Synth");
System.exit(-3);
}
JFrame frame = new JFrame("First");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
JLabel label = new JLabel("Hello, Synth");
label.setHorizontalAlignment(JLabel.CENTER);
frame.add(label);
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
The HelloSynth program creates a new SynthLookAndFeel object and loads an XML file, synth.xml, with synth.load(InputStream, Class). This means that you need the XML file to complete this first example:
<synth>
<style id="label">
<font name="Monospaced" size="18" style="BOLD ITALIC"/>
<opaque value="true"/>
<state>
<color value="PINK" type="FOREGROUND"/>
<color value="YELLOW" type="BACKGROUND"/>
</state>
</style>
<bind style="label" type="region" key="Label"/>
</synth>
Run the HelloSynth program with the synth.xml file. The program reads in the XML file, creates a JLabel, and adds it to the screen. Notice the absence of any color or font configuring of the label in the program. Instead, that's done through the XML file. The synth.xml file configures the label as follows: 18-point bold-italic font, opaque, foreground color of pink (text color), and background color of yellow. The HelloSynth program then draws the label according to those specifications.
java HelloSynth synth.xml
If you don't like that color-font combination, you can change the information in the XML file to produce something more appealing. For example, you might change the font to bold, the text color to red, and the background to black:
<synth>
<style id="label">
<opaque value="true"/>
<font name="Monospaced" size="18" style="BOLD"/>
<state>
<color value="RED" type="FOREGROUND"/>
<color value="BLACK" type="BACKGROUND"/>
</state>
</style>
<bind style="label" type="region" key="Label"/>
</synth>
By simply changing the XML file, and without recompiling your program, you change the look produced by the program.
java HelloSynth synth.xml
And that is the power of the Synth look and feel. It allows you to change the look produced by the program without reprogramming.
The format of the XML file used with Synth is described in the Synth File Format document. Inside the outermost synth tag, you can have tags such as <style>, <bind>, <font>, <color>, <imagePainter>, and <imageIcon>. There are also special properties that you can set, these are listed in the Component Specific Properties document. Essentially, you define styles and bind them to components, as shown in the previous examples.
The <style> element contains the most information. You can configure numerous elements for each named style:
<property>
<defaultsProperty>
<state>
<font>
<painter>
<imagePainter>
<backgroundImage>
<opaque>
<imageIcon>
<inputMap>
The <font> tag allows you to specify the font's name, size, and style. Supported styles are PLAIN, BOLD, and ITALIC. To get both BOLD and ITALIC, provide both in a quoted string, with a space between the two:
<font style="BOLD ITALIC"/>
The <state> tag can be more complex than shown in the examples. For instance, if you describe a button, and you want a different look when the mouse is over the button, you could configure a MOUSE_OVER state, as follows:
<state value="MOUSE_OVER">
<font name="Serif" size="24" style="ITALIC"/>
</state>
This sets the font to a 24-point, italic, Serif font, at the appropriate state change.
In addition to MOUSE_OVER, other available state settings are as follows:
ENABLED
PRESSED
DISABLED
FOCUSED
SELECTED
DEFAULT
Depending on the component, you can configure any or all of these seven states to have different properties. Be careful if the state change causes a size change to the component. It will not revalidate itself when the state changes.
You can also work with icons, using the imageIcon element. For example, the following defines a style that changes the default tree icons:
<style id="tree">
<imageIcon id="collapsedIcon" path="collapsed.png"/>
<property key="Tree.collapsedIcon" value="collapsedIcon"/>
<imageIcon id="expandedIcon" path="expanded.png"/>
<property key="Tree.expandedIcon" value="expandedIcon"/>
</style>
The <style> element in the previous example needs to be bound to the tree components, so you need to specify a bind tag. The collapsed.png and expanded.png image files are then located relative to the Class object passed into the load method for the SynthLookAndFeel.
Synth also allows you to embed object references in the configuration file. Synth provides this through its support for long-term persistence of JavaBeans components. This support allows you to embed any object reference, however it's typically used to embed a reference to a Painter object. For example, if you include the following element in a configuration file:
<object id="gradient" class="GradientPainter"/>
it specifies a class named GradientPainter that is identified by the gradient id. The class extends the SynthPainter class.
Then, if you include the following element:
<painter method="textFieldBackground" idref="gradient"/>
it specifies that the paintTextFieldBackground method of GradientPainter (matching the idref of painter to the id of object) should be called for the component to which this painter is associated.
One thing Synth doesn't provide is a way to customize the feel of a component. For example, if you don't like having to click a button to select it, Synth doesn't provide a way to customize the selection action -- perhaps to voice activation.
With little or no programming, you can now skin the look and feel for your applications. By separating the look of an application from its internal logic, graphic design and programming tasks can be better separated, leading to better designed programs that are more easily maintained. When appropriate, consider switching to the Synth look and feel for both new and existing applications.
You can learn more about Synth in the article The Synth Look and Feel.
OTHER RESOURCES
Got a question about Java technologies, tools, or resources? Get answers at the following event:
Chat: Oct. 26th at 11:00 A.M. PDT/18:00 UTC
Project Looking Glass
Guests: Hideya Kawahara, Paul Byrne, and Deron Johnson
|
|
 |
 |
|
|
 |
 |
IMPORTANT: Please read our Licensing, Terms of Use, and Privacy policies:
http://developer.java.sun.com/berkeley_license.html
http://www.sun.com/share/text/termsofuse.html
Privacy Statement: Sun respects your online time and privacy (http://sun.com/privacy). You have received this based on your email preferences. If you would prefer not to receive this information, please follow the steps at the bottom of this message to unsubscribe.
Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=newslet
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 Sun Developer Network publications:
- Go to the Sun Developer Network Subscriptions 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/developer/JDCTechTips/index.html
Copyright 2004 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/developer/copyright.html
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.
|