Confused about the invokeMethod method in the Groovy MOP - groovy

First look at the following Groovy code:
class Car {
def check() { System.out.println "check called..." }
def start() { System.out.println "start called..." }
}
Car.metaClass.invokeMethod = { String name, args ->
System.out.print("Call to $name intercepted... ")
if (name != 'check') {
System.out.print("running filter... ")
Car.metaClass.getMetaMethod('check').invoke(delegate, null)
}
def validMethod = Car.metaClass.getMetaMethod(name, args)
if (validMethod != null) {
validMethod.invoke(delegate, args)
} else {
Car.metaClass.invokeMissingMethod(delegate, name, args)
}
}
car = new Car()
car.start()
The output is:
Call to start intercepted... running filter... check called...
start called...
According to the Groovy method dispatching mechanism I think the start method in the Car should be called directly instead of being intercepted by the invokeMethod in the Car's metaClass. Why is the start method intercepted by the invokeMethod? How is the invokeMethod invoked when a method is called on an object?
If you can give me some detailed explanations about Groovy method dispatching mechanism(MOP) I will appreciate that.

In short you are not using the standard meta class, so you don't get the standard Groovy MOP.
Car.metaClass.invokeMethod = { will let Car have an ExpandoMetaClass as meta class. This meta class uses the invokeMethod you give in as open block (like you do) to intercept calls. This is very different from defining an invokeMethod in the class itself.

Related

Groovy CliBuilder: any method defined?

I'm very new to Groovy.
Very simple question about the code found in CliBuilder.
http://docs.groovy-lang.org/latest/html/gapi/index.html?overview-summary.html
def cli = new CliBuilder(name:'ls')
cli.a('display all files')
cli.l('use a long listing format')
cli.t('sort by modification time')
def options = cli.parse(args)
assert options // would be null (false) on failure
assert options.arguments() == ['*.groovy']
assert options.a && options.l && options.t
The CliBuilder class behaves as knowing whatever methods we want to call in advance. By what Groovy's feature it can be supported?
This is called Runtime metaprogramming.
If you want to create your own class with "dynamic methods", the easiest way is to implement the GroovyInterceptable interface and add the invokeMethod method to your class.
class Interception implements GroovyInterceptable {
def definedMethod() { }
def invokeMethod(String name, Object args) {
'invokedMethod'
}
}
Whenever a method is called on an instance if the class Interception, invokeMethod is called instead. Note that this is also true for methods actually defined in the class (e.g. definedMethod)
You can use the metaClass to call the actual method like this
class Interception implements GroovyInterceptable {
def definedMethod() { }
def invokeMethod(String name, Object args) {
if (name == "actualMethod") {
return metaClass.invokeMethod(this, name, args)
}
return "invokedMethod: $name($args)"
}
def actualMethod() {
return 'hello there'
}
}
Here a call to actualMethod still goes through invokeMethod, however invokeMethod contains logic to call the actual method.
There are some other ways (see link on top) to acomplish similar behavior, but I found this to be the easiest.
Note that runtime metaprogramming is incompatible with #CompileStatic unless you add a TypeCheckingExtension to mitigate this.
Run Example

Is it possible to add GroovyObject methods through a category?

In Groovy in Action, 2nd Edition published in 2015 in chapter 8.4.5 they say that categories can be used to add GroovyObject methods:
Category method names can well take the form of property accessors
(pretending property access), operator methods, and GroovyObject
methods. MOP hook methods cannot be added through a category class.
This is a restriction as of Groovy 2.4. The feature may become
available in later versions.
which I interpreted as you can add getMetaClass(), setMetaClass(MetaClass), getProperty(String), setProperty(String, Object) and invokeMethod(String, Object) but you can't add methodMissing(String, Object) or propertyMissing(String)
but when I tried to add invokeMethod() and getProperty() through a category it didn't have any effect:
class MyClass{}
a = new MyClass()
#Category(MyClass)
class MyCategory {
def missingMethod(String name, def args) { "missingMethod" } // GINA says no MOP hook method
def invokeMethod(String name, def args) { "invokeMethod" } // but GroovyObject method should be fine
def getProperty(String name) { "missingProperty" }
def getMyProperty() { "prop1" }
}
use(MyCategory) {
assert "missingMethod" == a.missingMethod('a', 'b') // methods are the
assert "invokeMethod" == a.invokeMethod('a', 'b')
assert "prop1" == a.myProperty
// but they are not in effect
// assert "missingMethod" == a.method1() // MissingMethodException
// assert "invokeMethod" == a.method2() // MssingMethodException
// assert "missingProperty" == a.property // MissingPropertyException
}
So far invokeMethod, getProperty and getMetaClass so there is only other two methods from GroovyObject left : setMetaClass and setProperty but since the getter versions of those don't work I suspect the setter versions won't work either. So I can't really add any GroovyObject method at all in this way.
In this other SO question: get vs getProperty in groovy there is some discussion about the MOP and the only answer points to the "add method to the metaclass instead of using categories" solution. But my question is different, is it really possible or not to use a category to add invokeMethod or methodMissing?
So what is the right way (if any) to add GroovyObject methods via categories then?
Current Groovy 2.4.x can't do this but it has been added for 2.5.0-rc-3, 2.6.0-alpha-4, 3.0.0-alpha-3. So future versions of Groovy will allow redefining methodMissing and propertyMissing via a category classes. This feature was added on commit ad664b1 on May 2018 and was tracked by GROOVY-3867
// since groovy 2.5.0-rc-3
class X{ def bar(){1}}
class XCat{ static bar(X x){2}}
class XCat2{ static bar(X x){3}}
class XCat3{ static methodMissing(X x, String name, args) {4}}
class XCat4{ static propertyMissing(X x, String name) {"works"}}
def x = new X()
shouldFail(MissingPropertyException) {
assert x.baz != "works" // accessing x.baz should throw MPE
}
use(XCat4) {
assert x.baz == "works"
}
shouldFail(MissingPropertyException) {
assert x.baz != "works" // accessing x.baz should throw MPE
}

Unexpected behaviour skips Agent.send method second time through when delegating methods to Agent protected value

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
}
}

