5 things you didn't know about ... everyday Java tools

Java tools for everyday things, like parsing, timing, and sound

Some Java™ tools defy categorization and are frequently collected under the rubric of "things that work." This installment of 5 things offers up a collection of tools you'll be glad to have, even if you end up storing them in your kitchen drawer.

Share:

Ted Neward, Principal, Neward & Associates

Ted Neward photoTed Neward is the principal of Neward & Associates, where he consults, mentors, teaches, and presents on Java, .NET, XML Services, and other platforms. He resides near Seattle, Washington.



14 September 2010

Also available in Chinese Russian Japanese Portuguese

Many years ago, when I was in high school and thinking of pursuing a career as a fiction writer, I had a subscription to a magazine called Writer's Digest. One article, I remember, was a column about "bits of string too small to save," the columnist's way of describing a kitchen junk drawer overflowing with all the stuff that simply didn't belong anywhere else. That phrase has stayed with me, and it seems an apt description of my topic in this, my final column in this series (at least for a while).

The Java platform is full of "bits of string" — useful command-line tools and libraries that most Java developers will never even know about, let alone use. Many of them don't fit neatly into any of the programming categories I've covered so far in the 5 things series, but try them out anyway: some could still earn a place in your virtual kitchen drawer.

1. StAX

When XML first appeared on most Java developers' radar, back around the turn of the millennium, there were two basic approaches to parsing XML files. The SAX parser is essentially a giant state machine of events fired back at the developer via a series of callback methods. The DOM parser pulls the entire XML document into memory and slices it up into a series of discrete objects, which are linked together to form a tree. The tree describes the entire XML Infoset representation of the document. Both parsers had their drawbacks: SAX was too low-level to use, but DOM was too expensive, particularly for large XML files — the whole tree got to be a pretty hefty heap hog.

About this series

So you think you know about Java programming? The fact is, most developers scratch the surface of the Java platform, learning just enough to get the job done. In this series, Ted Neward digs beneath the core functionality of the Java platform to uncover little-known facts that could help you solve even the stickiest programming challenges.

Fortunately, Java developers figured out a third way to parse XML files, by modeling the document as a series of "nodes" that could be pulled one at a time from the document stream, examined, and either dealt with or discarded. This "stream" of "nodes" offered a middle-ground between SAX and DOM, and was named the "Streaming API for XML," or StAX. (The acronym was used to differentiate the new API from the original SAX parser, which went by the same name.) The StAX parser was later folded into the JDK and lives in the javax.xml.stream package.

Using StAX is pretty straightforward: Instantiate an XMLEventReader, point it at a well-formed XML file, then "pull" the nodes (usually in a while loop) one at a time for examination. For instance, you could list all of the targets in an Ant build script as in Listing 1:

Listing 1. Just point StAX at the target
import java.io.*;
import javax.xml.namespace.QName;
import javax.xml.stream.*;
import javax.xml.stream.events.*;
import javax.xml.stream.util.*;

public class Targets
{
    public static void main(String[] args)
        throws Exception
    {
        for (String arg : args)
        {
            XMLEventReader xsr = 
                XMLInputFactory.newInstance()
                    .createXMLEventReader(new FileReader(arg));
            while (xsr.hasNext())
            {
                XMLEvent evt = xsr.nextEvent();
                switch (evt.getEventType())
                {
                    case XMLEvent.START_ELEMENT:
                    {
                        StartElement se = evt.asStartElement();
                        if (se.getName().getLocalPart().equals("target"))
                        {
                            Attribute targetName = 
                                se.getAttributeByName(new QName("name"));
                            // Found a target!
                            System.out.println(targetName.getValue());
                        }
                        break;
                    }
                    // Ignore everything else
                }
            }
        }
    }
}

The StAX parser doesn't replace all the SAX and DOM code in the world, but it definitely can make certain tasks easier. It's particularly handy for tasks where you don't need the entire tree structure of the XML document.

