Home » JVM Languages » Groovy » Groovy Goodness: Use Builder AST Transformation for Fluent API

About Hubert Ikkink

My name is Hubert A. Klein Ikkink also known as mrhaki. I work at the great IT company JDriven. Here I work on projects with Groovy & Grails, Gradle and Spring. At JDriven we focus on SpringSource technologies. All colleagues want to learn new technologies, support craftmanship and are very eager to learn. This is truly a great environment to work in.

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([email protected]')  // Method per property.
        .to([email protected]')
        .subject('Sample mail')
        .body('Groovy rocks!')
        .build()  // Create instance of Message

assert message.body == 'Groovy rocks!'
assert message.from == [email protected]'
assert message.subject == 'Sample mail'
assert message.to == [email protected]'

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([email protected]')
        .body('Groovy rocks!')
        .create()

assert message.body == 'Groovy rocks!'
assert message.from == [email protected]'

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([email protected]')
        .assignBody('Groovy rocks!')
        .build()

assert message.body == 'Groovy rocks!'
assert message.from == [email protected]'

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([email protected]')
        .to([email protected]')
        .subject('Groovy 2.3 is released')
        .build()

assert message.from == [email protected]'
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([email protected]')  // Method per property.
        .assignTo([email protected]')
        .assignSubject('Sample mail')
        .assignBody('Groovy rocks!')

assert message.body == 'Groovy rocks!'
assert message.from == [email protected]'
assert message.subject == 'Sample mail'
assert message.to == [email protected]'

We will see other feature of the @Builder annotation in future blog posts.

Code written with Groovy 2.3.

 

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

 

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

 

and many more ....

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Want to take your Java skills to the next level?

Grab our programming books for FREE!

Here are some of the eBooks you will get:

  • Spring Interview QnA
  • Multithreading & Concurrency QnA
  • JPA Minibook
  • JVM Troubleshooting Guide
  • Advanced Java
  • Java Interview QnA
  • Java Design Patterns