Access a property inside invokeMethod() implemented in a trait - groovy

The following Groovy trait implements the GroovyInterceptable interface to allow execution of code before and after method calls.
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}
The following class implements the trait Bar.
class Foo implements Bar {
}
Have a look at the following code.
def foo = new Foo()
foo.doSomething()
The call to doSomething() is being intercepted by invokeMethod(). A java.lang.StackOverflowError occurs because accessing the property bar inside invokeMethod() implicitly makes a call to the getter of bar which in turn is intercepted by invokeMethod() just trying to access bar again.
How can I access a class property inside invokeMethod without calling this property's getter or setter?
In combination with the trait using this.#bar to access the property does not work.
The code metaClass.getMetaMethod(name, args).invoke(this, args) to invoke the intercepted method could be incorrect although it works when using the trait logic directly inside a class.
Edit for Solution:
The accepted answer contributed by user Opal works like a charm in a script environment. Since the trait is part of a larger project and defined in its own file I made it work like this:
package com.example.project
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(this.com_example_project_Bar__bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}

It turns out that there's no need to use # for direct field access:
trait Bar implements GroovyInterceptable {
def bar = "bar"
#Override
invokeMethod(String name, Object args) {
System.out.println(Bar__bar)
metaClass.getMetaMethod(name, args).invoke(this, args)
}
def doSomething() {
}
}
class Foo implements Bar {
}
def foo = new Foo()
foo.doSomething()

Related

Trying to call a function from inside the class in Groovy, getting MissingMethodException

I'm running the following code (in Jenkins script console):
def sayHello() {
println "Hello"
}
class MyClass {
MyClass() {
sayHello()
}
}
def a = new MyClass()
In all the good faith, I expect the constructor code to call the function that will print Hello.
Instead I get
groovy.lang.MissingMethodException: No signature of method: MyClass.sayHello() is applicable for argument types: () values: []
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.unwrap(ScriptBytecodeAdapter.java:58)
What is going on here? I can't call the function from inside the class?
You get the error because you cannot access methods of one class from another class, unless you access an instance of that class.
In your case, the code is embedded automatically into a run() method inside the Main class derived from groovy.lang.Script. The class MyClass is an inner class of the Main class. See here Scripts versus classes.
Solution: to access the method sayHello() of the Main class, you must pass an instance of it, using this keyword:
def sayHello() {
println "Hello"
}
class MyClass {
MyClass(Script host) {
host.sayHello()
}
}
def a = new MyClass(this)
Not sure what and why you are trying to do, but the simplest option to call a "function" from a constructor inside a Script is to put it in another class:
class A {
static sayHello() {
println "Hello"
}
}
class MyClass {
MyClass() {
A.sayHello()
}
}
def a = new MyClass()

access global variable in static scope

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.

Invoking a call() method introduced by a trait

I'm seeing a rather strange thing with the new Groovy trait implementation. I have a (single method interface) class that implements a call() method so I can call call its only method just like a closure: instance() instead of having to do instance.call() . However as soon as I introduce such a reference (a Callable) using a trait that breaks. Anyone who can give a hint if this is something that is as expected or is there a workaround to that?
class MethodCallSpec extends Specification {
def "call a callable"() {
expect:
def thing = new Callable()
"hello" == thing.call("hello")
}
def "call a callable like a closure"() {
expect:
def thing = new Callable()
"hello" == thing("hello")
}
def "call with callable"() {
expect:
def thing = new SomethingWithCallable()
"hello" == thing.doSomething("hello")
}
}
class Callable {
Object call(def message) {
message
}
}
trait WithCallable {
Callable callable = new Callable();
}
class SomethingWithCallable implements WithCallable {
def doSomething(def message) {
callable(message) // this breaks unless written as callable.call(message)
}
}

can not override invokeMethod inherited from GroovyObject

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()

Groovy methodMissing

I have a closure within an object Foo and inside the closure i define a method called 'myStaticMethod' that I want to resolve once the closure is called outside the object Foo. I also happen to have 'on purpose' a static method within my object Foo with the same name. When I call the closure i set the 'resolve strategy' to DELEGATE_ONLY to intercept the call to myStaticMethod that is defined within the closure.
I tried to achieve that through missingMethod but the method is never intercepted. When i make the Foo.myStaticMethod non static, the method is intercepted. I don't quite understand why this is happening though my resolve strategy is set to DELEGATE_ONLY. having the Foo.myStaticMethod static or not shouldn't matter or I am missing something
class Foo {
static myclosure = {
myStaticMethod()
}
static def myStaticMethod() {}
}
class FooTest {
def c = Foo.myclosure
c.resolveStrategy = Closure.DELEGATE_ONLY
c.call()
def missingMethod(String name, def args) {
println $name
}
}
To solve the problem, I ended up overriding the invokeMethod right before calling the closure in FooTests
Foo.metaClass.'static'.invokeMethod = { String name, args ->
println "Static Builder processing $name "
}
While trying to solve this problem, i discovered a very weird way to intercept missing static methods. Might be useful to some of you in the future.
static $static_methodMissing(String name, args) {
println "Missing static $name"
}
-Ken
Static methods unfortunately aren't intercepted by the closure property resolution. The only way that I know to intercept those is to override the static metaClass invokeMethod on the class that owns the closure, ex:
class Foo {
static myclosure = {
myStaticMethod()
}
static myStaticMethod() {
return false
}
}
Foo.metaClass.'static'.invokeMethod = { String name, args ->
println "in static invokeMethod for $name"
return true
}
def closure = Foo.myclosure
assert true == closure()

Resources