A Fluent Interface for XML DOM APIs

The concept of fluent interfaces is certainly not new to me, but recently I’ve had the chance to bring it into practice by using EasyMock 2. EasyMock 2 uses the features of Java 5 to greatly simplify the programming model for mock objects.

This got me thinking about XML, and DOM in particular. Let’s say we want to create the following piece of XML using DOM:

<contacts>
   <contact>
      <name>John Doe</name>
      <phone type="home">555-12345</phone>
      <phone type="work">555-67890</phone>
   </contact>
</contacts>
 Basically, the way to create this XML using the standard DOM API look something like:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.newDocument();
Element name = doc.createElement("name");
name.setTextContent("John Doe");
Element phone1 = doc.createElement("phone");
phone1.setAttribute("type", "home");
phone1.setTextContent("555-12345");
Element phone2 = doc.createElement("phone");
phone2.setAttribute("type", "work");
phone2.setTextContent("555-67890");
Element contact = doc.createElement("contact");
contact.appendChild(name);
contact.appendChild(phone1);
contact.appendChild(phone2);
Element contacts = doc.createElement("contacts");
contacts.appendChild(contact);
doc.appendChild(contacts);

Not really fluent in any way, and quite verbose as well.Things improve somewhat with JDOM, because it uses concrete classes rather than interfaces, so we don’t need the Document factory. Also, most methods conveniently return this, so you can chain them:

Document doc = new Document();
Element name = new Element("name").setText("John Doe");
Element phone1 = new Element("phone").setAttribute("type", "home").setText("555-12345");
Element phone2 = new Element("phone").setAttribute("type", "work").setText("555-67890");
Element contact = new Element("contact").addContent(name).addContent(phone1).addContent(phone2);
Element contacts = new Element("contacts").addContent(contact);
doc.addContent(contacts);

This is certainly less verbose, but is it fluent? Those two concepts don’t necessarily mean the same thing. An API can be very succinct, but that does not necessarily mean it is fluent. Quoting Martin Fowler: The [fluent] API is primarily designed to be readable and to flow. The example above doesn’t flow; the approach is quite linear, even though we are creating a hierarchal tree.

Compare this to the API of XLinq, the XML component of Microsoft’s LINQ effort1:

XElement contacts = 
   new XElement("contacts", 
      new XElement("contact", 
         new XElement("name", "John Doe"), 
         new XElement("phone", "555-12345",  
            new XAttribute("type", "home")), 
         new XElement("phone", "555-67890", 
            new XAttribute("type", "work"))
        )
      ); 

Note that by indenting (and squinting a bit) the code to construct the XML tree shows the structure of the underlying XML. And this is due to the way the API was designed: clever use of .NET operator overloading, variable constructor arguments, and generics allow for this.

I really miss something like this in the Java space. So much, in fact, that I’m thinking about writing one myself. We don’t have operator overloading in Java, but we do have static imports. So that could mean we could end up with something along the lines of:

import static org.easydom.EasyDom.*;

Element contacts =
   element(name("contacts"),
      element(name("contact"),
         element(name("name"), "John Doe"),
         element(name("phone"), "555-1234",
            attribute(name("type"), "home")),
         element(name("phone"), "555-567890",
            attribute(name("type"), "work"))
         )
      );

The idea is that the EasyDom object contains nothing but static methods, just like EasyMock does. The name() method, for instance, returns a QName, necessary to construct an element using element(), or an attribute using attribute(). I’m not sure about the name() requirement, dropping that would make it more consice. So far, so good.

There are two things stopping me from creating this EasyDom API:

  1. Does the world really need another DOM API? I thought about returning or wrapping W3C DOM, but that would mean that only the DOM creation API is fluent, and the end result still has weird behavior like using NodeList where a List<Element> would be nicer.
  2. Creating a DOM API is hard. Creating a fluent interface is even harder. I’ve got enough work already.
Leave a comment to change my mind :) .

