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
Related
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
}
Is there a way to access global variable, declared in the script, from the static method of the class, declared in the same script?
For example
def s = "12345"
class MyClass {
static def method() {
println s
}
}
Because that way it fails with the error
You attempted to reference a variable in the binding or an instance variable from a static context
which is expected though.
There is a related discussion at this question:
Groovy scope - how to access script variable in a method
Related in that both questions refer to using a global variable within a class, but this question differs somewhat in that you are seeking to use a static method which alters how you pass the script instance or binding (2 choices).
Passing the Script's Instance
import groovy.transform.Field
#Field def s = "12345"
class MyClass {
static def method(si) {
return si.s
}
}
assert MyClass.method(this) == "12345"
Passing the Script's binding
s = "12345" // NOTE: no 'def'
class MyClass {
static def method(b) {
return b.s
}
}
assert MyClass.method(binding) == "12345"
Well, the problem is that in Groovy there is no such thing as a global variable. What is loosely considered a global variable is actually a static property within some class.
For example, if you remove the println line so that the code compiles, you get something like this out of the compiler:
public class script1455567284805 extends groovy.lang.Script {
...
public java.lang.Object run() {
return java.lang.Object s = '12345'
}
...
}
public class MyClass implements groovy.lang.GroovyObject extends java.lang.Object {
...
public static java.lang.Object method() {
// This is where the println would have been.
return null
}
...
}
As you can see, an additional class is created and the the s variable is declared within the method run() of that class. This makes the variable inaccessible to your other class.
Note: Removing the def will not address this issue.
Solution
Your best bet is to place your "global variables" into a class, possibly as static properties, like this:
class Global {
static Object S = "12345"
}
class MyClass {
static def method() {
println Global.S
}
}
You included a variable type with the s variable (by using the def type). In a Groovy script, this is treated as a local variable - and local to the run() method that is generated - which is kind of like a main() class. As a result, the variable is not available in other methods or classes.
If you remove the def you will be able to make use of the s variable.
Here is the Groovy documentation that explains this further.
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.
Since every Groovy object implements GroovyObject interface, i would try to override invokeMethod(), here is my test :
class MyGrrovyClass {
static test(){
println 'i am in test'
}
Object invokeMethod(String name, Object args){
log.info('method intercepted')
def metaClass = InvokerHelper.getMetaClass(this)
def result = metaClass.invokeMethod(this, name, args)
return result
}
public static void main(String[] args) {
test()
}
}
but it seems doesn't work, i've never seen log message in my console
My second question is : GroovyInterceptable is the subinterface of GroovyObject, what the difference between that i override directly invokeMethod of GroovyObject and i implement invokeMethod of GroovyInterceptable interface?
thanks
According to the documentation (http://groovy.codehaus.org/Using+invokeMethod+and+getProperty) you must implement GroovyInterceptable to intercept existing methods I think this answers your first and second questions!
I made some slight changes to get your sample class working although was surprised to see that my println was intercepted but not System.out.println - this meant that I was getting a stack overflow because I originally had a simple println in the invokeMethod and that was getting recursively called.
class MyGrrovyClass implements GroovyInterceptable {
def test(){
println 'i am in test'
}
def invokeMethod(String name, args){
System.out.println('method intercepted: '+ name)
def result= metaClass.getMetaMethod(name, args).invoke(this, args)
}
}
def mgc= new MyGrrovyClass()
mgc.test()
At runtime I'm grabbing a list of method names on a class, and I want to invoke these methods. I understand how to get the first part done from here:
http://docs.codehaus.org/display/GROOVY/JN3535-Reflection
GroovyObject.methods.each{ println it.name }
What I can't seem to find information on is how to then invoke a method once I've grabbed its name.
What I want is to get here:
GroovyObject.methods.each{ GroovyObject.invokeMethod( it.name, argList) }
I can't seem to find the correct syntax. The above seems to assume I've overloaded the default invokeMethod for the GroovyObject class, which is NOT the direction I want to go.
Once you get a MetaMethod object from the metaclass, you can call invoke on it. For example:
class MyClass {
def myField = 'foo'
def myMethod(myArg) { println "$myField $myArg" }
}
test = new MyClass()
test.metaClass.methods.each { method ->
if (method.name == 'myMethod') {
method.invoke(test, 'bar')
}
}
Alternatively, you can use the name directly:
methodName = 'myMethod'
test."$methodName"('bar')
Groovy allows for dynamic method invocation as well as dynamic arguments using the spread operator:
def dynamicArgs = [1,2]
def groovy = new GroovyObject()
GroovyObject.methods.each{
groovy."$it.name"(staticArg, *dynamicArgs)
}
Reference here
Question answered here.