Introducing CoffeeDOM, a JDOM fork for Java 5

When it comes time to work with XML in Java, the first thing I usually do is go to the JDOM website to check for a Java 5 update. Unfortunately, I am always disappointed. There has not been a major JDOM release in over 6 years and, if the JDOM mailing list is to be believed, no Java 5 version is planned. As a result, I have decided to take my own initiative and make CoffeeDOM, a JDOM fork with Java 5 support.

CoffeeDOM is intended as a natural evolution for JDOM developers. As such, there have been minimal changes to the API. CoffeeDOM adds support for Java 5 features like generics, enums, and covariant method return types, and reduces the amount of boilerplate required by making previously checked exceptions (like JDOMException) unchecked. In this article, I will briefly go over these changes.

(If you don’t want to bother with the article, you can skip right to the Google Code page or browse the Javadoc API documentation.)

Generic collections

JDOM makes heavy use of the Java Collections Framework, principally Lists and Iterators. Thus, in JDOM, to get a list of all child elements of an element, we do the following:

Element element = ...;
List<?> children = element.getChildren();
for (Object childObject : children) {
    // Cast :(
    Element child = (Element) childObject;
 
    // ...
}

See that pesky cast? How inconvenient. Another approach is to cast the List:

Element element = ...;
// Cast :(
List<Element> children = (List<Element>) element.getChildren();
for (Element child : children) {
    // ...
}

… but then we would need to either ignore a warning (not recommended) or add a @SuppressWarnings("unchecked") annotation. Neither is a desirable solution.

Fortuantely, CoffeeDOM comes to the rescue. In CoffeeDOM, all JCF classes like List, Collection, Iterable, etc. have appropriate generic parameters:

Element element = ...;
List<Element> children = element.getChildren();
for (Element child : children) {
    // ...
}

Enjoy the comfort of compile time type-checking.

Covariant method return types

In JDOM, in order to copy nodes, the built-in Object#clone() method is used:

Element element = ...;
Element clonedElement = (Element) element.clone();

Oh look, another silly cast. Fortunately, thanks to covariant method return types, CoffeeDOM eliminates this unneeded cast:

Element element = ...;
Element clonedElement = element.clone();

CoffeeDOM also removes the need for casting when using detach, setParent and getParent (in most cases).

Enums instead of int constants

This one is pretty straightforward. In JDOM, when we make a new Attribute, we can set the attribute type using an int constant like Attribute.CDATA_TYPE, Attribute.ENTITIES_TYPE, etc. Here’s a (contrived) example:

Element element = ...;
Attribute attribute = new Attribute("name", "value", 
        Attribute.UNDECLARED);
element.setAttribute(attribute);
// ...or just element.setAttribute("name", "value")

Unfortunately, modern IDEs have issues with int constants (they can’t tell which ones go with a particular method). Moreover, operations like iterating through the enum or converting from a string to a constant, are more difficult and messy.

CoffeeDOM remedies this by using Java 5′s enum structure. The above code turns into something rather similar:

Element element = ...;
Attribute attribute = new Attribute("name", "value", 
        Attribute.Type.UNDECLARED);
element.setAttribute(attribute);

…but with the added plus that we can do things like:

// Cycle through the constants.
for (Attribute.Type type : Attribute.Type.values()) {
    ...
}
 
// Get a constant from a string.
Attribute.Type type = Attribute.Type.valueOf("UNDECLARED");

Improved for-each compatibility

In Java 5, Sun juiced up the for loop by enabling it to cycle through Iterable instances. Unfortunately, since JDOM predated this enhancement, some methods return for loop unfriendly Iterator instances. This results in a lot of unneeded boilerplate:

Element element = ...;
Iterator<?> it = element.getDescendants();
while (it.hasNext()) {
    Content descendant = (Content) it.next();
    // ...
}

CoffeeDOM corrects this by making getDescendants return an Iterable instance instead, allowing it to be used with the for loop:

Element element = ...;
for (Content descendant : element.getDescendants()) {
    // ...
}

This change only affects classes implementing the JDOM Parent interface.

Unchecked exceptions

