Groovy Goodness: Use Builder AST Transformation for Fluent API
Since Groovy 2.3 we can easily create a fluent API for our classes with the @Builder
AST transformation. We can apply the annotation to our classes and the resulting class file will have all the necessary methods to support a fluent API. We can customize how the fluent API is generated with different annotation parameters. In Groovy code we already can use the with
method to have a clean way to set property values or use the named constructor arguments. But if our classes need to be used from Java it is nice to give the Java developers a fluent API for our Groovy classes.
In the following sample we apply the @Builder
annotation to a simple class Message
with some properties. We leave everything to the default settings and then the resulting Message
class file will have a new builder
method that return an internal helper class we can use to set our properties. For each property their is a new method with the name of the property so we can set a value. And finally our class contains a build
that will return a new instance of the Message
class with the correct values for the properties.
import groovy.transform.builder.Builder
@Builder
class Message {
String from, to, subject, body
}
def message = Message
.builder() // New internal helper class.
.from('mrhaki@mrhaki.com') // Method per property.
.to('mail@host.nl')
.subject('Sample mail')
.body('Groovy rocks!')
.build() // Create instance of Message
assert message.body == 'Groovy rocks!'
assert message.from == 'mrhaki@mrhaki.com'
assert message.subject == 'Sample mail'
assert message.to == 'mail@host.nl'
If we want to change the names of the builder
and build
methods we can use the annotation parameters builderMethodName
and buildMethodName
:
import groovy.transform.builder.Builder
@Builder(builderMethodName = 'initiator', buildMethodName = 'create')
class Message {
String from, to, subject, body
}
def message = Message.initiator()
.from('mrhaki@mrhaki.com')
.body('Groovy rocks!')
.create()
assert message.body == 'Groovy rocks!'
assert message.from == 'mrhaki@mrhaki.com'
We see that for each property a corresponding method is generated. We can also customize the prefix for the generated method name with the annotation parameter prefix
. In the following sample we define the prefix assign for the method names:
import groovy.transform.builder.Builder
@Builder(prefix = 'assign')
class Message {
String from, to, subject, body
}
def message = Message.builder()
.assignFrom('mrhaki@mrhaki.com')
.assignBody('Groovy rocks!')
.build()
assert message.body == 'Groovy rocks!'
assert message.from == 'mrhaki@mrhaki.com'
Finally we can also include and exclude properties to need to be included or excluded from our fluent API. We use the annotation parameters includes
and excludes
to define the names of the properties. This can be a list or a comma separated list of names.
import groovy.transform.builder.Builder
@Builder(excludes = 'body' /* or includes = 'from,to,subject' */)
class Message {
String from, to, subject, body
}
def message = Message.builder()
.from('mrhaki@mrhaki.com')
.to('mail@host.nl')
.subject('Groovy 2.3 is released')
.build()
assert message.from == 'mrhaki@mrhaki.com'
assert message.subject == 'Groovy 2.3 is released'
try {
message = Message.builder().body('Groovy rocks!').build()
} catch (MissingMethodException e) {
assert e.message.readLines().first() ==
'No signature of method: static Message.body() is applicable for argument types: (java.lang.String) values: [Groovy rocks!]'
}
The @Builder
AST transformation also checks if the @Canonical
AST transformation is applied to a class. Any included or excluded properties defined in the @Canonical
transformation are also included or excluded for the generated builder code.
We can define the SimpleStrategy
strategy with the builderStrategy
annotation parameter. Then the generated class will not have a separate inner helper builder class and build method. The default prefix
is set to set
, but we can change that if we want to:
import groovy.transform.builder.Builder
import groovy.transform.builder.SimpleStrategy
@Builder(builderStrategy = SimpleStrategy, prefix = 'assign')
class Message {
String from, to, subject, body
}
def message = new Message()
.assignFrom('mrhaki@mrhaki.com') // Method per property.
.assignTo('mail@host.nl')
.assignSubject('Sample mail')
.assignBody('Groovy rocks!')
assert message.body == 'Groovy rocks!'
assert message.from == 'mrhaki@mrhaki.com'
assert message.subject == 'Sample mail'
assert message.to == 'mail@host.nl'
We will see other feature of the @Builder
annotation in future blog posts.
Code written with Groovy 2.3.