Writing a DSL for XML Documents

Published 01 May 07 07:11 AM | andersnoras 

In a previous post I lifted the veil on my proposed session for this year's JavaZone conference. The talk will be about how one can leverage the language features in Java 1.5 and 1.6 to create embedded DSLs with Java. The post showed off the client code for a DSL which allows developers to create an XML DOM document in a similar fashion to Java 1.7 and Visual Basic 9's language level XML support. In that post I promised to write a post about how the DSL and supporting APIs are implemented - now I'm keeping that promise.

The DSL makes use of two new features introduced in Java 1.5; static imports and varargs. The static import feature allows unqualified access to the static members of a type without inheriting from that type and the varargs feature allows you to pass an arbitrary number of arguments to a method without passing it as an array.

To make the DSL available as an embedded language you need add a static import to the client code.

import static com.andersnoras.jz07.XmlDsl.XmlBuilder.*;

This allows you to invoke the static methods on the XmlBuilder class without qualifying the reference as shown below.

@Test
public void example1()
{
    Document doc = document("1.1", true,
            element("Employees",
                    element("Employee",
                            comment("Highly valued employee..."),
                            element("Id", 42),
                            element("Name", "Arthur Dent"),
                            element("ContactInfo",
                                    element("Phonenumber", "555-5555"),
                                    element("Address", "Upda Road 42"),
                                    element("Country", "England")))
                    )
            )
    );
    assertNotNull(doc);
    printXml(doc);
}

The document, element, comment and a range of other constructs are part of the DSL. These constructs are actually nothing but static factory methods on the XmlBuilder class. For instance you can create an empty XML document by invoking one of the document methods.

public static Document document(final String version, final boolean standalone)
{
    Document result=document();
    result.setXmlVersion(version);
    result.setXmlStandalone(standalone);
    return result;
}

Creating an empty document isn't very impressive, so lets take a look at how the vararg feature comes into play.

public static Element element(final String elmName, final Node... childNodes)
{
    Element elm = doc.get().createElement(elmName);
    for (Node node : childNodes)
    {
        if (node instanceof Attr)
        {
            elm.setAttributeNode((Attr) node);
        }
        else
        {
            elm.appendChild(node);
        }
    }
    return elm;
}

Above you can see one of the Element factories. This method accepts any number of Node objects in addition to an element name. This allows us to write the client code like this.

Document doc = document(
	element("Root",
		element("Elm1",
			element("Elm1a","1A"),
			element("Elm1b","1B")
		),
		element("Elm2"),
		element("Elm3")
	)
);

Let's look at what happens above. First the innermost factories, that is the factories that create the Elm1a and Elm1b elements, will be invoked. These will produce two Element objects which again will be passed as an array via the varargs abstraction to the Elm1 factory. The Elm1 factory will create a new element in the DOM and append the two nodes Elm1a and Elm1b to this element.
Then the Elm2 and Elm3 elements will be created and again the Elm1, Elm2 and Elm3\ nodes will be passed as an array to the Root factory. Just like Elm1, the Root factory will create a new DOM element and append the child elements to this element. Finally the Root node will be passed to the document factory which appends it to the document and returns the entire document to the client.

Since every element added to a DOM document has to be created by the same document, astute readers probably wonder how the various factories can use nodes created by a number of different factories. Since the API consists of nothing but static methods, we're able to keep a reference to a static Document instance which we can use from within all the different factories to create new nodes. If you look at the element factory above, you'll notice that new elements are created with the following construct.

Element elm = doc.get().createElement(elmName); 

To make the API threadsafe, doc is a ThreadLocal which keeps the Document instance used to construct the current document. Below you can see how this is achieved.

private final static ThreadLocal<Document> doc = new ThreadLocal<Document>()
{
    public Document get()
    {
        Document instance=super.get();
        if (instance == null)
        {
            try
            {
                DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
                instance=builder.newDocument();
                super.set(instance);
            }
            catch (ParserConfigurationException e)
            {
                throw new RuntimeException("Could not create a new Document instance.", e);
            }
        }
        return instance;
    }
};

If there is an instance for the current thread, we assume that this is the document being built, otherwise we create a new instance. Throughout the factories we use this instance as an internal factory to create new nodes. When the document is returned to the client via one of the document factories the internal document instance is cloned and the ThreadLocal instance is removed.

public static Document document()
{
    Document result = (Document) doc.get().cloneNode(true);
    doc.set(null);
    return result;
}

Cloning the document also ensures that any orphan nodes (you can create an orphan by calling any node factory outside the context of the document factory) will be removed from the document since these never will be added to the DOM unless you do so through the hierarchy of factories.

This DSL makes building XML DOMs much easier than it would be using bare JDOM code, and since the client code resembles the structure of an XML document readability is increased. This is one of the benefits of using DSLs for different tasks when writing code. Rubyists have been using embedded DSLs for this for a long time. The Ruby Electric XML (REXML) API has for long been the XML API of choice for most Ruby developers. Even if my little DSL is not as powerful as this API, it shows that you can create embedded DSLs with static languages such as Java. DSLs aren't really something that is only available to dynamic language buffs or Antlr geeks. It is a question of programming style.

The code for my little XML DSL can be downloaded from here.

Filed under: ,

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

# Anders Norås' Blog said on May 23, 2007 11:54 AM:

I just got an email from the program committee for the JavaZone conference confirming that my proposed

# Aaron Harnly said on October 4, 2007 4:40 PM:

...and slowly, slowly, language design approaches Lisp ;)

# andersnoras said on October 5, 2007 1:17 AM:

@Aaron;

Funny that you mention this, because when you wrote your comment, I was at the Oslo JUG giving a talk on Java DSLs (and Quaere of course) and I made the exact same point when I showed the XML example.

# MIraclestudios said on June 18, 2008 2:09 AM:

Come fly with us on the wings of creativity, we will design an original , concept based adobe for your business.

We are not for sprouting "ugliness" on the web, in fact we are for applying the creative "Magic Wand" : which will transform all the ugliness in to beauty.

Are you ready to try our "Creative Magic Wand" on your website .

Cautions: Some clients experienced intense heart palpitation's after seeing the reincarnation of their online assets by MIracle Studios.

Don't go by our words! Just take glimpse at our award winning website : http://www.miraclestudios.in

Take a glimpse at our award winning web design : http://www.miraclestudios.in

Leave a Comment

(required) 
(optional)
(required) 
Enter the code you see below