Skip to main content

Closure and groovy builder

Posted by forax on April 1, 2008 at 6:47 AM EDT

One think i really like in Groovy, it's its concept of Builder.
It allows to simply create tree of objects like XML trees using a concise syntax.

An HTML tree in Groovy is defined like that

  html {
      head {
        title "hello groovy builder"
      }
  }

JavaFX uses a quite similar syntax but the syntax is built-in. The beauty of the groovy beast is that the builder syntax relies on closure.

Closure

The trick is the following:

  1. An XML node is a method.
  2. The content of a XML Node is a closure taken as argument of the method

Ok, so let's try to do the same in Java using the BGGA closure proposal. CICE is too verbose for that case and it should work seamlessly with FCM+ JCA

Proxiiiiiiiiii

But before, how to define all the allowed markups of an XML dialect ?
Ok, we need one method by markup, but because the code is identical for two different markups, instead of writing boilerplate code, I will use the class java.lang.reflect.Proxy.
To use the Proxy, we have to define an interface. The one below allow us to generate XHTML.

public interface XHTMLBuilder {
  public void html(Object textOrClosure);
  public void head(Object textOrClosure);
  public void title(String text);
  public void body(Object textOrClosure);
  public void h1(Object textOrClosure);
  public void br();
}

A Proxy acts as a multiplexer a call to any of its methods is redirected to a single generic method named invoke.

public class XMLBuilderFactory {
  final Appendable appendable;
  
  public XMLBuilderFactory(Appendable appendable) {
    this.appendable=appendable;
  }
  
  public void text(CharSequence seq) {
    try {
      appendable.append(seq);
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }
  
  public <T> T createBuilder(Class<T> generatorInterface) {
    return generatorInterface.cast(Proxy.newProxyInstance(
      generatorInterface.getClassLoader(),
      new Class<?>[]{generatorInterface},
      new InvocationHandler() {
        public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
          
          ...
          return null;
        }
      }));
  }
}

Here appendable acts as a writable stream of characters, something on which I can append characters.
The method createBuilder takes an interface as argument and returns a class that implements that interface. All methods of thi interface are routed to the method invoke of the InvocationHandler.

Java Builder

Now, suppose that the method invoke is written, we will see after how to write it.
To write an XHTML tree on the standard ouput, using the BGGA closure syntax, the code is:

public class Main {
  public static void main(String[] args) {
    XMLBuilderFactory factory=new XMLBuilderFactory(System.out);
    XHTMLBuilder b=factory.createBuilder(XHTMLBuilder.class);
    
    b.html({=>
      b.head({=>
        b.title("hello Java 7 builder");
      });
      
      b.body({=>
        b.h1("Hello");
        factory.text("Greetings from");
        b.br();
        factory.text("Java 7 builder");
      });
    });
  }
}

It's even more readable if we use the control invocation syntax.

    b.html() {
      b.head() {
        b.title("hello Java 7 builder");
      }
      
      b.body() {
        b.h1("Hello");
        factory.text("Greetings from");
        b.br();
        factory.text("Java 7 builder");
      }
    }

This construct has several advantages. I can't produce ill formed XML, markup are opened and closed by the same code. Because the builder uses an interface I can't create markup with the wrong name. Even better, refactoring the markup name will works !

How to write the method invoke ?

For each argument of the method, i have to determine if it's a closure or any other objects. If it's a closure, i have to invoke it. If it's another object, i can append it into the Appendable.
The question is how to dynamically test if an object is or not a closure.
I don't want any closure i just want a closure that takes no parameter and returns void, a {=>void}.

  if (arg instanceof {=>void}) {
    (({=>void})arg).invoke();
  }

There is two poblem with that code. First, there are two kinds of closure (function type), unrestricted and restricted one. A restricted closure is a closure that can't break the control flow using break, continue or return. I am not sure i want my closure be able to break the control flow but the control invocation syntax creates unrestricted closure, so i need unrestricted closure. So instead of using '=>' in the function type, i have to use '==>'
By the way, i think the spec should be revised to swap the two notations ('=>' and '==>'), '=>' should be used for unrestricted closure. If i want a restricted closure which is not the default case, i have to take care about that and use the '==>' syntax. Append a second '=' is equivalent to say don't worry compiler i know what i'am doing.
So currently, the code should be:

  if (arg instanceof {==>void}) {
    (({==>void})arg).invoke();
  }

But there is another problem, currently, {==>void} is a parameterized type and so it's illegal to use it in an instanceof. Here, i have ask Neal, he says he planed to make "types such as this one non-generic".
There is a workaround, currently the closure prototype works with 1.4/1.5 VM and thus generates interfaces corresponding to function types. The interface corresponding to the raw type of {==>void} is javax.lang.function.unrestricted.V.
Here is the code that works:

  String name=method.getName();
  if (args==null || args.length==0) {
    appendable.append('<'+name+"/>");
    return null;
  }
          
  appendable.append('<'+name+'>');
  try {
    for(Object arg:args) {
      if (arg instanceof javax.lang.function.unrestricted.V) {
        ((javax.lang.function.unrestricted.V)arg).invoke();
      } else {
        appendable.append(arg.toString());
      }
    }
  } finally {
    appendable.append("');
  }

A zip containing the wole codes is here: closure-builder.zip
You need the prototype of the BGGA closure too.

Cheers,
Rémi

Related Topics >>

Comments

Rather than doing instanceof tests, you should overload the methods.

Hey guy what a groovy idea you have here! Thank you akiri for sharing it with us

I agree with akiri... what about a closure and groovy notation Builder for students ?

hmm interesting and what about our school notation ?

It might be a good a Idea to create a notation software because the random notation software used for system has a lof a bugs ... By the way it has been 125 days between the moment we ended the concurency exam and today and we still have nothing !!!

Nice experiment, but I still have a preference for the Groovy builder ;-)