Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

To create a new builder like a the MarkupBuilder or AntBuilder, you have to implement in java (in groovy later too) a subclass of the groovy.util.BuilderSupport class.
The main methods to be implemnted are the following :

  • protected abstract void setParent(Object parent, Object child);
  • protected abstract Object createNode(Object name); // anode without parameter and closure
  • protected abstract Object createNode(Object name, Object value); //a node without parameters, but with closure
  • protected abstract Object createNode(Object name, Map attributes); // aNode without closure but with parameters
  • protected abstract Object createNode(Object name, Map attributes, Object value); //a node with closure and parameters
  • protected Object getName(String methodName)

The BuilderSupport.java class

Code Block
package groovy.util;

import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;

import java.util.List;
import java.util.Map;

import org.codehaus.groovy.runtime.InvokerHelper;

public abstract class BuilderSupport extends GroovyObjectSupport {

    private Object current;
    private Closure nameMappingClosure;
    private BuilderSupport proxyBuilder;
    
    public BuilderSupport() {
        this.proxyBuilder = this;
    }
    
    public BuilderSupport(BuilderSupport proxyBuilder) {
        this(null, proxyBuilder);
    }
    
    public BuilderSupport(Closure nameMappingClosure, BuilderSupport proxyBuilder) {
        this.nameMappingClosure = nameMappingClosure;
        this.proxyBuilder = proxyBuilder;
    }
    
    public Object invokeMethod(String methodName, Object args) {
        Object name = getName(methodName);
        return doInvokeMethod(methodName, name, args);
    }
    
    protected Object doInvokeMethod(String methodName, Object name, Object args) {
        Object node = null;
        Closure closure = null;
        List list = InvokerHelper.asList(args);

        //System.out.println("Called invokeMethod with name: " + name + " arguments: " + list);

        switch (list.size()) {
        		case 0:
        		    break;
        	    	case 1:
        	    	{
        	    	    	Object object = list.get(0);
        	    	    	if (object instanceof Map) {
        	    	    	    node = proxyBuilder.createNode(name, (Map) object);
        	    	    	} else if (object instanceof Closure) {
        	    	    	    closure = (Closure) object;
        	    	    	    node = proxyBuilder.createNode(name);
        	    	    	} else {
        	    	    	    node = proxyBuilder.createNode(name, object);
        	    	    	}
        	    	}
        	    	break;
        	    	case 2:
        	    	{
        	    	    Object object1 = list.get(0);
    	    	        Object object2 = list.get(1);
        	    	    if (object1 instanceof Map) {
        	    	        if (object2 instanceof Closure) {
        	    	            closure = (Closure) object2;
        	    	            node = proxyBuilder.createNode(name, (Map) object1);
        	    	        } else {
        	    	            node = proxyBuilder.createNode(name, (Map) object1, object2);
        	    	        }
        	    	    } else {
        	    	        if (object2 instanceof Closure) {
        	    	            closure = (Closure) object2;
        	    	            node = proxyBuilder.createNode(name, object1);
        	    	        }
        	    	    }
        	    	}
        	    	break;
        	    	case 3:
        	    	{
        	    	    Object attributes = list.get(0);
        	    	    Object value = list.get(1);
        	    	    closure = (Closure) list.get(2);
        	    	    node = proxyBuilder.createNode(name, (Map) attributes, value);
        	    	}
        	    	break;
        }

        	if (node == null) {
	    	    node = proxyBuilder.createNode(name);
        	}
        
        if (current != null) {
            proxyBuilder.setParent(current, node);
        }

        if (closure != null) {
            // push new node on stack
            Object oldCurrent = current;
            current = node;

            // lets register the builder as the delegate
            setClosureDelegate(closure, node);
            closure.call();

            current = oldCurrent;
        }

        proxyBuilder.nodeCompleted(current, node);
        return node;
    }

 
    protected void setClosureDelegate(Closure closure, Object node) {
        closure.setDelegate(this);
    }

    protected abstract void setParent(Object parent, Object child);
    protected abstract Object createNode(Object name);
    protected abstract Object createNode(Object name, Object value);
    protected abstract Object createNode(Object name, Map attributes);
    protected abstract Object createNode(Object name, Map attributes, Object value);

    protected Object getName(String methodName) {
        if (nameMappingClosure != null) {
            return nameMappingClosure.call(methodName);
        }
        return methodName;
    }

    protected void nodeCompleted(Object parent, Object node) {
    }

    protected Object getCurrent() {
        return current;
    }
    
    protected void setCurrent(Object current) {
        this.current = current;
    }
}


The NodeBuilder example

To wbe able to write such a code :

Code Block
def someBuilder = new NodeBuilder()

someBuilder.people(kind:'folks', groovy:true) {
  person(x:123,  name:'James', cheese:'edam') {
    project(name:'groovy')
    project(name:'geronimo')
  }
  person(x:234,  name:'bob', cheese:'cheddar') {
    project(name:'groovy')
    project(name:'drools')
  }
}