Note that StAX also has a lower level API in the XMLStreamReader, if the event objects are still too high-level to work with. And, although perhaps not as useful as the readers, StAX also has an XMLEventWriter and, similarly, an XMLStreamWriter class for XML output.


2. ServiceLoader

Java developers frequently wish to decouple the knowledge necessary to use a component from the knowledge necessary to create one. This is typically accomplished by creating an interface describing the actions the component can perform, and using some kind of intermediary to create the component instances. Many developers use the Spring framework for this purpose, but there's another approach that is even more lightweight than a Spring container.

The ServiceLoader class from java.util can read a configuration file tucked away in a JAR file and find implementations of an interface, then make those implementations available as a list of objects to choose from. If you needed a personal-servant component to carry out your tasks, for instance, you could acquire one with the code in Listing 2:

Listing 2. IPersonalServant
public interface IPersonalServant
{
    // Process a file of commands to the servant
    public void process(java.io.File f)
        throws java.io.IOException;
    public boolean can(String command);
}

The can() method is what allows you to decide if the personal servant implementation being offered meets your requirements. The ServiceLoader in Listing 3 is essentially a list of IPersonalServants fitting those requirements:

Listing 3. Will this IPersonalServant do?
import java.io.*;
import java.util.*;

public class Servant
{
    public static void main(String[] args)
        throws IOException
    {
        ServiceLoader<IPersonalServant> servantLoader = 
            ServiceLoader.load(IPersonalServant.class);

        IPersonalServant i = null;
        for (IPersonalServant ii : servantLoader)
            if (ii.can("fetch tea"))
                i = ii;

        if (i == null)
            throw new IllegalArgumentException("No suitable servant found");
        
        for (String arg : args)
        {
            i.process(new File(arg));
        }
    }
}

Assuming there is an implementation of that interface, you'll get Listing 4:

Listing 4. Jeeves implements IPersonalServant
import java.io.*;

public class Jeeves
    implements IPersonalServant
{
    public void process(File f)
    {
        System.out.println("Very good, sir.");
    }
    public boolean can(String cmd)
    {
        if (cmd.equals("fetch tea"))
            return true;
        else
            return false;
    }
}

The only thing remaining is to configure the JAR file containing these implementations to be recognizable to the ServiceLoader— which can be tricky. The JDK wants the JAR file to have a META-INF/services directory that contains a text file whose name matches the fully-qualified interface class name — which, in this case, would be META-INF/services/IPersonalServant. The contents of that interface class name are implementation names, one per line, like Listing 5:

Listing 5. META-INF/services/IPersonalServant
Jeeves   # comments are OK

Fortunately, the Ant build system (ever since 1.7.0) includes a service tag to the jar task that makes this relatively painless, shown in Listing 6:

Listing 6. Ant-built IPersonalServant
    <target name="serviceloader" depends="build">
        <jar destfile="misc.jar" basedir="./classes">
            <service type="IPersonalServant">
                <provider classname="Jeeves" />
            </service>
        </jar>
    </target>

From here, it's trivial to invoke the IPersonalServant to ask it to carry out the commands. Parsing and executing those commands can be tricky, however. Which brings me to my next "little bit of string."


3. Scanner

Numerous Java utilities will assist you in building a parser, and the functional languages of the world have had some success in building parser function libraries (parser combinators). But what if all you have to parse is a comma-separated value file, or a series of space-delimited text files? Most utilities are overkill for this purpose, and yet String.split() is underkill. (As for regular expressions, remember that old saying that goes: "You had a problem and you tried to use regular expressions to solve it. Now you have two problems.")

The Java platform's Scanner class could be your best bet in these cases. Intended to be used as a lightweight text parser, Scanner provides a relatively straightforward API for pulling apart structured text into strongly-typed parts. Imagine, if you will, a series of DSL-like commands (from a Terry Pratchett Discworld novel) arranged in a text file like Listing 7:

Listing 7. Tasks for Igor
fetch 1 head
fetch 3 eye
fetch 1 foot
attach foot to head
attach eye to head
admire

