I'm banging my head against this wall for more than a week now, let me explain briefly what I'm trying to achieve.
I have a DSL defined like this (brief example)
TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
Note the something in there that is not passed anywhere intentionally, because I want to limit the scope of use of my DSL by the will be users, so what I actually want is to somehow "inject" the something in that DSL instance programmatically. This is being done by using a Closure.
Myscript.execute( {
TestingSpec
.newInstance()
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
})
and MyScript will invoke it by passing the something value to it, either by passing a parameter, or by adding a property or by adding a Binding (don't know what's the best, really)
close.call("Toni")
close.metaClass.something = "Toni"
def binding = new Binding()
binding.setVariable("something", "Toni")
close.setBinding(binding)
But now I'm stuck in what I thought it was going to be easy, how from the TestingSpec code can I access the Closure something? For instance
public newInstance() {
def instance = new TestingSpec()
instance.something = *MY CLOSURE SOMETHING*
return instance
}
I've tried several options, like messing with the this, owner and delegate of the closure, but couldn't do it.
Any hint will be very welcome...
Cheers.
I don't think that's doable. TestingSpec is too high on the stack to be aware he is being invoked from a closure. I'd like to suggest two solutions
1. Pass something to TestingSpec programmatically.
Myscript.execute( {
TestingSpec
.newInstance(something)
.sayHello() --> print "Hello "+something
.sayGoddbye() --> print "Goddbye"+something
})
2. Make the TestingSpec instance be created by MyScript
I like this solution more. It is about MyScript.execute({}) being responsible for creating and handling the lifecycle of TestingSpec:
class TestingSpec {
def something
def sayHello() {
println "Hello " + something
this
}
def sayGoddbye() {
println "Goddbye " + something
this
}
}
class Myscript {
static TestingSpec testingSpec
static execute(closure) {
def binding = new Binding(something: 'test for echo')
testingSpec = new TestingSpec(something: binding.something)
binding.testingSpec = testingSpec
closure.binding = binding
closure()
}
}
Myscript.execute( {
testingSpec // this is an instance, and not a static call
.sayHello() // print "Hello "+something
.sayGoddbye() // print "Goddbye"+something
})
Output:
$ groovy Spec.groovy
Hello test for echo
Goddbye test for echo
3. Delegate the closure to testingSpec
If you set the closure delegate to testingSpec, you can invoke its methods without referencing testingSpec directly, providing a very clean code. Note i also updated the example to remove MyScript:
class TestingSpec {
static TestingSpec testingSpec
static execute(closure) {
def binding = new Binding(something: 'test for echo')
testingSpec = new TestingSpec(something: binding.something)
binding.testingSpec = testingSpec
closure.delegate = testingSpec
closure.binding = binding
closure()
}
def something
def sayHello() {
println "Hello " + something
this
}
def sayGoddbye() {
println "Goddbye " + something
this
}
}
TestingSpec.execute( {
sayHello() // print "Hello "+something
sayGoddbye() // print "Goddbye"+something
})
4. Inject testingSpec as a parameter into your closure
As per your answer, a simple suggestion: consider giving a testingSpec var that you've built yourself to the user, since it allows more control and customization to you. Think a simple inversion of control (this allows testingSpec to see dynamic variables in the closure's binding):
.withDistinctNames({ testingSpec ->
testingSpec
.from(name)
.withAddress(email)
.sendEmail(template)
})
With a full test:
class TestingSpec {
def commands = []
static newInstance(listOfNames) {
new TestingSpec()
}
def sayHello() { commands << "sayHello"; this }
def waveGoddbye() { commands << "waveGoddbye"; this }
def withAddress(address) { commands << "withAddress $address"; this }
def sendEmail(template) { commands << "sendEmail $template"; this }
def withDistinctNames(closure) {
commands << "withDistinctNames"
closure.binding = new Binding(address: "sunset boulevard", template: 'email is $email')
closure(this)
this
}
}
test = TestingSpec
.newInstance([:])
.sayHello()
.withDistinctNames({ me ->
me
.withAddress(address)
.sendEmail(template)
})
.waveGoddbye()
assert test.commands == [
'sayHello',
'withDistinctNames',
'withAddress sunset boulevard',
'sendEmail email is $email',
'waveGoddbye'
]
I finally reach a solution to my problem that while not ideal it's "elegant" enough and "easy" to understand by would-be users of my DSL. So, what I want to do is allow the DSL users to write things like this:
TestingSpec
.newInstance()
.from(listOfNames)
.sayHello()
.withDistinctNames({
TestingSpec
.newInstance()
.from(*****) <---- problem
.withAddress()
.sendEmail(template)
})
.waveGoddbye()
There could be a number of with methods that basically serve as a filter+loop, so in this example it will filter the names to be unique and then apply the Closure to each name in turn. Of course this would be extremely easy to do by just doing
.withDistinctNames({
TestingSpec
.newInstance()
.from(it)
.withAddress()
.sendEmail(template)
})
or for more complicated cases
.withDistinctNames({ name, email, template ->
TestingSpec
.newInstance()
.from(name)
.withAddress(email)
.sendEmail(template)
})
The prob is I don't know who is going to use this DSL, it may not be people familiar with closures, parameters, functions, whatnots, it may be people even from outside my organization, so my goal was to keep it simple by passing the needed variables from the outer Spec to the inner Spec. In my TestingSpec implementation I would have:
public TestingSpec withAll(Closure k, Closure f) { // withAll is used by all with* methods
def result = k.call(root) // root is the entire listOfNames
def results = result
.collect {
// HERE'S THE PROBLEM, HOW TO INJECT ALL THE PARAMETERS INTO THE INNER SPEC?
f.call(it) // this line passes vars to the Closure, not the inner Spec, and I found no way for the Closure itself to inject them in the Spec
}
return this;
}
After I saw it was impossible to inject params in the inner Spec itself (that is yet to be instantiated) I tried to pass them to the Closure and from there to the Spec, like this:
.withDistinctNames({
TestingSpec
.newInstance()
.from(this) <-------------------
.withAddress()
.sendEmail(template)
})
I supposed that this would be the outer Spec that contains all the info I need, but it was not. I tried with this, this.thisObject, this.owner, this.delegate.
But then, with the help of the 3rd above suggestion I ended up doing this:
public TestingSpec withAll(Closure k, Closure f) {
f = f.rehydrate(f.getDelegate(), f.getOwner(), this)
def result = k.call(root)
def results = result
.collect {
this.parameters = [whatever I need]
f.call()
}
return this;
}
This way the this in the DSL is actually the outer Spec, so the users can now use the more intuitive "this" keyword. To avoid confusion I got rid of the .from() method, like this:
TestingSpec
.newInstance(listOfNames)
.sayHello()
.withDistinctNames({
TestingSpec
.newInstance(this) // my newInstance implementation can parse all the parameters as it wishes
.withAddress()
.sendEmail(template)
})
.waveGoddbye()
While it's not ideal is close enough, from the perspective of the potential users, I think. If somebody has suggestions please let me know.
Thanks Will P and all.
After working on a small similar Groovy DSL tool recently and digging a bit into griffon and swing and other material I currently ended with the following, that I submit as another suggestion to the answer.
Any comment/suggestion will be greatly appreciated.
IMHO a clear separation between the domain model (a Model class) , and the support classes (building, aggregation , context sharing, and filtering part of the model, at least a Builder class) is one of the key elements
to have a simple and efficient model. This is the pattern used in groovy swing and griffon and showed to be very flexible.
It allows constructs with each {}, with {} and collection operations which are clean and understandable enough,
and mostly without chaining operations (ie adding an annoying return this at the end of each operation).
Like here for example (source code at the end, MSys is a Facade to the underlying system):
MSys.with {
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
or
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
or
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
or ...
(lot of possibilities)
It also allows to have some shared context very easily (in the example , context is a Map shared between all the Model elements where almost anything could be put, connection information, user preferences, cache etc.),
and to hide all the boiler plate to the user by putting it in another class/script .
The responsibilities would be :
SpecModel : Domain model : say hello, goodbye, properties etc
SpecBuilder : creates models (from a list in the example), holds shared context (a map) and eventually take care of the closure delegate context for some operations
This separation is important to deal with set operations on one side and entity (model) operations on the other.
Apart from beeing a builder, from a user POV it is a facade
and from a developer POV BTW it should be a Facade to several classes including the builder -but better start simple.
The next step would be to integrate FactoryBuilderSupport in this builder in order to benefit from Groovy DSL builder facilities, but this one big step beyond and I'm not comfortable with that yet ... (WIP right now)
Few Groovy recaps that you certainly already know but part of the problem if you want a light syntax :
In Groovy , , ; and () are optional except when there is a conflict.
Even where there is no direct conflict, the delegation strategy is not always direct and
run time errors are not always clear (see link on closures doc below)
Having a public getter method for properties, allows the use of property like access methods without the (),
the get and set prefixes are inferred
ie with a method like
public getSendEmail() {
println "email for ${name}"
}
you can use the syntax :
myObject sendEmail
Groovy assumes a property getter call and do the rest.
This helps removing the () in the dsl syntax.
The context is the delegate context of the closure it not the this
When you use closures, you use it to reference the object you are working on in the closure
(ie the receiver of the message).
Example:
[1, 2, 3].each { println it }
is ok
If you haved used
[1, 2, 3].each { println this }
you would have print a reference to the outer object, the console in groovy console
(Which IIRC was one of the problem you had in your first post)
This is well explained in the Groovy Closures doc :
(excerpt:)
this : the enclosing class where the closure is defined
owner : the enclosing object where the closure is defined, a class or a closure
delegate : third party object where methods calls or properties are resolved whenever the receiver of the message is not defined
The message delegation strategy (explained in the same document) is not always direct and this was your real problem I think.
That said, another key point in a DSL is the coherence from a user POV.
This means consistence in data access patterns and here the standard Groovy collections (Lists, maps each{} etc) can help a lot.
Plus it can be a huge plus for power users.
Syntactic sugar methods like eachModelDo can easily be done on a builder/facade class:
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
NB: eachModelDo is very simple but a bit tricky to debug
At one point it "worked" well without accessing correct variables :(
I have the feeling that something is wrong here (?) or at least it should be improved (comments welcome)
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
Bellow is the source code of a small test I did that you can cut and paste in a groovy console
The only part visible to the user should be the method Demo.run()
All the other stuff should be hidden
Any comments are welcome
/**
* The builder build Specs and defines utility methods, filters
* It shares its context with all elements in the domain
*/
class SpecBuilder {
/** the context will be shared with all domain model objects */
private context
/** model = all the domain model objects */
private model
/** getters / setters */
public getModel() { return model }
public getContext() {
return context
}
/** constructors and helpers */
public SpecBuilder(aContext) {
context = aContext
}
/** Default constructor forbidden */
private SpecBuilder() {}
public from(aList, closure) {
from(aList);
model.each { closure(it) }
return this
}
public from(aList) {
model = aList.collect { new SpecModel(it, context) }
return this
}
/* TODO filters etc */
/** stats: print counters */
public stats() {
println " Hello counts"
model.each {
def key = it.name
def value = this.context[key]
println "Counter for ${key} = ${value}"
}
}
/**
* syntactic sugar
* direct access without 'it' (optional)
*/
public SpecBuilder eachModelDo(closure) {
model.each {
closure.delegate = it;
closure(it)
}
}
}
/**
* The Spec Domain Model
*/
class SpecModel {
/** the shared context */
private context;
/** other properties */
private name;
public address;
/** getters and setters */
public getName() { return name }
public void setAddress(a) { address = a }
public getAddress() { return address }
public sayHello() { return getSayHello }
public sayGoodBye() { return getSayGoodBye }
public sendEmail() { return getSendEmail }
/** constructors */
public SpecModel(aName, aContext) {
name = aName
context = aContext
}
/** Default constructor forbidden */
private SpecModel() {}
/** method used like properties, without 'get' and ()*/
public getSayHello() {
println "(!) hello ${name}"
context[name] = context.get(name,0) +1;
}
public getSayGoodBye() {
println "goodBye ${name} !"
}
public getSendEmail() {
println "email for ${name}"
if (address)
println "Address ${address}"
}
public getPrintContext() {
println context
}
/**
* Returns info to caller
*/
public gatherInfo() {
"info for ${name} : ${new java.util.Random().nextInt(50000)}"
}
}
class Demo {
// several Groots here to test uniques ...
def customers = ['Groot', 'Gamora', 'Groot', 'Groot', 'Groot', 'Star-Lord']
/**
* Utility function who generates a random address
* #param name will prefix the address
* #return the address
*/
public randomAddress(def name) {
// good places ... :)
def places = [
"Grande Rue",
"Cours Emile Zola",
"Place Antonin Poncet",
"Rue de la République",
"Boulevard de la Croix Rousse",
"Place Bellecour"
]
def random = new java.util.Random();
return new StringBuilder().append(name).append(" ... ")
// why not 42?
.append( random.nextInt(155)).append(" ")
.append( places[random.nextInt(places.size())] )
.toString();
}
/**
* ======================== Main user program =========================
*/
def run() {
/** the shared context */
def context = [:].asSynchronized() // In case of multi threading access
/** The whole system From a user POV : a big façade */
def MSys = new SpecBuilder(context).from(customers) ;
println "*** 1 ==================== "
/** First form */
MSys.model.each {
it.with {
sayHello
sayGoodBye
setAddress randomAddress(name) ;
sendEmail
}
}
/** other forms
* MSys.with{ is handy
* one could write MSys... on each line
*/
MSys.with {
println "*** 2 ==================== "
model.each { it.sayHello };
println "*** 3 ==================== "
model.with { println " a Model entry = ${it.name} + ${it.address}" }
println "*** 4 ==================== "
/** false not to mutate the model !!! */
model.unique(false, { a, b -> a.name <=> b.name }).each { it.sayHello }
println "*** 5 ==================== "
context['aKey'] = 42
// verify that each entity has the same context
model.with { println " a shared context for ${it.name} : " + it.context }
println "*** Stats ================ "
/** stats on the shared context */
stats()
}
println "*** 6 Info to process ======== "
/** Gather info to process (addresses)*/
def data = MSys.model.inject([:]) { result, entity ->
result[entity.name] = entity.address
result
}
println data
MSys.with {
println "*** 7 ==================== "
model.each {
it.sayHello
it.sayGoodBye
it.setAddress randomAddress(it.name);
it.sendEmail
}
println "*** 8 ==================== "
println " Hello counts"
model.each {
def key = it.name
def value = MSys.context[key]
println "Counter for ${key} = ${value}"
}
}
println "*** 9 ==================== "
MSys.eachModelDo {
sayHello
sayGoodBye
setAddress randomAddress(it.name);
sendEmail
}
}
}
new Demo().run()
/* end of script */
Related
I had a test. It was using mockFor and was working and I was happy. Until something changed and I was forced to use a method pointer operator, and then my happiness was a just a memory.
Here a constricted example of the problem
import groovy.mock.interceptor.*
// I am testing againts a interface, not an actual class
// this is not negociable.
interface Person {
void sayHello(name);
}
// Setup (can do whatever I need)
def personMock = new MockFor(Person)
personMock.demand.sayHello { name -> println "Hello $name" }
def person = personMock.proxyInstance()
// End Setup
// Exercise (can not change)
def closureMethod = person.&sayHello.curry("Groovy!")
closureMethod()
// End Exercise
personMock.verify(person)
Which would be the safest and simplest way to fix the test?
Currently, the test fails with java.lang.UnsupportedOperationException
Even when I am testing against a Interface, nothing prevents me from creating a mock class that implements the interface and do a poor-men verify demands.
import groovy.mock.interceptor.*
// I am testing against a interface, not an actual class
// this is not negociable.
interface Person {
void sayHello(name);
}
class PersonMock implements Person {
def calls = []
void sayHello(name) { calls << "sayHello" }
}
// Setup (can do whatever I need)
def person = new PersonMock()
// End Setup
// Exercise (can not change)
def closureMethod = person.&sayHello.curry("Groovy!")
closureMethod()
// End Exercise
assert person.calls == ['sayHello']
I have been trying to do a small Groovy project, and wanted a ConcurrentLinkedHashSet, but Java doesn't provide one. So I set about creating my own using a Gpars agent to 'protect' an ordinary LinkedHashSet. I then created my wrapper class to hold the agent, and delegated the methods on my class to the internal Agent like this (this version is using methodMissing as delegation approach). I tried Groovy interceptable/invokeMethod and could get it to work either
class ConcurrentLinkedHashSet /*implements GroovyInterceptable*/ {
Agent $concurrentHashSet = new Agent (new LinkedHashSet())
/*Object[] toArray () {
$concurrentHashSet.val.asType(LinkedHashSet).toArray()
}*/
def asType (Set) {
$concurrentHashSet.val
}
//set up delegation action to pass all methods to the agent to execute
def /*invokeMethod*/methodMissing (String name, args){
def result
System.out.println "methodMissing with $name and $args on $this"
System.out.println "agent value is : ${$concurrentHashSet.val.dump()} is instance of: ${$concurrentHashSet.getClass()}"
$concurrentHashSet {
System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "
if (args == []) {
System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with no args "
result = it."$name"()//it.invokeMethod (name)
} else {
System.out.println "\t\tconcHashSet methodMissing: invoke method '$name' with args $args"
result = it.invokeMethod(name, *args)//"$name" (*args)//it.invokeMethod(name, args)
}
System.out.println "\tconcHashSet methodMissing: 'it' is now showing as > '$it'"
"success"
}
//System.out.println "agent protected value is now : " + $concurrentHashSet.val + " and result now $result"
System.out.println "agent protected value is now : " + $concurrentHashSet.val.dump() + " and result now $result"
$concurrentHashSet.val
}
}
however when I try to use this - it works first time through, my strings is added, but on the second call on the same missing method the agent.send call is never made, and gets skipped.
So my simple script consumer looks like this
// delegates add method via agent.send first time through but not after !
ConcurrentLinkedHashSet conhs = new ConcurrentLinkedHashSet ()
conhs.add ('col1')
println "1st conHashSet as list : ${conhs.toArray()}"
conhs.add ('col2')
println "2nd conHashSet as list : ${conhs.toArray()}"
// direct calls on an agent using invokeMethod
Agent myHash = new Agent (new LinkedHashSet())
myHash {it.invokeMethod ('add', 'col1')}
println "1st agentHashSet as list : ${myHash.val.toArray()}"
myHash {it.invokeMethod ('add','col2')}
println "2nd agentHashSet as list : ${myHash.val.toArray()}"
and my simple trace log looks like this on the console output
methodMissing with add and [col1] on org2.softwood.rules.utils.ConcurrentLinkedHashSet#3b0090a4
agent value is : <java.util.LinkedHashSet#0 map=[:]> is instance of: class groovyx.gpars.agent.Agent
concHashSet methodMissing: it is of type class java.util.LinkedHashSet so called [] (add, [col1])
concHashSet methodMissing: invoke method 'add' with args [col1]
concHashSet methodMissing: 'it' is now showing as > '[col1]'
agent protected value is now : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> and result now true
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet#3b0090a4
agent value is : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> and result now null
1st conHashSet as list : [col1]
methodMissing with add and [col2] on org2.softwood.rules.utils.ConcurrentLinkedHashSet#3b0090a4
agent value is : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> and result now null
methodMissing with toArray and [] on org2.softwood.rules.utils.ConcurrentLinkedHashSet#3b0090a4
agent value is : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> is instance of: class groovyx.gpars.agent.Agent
agent protected value is now : <java.util.LinkedHashSet#2eaeb1 map=[col1:java.lang.Object#6a2f6f80]> and result now null
2nd conHashSet as list : [col1]
1st agentHashSet as list : [col1]
2nd agentHashSet as list : [col1, col2]
As you can see on the first attempt at delegation you can see the 'concHashSet methodMissing:' trace and the agent calls invokeMethod on it in the agent to effect adding the element.
On the second call to conhs.add ('col2') the agent.sand call never happens and so my extra item never gets added.
This is annoying as I thought I had an easy way to create my ConcurrentLinkedHashSet, but the code doesn't work. What mechanism could I use to get the right outcome?
As you can see when I invokeMethod (add) directly on an Agent<LinkedHashSet> it works just fine. In my real consuming class if I replace my ConcurrentLinkedHashSet with an ordinary LinkedHashSet it works a dream, but isn't thread safe. I wanted create a thread safe version which depended on trying to get this to work.
I guess I could try and replace the Agent and just use synchronize blocks round my LinkedHashSet - but its a bit ugly - I thought the Gpars Agent would sort all this for me as a general solution pattern as a wrapper with delegation.
PS I tried another tack and this sort of works I think, but doesn't feel right - it uses #Synchronise on invokeMethod on class that implements GroovyInterceptable to achieve a thread safe call when delegating. I am not sure if this truly thread safe or not.
class ConcurrentLinkedHashSet2 implements GroovyInterceptable{
#Delegate private LinkedHashSet $mySet = new LinkedHashSet()
#Synchronized
def invokeMethod (String name, args) {
System.out.println "call to $name intercepted invoke using metaclass invoke"
ConcurrentLinkedHashSet2.metaClass.getMetaMethod(name).invoke (this, *args)
}
}
It works as expected after commenting out the trace output:
System.out.println "\t\tconcHashSet methodMissing: it is of type ${it.getClass()} so called $it.invokemethod ($name, $args) "
This line causes an unhandled exception to be thrown from the agent, which terminates the agent.
Vaclav spotted my error - my System.out.println had an error that caused an exception inside the body of the agent.
So I've built a corrected version of my original example:
/**
* using methodMissing to delegate calls
*
* Created by William on 29/12/2016.
*/
class ConcurrentLinkedHashSet2 implements GroovyInterceptable {
Agent $concurrentHashSet = new Agent (new LinkedHashSet())
//set up delegation action to pass all methods to the agent to execute
def invokeMethod (String name, args){
DataflowVariable invokeResult = new DataflowVariable()
$concurrentHashSet {
invokeResult << it?.invokeMethod(name, args)
}
invokeResult.val
}
}
This led to a more generalised answer in the Genie class - I hope this maybe of use to others. In this version decided to delegate equals, and toString to wrapped variable, as class was hanging, this fixes that. This version takes any Class or an instance variable and wraps it with thread safe Agent, and then delegates method calls to the wrapped variable - hence the 'Genie' moniker.
/**
* Thread safe container for instance of any type, where the
* genies invokeMethod is used to delegate calls to agent protected
* value. If the genie provides local methods, then the
* delegation calls the local method
*
* Created by William on 29/12/2016.
*/
class Genie<T> implements GroovyInterceptable {
Agent $concurrentVariable = new Agent ()
Genie (Class clazz) {
assert clazz instanceof Class
def instance = clazz.newInstance()
$concurrentVariable {
updateValue (instance)
}
def ref = $concurrentVariable.val
assert clazz.is (ref.getClass() )
}
Genie (instanceVariable) {
$concurrentVariable { updateValue (instanceVariable)}
}
void setVariable (instanceVariable) {
$concurrentVariable { updateValue (instanceVariable)}
}
def getVariable () {
$concurrentVariable.val
}
def getWrappedClass () {
$concurrentVariable.val?.getClass()
}
//set up delegation action to pass all methods to the agent to execute
def invokeMethod (String name, args) throws MissingMethodException {
DataflowVariable invokeResult = new DataflowVariable()
//if holding reference to a null then just try the call on the Genie instance
if ($concurrentVariable.val == null) {
invokeResult << this.metaClass.getMetaMethod("$name", args).invoke (this, args)
return invokeResult.val
}
//else look and see if method is expected to be called on the Genie instance
def listOfMethods = this.metaClass.getMethods().collect {it.name}
//we want delegation of toString() and equals(), and hashCode() to wrapped variable so remove from check of methods on Genie
listOfMethods.remove ("toString")
listOfMethods.remove ("equals")
listOfMethods.remove ("hashCode")
boolean methodMatched = listOfMethods.find {it == name}
if (methodMatched && this instanceof Genie) {
//see if there's a local method in Genie in scope, then call it
invokeResult << this.metaClass.getMetaMethod("$name", args).invoke (this, args)
} else {
def method = $concurrentVariable.val.metaClass.getMetaMethod(name, args)
if (method == null) {
invokeResult << $concurrentVariable.val.metaClass.invokeMissingMethod($concurrentVariable.val, name, args)
} else {
//delegate the method to the variable wrapped by the Agent<T>
$concurrentVariable {
MetaMethod validMethod = it.metaClass.getMetaMethod(name, args)
if (validMethod) {
invokeResult << validMethod.invoke(it, args)
} else invokeResult << null
}
}
}
invokeResult.val
}
}
Starting to grasp closures in general and some groovy features.
Given the following code:
class Mailer {
void to(final String to) { println "to $to" }
void from(final String from) { println "from $from" }
static void send(Closure configuration) {
Mailer mailer = new Mailer()
mailer.with configuration
}
}
class MailSender {
static void sendMessage() {
Mailer.send {
to 'them'
from 'me'
}
}
}
MailSender.sendMessage()
What happens under the hood when you pass a closure to Mailer.send method?
Does to and from are passed as arguments from the Closure point of view? Which types the Closure maps them?
And then inside the Mailer.send method at the moment the Mailer object calls mailer.with receiving the configuration object, the object maps them into method calls. Groovy does this by reflection?
Groovy can dynamically define the delegate of a closure and even the this object.
with is setting the delegate and executing the closure. This is a verbose way to achieve the same:
def math = {
given 4
sum 5
print
}
class PrintMath {
def initial
def given(val) {
initial = val
}
def sum(val) {
initial += val
}
def getPrint() {
println initial
return initial
}
}
math.delegate = new PrintMath()
math.resolveStrategy = Closure.DELEGATE_ONLY
assert math() == 9
What happens under the hood when you pass a closure to Mailer.send method?
It receives a not-yet-executed block of code.
Does to and from are passed as arguments from the Closure point of view?
No, it is better thinking of them as an anonymous class/lambda in java, or a function(){} in javascript.
Which types the Closure maps them?
None, they are method calls waiting to be executed. They can be delegated to different objects, though.
And then inside the Mailer.send method at the moment the Mailer object calls mailer.with receiving the configuration object, the object maps them into method calls. Groovy does this by reflection?
You can decompile a Groovy class file to see what is going on. IIRC, Groovy currently uses a "reflector" strategy (with an arrayOfCallSite caching) to make calls faster OR it can use invokedynamic.
The closure math in the code above will result in this class:
// .. a lot of techno-babble
public Object doCall(Object it) {
CallSite[] arrayOfCallSite = $getCallSiteArray();
arrayOfCallSite[0].callCurrent(this, Integer.valueOf(4));
arrayOfCallSite[1].callCurrent(this, Integer.valueOf(5));
return arrayOfCallSite[2].callGroovyObjectGetProperty(this);
return null;
}
I'd like to create a DSL with syntax like:
Graph.make {
foo {
bar()
definedMethod1() // isn't missing!
}
baz()
}
Where when the handler for this tree encounters the outermost closure, it creates an instance of some class, which has some defined methods and also its own handler for missing methods.
I figured this would be easy enough with some structure like:
public class Graph {
def static make(Closure c){
Graph g = new Graph()
c.delegate = g
c()
}
def methodMissing(String name, args){
println "outer " + name
ObjImpl obj = new ObjImpl(type: name)
if(args.length > 0 && args[0] instanceof Closure){
Closure closure = args[0]
closure.delegate = obj
closure()
}
}
class ObjImpl {
String type
def methodMissing(String name, args){
println "inner " + name
}
def definedMethod1(){
println "exec'd known method"
}
}
}
But the methodMissing handler interprets the entire closure inside Graph rather than delegating the inner closure to ObjImpl, yielding output:
outer foo
outer bar
exec'd known method
outer baz
How do I scope the missing method call for the inner closure to the inner object that I create?
The easy answer is to set the inner closure's resolveStrategy to "delegate first", but doing that when the delegate defines a methodMissing to intercept all method calls has the effect of making it impossible to define a method outside the closure and call it from inside, e.g.
def calculateSomething() {
return "something I calculated"
}
Graph.make {
foo {
bar(calculateSomething())
definedMethod1()
}
}
To allow for this sort of pattern it's better to leave all the closures as the default "owner first" resolve strategy, but have the outer methodMissing be aware of when there is an inner closure in progress and hand back down to that:
public class Graph {
def static make(Closure c){
Graph g = new Graph()
c.delegate = g
c()
}
private ObjImpl currentObj = null
def methodMissing(String name, args){
if(currentObj) {
// if we are currently processing an inner ObjImpl closure,
// hand off to that
return currentObj.invokeMethod(name, args)
}
println "outer " + name
if(args.length > 0 && args[0] instanceof Closure){
currentObj = new ObjImpl(type: name)
try {
Closure closure = args[0]
closure()
} finally {
currentObj = null
}
}
}
class ObjImpl {
String type
def methodMissing(String name, args){
println "inner " + name
}
def definedMethod1(){
println "exec'd known method"
}
}
}
With this approach, given the above DSL example, the calculateSomething() call will pass up the chain of owners and reach the method defined in the calling script. The bar(...) and definedMethod1() calls will go up the chain of owners and get a MissingMethodException from the outermost scope, then try the delegate of the outermost closure, ending up in Graph.methodMissing. That will then see that there is a currentObj and pass the method call back down to that, which in turn will end up in ObjImpl.definedMethod1 or ObjImpl.methodMissing as appropriate.
If your DSL can be nested more than two levels deep then you'll need to keep a stack of "current objects" rather than a single reference, but the principle is exactly the same.
An alternative approach might be to make use of groovy.util.BuilderSupport, which is designed for tree building DSLs like yours:
class Graph {
List children
void addChild(ObjImpl child) { ... }
static Graph make(Closure c) {
return new GraphBuilder().build(c)
}
}
class ObjImpl {
List children
void addChild(ObjImpl child) { ... }
String name
void definedMethod1() { ... }
}
class GraphBuilder extends BuilderSupport {
// the various forms of node builder expression, all of which
// can optionally take a closure (which BuilderSupport handles
// for us).
// foo()
public createNode(name) { doCreate(name, [:], null) }
// foo("someValue")
public createNode(name, value) { doCreate(name, [:], value) }
// foo(colour:'red', shape:'circle' [, "someValue"])
public createNode(name, Map attrs, value = null) {
doCreate(name, attrs, value)
}
private doCreate(name, attrs, value) {
if(!current) {
// root is a Graph
return new Graph()
} else {
// all other levels are ObjImpl, but you could change this
// if you need to, conditioning on current.getClass()
def = new ObjImpl(type:name)
current.addChild(newObj)
// possibly do something with attrs ...
return newObj
}
}
/**
* By default BuilderSupport treats all method calls as node
* builder calls. Here we change this so that if the current node
* has a "real" (i.e. not methodMissing) method that matches
* then we call that instead of building a node.
*/
public Object invokeMethod(String name, Object args) {
if(current?.respondsTo(name, args)) {
return current.invokeMethod(name, args)
} else {
return super.invokeMethod(name, args)
}
}
}
The way BuilderSupport works, the builder itself is the closure delegate at all levels of the DSL tree. It calls all its closures with the default "owner first" resolve strategy, which means that you can define a method outside the DSL and call it from inside, e.g.
def calculateSomething() {
return "something I calculated"
}
Graph.make {
foo {
bar(calculateSomething())
definedMethod1()
}
}
but at the same time any calls to methods defined by ObjImpl will be routed to the current object (the foo node in this example).
There are at least two problems with this approach:
Defining ObjImpl within the same context as Graph means that any missingMethod call will hit Graph first
Delegation appears to happen locally unless a resolveStrategy is set, e.g.:
closure.resolveStrategy = Closure.DELEGATE_FIRST
I'm implementing in Groovy a DSL for some existing file format.
In this format we have a construct like
group basic_test {
test vplan_testing {
dir: global_storage;
};
};
And here I have problem with this dir: global_storage - groovy considers "dir:" as a label, so I can't handle it.
Do you have an idea how I can receive some callback (getProperty, invokeMissingMethod) for this construct?
Thank you!
I don't believe you can achieve that this way, you need to change your dsl a bit to be able to capture that information. Here's how you could achieve that:
class Foo {
static plan = {
vplan_testing {
dir 'global_storage'
}
}
}
def closure = Foo.plan
closure.delegate = this
closure()
def methodMissing(String name, Object args) {
println "$name $args"
if(args[0] instanceof Closure)
args[0].call()
}
The output will be
dir [global_storage]
or you could defined you dsl this way:
class Foo {
static plan = {
vplan_testing {
test dir:'global_storage'
}
}
}
replace "test" by something meaningful to you domain. In this case the output would be
test [[dir:global_storage]]
Hope this helps
-ken