Changing and adding arguments of method before invocation

I want to reproduce the behavior of the default arguments by intercepting the call to some methods. The following code tries to give a default argument when the method display is called without arguments:
class Thing
{
void display(String text)
{
println(text)
}
def invokeMethod(String name, args)
{
if(name == "display" && args.length == 0)
{
metaClass.getMetaMethod(name, ["some text"]).
invoke(this, "some text")
}
else
{
metaClass.getMetaMethod(name, args).
invoke(this, args)
}
}
}
Thing thing = new Thing()
thing.display("stuff") //prints "stuff"
thing.display() //nothing happens
However, this does not work; nothing is printed when no argument is given.
This example is not very useful but I would want to make it work; my next goal is indeed to create methods whose arguments could be given in their name. Example:
Add1And2()// should return 3
Add4And9()// should return 13
Intercepting the call to these nonexistent methods and calling an existing method that would do the addition using the numbers used in the name of the nonexistent methods would make it possible...
Class Thing should implement GroovyInterceptable as below to make invokeMethod work:
class Thing implements GroovyInterceptable {
void display(String text) {
println(text)
}
def invokeMethod(String name, args) {
if(name == "display" && args.length == 0) {
metaClass.getMetaMethod(name).invoke(this, "some text")
} else {
metaClass.getMetaMethod(name, args).invoke(this, args)
}
}
}
Thing thing = new Thing()
thing.display("stuff") //prints "stuff"
thing.display()

How to differenciate between static and dynamic methodMissing and avoid StackOverflowError

I use methodMissing in a delegate.
What is possible in order to avoid mistake such this:
java.lang.StackOverflowError
just because:
def methodMissing(String methodName, args) {
pritln "did you notice the method name mistake ?"
}
I was thinking of between a mechanism to differenciate static / dynamic (i.e. when .call() is called on the closure)
Any pointers ?
What about this little Groovy script?
def methodMissing(String methodName, args) {
println "in methodMissing"
if (!methodCalled()) {
pritln "did you notice the method name mistake ?"
}
}
def methodCalled(methodName = 'methodMissing') {
Thread.currentThread().getStackTrace().findAll {it.className == this.class.name}.methodName.contains methodName
}
nothingHere()

Resources