You, or in this case a personal servant called Igor, could easily parse this series of nefarious commands using Scanner, shown in Listing 8:

Listing 8. Tasks for Igor, parsed by Scanner
import java.io.*;
import java.util.*;

public class Igor
    implements IPersonalServant
{
    public boolean can(String cmd)
    {
        if (cmd.equals("fetch body parts"))
            return true;
        if (cmd.equals("attach body parts"))
            return true;
        else
            return false;
    }
    public void process(File commandFile)
        throws FileNotFoundException
    {
        Scanner scanner = new Scanner(commandFile);
        // Commands come in a verb/number/noun or verb form
        while (scanner.hasNext())
        {
            String verb = scanner.next();
            if (verb.equals("fetch"))
            {
                int num = scanner.nextInt();
                String type = scanner.next();
                fetch (num, type);
            }
            else if (verb.equals("attach"))
            {
                String item = scanner.next();
                String to = scanner.next();
                String target = scanner.next();
                attach(item, target);
            }
            else if (verb.equals("admire"))
            {
                admire();
            }
            else
            {
                System.out.println("I don't know how to " 
                    + verb + ", marthter.");
            }
        }
    }
    
    public void fetch(int number, String type)
    {
        if (parts.get(type) == null)
        {
            System.out.println("Fetching " + number + " " 
                + type + (number > 1 ? "s" : "") + ", marthter!");
            parts.put(type, number);
        }
        else
        {
            System.out.println("Fetching " + number + " more " 
                + type + (number > 1 ? "s" : "") + ", marthter!");
            Integer currentTotal = parts.get(type);
            parts.put(type, currentTotal + number);
        }
        System.out.println("We now have " + parts.toString());
    }
    
    public void attach(String item, String target)
    {
        System.out.println("Attaching the " + item + " to the " +
            target + ", marthter!");
    }
    
    public void admire()
    {
        System.out.println("It'th quite the creathion, marthter");
    }
    
    private Map<String, Integer> parts = new HashMap<String, Integer>();
}

Assuming that Igor is registered in the ServantLoader, it's trivial to change the can() call to something more appropriate and reuse the Servant code from earlier, shown in Listing 9:

Listing 9. What Igor does
import java.io.*;
import java.util.*;

public class Servant
{
    public static void main(String[] args)
        throws IOException
    {
        ServiceLoader<IPersonalServant> servantLoader = 
            ServiceLoader.load(IPersonalServant.class);

        IPersonalServant i = null;
        for (IPersonalServant ii : servantLoader)
            if (ii.can("fetch body parts"))
                i = ii;

        if (i == null)
            throw new IllegalArgumentException("No suitable servant found");
        
        for (String arg : args)
        {
            i.process(new File(arg));
        }
    }
}

A real DSL implementation would obviously do more than just print to the standard out stream. I'll leave the details of tracking which parts are attached to which parts up to you (and faithful Igor, of course).


4. Timer

The java.util.Timer and TimerTask classes provide convenient and relatively simple ways to execute tasks on a periodic or one-shot delayed basis:

Listing 10. Execute later
import java.util.*;

public class Later
{
    public static void main(String[] args)
    {
        Timer t = new Timer("TimerThread");
        t.schedule(new TimerTask() {
            public void run() {
                System.out.println("This is later");
                System.exit(0);
            }
        }, 1 * 1000);
        System.out.println("Exiting main()");
    }
}

Timer has a number of schedule() overloads, indicating whether a given task is to be one-shot or a repeating, and takes a TimerTask instance to fire. TimerTask is essentially a Runnable (it implements it, in fact), but also comes with two additional methods: cancel(), to kill the task, and scheduledExecutionTime(), to return an approximation of when the task will fire.

Note that Timer creates a non-daemon thread to fire the tasks in the background, however, so in Listing 10 I have to kill the VM via a call to System.exit(). In a long-running program, the Timer is probably best created as a daemon thread (using the constructors that take a boolean parameter to indicate daemon status), so it won't keep the VM alive.