we need :

Code Block


package groovy.util;

import java.util.ArrayList;
import java.util.Map;

/**
 * A helper class for creating nested trees of Node objects for
 * handling arbitrary data
 *
 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
 * @version $Revision: 1.3 $
 */
public class NodeBuilder extends BuilderSupport {

    public static NodeBuilder newInstance() {
        return new NodeBuilder();
    }

    protected void setParent(Object parent, Object child) {
    }

    protected Object createNode(Object name) {
        return new Node(getCurrentNode(), name, new ArrayList());
    }

    protected Object createNode(Object name, Object value) {
        return new Node(getCurrentNode(), name, value);
    }

    protected Object createNode(Object name, Map attributes) {
        return new Node(getCurrentNode(), name, attributes, new ArrayList());
    }

    protected Object createNode(Object name, Map attributes, Object value) {
        return new Node(getCurrentNode(), name, attributes, value);
    }

    protected Node getCurrentNode() {
        return (Node) getCurrent();
    }
}

The MarkupBuilder.java class as second example

Code Block
package groovy.xml;

import groovy.util.BuilderSupport;
import groovy.util.IndentPrinter;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;

/**
 * A helper class for creating XML or HTML markup
 * 
 * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
 * @author Stefan Matthias Aust
 * @version $Revision: 1.8 $
 */
public class MarkupBuilder extends BuilderSupport {

    private IndentPrinter out;
    private boolean nospace;
    private int state;
    private boolean nodeIsEmpty = true;

    public MarkupBuilder() {
        this(new IndentPrinter());
    }

    public MarkupBuilder(PrintWriter writer) {
        this(new IndentPrinter(writer));
    }

    public MarkupBuilder(Writer writer) {
        this(new IndentPrinter(new PrintWriter(writer)));
    }

    public MarkupBuilder(IndentPrinter out) {
        this.out = out;
    }

    protected void setParent(Object parent, Object child) {
    }

    /*
    public Object getProperty(String property) {
        if (property.equals("_")) {
            nospace = true;
            return null;
        } else {
            Object node = createNode(property);
            nodeCompleted(getCurrent(), node);
            return node;
        }
    }
    */

    protected Object createNode(Object name) {
        toState(1, name);
        return name;
    }

    protected Object createNode(Object name, Object value) {
        toState(2, name);
        out.print(">");
        out.print(value.toString());
        return name;
    }

    protected Object createNode(Object name, Map attributes, Object value) {
        toState(1, name);
        for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
            Map.Entry entry = (Map.Entry) iter.next();
            out.print(" ");
            print(transformName(entry.getKey().toString()));
            out.print("='");
            print(transformValue(entry.getValue().toString()));
            out.print("'");
        }
        if (value != null)
        {
            nodeIsEmpty = false;
            out.print(">" + value + "</" + name + ">");
        }
        return name;
    }

    protected Object createNode(Object name, Map attributes) {
        return createNode(name, attributes, null);
    }
    
    protected void nodeCompleted(Object parent, Object node) {
        toState(3, node);
        out.flush();
    }

    protected void print(Object node) {
        out.print(node == null ? "null" : node.toString());
    }

    protected Object getName(String methodName) {
		return super.getName(transformName(methodName));
	}

    protected String transformName(String name) {
    	if (name.startsWith("_")) name = name.substring(1);
    	return name.replace('_', '-');
    }

    protected String transformValue(String value) {
        return value.replaceAll("\\'", "&quot;");
    }

    private void toState(int next, Object name) {
        switch (state) {
            case 0:
                switch (next) {
                    case 1:
                    case 2:
                        out.print("<");
                        print(name);
                        break;
                    case 3:
                        throw new Error();
                }
                break;
            case 1:
                switch (next) {
                    case 1:
                    case 2:
                        out.print(">");
                        if (nospace) {
                            nospace = false;
                        } else {
                            out.println();
                            out.incrementIndent();
                            out.printIndent();
                        }
                        out.print("<");
                        print(name);
                        break;
                    case 3:
                        if (nodeIsEmpty) {
                            out.print(" />");
                        }
                        break;
                }
                break;
            case 2:
                switch (next) {
                    case 1:
                    case 2:
                        throw new Error();
                    case 3:
                        out.print("</");
                        print(name);
                        out.print(">");
                        break;
                }
                break;
            case 3:
                switch (next) {
                    case 1:
                    case 2:
                        if (nospace) {
                            nospace = false;
                        } else {
                            out.println();
                            out.printIndent();
                        }
                        out.print("<");
                        print(name);
                        break;
                    case 3:
                        if (nospace) {
                            nospace = false;
                        } else {
                            out.println();
                            out.decrementIndent();
                            out.printIndent();
                        }
                        out.print("</");
                        print(name);
                        out.print(">");
                        break;
                }
                break;
        }
        state = next;
    }

}