Loading...

May 5, 2014

Groovy Goodness: Using Builder to Create Fluent API for Other Classes

In a previous post we learned about the new @Builder AST transformation introduced in Groovy 2.3. We applied to the annotation to our class files and we got a nice fluent API to set property values. But what if we cannot change the class itself, for example if we want to create a fluent API for classes in an external library. Then we can still use the @Builder AST transformation but we use a different strategy. We can define the builder strategy via a annotation parameter.

In the following sample we assume the Message class is from an external library and we cannot or do not want to change the class definition. We create a new Groovy class and set the @Builder annotation on this new class. We use the annotation parameters builderStrategy to indicate the generated code is not for the new class, but for the class set with the annotation parameter forClass.

import groovy.transform.builder.Builder
import groovy.transform.builder.ExternalStrategy

// We don't want to change the definition
// of this class to get a fluent API.
class Message {
    String from, to, subject, body
}

// New builder class for the Message class.
@Builder(builderStrategy = ExternalStrategy, forClass = Message)
class MessageBuilder {}

def message = new MessageBuilder()  // Create new instance.
        .from('mrhaki@mrhaki.com')
        .to('mail@host.nl')
        .subject('Groovy 2.3 is released')
        .body('Groovy rocks!')
        .build()  // Return filled Message instance.

assert message.body == 'Groovy rocks!'
assert message.from == 'mrhaki@mrhaki.com'
assert message.subject == 'Groovy 2.3 is released'

We can also customize the prefix for the method names to set property values and we can change the name of the build method. And we can include or exclude properties with the includes and excludes properties:

import groovy.transform.builder.Builder
import groovy.transform.builder.ExternalStrategy

class Message {
    String from, to, subject, body
}

@Builder(builderStrategy = ExternalStrategy, forClass = Message,
        prefix = 'assign', buildMethodName = 'create',
        includes = 'from,subject')
class MessageBuilder {}

def message = new MessageBuilder()
        .assignFrom('mrhaki@mrhaki.com')
        .assignSubject('Groovy 2.3 is released')
        .create()

assert message.from == 'mrhaki@mrhaki.com'
assert message.subject == 'Groovy 2.3 is released'

Code written with Groovy 2.3.