There's really nothing magical about this class, but it does allow us to be more clear about the intent of firing background tasks. It also can save a few lines of Thread code and serves as a lightweight ScheduledExecutorService (for anyone not quite ready to step into the whole java.util.concurrent package).


5. JavaSound

Although it doesn't come up often in a server-side application, sound can serve as a useful "passive" sense for administrators — and it's good material for a prank. Though it came late to the Java platform, the JavaSound API eventually made it into the core runtime library, tucked away in the javax.sound * packages — one package for MIDI files and one for sampled audio files (like the ubiquitous .WAV file format).

The "hello world" of JavaSound is to play a clip, shown in Listing 11:

Listing 11. Play it again, Sam
public static void playClip(String audioFile)
{
    try
    {
        AudioInputStream audioInputStream =
            AudioSystem.getAudioInputStream(
                this.getClass().getResourceAsStream(audioFile));
        DataLine.Info info = 
            new DataLine.Info( Clip.class, audioInputStream.getFormat() );
        Clip clip = (Clip) AudioSystem.getLine(info);
        clip.addLineListener(new LineListener() {
                public void update(LineEvent e) {
                    if (e.getType() == LineEvent.Type.STOP) {
                        synchronized(clip) {
                            clip.notify();
                        }
                    }
                }
            });
        clip.open(audioInputStream);        
        
        clip.setFramePosition(0);

        clip.start();
        synchronized (clip) {
            clip.wait();
        }
        clip.drain();
        clip.close();
    }
    catch (Exception ex)
    {
        ex.printStackTrace();
    }
}

Most of this is pretty straightforward (or at least as straightforward as JavaSound gets). The first step is to create an AudioInputStream around the file to play. In order to keep this method as context-free as possible, we grab the file as an InputStream out of the ClassLoader that loaded the class. (AudioSystem also takes a File or a String, if the exact path to the sound file is known ahead of time.) Once that's done, the DataLine.Info object is fed to the AudioSystem to get a Clip, which is the easiest way to play back an audio clip. (Other approaches offer more control over the clip — such as getting a SourceDataLine— but they're overkill for a simple "play.")

From here, it should be as simple as calling open() around the AudioInputStream. ("Should" meaning if you don't encounter the bug discussed in the next section.) Call start() to begin playback and drain() to wait until the playback is complete, and close() to release the audio line. The playback occurs on a separate thread, so a call to stop() will halt playback, and a subsequent call to start() will pick back up where the playback was paused; use setFramePosition(0) to reset back to the start.

No sound?

A nasty little bug has been filed with the JDK 5 release: On some platforms with short audio clips, the code appears to run fine, but ... no sound. Apparently the media player fires a STOP event sooner than it should on these short clips. (See the Resources section for a link to the bug page.)

This bug has been marked as "won't fix," but the workaround is pretty straightforward: register a LineListener to listen for STOP events, and when one is hit, call notifyAll() on the clip object. Then in the "caller" code, wait for the clip to finish (and the notifyAll() to be called) by calling wait(). On platforms where the bug isn't present, these steps will be redundant, but on Windows® and some Linux® distributions, they'll make the difference between "happy developer face" and "angry developer face."


In conclusion

Well there you have it, the kitchen sink of tooling. I know many of you are well-aware of the tools I've covered here, but my professional travels tell me that there are many who will benefit from this introduction, or from a reminder of tools long forgotten in the cluttered drawer.

I'm taking a brief hiatus from this series to allow other contributors an opportunity to chime in with their areas of expertise. But don't worry, I'll be back, whether on this series or on a new 5 things adventure that explores other areas of interest. Until then, I encourage you to keep investigating the Java platform to uncover the hidden gems that will make you a more productive programmer.

Resources

Learn

Get products and technologies

  • StAX, the Streaming API for XML, is a standard XML processing API that allows you to stream XML data from and to your application.

Discuss

  • Get involved in the My developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=518181
ArticleTitle=5 things you didn't know about ... everyday Java tools
publish-date=09142010