Constantly having to write exception checking code in Java can obfuscate code, especially if you’re just only re-throwing or logging checked exceptions to make the compiler happy. Consider this example in JDOM:

try {
    SAXBuilder builder = new SAXBuilder();
    Document document = builder.build("path/to/file.xml");
    XMLOutputter outputter = new XMLOutputter();
    outputter.output(doc, System.out);
} catch (IOException e) {
    e.printStackTrace();
} catch (JDOMException e) {
    e.printStackTrace();
}

To address this issue, CoffeeDOM changes CoffeeDOMException (the CoffeeDOM equivalent to JDOMException) to an unchecked exception. This reduces some of the exception noise:

// Ahhh... fewer checked exceptions.
try {
    SAXBuilder builder = new SAXBuilder();
    Document document = builder.build("path/to/file.xml");
    XMLOutputter outputter = new XMLOutputter();
    outputter.output(doc, System.out);
}  catch (IOException e) {
    e.printStackTrace();
}

Now with less noise

CoffeeDOM was designed to be an evolutionary step forward for JDOM. The main design goal of CoffeeDOM was to build on the excellent JDOM API by using modern Java features like generics, enums and covariance method return types. The result is better compile-time checking, fewer casts and less annoying exception handling.

Where do I get it? When can I use it?

CoffeeDOM is available now via Google Code as a downloadable JAR file or a Mercurial repository. It is also available from Maven Central using the following dependency markup:

<dependency>
  <groupId>org.cdmckay.coffeedom</groupId>
  <artifactId>coffeedom</artifactId>
  <version>1.0.0</version>
</dependency>

Learn more

As CoffeeDOM is brand new, there isn’t a whole lot of documentation available beyond the Javadoc reference and this article. However, since CoffeeDOM shares a similar API to JDOM, any JDOM tutorial can make a good starting point. See the JDOM documentation page for links to several tutorials that can get you started.

If code samples are your thing, the CoffeeDOM Mercurial repository has a CoffeeDOM-samples project that contains all the JDOM sample projects updated for CoffeeDOM.

10 Comments so far

  1. kgrad on May 20th, 2011

    Nice work! Getting rid of casts and checked exceptions will make my life much less frustrating. I also like that its already up in Maven!

  2. Dmitriy on May 23rd, 2011

    Thanks for breaking out of the java compatibility trap.

  3. Brian on June 7th, 2011

    I love it!… except the exception softening.

    I hate endlessly catching unnecessarily-checked exceptions as much as the next guy (looking at you, Reflection API…), but when actually ingesting external data, these exceptions need to be checked or else my idiot coworkers won’t write any handling code until it crashes in production.

    Obviously, we should write the handlers anyway. But my 2 cents are that IOException may be the _only_ exception that should be checked.

  4. cdmckay on June 7th, 2011

    Hmmm, that’s a good point. It did feel a bit odd to be writing a wrapper for IOException. Lemme think about it.

  5. Hani Suleiman on November 28th, 2011

    This is fairly awesome. It is a bit worrying though the huge list of dependencies in maven, why does it need icu4j, xalan, xerces, xom, jdom, and dom4j?

  6. cdmckay on November 28th, 2011

    @Hani Suleiman:
    Unfortunately, these are the dependencies that JDOM already has. I didn’t add any new ones.

  7. Hani Suleiman on November 28th, 2011

    Yeah it’d be worth trimming it a bit and using your own pom rather than relying on the jdom one (by the way, the culprit I think is jaxen, which seems to depend on every xml related jar ever invented).

  8. bmanc on December 6th, 2011

    Excellent. I’ve been looking for something like this.

  9. Thomas on January 16th, 2012

    Hate to tell you but there were people how did this before you. Like three years earlier.

    http://www.junlu.com/list/25/883674.html

  10. cdmckay on January 16th, 2012

    @Thomas:
    That’s alright, there’s room for two.

Leave a reply

ERROR: si-captcha.php plugin says GD image support not detected in PHP!

Contact your web host and ask them why GD image support is not enabled for PHP.

ERROR: si-captcha.php plugin says imagepng function not detected in PHP!

Contact your web host and ask them why imagepng function is not enabled for PHP.