Groovy 2.1: The @DelegatesTo Annotation

http://docs.codehaus.org/display/GROOVY/Eclipse+Plugin#EclipsePlugin-DevelopmentBuildsGroovy 2.1 comes with a neat feature for DSL and Groovy library others: the @DelegateTo [0] annotation.

The Delegate and The Closure

Groovy supports the concept of closures [1]. One closure feature is to being able to modifiy the closure code delegation mechanism. Once a closure is defined, its delegate object can be changed by setting the delegate property:
 def me = 'André'
def closure = { greet me }

closure.delegate = new Greeter()

closure() 
In the code sample above, we change the delegate to an instance of Greeter.
 class Greeter {
    void greet(def another)  {
        println "Hello, $another"
    }
} 
When executing the code in groovConsole [2], it prints
 No signature of method: ConsoleScript1.greet() is applicable for argument types: (java.lang.String) values: [André] 
Ups, obviously we did forget one little thing: the resolve strategy. By setting the resolve strategy of a single groovy.lang.Closure instance, we can chose between the following types: In our example the resolve strategy should be set to DELEGATE_FIRST or OWNER_FIRST, as we call Greeter#greet and have a reference to the script variable me.
 def me = 'André'
def closure = { greet me }

closure.delegate = new Greeter()
closure.resolveStrategy = Closure.DELEGATE_FIRST

closure() 

What's the deal?

Modifying the closure resolving strategy is a powerful technuique for DSL and Groovy library authors. For example, take a look at GContracts [3], a Design by Contract extension for Groovy, that uses closure annotations to specify contracts on methods and classes:
 @Invariant({ elements != null })
class Stack {

    List elements

    @Ensures({ is_empty() })
    def Stack()  {
        elements = []
    }

    // ...


    @Ensures({ last_item() == item })
    def push(T item)  {
       elements.add(item)
    }

    @Requires({ !is_empty() })
    @Ensures({ last_item() == item })
    def replace(T item)  {
        remove()
        elements.add(item)
    }

    @Requires({ !is_empty() })
    @Ensures({ result != null })
    T remove()  {
        elements.remove(count() - 1)
    }

    String toString() { elements.toString() }
}

def stack = new Stack() 
Another example is definitely Gradle [4], the Groovy-based build automation framework:
 task hello << {
    println 'Hello Earth'
}
hello.doFirst {
    println 'Hello Venus'
}
hello.doLast {
    println 'Hello Mars'
}
hello << {
    println 'Hello Jupiter'
} 
When working with one of these libraries in IntelliJ or Eclipse, one shortage becomes immediately apparent: whenever a custom delegate object is set, IntelliJ or Eclipse do not provide code completion for closure code, except the IDE of choice comes with specific framework support.

@DelegatesTo comes into play

As of version 2.1, Groovy provides a way for DSL, library and framework authors to hint IDEs in the right direction: the @DelegatesTo annotation. In its simplest application, @DelegatesTo has only a single default argument of type java.lang.Class. Back to our previous example, we have to introduce an indirection as @DelegatesTo can only be applied on parameters:
 class Greeter {
    void greet(def another)  {
        println "Hello, $another"
    }
}

class Marketplace {

    void interactions(@DelegatesTo(Greeter) Closure closure) {
        closure.delegate = new Greeter()
        closure.resolveStrategy = Closure.DELEGATE_FIRST
        closure()
    }
}

def marketplace = new Marketplace()

def me = 'André'
marketplace.interactions{
    greet me
} 
We can even provide more meta-data by specifying the strategy attribute:
 // ...

void interactions(@DelegatesTo(strategy = Closure.DELEGATE_FIRST, value = Greeter) Closure closure) {
// ... 

The DelegatesTo.Target Annotation

@DelegatesTo has one last additional meta-data that can be applied on method paramters: the @DelegatesTo.Target annotation. Whenever an actual parameter will be set as the closure's delegate object, the Target can be used to document this circumstance:
 // ...

void interactions(@DelegatesTo.Target Greeter greeter, @DelegatesTo(Greeter) Closure closure) {
    closure.delegate = greeter // <-- the argument will be set as closure delegate

    // ....

}

// ... 
For the special case of having multiple parameters of the same type, there is a target annotation element type that allows for specifying unique String ids.
 // ...

void interactions(@DelegatesTo.Target('id') Greeter greeter, @DelegatesTo(target = 'id') Closure closure) {
    closure.delegate = greeter // <-- the argument will be set as closure delegate

    // ....

}

// ... 
Note, in the example above we did not specify a class for the closure parameter. This can be omitted since the type will be inferred from the greeter parameter.

IDE Support

Besides supporting IDEs the @DelegatesTo information is evaluated by @TypeChecked and @CompileStatic. As of writing, neither IntelliJ nor the Groovy Eclipse plugin comes with support for @DelegatesTo, but it is expected to become supported in the near future:

Conclusion

DSL, library and Groovy framework authors should definitely consider adding @DelegatesTo to their method meta-data. Besides the benefit of better IDE support (think of code completion), additional meta-data is utilized by Groovy 2.0's @TypeChecked and @CompileStatic to catch even more typing errors. It's a small step for third party authors, but a big step for Groovy IDE support. Update As announced today (2013-02-07), @DelegatesTo is supported by the Groovy-Eclipse plugin. Snapshot builds are available at http://docs.codehaus.org/display/GROOVY/Eclipse+Plugin#EclipsePlugin-DevelopmentBuilds

[0] GROOVY-5714: @DelegatesTo annotation for IDEs/TypeChecker
[1] Wikipedia: Closure
[2] grooovyConsole
[3] GContracts
[4] Gradle - Build Automation Framework