Se here is the thing. I fell in love with Groovy recently.

But back to the point. Or almost. A little disclaimer – this post is for a total groovy-newbies. If you speak groovy in your dreams, then this might sound infantile and too easy for you ;-). You have been warned.

At the place I work, for the client I work (a Big Bank in South Africa) we are developing a set of intranet applications to drive the credit business. As you might imagine this is a heavily form-oriented business. It hugely depends on gathering client’s data and then judging if the bank should be alarmed or not just yet.

At the same time we are nerds. So we decided to create a little framework that will serve those forms and allow us to focus on the data we are gathering and the business rules, rather then the look and feel, and AJAX, and REST calls, and and and. Really. I just cannot imagine writing dozens of forms, from scratch, in JSF or anything alike. Every frikken time. Not for me.

So we have created the framework. A little on how it works and we can get to the point. The framework is called Jastin. And the project has a codename Biber. Yep.

First of all there is a Model. Model is just hibernate entities. And the model points to a database we do not own. The database is designed to store the bank’s crucial information and we as a Java team are just the user of it.

So we read those entities from the database and we map them to things called JAOs (Jastin Access Objects). The JAO might be a single entity, but it is an object that represents the form. So often it has a lot of dependencies, it also has all the annotations to make the fields render the way they should render.

And at the end there is a yaml file to describe how to layout those fields. It can look like this:


foo.bar.UserJAO:
  personalDetails:
    - name lastname
    - shoeSize
  addressDetails:
    - street streetNo appartmentNo
    - city country

So you can see that the backing entity here will be a foo.bar.UserJAO, we will show two sections on the screen – Personal Details with name and lastname in the first row and the shoe size in the seconds.
Below that we will have Address Details with street, numbers, city and country laid out in two rows as well.

So what is wrong, you ask?

Well first of all, lack of IDE support – the class name will not refactor, If we change it. There is no intellisense for the field names. It sux basically.

What can we do? Well either we can write a IDE plugin that will understand It or… or what? A-ha we can use groovy to script out those layouts.

Now it would be super-cool If I could somehow use groovy, that will use the object of type foo.bar.UserJAO so that I could call those properties and ta-dam intellisense works, refactoring works. We are good to go.

Part 1 Property Access

Ok fine. So what do we want to do here? We would like to access a property like we access it normally, but in return we would like to get the name of the property, not it’s value.

Here comes metaClass for help. MetaClass in groovy is the sugar of dynamicity. You can either manipulate it on class-level (so that all instances of it will have it) or on the object level (so that only one object is adjusted). You can add methods to it and you can add some “special” ones, like methodMissing (called every time a non-existing method is called) or getProperty/setProperty.

So let’s take a look at this getProperty. And in this case we really do not want to mess up the class across the classpath, so we will adjust the object only.

def giveMeObjectThatReturnsProperties(Class aClass) {
  def object = mock(aClass)

  object.metaClass.getProperty = {
    String propertyName -> return propertyName
  }

  return object;
}

mock(aClass) is used from the great mocking library Mockito and I just use it to get the proper instance of an object, but given some required constructors (like the default one for example) we could’ve constructed here the actual object.

And then It is just a matter of the mentioned method called getProperty. This method accepts one string parameter (yep, It is the property name) and will intercept all property access. And then we just return the property name instead of it’s value. Dead easy.

You might be worried by the notation of the assignment to the getProperty. This is groovy. Let me explain.

The {} is a closure literal. Closure is just a piece of code you can pass and later execute. Some say we will have those in Java in 2128 with the release of Java 37. Bottom line is that this is just a method that I am passing that expects one parameter which is String name of the property. By using the arrow -> you first define the expected input parameters and then you write the body of the closure.

Hey but why, how, what? You may think. But I have an Integer property bazinga and It returns String now?! Yep. This is groovy baby. Dynamic. You can mess it up really bad, but in this case it is just pure awesomeness.

Do we need anything else? Practically we can create now a DSL for the form layout with it.

Part 2 Closure Me

Ok. So now let’s take a look at some less trivial yaml example we have.


foo.bar.UserJAO:
  addressDetails:
    field: addresses
    columns: [street, streetNo, appartmentNo, city, country]

This is how we represent a table that should be generated from a list of elements of type AddressJAO. This AddressJAO has all the 5 properties used as columns.

So we need a way to actually pass somehow a typed object and then similarly like before use properties, but not to get their values but their names. We know how to do that, but how to pass that object?

UserJAO u = giveMeObjectThatReturnsProperties(UserJAO)

// so here is the usage

table("addressDetails", u.addresses, columns({
  AddresJAO a -> [a.street, a.streetNo, a.appartmentNo, a.city, a.country]
}))

// and the groovy handling

    def table(String tableSectionName, Object field, List columns) {
        output << "  $tableSectionName:\n"
        output << "    field: $field\n"
        output << "    columns: [" << columns[0]

        for (def i = 1; i < columns.size(); i++) {
            output << ", " << columns[i]
        }

        output << "]\n"
    }

    def columns(columnClosure) {
        Class aClass = columnClosure.parameterTypes[0]

        return columnClosure(giveMeObjectThatReturnsProperties(aClass))
    }

What happened here?

I have just used few nice things about groovy.

So first of all we have passed a closure as a parameter to columns method. The closure accepts an AddressJAO instance.

Then what automatically gets returned (If not specified, groovy will return the result of last line in the method’s body) is the long list of fields that are properties on the AddressJAO instance wrapped with []. This is a List literal. So what happens is that the closure, called with the parameter AddressJAO, extended with our getProperty from the previous example, will return the list of the properties we just called. And they are intellisensed, refactorable and magic.

What else might you not understand? The << operator is the syntax-sugar for dealing with buffers. Then you might also notice you can refer a variable inside a string prefixing it with a $.

And here you go. DSL ready. Hope you've learned something new. Finally our form definition looks like this:

public class UserForm extends ExampleForm<UserJAO> {

    UserForm() {
        super(UserJAO)
    }

    @Override
    def formDefinition(UserJAO u) {

        section("personDetails")
        row(u.name, u.lastName)
        row(u.shoeSize)

        table("addressDetails", u.addresses, columns({
            AddressJAO a -> [a.street, a.streetNumber, a.appartmentNumber, a.city, a.country]
        }))
    }
}

Why is groovy so awesome for a java developer? Because it is a beautiful language, but the learning curve for a java dev is zero. Almost every java code will compile as-is by just changing the suffix to .groovy. And then step by step you can learn the nitty-gritties.

I have put the whole example on github. You can go and play with it yourself.

Writing a DSL with groovy

One thought on “Writing a DSL with groovy

Leave a Reply

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