Update 20070514: Guillaume is right when he says that the Groovy Markup builder has a fluent interface. And on that note, Tareq Abed Rabbo has written a nice post about combining Groovy with Spring-WS.


1 - If you don’t know anything about LINQ, read this. I wish we had something similar in the Java space.

6 Comments

  1. Guillaume Laforge said,

    May 10, 2007 @ 1:26

    What about Groovy’s Markup builder?

    def mkp = new MarkupBuilder()
    mkp.contacts {
       contact {
          name("John Doe")
          phone(type: "home", "555-12345")
          phone(type:"work", "555-67890")
       }
    }
    
  2. Arjen Poutsma said,

    May 10, 2007 @ 1:50

    @Guillaume,

    Yeah, the Groovy Markup builder is definitely fluent as well; I forgot to mention it. Writing a fluent interface in a more dynamic language like Groovy is easier in a way. The only downside is that I cannot use it in Java.

  3. Craig Wals said,

    May 10, 2007 @ 5:05

    Anticipating Guillaume’s next question: Why do you need to use it in Java? Why not just use Groovy markup in a Groovy class, then have your Java code delegate to that class for XML handling? Push all of your XML access code into a DAO-like class (XAO?) and then call that whenever you need to create some XML.

  4. Dan Diephouse said,

    May 10, 2007 @ 18:26

    I like the idea. I think it’d be great to make this a “single class library”. I.e. you could just create a class and various people can stick it in their util directory instead of Yet Another Jar.

    Groovy isn’t the solution for all things because I don’t necessarily want to embed groovy in all my apps just to do this.

  5. Erik-Jan Blanksma said,

    May 11, 2007 @ 21:06

    I had a go at creating a utility class that mimmicks the Xlinq API. Here’s what I came up with:


    class XElement{
    List children = new ArrayList();
    Listattributes = new ArrayList();
    final String name;
    String text;

    public XElement(String name){
        this.name = name;
    }
    
    public XElement(String name, String text, XAttribute... xAttributes){
        this(name);
        this.text = text;
        attributes.addAll(Arrays.asList(xAttributes));
    }
    
    public XElement (String name, XElement... xElements){
        this(name);
        children.addAll(Arrays.asList( xElements));
    }
    
    public Document toDocument(){
        return build(this);
    }
    
    public static Document build(XElement xElement) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db;
        try {
            db = dbf.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document doc = db.newDocument();
        doc.appendChild(buildElement(doc, xElement));
        return doc;
    }
    
    public static Element buildElement(Document document, XElement xElement){
        Element element = document.createElement(xElement.name);
        for (Iterator iter = xElement.attributes.iterator(); iter.hasNext();) {
            XAttribute attribute = (XAttribute) iter.next();
            element.setAttribute(attribute.name, attribute.value);
        }
        if(xElement.text!=null){
            element.setTextContent(xElement.text);
        }else{
            for (XElement childElement : xElement.children) {
                element.appendChild(buildElement(document, childElement));
            }
        }
        return element;
    }
    

    }

    class XAttribute{
    final String name;
    final String value;

    public XAttribute(String name, String value){
        this.name = name;
        this.value = value;
    }
    

    }

    To create a document:

    Document doc =
    new XElement("contacts",
    new XElement("contact",
    new XElement("name", "John Doe"),
    new XElement("phone", "555-12345",
    new XAttribute("type", "home")),
    new XElement("phone", "555-67890",
    new XAttribute("type", "work"))
    )
    ).toDocument();

  6. Stefan Tilkov said,

    May 13, 2007 @ 18:54

    In Ruby:

    require 'builder'
    x = Builder::XmlMarkup.new(:target => $stdout, :indent => 2)
    x.contacts {
      x.contact {
        x.name('John Doe')
        x.phone '555-12345', :type => 'home'
        x.phone '555-67890', :type => 'work'
      }
    }
    

RSS feed for comments on this post