Home > Groovy > Empowering Annotations with Groovy Closures

Empowering Annotations with Groovy Closures

A few days ago, we received the following feature request for the Spock Framework:

Run a specification only if JDK 1.6 or higher is present, and skip it otherwise.

Given Spock’s extensible nature, it would have been easy to jump straight in and implement this as an extension activated with @JdkVersion(x). But this approach didn’t feel right to us. Surely, users would soon come up with other constraints (JDK version less than x, OS version equal to y, and so on), and we wanted a solution that could handle them all. Alas, if Groovy just allowed us to write:

@RunIf({ jdkVersion >= 1.6 })
class MySpec extends Specification { ... }

Now, this would be heaven. No more @JdkVersion, @OsVersion, and so on. Instead just one annotation that takes an arbitrary Groovy expression (contained in a closure) and is processed by one Spock extension. Problem solved – forever. (At this point you might wonder where “jdkVersion” comes from. I assume we would provide some convenience properties like it, but with the power of Groovy at your fingertips, you could always go beyond them.)

Staring at my screen, I became a little saddened because I knew that Groovy wouldn’t let me stick a closure into an annotation. But wait – the code in my IDE wasn’t underlined in red! Was it a bug, or were I up to something here? Hastily I fired up Groovy’s AST Browser and noticed that Groovy’s grammar does allow closures as annotation values. It is only the compiler’s code generator that eventually raises a compile error. At this point I became nervous. Could it be that I was just one AST transformation away from heaven?

In case you haven’t heard about AST transformations yet, let me quickly explain what they are: a way to hook into the Groovy compiler and participate in a program’s compilation. What’s particularly nice about AST transformations is that they are trivial to activate from the outside: Just put a transformation on the class path, and the compiler will pick it up automatically. Some of Groovy’s built-in features like @Lazy and @Immutable are in fact based on AST transformations, and so are many of Spock’s features.

With a rough idea in my head, I started coding an AST transformation that would allow me to pass closures as annotation values. A few hours later, I had a pretty compelling prototype done. Let me show you an example of what you can do with it today. Let’s say we wanted to build a simple field-based POGO (Plain Old Groovy Object) validator. To start out, let’s define an annotation type:

@Retention(RetentionPolicy.Runtime)
@interface Require {
  Class value()
}

The annotation type has one attribute of type Class. That’s where we will stick in our closure. Really, it’s a simple as that. Now let’s code the validator:

class Validator {
  def isValid(pogo) {
    pogo.getClass().declaredFields.every {
      isValidField(pogo, it)
    }
  }

  def isValidField(pogo, field) {
    def annotation = field.getAnnotation(Require)
    !annotation || meetsConstraint(pogo, field, annotation.value())
  }

  def meetsConstraint(pogo, field, constraint) {
    def closure = constraint.newInstance(null, null)
    field.setAccessible(true)
    closure.call(field.get(pogo))
  }
}

To paraphrase the first two methods, a POGO is valid iff every of its fields is valid, and a POGO field is valid iff its constraint is met (or it has no @Require annotation). This leaves us with the meetsConstraint() method, which deserves a closer look. Remember that constraint holds the closure’s Class, which now gets instantiated with Class.newInstance. Since the closure only needs access to the field’s value, we pass null for delegate and thisObject. Finally we make the field accessible (important for non-public fields), get the field’s value, and call the closure. That’s it!

To demonstrate that our validator works, let us add some test code. First we define the POGO that is going to be validated:

class Person {
  @Require({ it ==~ /[a-z A-Z]?/ })
  String name
  @Require({ it in (0..130) })
  int age
}

What we are saying here is that a Person’s name may only consist of the characters a-z (in lower or upper case) and the space character, and that a Person’s age must lie between 0 and 130. Admittedly these are quite simple constraints, but it’s all just plain Groovy! If necessary, we could compute the answer to the universe instead. Well, almost. But you get the point.

Now it’s time to validate some persons:

def validator = new Validator()

def fred = new Person(name: "Fred Flintstone", age: 43)
assert validator.isValid(fred)

def barney = new Person(name: "!!!Barney Rubble!!!", age: 37)
assert !validator.isValid(barney)

def dino = new Person(name: "Dino", age: 176)
assert !validator.isValid(dino)

