Java tutorial
/** * Copyright (C) 2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.googlecode.jatl; import static org.apache.commons.lang.StringUtils.isBlank; import static org.apache.commons.lang.Validate.isTrue; import static org.apache.commons.lang.Validate.notEmpty; import static org.apache.commons.lang.Validate.notNull; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Stack; import org.apache.commons.lang.text.StrSubstitutor; import com.googlecode.jatl.Indenter.TagIndentSpot; /** * <a href="http://en.wikipedia.org/wiki/Fluent_interface">Fluent styled</a> * markup builder that writes to a {@link Writer}. * * <h2>Description</h2> * Writes XML markup to a {@link Writer}. * The Builder part of the name is somewhat misleading as * this class does not store or keep track of all the markup objects created but rather * writes using the writer as soon as it can. * Thus the order of operations performed on this class is very important and should * follow the order that you would write the markup. * * <h2>Usage</h2> * You should not use this class directly but rather one of its subclasses * that is parameterized with itself (see Extending). You generally define the markup you want to write * using Java anonymous classes that extend either a {@link MarkupBuilder} or {@link MarkupWriter}. * Markup builders write immediately to a writer where as a markup writer * writes when told to. But again in both cases you generally define the markup with an anonymous class. * <p> * For examples of use its best to see {@link Html} for an XHTML builder which will write XHTML * markup immediatly or {@link HtmlWriter} which allows you to define XHTML markup then write it later. * <p> * {@link MarkupWriter Markup writers} are more useful for MVC frameworks and {@link MarkupBuilder builders} * are more for convenience. * * <h2>Extending</h2> * If you would you would like to make your own builder you should subclass * this class or a subclass that has generic parameter (<T>). * <p> * <em>The custom builder should be parameterized to itself to support fluent style.</em> * <p> * <strong>Example:</strong> * <pre> public class MyMarkup extends MarkupBuilder<MyMarkup> { public MyMarkup(Writer builder) { super(builder); } protected MyMarkup getSelf() { return this; } public MyMarkup myTag() { return start("myTag"); } } * </pre> * <h2>Composition</h2> * Besides the extending method above you can achieve composition of markup builders by using MarkupWriter's instead. * See {@link MarkupWriter}. <p> * See {@link #write(MarkupWriter...)}. * * <h2>Writing tags and attributes</h2> * See {@link #start(String)} and {@link #end()} for writing tags. * See {@link #attr(String...)} for writting attributes. * * <h2>Variable expansion</h2> * Simple named variable replacements are supported through the <pre>${...}</pre> notation. * See {@link #bind(String, Object)}, and {@link #text(String)}. * * <h2>Pretty Printing aka Indenting</h2> * See #indent {@link Indenter}. For most cases you can use {@link SimpleIndenter} * * <h2>Namespaces</h2> * You can either manually maintain namespaces and namespace prefixes by setting the correct attributes and * then writing tags with the namespace prefix. Example: {@code start("prefix:tagName")}. * Or you can use the namespace methods: {@link #ns()}, {@link #ns(String)}, {@link #xmlns(String, String)} * The following Nested builders are very helpful for working with multiple XML schemas. * * <h2>Nested builders</h2> * Nested builders are an easy way to allow different markup styles to coexist elegantly. * An example might be HTML with in XSL. You would have an HTML builder nested inside * the XSL builder.<p> * <strong>Example:</strong> * <pre> * new Xslt(writer) {{ * ns("xsl"); * template().match("stuff"); * new Html(writer) {{ * html().body().done(); * }} * done(); * }} * </pre> * See {@link #MarkupBuilder(MarkupBuilder)}. * * <h2>Thread safety</h2> * <em>This class and subclasses are not thread safe.</em> * One way to make a builder thread safe is to synchronize on the passed in writer: * <pre> * synchronize(writer) { * new SomeBuilder(writer) {{ }}; * } * </pre> * <h2>Efficiency and Validation</h2> * To keep the builder efficient and elegant there is very little XML validation. * There is no guarantee that the markup generated will always be well formed. * Generally {@link #text(String) Text} will be escaped appropriately. * <p> * <em>For those seriously concerned with performance an appropriate implementation of * {@link Writer} should be given to the builder.</em> * <p> * For example: In a single threaded environment where you are writing to memory * <a href="http://commons.apache.org/io/apidocs/org/apache/commons/io/output/StringBuilderWriter.html"> * Commons IO StringBuilderWriter * </a> is a good choice over {@link StringWriter}. * <p> * On the other hand if you are writing very large XML document to a file * or network socket {@link PrintWriter} is probably a better choice. * * @author adamgent * @param <T> This should always be parameterized with the exact same * class that is extending the {@link MarkupBuilder} to support fluent style. * @see TagClosingPolicy * @see #start(String, TagClosingPolicy) * @see #attr(String...) * @see #end() * @see #done() */ public abstract class MarkupBuilder<T> { private Stack<Tag> tagStack = new Stack<Tag>(); private Writer writer; private Map<String, String> attributes = createMap(); private Map<String, Object> bindings = new HashMap<String, Object>(); private MarkupBuilder<?> previousBuilder = null; private int depth = 0; private String namespacePrefix = null; private Indenter previousIndenter = indentOn; private Indenter indenter = indentOn; private static final String q = "\""; /** * Create a builder using the given writer. * @param writer never <code>null</code>. */ public MarkupBuilder(Writer writer) { super(); notNull(writer, "writer"); this.writer = writer; } /** * Create a nested builder from given builder. * Make sure {@link #done()} is called when finished with the * nested builder so that the parent builder can resume using the writer. * * @param builder parent builder, never <code>null</code>. * @see #done() */ public MarkupBuilder(MarkupBuilder<?> builder) { this(builder, true); } /** * Use for deferred writer. * The writer should be {@link #setWriter(Writer) set} before * the builder is used. * * @see #setWriter(Writer) * @see MarkupWriter */ protected MarkupBuilder() { } /** * Create a nested builder from a builder or resume from a builder. * @param builder never <code>null</code>. * @param nested <code>true</code> means nested, <code>false</code> means resuming. */ public MarkupBuilder(MarkupBuilder<?> builder, boolean nested) { builder.checkWriter(); if (nested) builder.writeCurrentTag(); if (nested && !builder.tagStack.isEmpty()) { Tag t = builder.tagStack.peek(); this.depth = 1 + t.depth + builder.depth; } else { this.depth = builder.depth; } this.writer = builder.writer; builder.writer = null; if (nested) { //Clone the previous builders binding this.previousBuilder = builder; this.bindings = new HashMap<String, Object>(builder.bindings); } else { this.tagStack = builder.tagStack; this.bindings = builder.bindings; this.attributes = builder.attributes; } } /** * Needed for fluent style and Java parameterization limitations. * Almost all public methods should return whatever this method returns. * <p> * Most implementations only have to do: * <pre> * return this; * </pre> * @return the current builder which is usually <code>this</code> object. */ protected abstract T getSelf(); /** * Restores the current namespace prefix * to whatever the surrounding tags prefix is. * To set the namespace to the default namespace call: * <p> * {@code ns(null);} * @return maybe <code>null</code>. * @see #ns(String) */ public final T ns() { if (tagStack.isEmpty()) { return ns(null); } return ns(tagStack.peek().prefix); } /** * Sets the current namespace prefix. If a tag is started when * the prefix is set to a non-null non-empty string, the prefix will * be added in front of the tag. * <p> * <strong>Example:</strong><p> * <pre> * ns("html").div().end(); * </pre> * <strong>Result:</strong> * <p> * <pre> * <html:div></html:div> * </pre> * @param prefix maybe <code>null</code> * @return this, not <code>null</code>. */ public final T ns(String prefix) { this.namespacePrefix = prefix; return getSelf(); } /** * Sets the writer after the builder has been created. * Only useful with the empty constructor. * @param writer never <code>null</code> * @throws IllegalArgumentException if the writer has already been set or the given writer is null. */ public final void setWriter(Writer writer) { isTrue(this.writer == null, "Writer is already set."); notNull(writer, "writer"); this.writer = writer; } /** * Sets the indent depth after the builder has been created. * Only useful with the empty constructor. * @param depth should greater than or equal <code>0</code> */ public final void setDepth(int depth) { this.depth = depth; } /** * Sets the indenter. * @param indenter if <code>null</code> reverts to the previous builder. * @return the builder. * @see #indentOn * @see #indentOff * @see #indentSameLine */ public T indent(Indenter indenter) { if (indenter == null) this.indenter = previousIndenter; else { this.previousIndenter = this.indenter; this.indenter = indenter; } return getSelf(); } private final Map<String, String> getAttributes() { return attributes; } /** * Writes variable expanded escaped text inside a tag. * * @param text the text will be escaped and variables will be expanded. * @return never <code>null</code>. * @see #raw(String) * @see #bind(String, Object) */ public final T text(String text) { if (text != null) { writeCurrentTag(); write(escapeElementMarkup(expand(text))); } return getSelf(); } /** * Write text with out escaping or variable expansion. * @param text * @return never <code>null</code>. * @see #raw(String, boolean) */ public final T raw(String text) { return raw(text, false); } /** * Writes text with out escaping. * @param text * @param expand <code>true</code> does variable expansion. * @return never <code>null</code>. */ public final T raw(String text, boolean expand) { if (text != null) { writeCurrentTag(); text = expand ? expand(text) : text; write(text); } return getSelf(); } /** * Binds a named variables to be used for expansion in {@link #attr(String...) attributes} * and {@link #text(String) text}. Variables are represented with by <code>${...}</code>. * <p> * <strong>Example</strong> * <pre> * bind("name", "Ferris"); * text("${name}"); * </pre> * <p> * <em>Variables are expanded in order and can be referred in a later binding.</em> * <p> * <pre> * bind("firstName", "Adam"); * bind("lastName", "Gent"); * bind("fullName", "${firstName} ${lastName}"); * </pre> * @param name never <code>null</code> or empty. * @param value maybe <code>null</code>. * @return never <code>null</code>. */ public final T bind(String name, Object value) { notEmpty(name); Object v = value != null && value instanceof String ? expand(value.toString()) : value; bindings.put(name, v); return getSelf(); } /** * Removes a binding. * There is no failure if the binding does not exist. * @param name maybe <code>null</code>. * @return never <code>null</code>. * @see #bind(String, Object) */ public final T unbind(String name) { if (bindings.containsKey(name)) bindings.remove(name); return getSelf(); } /** * Convenience for {@link #bind(String, Object)} * @param nvps never <code>null</code>. * @return never <code>null</code>. */ public final T bind(Collection<Entry<String, Object>> nvps) { for (Entry<String, Object> nvp : nvps) { bind(nvp.getKey(), nvp.getValue()); } return getSelf(); } /** * Starts a tag using the default closing policy {@link TagClosingPolicy#NORMAL}. * <p> * Equivalent to: {@code start("tag", TagClosingPolicy.NORMAL)}. * * @param tag * @return this, never <code>null</code>. * @see #start(String, TagClosingPolicy) */ public final T start(String tag) { return start(tag, TagClosingPolicy.NORMAL); } /** * Starts a tag but does not immediately write it till the next * tag is started. * The {@link TagClosingPolicy} dictates whether or not the tag * needs to be {@link #end() closed}. Thus {@link #end()} does not need to be called * for all tags. * * @param tag never <code>null</code> or empty. * @param policy never <code>null</code>. * @return never <code>null</code>. * @see #end() */ public final T start(String tag, TagClosingPolicy policy) { writeCurrentTag(); Tag t = new Tag(tag); t.closePolicy = policy; t.depth = tagStack.size(); t.prefix = this.namespacePrefix; tagStack.push(t); return getSelf(); } /** * Adds attributes to the last {@link #start(String) start tag}. * Attributes do not need an {@link #end()} call. * @param attrs name value pairs. Its invalid for an odd number of arguments. * @return never <code>null</code>. * @throws IllegalArgumentException odd number of arguments. * @throws IllegalStateException if there are no open start tags. */ public final T attr(String... attrs) { isTrue(attrs.length % 2 == 0); if (tagStack.isEmpty() || (tagStack.peek().start)) { throw new IllegalStateException("There are no open tags to add attributes too. " + "Markup attributes should only be added " + "immediatly after starting a tag."); } checkWriter(); for (int n = 0, v = 1; v < attrs.length; n += 2, v += 2) { getAttributes().put(attrs[n], attrs[v]); } return getSelf(); } /** * Sets the default namespace on the last started tag. * @param uri if <code>null</code> nothing will happen. * @return this, never <code>null</code>. * @see #xmlns(String, String) */ public final T xmlns(String uri) { return xmlns(uri, null); } /** * Sets an XML namespace. * @param uri if <code>null</code> nothing will happen. * @param prefix if <code>null</code> or blank will act * like the default namespace and no prefix. * @return this, never <code>null</code>. */ public final T xmlns(String uri, String prefix) { if (isBlank(uri)) return getSelf(); String n = isBlank(prefix) ? "xmlns" : prefix + ":xmlns"; return attr(n, uri); } /** * Closes the inputed number of open tags. * @param i less than zero will do nothing. * @return never <code>null</code>. * @see #end() */ public final T end(int i) { while (i-- > 0 && !tagStack.isEmpty()) { end(); } return getSelf(); } /** * Closes the last {@link #start(String) start tag}. * This is equivalent to <code></tag> or <tag/> depending * on the {@link TagClosingPolicy}. * @return never <code>null</code>. * @see #start(String, TagClosingPolicy) * @see #endAll() * @see #done() */ public final T end() { checkWriter(); Tag t = tagStack.peek(); writeStartTag(t); writeEndTag(t); tagStack.pop(); attributes.clear(); return getSelf(); } /** * Closes all open tags. * @return never <code>null</code>. * @see #end() */ public final T endAll() { while (!tagStack.isEmpty()) { end(); } return getSelf(); } /** * Call when completely done with the builder. * This is required for nested builders. * <p> * If a builder is used again after {@code done()} is called * an exception (usually {@link IllegalArgumentException}) will be thrown. */ public final void done() { endAll(); if (previousBuilder != null) { isTrue(previousBuilder.writer == null, "The previous builder has its writer set. It should not."); previousBuilder.writer = writer; } this.writer = null; } /** * Writes immediately by passing the writer to each {@link MarkupWriter} * in the order passed in. * @param writers never <code>null</code>, null elements passed in are ignored. * @return never <code>null</code>. * @since 0.2.2 */ public final T write(MarkupWriter... writers) { checkWriter(); int depth = 1 + (tagStack.isEmpty() ? 0 : tagStack.peek().depth); writeCurrentTag(); notNull(writers, "writers"); for (MarkupWriter w : writers) { if (w != null && w instanceof MarkupBuilderWriter) { ((MarkupBuilderWriter) w).write(this.writer, this.depth + depth); } else if (w != null) { w.write(this.writer); } } return getSelf(); } private void writeCurrentTag() { checkWriter(); if (!tagStack.isEmpty()) { Tag current = tagStack.peek(); if (current.empty && !current.end) { current.empty = current.closePolicy.isAlwaysSelfClosing(); writeStartTag(current); if (current.closePolicy.isAlwaysSelfClosing()) { end(); } } } attributes.clear(); } private void writeStartTag(Tag t) { if (!t.end && !t.start) { writeIndent(t, TagIndentSpot.BEFORE_START_TAG); writeTag(t.getQualifiedName(), t.isSelfClosing()); writeIndent(t, TagIndentSpot.AFTER_START_TAG); if (t.isSelfClosing()) { writeIndent(t, TagIndentSpot.AFTER_END_TAG); } t.end = t.isSelfClosing(); t.start = true; } } private void writeTag(String tag, boolean close) { Map<String, String> attrs = getAttributes(); write("<" + tag); if (attrs != null && !attrs.isEmpty()) { write(" "); writeAttributes(attrs); } if (close) write("/>"); else write(">"); } private void writeEndTag(Tag t) { if (!t.end) { writeIndent(t, TagIndentSpot.BEFORE_END_TAG); write("</" + t.getQualifiedName() + ">"); t.end = true; writeIndent(t, TagIndentSpot.AFTER_END_TAG); } } private void writeAttributes(Map<String, String> attrs) { if (attrs == null) return; boolean space = false; for (Entry<String, String> attr : attrs.entrySet()) { String key = attr.getKey(); String value = attr.getValue(); if (space) { write(" "); } else { space = true; } writeAttr(key, value); } } private void writeAttr(String name, String value) { if (value != null && name != null) { write(expand(name + "=" + q(value))); } } private String q(String raw) { return q + escapeAttributeMarkup(expand(raw)) + q; } private String expand(String text) { StrSubstitutor s = new StrSubstitutor(bindings); return s.replace(text); } private void write(String raw) { checkWriter(); try { writer.write(raw); } catch (IOException e) { throw new RuntimeException("Writer for Builder failed:", e); } } private void checkWriter() { notNull(writer, "The writer has not been seet " + "or is in use by another builder."); } private Map<String, String> createMap() { return new LinkedHashMap<String, String>(); } /** * DEPRECATED. * @param raw maybe <code>null</code>. * @return maybe <code>null</code> if null for input. * @see #text(String) * @deprecated Please use {@link #escapeElementMarkup(String)} and {@link #escapeAttributeMarkup(String)} */ protected String escapeMarkup(String raw) { return MarkupUtils.escapeElementEntities(raw); } /** * The strategy for escaping element markup. * The default is escape for XML. * @param raw maybe <code>null</code>. * @return maybe <code>null</code> if null for input. * @see #text(String) * @see #escapeAttributeMarkup(String) */ @SuppressWarnings("deprecation") protected String escapeElementMarkup(String raw) { return escapeMarkup(raw); } /** * The strategy for escaping attribute markup. * The default escaping is XML. * Entities will be used for white space characters: * <code>#xD, #xA, #x9</code><p> * CR, newline, and tab, respectively. * @param raw maybe <code>null</code>. * @return maybe <code>null</code> if null for input. */ protected String escapeAttributeMarkup(String raw) { return MarkupUtils.escapeAttributeEntities(raw); } private void writeIndent(Tag t, TagIndentSpot spot) { try { indenter.indentTag(this.writer, t.depth, this.depth, spot, t.name, t.closePolicy, t.empty); } catch (IOException e) { throw new RuntimeException("Builder Indenting failed:", e); } } /** * A default indenter that uses tabs. */ protected static Indenter indentOn = new SimpleIndenter("\n", "\t", "\n", "\t"); /** * An indenter that turns off indenting. */ protected static Indenter indentOff = new SimpleIndenter(null, null, null, null); /** * Indents by keeping a tag pair on the same line. */ protected static Indenter indentSameLine = new SimpleIndenter("\n", "\t", null, null); private static class Tag { public Tag(String name) { super(); this.name = name; } public int depth = 0; public String name; public String prefix; public boolean empty = true; public boolean start = false; public boolean end = false; public TagClosingPolicy closePolicy = TagClosingPolicy.NORMAL; public String getQualifiedName() { return isBlank(this.prefix) || this.name.contains(":") ? this.name : this.prefix + ":" + this.name; } public boolean isSelfClosing() { return empty && closePolicy.isSelfClosing(); } } /** * Policy for how tags should be closed. * @author adamgent * */ public enum TagClosingPolicy { /** * The tag can either be closed with a matching closing tag * or self closing. It will be self closing if the tag contains * no child tags or text. * <ul> * <li><code><tag/></code></li> * <li><code><tag>...</tag></code></li> * </ul> * <em>Unlike {@link #SELF self closing} tags a {@link #NORMAL} tag must be explicitly closed.</em> */ NORMAL, /** * The tag is always a self closing tag. * <ul> * <li><code><tag/>...</code></li> * </ul> * <em>When a tag has this policy the tag can be implicitly closed * by {@link MarkupBuilder#start(String, TagClosingPolicy) starting the next tag}:</em><p> * <pre> * start("self",TagClosingPolicy.SELF).start("next"); * </pre> * Result:<p> * <pre> * <self/> * <next> * ... * </pre> * */ SELF, /** * The tag is always closed with a matching closing tag * regardless if there is no child tag or text. * <ul> * <li><code><tag>...</tag></code></li> * <li><code><tag></tag></code></li> * </ul> */ PAIR; public final boolean isAlwaysSelfClosing() { return this == SELF; } /** * @return <code>true</code> if the tag is allowed to self close. */ public final boolean isSelfClosing() { return this == SELF || this == NORMAL; } /** * @return <code>true</code> if the tag is allowed to close with a matching end tag (<code></tag></code>). */ public final boolean isPairClosing() { return this == PAIR || this == NORMAL; } } }