Written on
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()
class Greeter {
void greet(def another) {
println "Hello, $another"
}
}
No signature of method: ConsoleScript1.greet() is applicable for argument types: (java.lang.String) values: [André]
- OWNER_ONLY: delegate method calls to the owner of the closure (e.g. the instance creating the closure instance)
- DELEGATE_ONLY: delegate method calls to the delegate of the closure
- OWNER_FIRST: delegate to the owner first, then the delegate
- DELEGATE_FIRST: delegate to the delegate first, then the owner
- SELF_ONLY: method calls are resolved against the closure instance only
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()
task hello << {
println 'Hello Earth'
}
hello.doFirst {
println 'Hello Venus'
}
hello.doLast {
println 'Hello Mars'
}
hello << {
println 'Hello Jupiter'
}
@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
}
// ...
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
// ....
}
// ...
// ...
void interactions(@DelegatesTo.Target('id') Greeter greeter, @DelegatesTo(target = 'id') Closure closure) {
closure.delegate = greeter // <-- the argument will be set as closure delegate
// ....
}
// ...
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[2] grooovyConsole
[3] GContracts