I have a class that implements GroovyInterceptable, and I thought it should make all method invocation to go through the 'invokeMethod'. But I find that this is not the case.
class Test implements GroovyInterceptable{
String name
#Override
Object invokeMethod(String name, Object args) {
def metaMethod = this.metaClass.getMetaMethod(name, args)
return metaMethod.invoke(this, "BAR")
}
public static void main(String[] args) {
Test.metaClass.NEWATTRIBUTE = null
def test = new Test()
test.NEWATTRIBUTE = "MEOW1"
println test.NEWATTRIBUTE // shouldnt this be BAR ?
test.setNEWATTRIBUTE("MEOW2")
println test.NEWATTRIBUTE // prints 'BAR'
// much simpler example ....
test.name = "MOOO"
println test.name // shouldnt this be BAR ?
test.setName("MOOO")
println test.name // prints 'BAR'
}
}
I think I read somewhere, that
test.NEWATTRIBUTE = "MEOW1"
doesnt really access the field directly. Groovy will still call the setter method - and hence the invokeMethod should get invoked, shouldnt it?
Thanks
Alex
When setting a property, Groovy invokes setProperty. Add this method to the class:
void setProperty(String prop, val) {
System.out.println "set property $prop with $val"
}
WillP: Thanks a lot. Here is the complete code:
class Test implements GroovyInterceptable{
String name
#Override
Object invokeMethod(String name, Object args) {
def metaMethod = this.metaClass.getMetaMethod(name, args)
return metaMethod.invoke(this, "BAR")
}
void setProperty(String prop, val) {
getMetaClass().setProperty(this, prop, "BAR2");
}
public static void main(String[] args) {
Test.metaClass.NEWATTRIBUTE = null
def test = new Test()
test.NEWATTRIBUTE = "MEOW1"
println test.NEWATTRIBUTE // prints BAR2
test.setNEWATTRIBUTE("MEOW2")
println test.NEWATTRIBUTE // prints BAR
test.name = "MOOO"
println test.name // prints BAR2
test.setName("MOOO")
println test.name // prints BAR
}
}
as it turns out, setProperty() is called regardless the class implements GroovyInterceptable or not. But invokeMethod() is only called when the class implements GroovyInterceptable
Related
I have the following code. I have an abstract JobParams, a class extending that abstract GradleJobParams, and a gjp variable with value using anonymous class declaration.
I want to test the overriding behavior of groovy. I can override the method setupRoot() but not the property testVar, why is that?
Tested on: https://groovyconsole.appspot.com/script/5146436232544256
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + testVar);
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
testVar:3,
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
The result is:
Starting
The testVar:1
Override root
Java (and thus Groovy) does not support overriding fields from the parent class with subclassing. Instead, it uses a mechanism called hiding fields:
Hiding Fields
Within a class, a field that has the same name as a field in the superclass hides the superclass's field, even if their types are different. Within the subclass, the field in the superclass cannot be referenced by its simple name. Instead, the field must be accessed through super, which is covered in the next section. Generally speaking, we don't recommend hiding fields as it makes code difficult to read.
Source: https://docs.oracle.com/javase/tutorial/java/IandI/hidevariables.html
It can be simply illustrated with the following example in Java:
final class SubclassHiddingFieldExample {
static abstract class A {
int value = 10;
void printValue1() {
System.out.println(value);
}
void printValue2() {
System.out.println(this.value);
}
void printValue3() {
System.out.println(((B)this).value);
}
}
static class B extends A {
int value = 12;
}
public static void main(String[] args) {
final B b = new B();
b.printValue1();
b.printValue2();
b.printValue3();
}
}
Output:
10
10
12
As you can see, only printValue3 prints out 3, because it cast this explicitly to B class.
Now, if you look at the decompiled bytecode of your JobParams class, you can see that the printTestVar method code is an equivalent of the following Java code:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import org.codehaus.groovy.runtime.callsite.CallSite;
public abstract class JobParams implements GroovyObject {
private int root;
private Object testVar;
public JobParams() {
CallSite[] var1 = $getCallSiteArray();
byte var2 = 1;
this.testVar = Integer.valueOf(var2);
MetaClass var3 = this.$getStaticMetaClass();
this.metaClass = var3;
}
public Object setupRoot() {
CallSite[] var1 = $getCallSiteArray();
return var1[0].callCurrent(this, "The root");
}
public Object printTestVar() {
CallSite[] var1 = $getCallSiteArray();
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
}
public MetaClass getMetaClass() {
MetaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticMetaClass();
return this.metaClass;
}
}
public void setMetaClass(MetaClass var1) {
this.metaClass = var1;
}
public Object invokeMethod(String var1, Object var2) {
return this.getMetaClass().invokeMethod(this, var1, var2);
}
public Object getProperty(String var1) {
return this.getMetaClass().getProperty(this, var1);
}
public void setProperty(String var1, Object var2) {
this.getMetaClass().setProperty(this, var1, var2);
}
public int getRoot() {
return this.root;
}
public void setRoot(int var1) {
this.root = var1;
}
public Object getTestVar() {
return this.testVar;
}
public void setTestVar(Object var1) {
this.testVar = var1;
}
}
You can see that the line that prints out the value of the testVar field is represented by:
return var1[1].callCurrent(this, var1[2].call("The testVar:", this.testVar));
It means that no matter what value of testVar your subclass defines, the printTestVar method uses testVar field defined in the JobParams class. Period.
Using Groovy auto getter methods
There is one way you to implement the expected behavior. Every class field in Groovy has a getter method associated with that field compiled by Groovy for you. It means that you can access testVar by calling the getTestVar() method generated by the Groovy compiler. You can use it to override the value returned by a getter method for any field from the subclass. Consider the following example:
abstract class JobParams {
int root
def testVar=1
def setupRoot () {
println("The root");
}
def printTestVar () {
println("The testVar:" + getTestVar()); // <-- using a getTestVar() method instead a testVar field
}
}
class GradleJobParams extends JobParams {
}
def gjp = [
getTestVar: 3, // <-- stubbing getTestVar() method to return a different value
setupRoot:{
println("Override root");
}
] as GradleJobParams;
println("Starting");
gjp.printTestVar();
gjp.setupRoot();
Output:
Starting
The testVar:3
Override root
I'd like to override the getProperty() method in a Spock test, per the documentation of get/setProperty. This is trivial in a normal Groovy class, but doesn't seem to work inside a Spock specification.
class MainSpec extends Specification {
#Override
def getProperty(String name) {
def value = metaClass.getProperty(this, name)
println "$name == $value"
return value
}
String foo = 'foo'
def test() {
expect:
foo
println foo
}
}
This example does not invoke the getProperty() method. It appears Spock is bypassing it somehow. Is there a way to hook into Spock's property resolution mechanism, or tell Spock to use my overridden method?
Yes, you can use "Expando" style, where the property should not be defined on the Spec class but is stored in a map (storage) and manipulated by setProperty and getProperty methods:
class MetaprogrammingSpec extends Specification {
def storage = [:]
#Override
def getProperty(String name) {
def value = storage[name]
println "$name == $value"
return value
}
#Override
void setProperty(String name, value) {
storage[name] = value
}
def test() {
when:
foo = 'bar'
then:
foo
println foo
}
}
Then the result is:
Update after comment: The getProperty() method is also called when you don't initialize the foo property. When you change the test in this way:
def test() {
expect:
foo
println foo
}
The result is as expected - getProperty() was called but test failes because foo is null:
It turns out that moving the property to a parent class is sufficient to make it pass through the getProperty() interceptor, even when the parent is another Specification class.
class BaseSpec extends Specification {
def foo = 'foo'
}
class MainSpec extends BaseSpec {
#Override
def getProperty(String name) {
def value = super.getProperty(name)
println "$name == $value"
return value
}
def test() {
expect:
foo
println foo
}
}
The Groovy code shown below contains closures and a method. That format changed label and expected to receive that change label shows the problem and the requirement.
def method (String a, Closure c) {
Query q = new Query()
q.a = a
c.delegate = q
c.call()
def str = q.str
}
class Query
{
def str
def a
void key (String str, Closure cls) {
this.str = str
Pass p = new Pass()
p.a=a
cls.delegate=p
cls.call()
def val=p.a // Expcted to receive that change
println val
}
class Pass
{
String a
}
}
method("got") {
key ("got"){
a=a.toUpperCase() // Format Changed here
println a
}
}
Actual Output is :
GOT
got
But my expected output is:
GOT
GOT
Why that a = a.toUpperCase() doesn't change a value in p object after the cls.call()? How to pass this change ?
You have to change delegate resolving strategy for cls in key(String str, Closure cls) method to:
cls.resolveStrategy = Closure.DELEGATE_FIRST
Default strategy is Closure.OWNER_FIRST. In case of a Groovy script it means that the owner of this closure is an instance of a class that was generated by Groovy to run the script. In case of your Groovy script this class looks like this:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class my_groovy_script extends Script {
public my_groovy_script() {
CallSite[] var1 = $getCallSiteArray();
}
public my_groovy_script(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, my_groovy_script.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
class _run_closure1 extends Closure implements GeneratedClosure {
public _run_closure1(Object _thisObject) {
CallSite[] var3 = $getCallSiteArray();
super(my_groovy_script.this, _thisObject);
}
public Object doCall(Object it) {
CallSite[] var2 = $getCallSiteArray();
class _closure2 extends Closure implements GeneratedClosure {
public _closure2(Object _thisObject) {
CallSite[] var3 = $getCallSiteArray();
super(_run_closure1.this, _thisObject);
}
public Object doCall(Object it) {
CallSite[] var2 = $getCallSiteArray();
Object var3 = var2[0].call(var2[1].callGroovyObjectGetProperty(this));
ScriptBytecodeAdapter.setGroovyObjectProperty(var3, _closure2.class, this, (String)"a");
return var2[2].callCurrent(this, var2[3].callGroovyObjectGetProperty(this));
}
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.doCall((Object)null);
}
}
return var2[0].callCurrent(this, "got", new _closure2(this.getThisObject()));
}
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.doCall((Object)null);
}
}
return var1[1].callCurrent(this, "got", new _run_closure1(this));
}
public Object method(String a, Closure c) {
CallSite[] var3 = $getCallSiteArray();
Query q = (Query)ScriptBytecodeAdapter.castToType(var3[2].callConstructor(Query.class), Query.class);
ScriptBytecodeAdapter.setGroovyObjectProperty(a, my_groovy_script.class, q, (String)"a");
ScriptBytecodeAdapter.setGroovyObjectProperty(q, my_groovy_script.class, c, (String)"delegate");
var3[3].call(c);
Object str = var3[4].callGroovyObjectGetProperty(q);
return str;
}
}
As you can see every Groovy script is actually a class that extends groovy.lang.Script class. There is one important thing about this class - it overrides:
public Object getProperty(String property)
public void setProperty(String property, Object newValue)
If you take a look at the source code of both methods you will see that it uses binding object to store and access all variables in scope of the closure. That's why the closure you pass to Query.key(String str, Closure cls) does not modify a a field of class Pass but instead it creates a local binding a with a value GOT. You can change this behavior by changing Closure's resolve strategy to Closure.DELEGATE_FIRST. This will do the trick because you explicitly set cls.delegate to p instance so the closure will firstly look for a field a in p instance. I hope it helps.
Updated Groovy script
def method(String a, Closure c) {
Query q = new Query()
q.a = a
c.delegate = q
c.call()
def str = q.str
}
class Query {
def str
def a
void key(String str, Closure cls) {
this.str = str
Pass p = new Pass()
p.a = a
cls.delegate = p
cls.resolveStrategy = Closure.DELEGATE_FIRST
cls.call()
def val = p.a // Expcted to receive that change
println val
}
class Pass {
String a
}
}
method("got") {
key("got") {
a = a.toUpperCase() // Format Changed here
println a
}
Output
GOT
GOT
I'm testing Groovy but I can't figure out how to properly call GroovyScriptEngine. It keeps producing an error below.
org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack
Song.Groovy
class Song {
def args;
{
println "Song has been called." + args;
}
String getArtist(){
return "sdfsdf";
}
public String toString(){
return "Hey!";
}
}
Java Main ->
String[] paths = { "C:\\Users\\User\\workspace\\GroovyTest\\src\\groovy" };
GroovyScriptEngine gse = new GroovyScriptEngine(paths);
Binding binding = new Binding();
Object s = "Default...";
binding.setVariable("args", s);
gse.run("Song.groovy", binding);
the args variable also produce null..
What to do ?
You are loading a class!
If you want to test your class, try something like this in the end of your Song.groovy:
// Instantiate an object of your class and use some methods!
def song = new Song()
println song.getArtist();
When you run
gse.run("Song.groovy", binding);
You are basically loading your class, but you are not doing anything with it.
See this example here
(Posted on behalf of the OP):
Working code:
Test1.java
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
public class Test1 {
public static void main(String[] args) throws Exception {
String[] paths = { "C:\\Users\\User\\workspace\\GroovyTest\\src\\groovy" };
GroovyScriptEngine gse = new GroovyScriptEngine(paths);
Binding binding = new Binding();
binding.setVariable("args", "Test Data");
String result = (String) gse.run("File1.groovy", binding);
System.out.println("Groovy Result: " + result);
}
}
File1.groovy
package groovy;
class Greeter {
String sayHello(String data) {
def greet = data;
return greet
}
}
static void main(String[] args) {
def greeter = new Greeter()
return greeter.sayHello(args);
}
I have 2 scripts, where I am trying to test passing an argument, and it fails. I examined the documentation for GroovyScriptEngine but it doesn't seem to handle the case where I want to pass a arg rather than a property value pair (in a binding).
Here is the error I get:
C:\AeroFS\Work\Groovy_Scripts>groovy scriptengineexample.groovy
hello, world
Caught: groovy.lang.MissingPropertyException: No such property: args
for class: hello
groovy.lang.MissingPropertyException: No such property: args for
class: hello
at hello.run(hello.groovy:4)
at Test.main(scriptengineexample.groovy:14)
Here are my scripts:
import groovy.lang.Binding;
import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException ;
import groovy.util.ScriptException ;
import java.io.IOException ;
public class Test {
public static void main( String[] args ) throws IOException,
ResourceException, ScriptException {
GroovyScriptEngine gse = new GroovyScriptEngine( [ '.' ] as String[] )
Binding binding = new Binding();
binding.setVariable("input", "world");
gse.run("hello.groovy", binding);
System.out.println( "Output: " + binding.getVariable("output") );
}
}
And this one:
//hello.groovy
println "hello.groovy"
for (arg in this.args ) {
println "Argument:" + arg;
}
Hello is looking for a string array in the binding called args. This is automatically provided to you when you run the script via the command line, but if you are running it outside of that context, you must add it to the Binding yourself:
This will pass the arguments sent to Test through to Hello as-is:
public class Test {
public static void main(String[] args) {
Binding b = new Binding()
b.setVariable("args", args)
Hello h = new Hello(b);
h.run()
}
}
If you want to send specific arguments, you must construct the array yourself:
public class Test {
public static void main(String[] args) {
Binding b = new Binding()
b.setVariable("args", ["arg1", "arg2", "etc."])
Hello h = new Hello(b)
h.run()
}
}
Even simpler, the Binding class has a Constructor which takes a String[], and add it as 'args' so you can just do this:
public class Test {
public static void main(String[] args) {
new Hello(new Binding(args)).run();
}
}