- Notifications
You must be signed in to change notification settings - Fork 10
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.
Descriptorbuilder = 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.
publicclassXHTMLHelperImplimplementsXHTMLHelper {
finalDocumentdocument;
publicXHTMLHelperImpl() {
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
} catch (Exceptionex) {
thrownewRuntimeException(ex);
}
}
@OverridepublicDocumentdone() {
returndocument;
}
@OverridepublicvoidstartElement(StringtagName, ObjectWrapper<ElementHelper> _helper1) {
Elementelement = document.createElement(tagName);
document.appendChild(element);
_helper1.set(newElementHelperImpl(element));
}
@OverridepublicvoidaddComment(Stringcomment) {
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.
publicclassElementHelperImplimplementsElementHelper {
privatefinalElementelement;
ElementHelperImpl(Elementelement) {
this.element = element;
}
@OverridepublicvoidendElement() {
// nothing
}
@OverridepublicvoidstartElement(StringtagName, ObjectWrapper<ElementHelper> _helper1) {
ElementnewElement = element.getOwnerDocument().createElement(tagName);
element.appendChild(newElement);
_helper1.set(newElementHelperImpl(newElement));
}
@OverridepublicvoidaddAttribute(Stringkey, Stringvalue) {
element.setAttribute(key, value);
}
@OverridepublicvoidaddComment(Stringcomment) {
CommentcommentNode = element.getOwnerDocument().createComment(comment);
element.appendChild(commentNode);
}
@OverridepublicvoidsetValue(Stringvalue) {
element.setTextContent(value);
}
}Here, a sample usage building an XML representation of a book library:
Documentdoc = XHTMLGenerator.createDocument(newXHTMLHelperImpl())
.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>
<bookISBN="978-0375703768">House of Leaves</book>
<bookISBN="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.
Documentdoc;
ElementBuilder_setValue<XHTMLBuilder<Document>> movies
= XHTMLGenerator.createDocument(newXHTMLHelperImpl())
.addComment("This is a list of movies in my library.")
.startElement("movies");
for (Moviemovie : 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.