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 children = (List) 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 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:
org.cdmckay.coffeedom
coffeedom
1.0.0
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.
11 comments