If you run this code (read on to learn how it’s done!), you will find that Fred meets both constraints, Barney gets a little too excited about his own name, and Dino is apparently too old (although he certainly doesn’t look like it). What more could we expect from a simple validator?

Although they are in their early days, annotation closures (that’s the working title) can already do more than what I showed you here. For now, they live in my groovy-extensions project on GitHub, and will be updated regularly. This means that you can start experimenting with annotation closures today, and use them in your apps tomorrow. In fact, if you happen to have Groovy 1.7 installed, do yourself a favor right now: Open GroovyConsole, paste in the code you’ve just seen, and kick off a few validations! Thanks to Grape, the groovy-extensions Jar (only a few kB large) will be downloaded on the fly. Alternatively, you can download the Jar from Spock’s build server (see the Artifacts tab).

Of course, my longer-term plan is to roll annotation closures into Groovy 1.8, and I’m pretty confident that it will happen. Spock users will be able to benefit from annotation closures even sooner; after all, someone* out there is waiting for @RunIf!

Now it’s your turn. Fork the groovy-extensions project, start your Groovy engines, and keep the feedback coming!

* Those of you following me on Twitter might know that I myself requested this feature, in order to simplify testing JDK 1.6-only features in Spock’s Spring extension.

About these ads
Categories: Groovy
  1. March 4, 2010 at 14:04

    Great post. Definitely see a world of possibilities with annotation closures.

  2. March 4, 2010 at 14:34

    this is wonderful. can’t wait to see it in 1.8

  3. March 4, 2010 at 21:53

    This pretty cool. I played but failed. What am I missing?
    I copy the code into groovy console (groovy 1.7.1 on windows) and run it and it failed with this error:
    2 compilation errors:

    unable to resolve class Retention , unable to find class for annotation
    at line: 3, column: 5

    unable to find class ‘RetentionPolicy.Runtime’ for annotation attribute constant
    at line: 3, column: 16

    ————————-code——————————————————

    @Retention(RetentionPolicy.Runtime)
    @interface Require {
    Class value()
    }
    class Validator {
    def isValid(pogo) {
    pogo.getClass().declaredFields.every {
    isValidField(pogo, it)
    }
    }

    def isValidField(pogo, field) {
    def annotation = field.getAnnotation(Require)
    !annotation || meetsConstraint(pogo, field, annotation.value())
    }

    def meetsConstraint(pogo, field, constraint) {
    def closure = constraint.newInstance(null, null)
    field.setAccessible(true)
    closure.call(field.get(pogo))
    }
    }
    class Person {
    @Require({ it ==~ /[a-z A-Z]?/ })
    String name
    @Require({ it in (0..130) })
    int age
    }
    def validator = new Validator()

    def fred = new Person(name: “Fred Flintstone”, age: 43)
    assert validator.isValid(fred)

    def barney = new Person(name: “!!!Barney Rubble!!!”, age: 37)
    assert !validator.isValid(barney)

    def dino = new Person(name: “Dino”, age: 176)
    assert !validator.isValid(dino)

    • March 4, 2010 at 21:58

      Yiguang,

      take the code from the link I gave. It contains the necessary imports and the @Grab that brings in the groovy-extensions Jar.

  4. March 5, 2010 at 03:07

    Thanks. That works!

  5. Roshan Dawrani
    March 5, 2010 at 03:38

    I had an early-bird opportunity to look at your annotation closures and first thing I also went to see was how come it was allowed without any grammar changes when all the time, the talk was that you can’t put arbitrary code in annotations!

    Is it the power of color coding in the IDE or power of the eyes that could see the color! :-)

    I am sure it will be very handy to have closures in annotations. There are already requests that can benefit from that – for ex, someone wants to know the progress of a @Grab downloads and progress needs to be shown using a code snippet passed in a closure.

    So, thanks for showing the way!

  6. March 27, 2010 at 14:06

    really nice – inspired me to write gconstracts, a library for providing “design by contract” for Groovy: http://grails-inside.blogspot.com/2010/03/contract-oriented-programming-with.html

  1. March 4, 2010 at 16:37
  2. March 27, 2010 at 20:30
  3. January 18, 2011 at 07:28

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: