-
Notifications
You must be signed in to change notification settings - Fork 11
XHTML Builder Example
Builders are well suited for working with nested operations, and Flapi provides for this with its concept of blocks. Let's build a descriptor which can be used to generate an XML document. In Java this uses the w3c Document
and Element
classes primarily.
Descriptor builder = Flapi.builder()
.setPackage("unquietcode.tools.flapi.examples.xhtml.builder")
.setStartingMethodName("createDocument")
.setDescriptorName("XHTML")
.addMethod("addComment(String comment)").any()
.addMethod("done()").last(Document.class)
.startBlock("Element", "startElement(String tagName)").any()
.addMethod("setValue(String value)").atMost(1)
.addMethod("addAttribute(String key, String value)").any()
.addMethod("addComment(String comment)").any()
.addMethod("endElement()").last()
.addBlockReference("Element", "startElement(String tagName)").any()
.endBlock()
.build();
We have one block, called Element
. Each element can have any number of elements inside of it, just as in XML. We can provide text values of the element once, any number of attributes, and any number of comments.
Implementing the XHTMLHelper
isn't so bad in this case, because our actions more or less mirror what we would call on the normal objects. All we need to do is keep track of our top level document, and add elements and comments as needed.
public class XHTMLHelperImpl implements XHTMLHelper {
final Document document;
public XHTMLHelperImpl() {
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
@Override
public Document done() {
return document;
}
@Override
public void startElement(String tagName, ObjectWrapper<ElementHelper> _helper1) {
Element element = document.createElement(tagName);
document.appendChild(element);
_helper1.set(new ElementHelperImpl(element));
}
@Override
public void addComment(String comment) {
document.appendChild(document.createComment(comment));
}
}
Likewise, the ElementHelper
can be implemented simply by keeping track of an Element
object and calling the appropriate methods on it. This is the ideal case for a builder—wrapping an existing object's functionality.
public class ElementHelperImpl implements ElementHelper {
private final Element element;
ElementHelperImpl(Element element) {
this.element = element;
}
@Override
public void endElement() {
// nothing
}
@Override
public void startElement(String tagName, ObjectWrapper<ElementHelper> _helper1) {
Element newElement = element.getOwnerDocument().createElement(tagName);
element.appendChild(newElement);
_helper1.set(new ElementHelperImpl(newElement));
}
@Override
public void addAttribute(String key, String value) {
element.setAttribute(key, value);
}
@Override
public void addComment(String comment) {
Comment commentNode = element.getOwnerDocument().createComment(comment);
element.appendChild(commentNode);
}
@Override
public void setValue(String value) {
element.setTextContent(value);
}
}
Here, a sample usage building an XML representation of a book library:
Document doc = XHTMLGenerator.createDocument(new XHTMLHelperImpl())
.addComment("This is a list of books in my library.")
.startElement("books")
.startElement("book")
.addAttribute("ISBN", "978-0375703768")
.setValue("House of Leaves")
.endElement()
.startElement("book")
.addAttribute("ISBN", "978-0399533457")
.setValue("The Cloudspotter's Guide")
.endElement()
.endElement()
.done();
Which corresponds to the following output when the document is written to a stream:
<!--This is a list of books in my library.-->
<books>
<book ISBN="978-0375703768">House of Leaves</book>
<book ISBN="978-0399533457">The Cloudspotter's Guide</book>
</books>
Now, it is certainly not required that each method be called in a chain. Every method returns a typed value, and so it is possible to break up the chain to perform repeated operations. In the next usage, I'm iterating a list and creating elements each time, then going back to finish the document.
Document doc;
ElementBuilder_setValue<XHTMLBuilder<Document>> movies
= XHTMLGenerator.createDocument(new XHTMLHelperImpl())
.addComment("This is a list of movies in my library.")
.startElement("movies");
for (Movie movie : getMovieList()) {
movies.startElement(movie.mediaType)
.setValue(movie.title)
.endElement();
}
doc = movies.endElement().done();
printDocument(doc);
...which creates this document:
<!--This is a list of movies in my library.-->
<movies>
<DVD>Hedwig and the Angry Inch</DVD>
<DVD>Best in Show</DVD>
<VHS>Blow Dry</VHS>
</movies>
The full source is located under src/test in the examples package, accessible here.