Groovy, DSLs and builders
DSLs made easy
The Groovy language is a platform of choice for building DSLs. Using closures, it's quite easy to create custom control structures, as well as it is simple to create builders. Imagine that you have the following code:
One way of implementing this is using the builder strategy, which implies a method, named email which accepts a closure as an argument. The method may delegate subsequent calls to an object that implements the from, to, subject and body methods. Again, body is a method which accepts a closure as an argument and that uses the builder strategy.
Implementing such a builder is not complicated:
the Email class implements the from, to, ... methods. By calling rehydrate, we're creating a copy of the closure for which we set the delegate, owner and thisObject values. Setting the owner and the "this" object is not very important here since we will use the DELEGATE_ONLY strategy which says that the method calls will be resolved only against the delegate of the closure. Then, we're just calling the code and we're done!
Documentation is important
One of the problems with the code that we've shown is that the user of the email method doesn't have any information about the methods that he's allowed to call inside the closure. The only possible information is from the method documentation. There are two issues with this: first of all, documentation is not always written, and if it is, it's not always available (javadoc not downloaded, for example). Second, it doesn't help IDEs. What would be really interesting, here, is for IDEs to help the developper by suggesting, once they are in the closure body, methods that exist on the Email class.
Moreover, if the user calls a method in the closure which is not defined by the Email class, the IDE should at least issue a warning (because it's very likely that it will break at runtime).
Type checking builders
Another problem with the code that we've shown is that it is not compatible with static type checking. If you try to perform type checking on this code:
Then the type checker will know that there's an email method accepting a closure, but it will complain for every method call inside the closure, because from, for example, is not a method which is defined in the class. Indeed, it's defined in the Email class and it has absolutely no hint to help it knowing that the closure delegate will, at runtime, be of type Email.
For those reasons, Groovy 2.1 introduces a new annotation named @DelegatesTo. The goal of this annotation is to solve both the documentation issue, that will let your IDE know about the expected methods in the closure body, and it will also solve the type checking issue, by giving hints to the compiler about what are the potential receivers of method calls in the closure body.
The idea is to annotate the Closure parameter of the email method:
What we've done here is telling the compiler (or the IDE) that when the method will be called with a closure, the delegate of this closure will be set to an object of type Email. But there is still a problem: the defaut delegation strategy is not the one which is used in our method. So we will give more information and tell the compiler (or the IDE) that the delegation strategy is also changed:
Now, both the IDE and the type checker (if you are using @TypeChecked) will be aware of the delegate and the delegation strategy. This is very nice because it will both allow the IDE to provide smart completion, but it will also remove errors at compile time that exist only because the behaviour of the program is normally only known at runtime!
@DelegatesTo supports multiple modes that we will describe with examples in this section.
In this mode, the only mandatory parameter is the value which says to which class we delegate calls. Nothing more. We're telling the compiler that the type of the delegate will always be of the type documented by @DelegatesTo (note that it can be a subclass, but if it is, the methods defined by the subclass will not be visible to the type checker).
In this mode, you must specify both the delegate class and a delegation strategy. This must be used if the closure will not be called with the default delegation strategy, which is Closure.OWNER_FIRST.
Delegate to parameter
In this variant, we will tell the compiler that we are delegating to another parameter of the method. Take the following code:
Here, the delegate which will be used is not created inside the exec method. In fact, we take an argument of the method and delegate to it. Usage may look like this:
Each of the method calls are delegated to the email parameter. This is a widely used pattern which is also supported by @DelegatesTo using a companion annotation:
A closure is annotated with @DelegatesTo, but this time, without specifying any class. Instead, we're annotating another parameter with @DelegatesTo.Target. The type of the delegate is then determined at compile time. One could think that we are using the parameter type, which in this case is Object but this is not true. Take this code:
Remember that this works out of the box without having to annotate with @DelegatesTo. However, to make the IDE aware of the delegate type, or the type checker aware of it, we need to add @DelegatesTo. And in this case, it will now that the greeter variable is of type Greeter, so it will not report errors on the sayHello method even if the exec method doesn't explicitely define the target as of type Greeter. This is a very powerful feature, because it prevents you from writing multiple versions of the same exec method for different receiver types!
In this mode, the @DelegatesTo annotation also supports the strategy parameter that we've described upper.
In the previous example, the exec method accepted only one closure, but you may have methods that take multiple closures:
Then nothing prevents you from annotating each closure with @DelegatesTo:
But more importantly, if you have multiple closures and multiple arguments, you can use several targets:
At this point, you may wonder why we don't use the parameter names as references. The reason is that the information (the parameter name) is not always available (it's a debug-only information), so it's a limitation of the JVM.
Statically compiled builders
So far, we've only talked about static type checking (@TypeChecked) and IDE completion, but not about static compilation (@CompileStatic). So the question is whether @CompileStatic support it too (and the answer is of course yes!). In this section, we will show you how you can create a type-safe, statically compiled builder leveraging @DelegatesTo. As an example, we will use an HtmlBuilder, which is pretty much like a MarkupBuilder, apart from the fact that we will only allow some tags (for the sake of the example). Also make sure that we are not saying this is the best way to implement such a builder, we're just showing you the capabilities of @DelegatesTo with regards to static compilation. Let's start with the top level class:
The usage would be:
So we miss the "html" method (other properties/methods removed for clarity):
Here, we defined two methods:
- delegateToTag takes a class argument and a closure, then creates a new instance of this class. The closure is then cloned and it's delegate is set to the tag and the closure is called.
- html tells that the closure will delegate to an HTML tag, so we create an empty (for now) HTML class
We have two problems to solve:
- make the tag builder aware of the stringbuilder so that we can generate our text
- make the tag itself able to support sub-tags
To solve the first issue, we will create an abstract base class for all tags:
Then our HTMLTag just needs to extends that class:
Note that we also moved the delegateToTag method and removed the static modifier, so that we can pass the stringbuilder accross several tag instances, and it now calls openTag before calling the closure, and closeTag after. So now, the main builder looks like this:
The next step is to generate HTML:
Now what we will do is add a head and a body tag:
Last but not least, let's make the body tag support a p tag:
Now let's see that we can statically compile the usage too:
But one problem here is that our p tag doesn't support subtags... Can we make it support it too? Sure!
And here's how to use it:
As you can see, here, inside the p tag, we're using sb. This is possible because delegateToTag sets the owner to sb. The static compiler recognizes it and is able to use it, without a single compilation error! So, let's put below the complete example:
Congratulations, you've built your first statically compiled builder in Groovy!