Message-ID: <1552354766.1961.1432466341880.JavaMail.email@example.com> Subject: Exported From Confluence MIME-Version: 1.0 Content-Type: multipart/related; boundary="----=_Part_1960_631930683.1432466341880" ------=_Part_1960_631930683.1432466341880 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable Content-Location: file:///C:/exported.html
The Groovy language is a platform of choice for building DSLs. Using clo= sures, it's quite easy to create custom control structures, as well as it i= s simple to create builders. Imagine that you have the following code:
One way of implementing this is using the builder strategy, which implie= s a method, named email which accepts a closure as an argumen= t. The method may delegate subsequent calls to an object that implements th= e from, to, subject and = body methods. Again, body is a method which accepts a cl= osure 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 an= d thisObject values. Setting the owner and the "this&quo= t; object is not very important here since we will use the DELEGATE_ONL= Y strategy which says that the method calls will be resolved only agai= nst the delegate of the closure. Then, we're just calling the code and we'r= e done!
One of the problems with the code that we've shown is that the user of t= he email method doesn't have any information about the methods tha= t 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 al= l, documentation is not always written, and if it is, it's not always avail= able (javadoc not downloaded, for example). Second, it doesn't help IDEs. W= hat would be really interesting, here, is for IDEs to help the developper b= y 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).
Another problem with the code that we've shown is that it is not compati= ble 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 meth= od 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 d= efined in the Email class and it has absolutely no hint to he= lp it knowing that the closure delegate will, at runtime, be of type <= em>Email.
For those reasons, Groovy 2.1 introduces a new annotation named @De= legatesTo. The goal of this annotation is to solve both the documentation i= ssue, 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 t= he compiler about what are the potential receivers of method calls in the c= losure 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 t= he delegation strategy is also changed:
Now, both the IDE and the type checker (if you are using @TypeChecke= d) 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, b= ut it will also remove errors at compile time that exist only because the b= ehaviour 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 wh= ich 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 v= isible to the type checker).
In this mode, you must specify both the delegate class and<= /strong> a delegation strategy. This must be used if the closure will not b= e called with the default delegation strategy, which is Closure.OW= NER_FIRST.
In this variant, we will tell the compiler that we are delegating to ano= ther parameter of the method. Take the following code:
Here, the delegate which will be used is not creat= ed inside the exec method. In fact, we take an argument of th= e method and delegate to it. Usage may look like this:
Each of the method calls are delegated to the email parame= ter. This is a widely used pattern which is also supported by @Del= egatesTo using a companion annotation:
A closure is annotated with @DelegatesTo, but this time, w= ithout specifying any class. Instead, we're annotating another parameter wi= th @DelegatesTo.Target. The type of the delegate is then dete= rmined at compile time. One could think that we are using the parameter typ= e, which in this case is Object but this is not true. Take this code:
Remember that this works out of the box without ha= ving 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 wil= l now that the greeter variable is of type Greeter= em>, so it will not report errors on the sayHello method = ;even if the exec method doesn't explicitely define the target as o= f type Greeter. This is a very powerful feature, because it preven= ts you from writing multiple versions of the same exec method= for different receiver types!
In this mode, the @DelegatesTo annotation also supports th= e 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 @De= legatesTo:
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 re= ferences. The reason is that the information (the parameter name) is not al= ways available (it's a debug-only information), so it's a limitation of the= JVM.
So far, we've only talked about static type checking (@TypeChecked= em>) and IDE completion, but not about static compilation (@CompileStat= ic). So the question is whether @CompileStatic support i= t 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&nbs= p;@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 s= uch a builder, we're just showing you the capabilities of @DelegatesTo<= /em> with regards to static compilation. Let's start with the top level cla= ss:
The usage would be:
So we miss the "html" method (other properties/methods removed= for clarity):
Here, we defined two methods:
We have two problems to solve:
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 remov= ed the static modifier, so that we can pass the stringbuilder accross sever= al tag instances, and it now calls openTag before calling the clos= ure, 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 ta= g:
Now let's see that we can statically compile the usage too:
But one problem here is that our p tag doesn't support sub= tags... 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 <= em>sb. This is possible because delegateToTag sets the&n= bsp;owner to sb. The static compiler recognizes it a= nd is able to use it, without a single compilation error! So, let's put bel= ow the complete example:
Congratulations, you've built your first statically compiled builder in = Groovy!