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
Related
I have the following groovy script compiled from an html file
package org.webpieces.plugins.fortesting
import org.webpieces.ctx.api.Current
class notFound_html extends org.webpieces.templating.impl.GroovyTemplateSuperclass {
public Object run() {
use(org.webpieces.templating.impl.source.GroovyExtensions) {
__out.print("<html>\n"); //htmlLine 0:2 groovyLine=8
__out.print(" <head>\n"); //htmlLine 2:3 groovyLine=9
__out.print(" </head>\n"); //htmlLine 3:4 groovyLine=10
__out.print(" <body>\n"); //htmlLine 4:5 groovyLine=11
__out.print(" Your page was not found\n"); //htmlLine 5:6 groovyLine=12
__out.print(" </body>\n"); //htmlLine 6:7 groovyLine=13
__out.print("</html>"); //htmlLine 7:7 groovyLine=14
}
}
}
This then compiles to a *.class file which decompiled I see looks like this
package org.webpieces.plugins.fortesting;
import groovy.lang.Closure;
import groovy.transform.Generated;
import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.webpieces.templating.impl.GroovyTemplateSuperclass;
public class notFound_html extends GroovyTemplateSuperclass {
#Generated
public notFound_html() {
CallSite[] var1 = $getCallSiteArray();
super();
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
final class _run_closure1 extends Closure implements GeneratedClosure {
public _run_closure1(Object _outerInstance, Object _thisObject) {
CallSite[] var3 = $getCallSiteArray();
super(_outerInstance, _thisObject);
}
public Object doCall(Object it) {
CallSite[] var2 = $getCallSiteArray();
var2[0].call(var2[1].callGroovyObjectGetProperty(this), "<html>\n");
var2[2].call(var2[3].callGroovyObjectGetProperty(this), " <head>\n");
var2[4].call(var2[5].callGroovyObjectGetProperty(this), " </head>\n");
var2[6].call(var2[7].callGroovyObjectGetProperty(this), " <body>\n");
var2[8].call(var2[9].callGroovyObjectGetProperty(this), " Your page was not found\n");
var2[10].call(var2[11].callGroovyObjectGetProperty(this), " </body>\n");
return var2[12].call(var2[13].callGroovyObjectGetProperty(this), "</html>");
}
#Generated
public Object doCall() {
CallSite[] var1 = $getCallSiteArray();
return this.doCall((Object)null);
}
}
return var1[0].callCurrent(this, var1[1].callGetProperty(var1[2].callGetProperty(var1[3].callGetProperty(var1[4].callGetProperty(var1[5].callGetProperty(var1[6].callGroovyObjectGetProperty(this)))))), new _run_closure1(this, this));
}
}
When running this template, I get an exception on looking up the property 'org'. Specifically, the superclass I control GroovyTemplateSuperclass which has a getProperty method like so ->
#Override
public Object getProperty(String property) {
String srcLocation = modifySourceLocation2(sourceLocal.get());
boolean isOptional = false;
if(property.endsWith("$")) {
isOptional = true;
property = property.substring(0, property.length()-1);
}
try {
return super.getProperty(property);
} catch (MissingPropertyException e) {
if(isOptional)
return null;
throw new IllegalArgumentException("No such property '"+property+"' but perhaps you forgot quotes "
+ "around it or you forgot to pass it in from the controller's return value(with the RouteId) OR "
+ "lastly, if this is inside a custom tag, perhaps the tag did not pass in the correct arguments."+srcLocation, e);
}
}
Groovy appears to be going through this line ->
use(org.webpieces.templating.impl.source.GroovyExtensions) {
and each of those is in an array and being called as a property. I am not sure why this is failing now and was working before though so I am providing all the info I have.
Specifically, I get ""No such property 'org'" and upon further inspection that comes from an array which has org, webpieces, templating, impl, source, GroovyExtensions.
I find this very strange and am still digging but thought I might post as I have a feeling I may waste hours on this one.
Consider the following code sample
class A {
int data
}
class B extends A {}
def o1 = new B(data: 1)
// This works correctly.
def o2 = new A(data:1) {}
// This will throw the following error
// Exception thrown
//
// groovy.lang.GroovyRuntimeException: Could not find matching constructor for: A(LinkedHashMap)
// at ConsoleScript2$1.<init>(ConsoleScript2)
// at ConsoleScript2.run(ConsoleScript2:11)
// at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
// at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64)
// at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
To me, the anonymous one should be the same as the named class. But it turns out that Groovy treats them differently. I want to know how to fix it. Thank you.
You see this error because of the nature of the dynamic map constructor - it is not added explicitly to the generated classes, but it is called through the CallSite.callConstructor(obj,map) method instead. However, there is a solution to that problem.
Consider the following exemplary test.groovy script:
class A {
int data
}
class B extends A {}
def a1 = new B(data: 1)
def a2 = new A(data: 2) {}
println a1
println a2
When you decompile generated A.class file, you will something like this:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import groovy.transform.Internal;
import java.beans.Transient;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class A implements GroovyObject {
private int data;
#Generated
public A() {
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
#Generated
#Internal
#Transient
public MetaClass getMetaClass() {
MetaClass var10000 = this.metaClass;
if (var10000 != null) {
return var10000;
} else {
this.metaClass = this.$getStaticMetaClass();
return this.metaClass;
}
}
#Generated
#Internal
public void setMetaClass(MetaClass var1) {
this.metaClass = var1;
}
#Generated
public int getData() {
return this.data;
}
#Generated
public void setData(int var1) {
this.data = var1;
}
}
This class has only one no-args constructor. When you decompile the test.class file (compiled Groovy script file), you will see something like this:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
import groovy.lang.Binding;
import groovy.lang.Script;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class test extends Script {
public test() {
CallSite[] var1 = $getCallSiteArray();
super();
}
public test(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, test.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
Object a1 = var1[1].callConstructor(B.class, ScriptBytecodeAdapter.createMap(new Object[]{"data", 1}));
Object a2 = new test.1(ScriptBytecodeAdapter.createMap(new Object[]{"data", 2}));
var1[2].callCurrent(this, a1);
return var1[3].callCurrent(this, a2);
}
public class 1 extends A {
}
}
Take a look at how objects a1 and a2 are initialized. The a1 object is initialized in the following way:
Object a1 = var1[1].callConstructor(B.class, ScriptBytecodeAdapter.createMap(new Object[]{"data", 1}));
It uses the CallSite.callConstructor() method to mimic the map constructor which does not exist in the A class. If we look at how the object a2 is initialized we will find this:
Object a2 = new test.1(ScriptBytecodeAdapter.createMap(new Object[]{"data", 2}));
We can see that Groovy in the case of the anonymous class (which is not anonymous at all - Groovy generates a class anyway), Groovy uses a direct constructor call. And it fails, because there is no A(LinkedHashMap) constructor in the parent class.
Solution
Luckily, there is a solution to this problem - you can use #MapConstructor and #InheritConstructors annotations to force creating map constructor in the A class, and to inherit this constructor in the B class. Take a look at this working example:
import groovy.transform.InheritConstructors
import groovy.transform.MapConstructor
#MapConstructor
class A {
int data
}
#InheritConstructors
class B extends A {}
def a1 = new B(data: 1)
def a2 = new A(data: 2) {}
println a1
println a2
The only requirement is to use at least the Groovy 2.5 version which introduced the #MapConstructor annotation.
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 have a Groovy script with a function func(Map data) that takes a map and reinitializes passed variable with an empty map - data = [:]. The problem I face is that passing non-empty map to this function does not override a map with an empty one. Why is that?
Here is my Groovy code snippet:
Map x = [data1 : 10, data2 : 20]
def func(Map data) {
data = [:]
}
def func2(Map data) {
data.clear()
}
func(x)
// Setting x = [:] outside function does set x to empty
print x // prints [data1:10, data2:20]
func2(x)
print x // prints [:] (as .clear() is working)
BTW: it behaves the same for lists.
It happens, because Groovy compiler creates a new local variable data inside func(Map data) function. Take a look at decompiled code:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.Binding;
import groovy.lang.Script;
import java.util.Map;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class test extends Script {
public test() {
CallSite[] var1 = $getCallSiteArray();
}
public test(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].call(InvokerHelper.class, test.class, args);
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
Map x = ScriptBytecodeAdapter.createMap(new Object[]{"data1", 10, "data2", 20});
var1[1].callCurrent(this, x);
var1[2].callCurrent(this, x);
var1[3].callCurrent(this, x);
return var1[4].callCurrent(this, x);
}
public Object func(Map data) {
CallSite[] var2 = $getCallSiteArray();
var2[5].callCurrent(this, "test");
Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);
return var3;
}
public Object func2(Map data) {
CallSite[] var2 = $getCallSiteArray();
return var2[6].call(data);
}
}
Check what func method is represented by at the bytecode level:
public Object func(Map data) {
CallSite[] var2 = $getCallSiteArray();
var2[5].callCurrent(this, "test");
Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);
return var3;
}
As you can see following Groovy code:
data = [:]
gets translated to something like this:
Map var3 = ScriptBytecodeAdapter.createMap(new Object[0]);
However, this kind of behavior is specific not only to Groovy, but for Java as well. Take a look at pretty similar example in Java:
import java.util.HashMap;
import java.util.Map;
final class TestJava {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
map.put("test", "foo");
func(map);
System.out.println("map outside = " + map);
}
static void func(Map<String, Object> map) {
map = new HashMap<>();
map.put("1", 2);
System.out.println("map inside = " + map);
}
}
If we run it we will see something similar to the Groovy use case:
map inside = {1=2}
map outside = {test=foo}
We could expect that func method should override map, but it is not happening here. If we decompile class file we will see something like this:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import java.util.HashMap;
import java.util.Map;
final class TestJava {
TestJava() {
}
public static void main(String[] var0) {
HashMap var1 = new HashMap();
var1.put("test", "foo");
func(var1);
System.out.println("map outside = " + var1);
}
static void func(Map<String, Object> var0) {
HashMap var1 = new HashMap();
var1.put("1", 2);
System.out.println("map inside = " + var1);
}
}
As you can see from JRE perspective we are creating a new HashMap stored as var1 variable instead of overriding var0 variable passed to the method.
Btw, the Java version I used: OpenJDK 1.8.0_191
I'm very new to Groovy. I wonder how Closures are implemented in Groovy.
Lets say :
def a = { println "Hello" }
a()
when a() is done, what is actually happening behind the scenes? Which method does a() calls to make the closure executable?
Thanks in advance.
Basically:
your closure is a class with specific name
a() invokes doCall() which invokes doCall(Object it) (implicit it in closures)
acallsite contains method names (2 x println) - and are invoked with appropriate arguments
Here you go:
For this Groovy Script:
def a = { println "Hello"; println "Hello2" }
a()
Closure a looks like this:
class Test$_run_closure1 extends Closure
implements GeneratedClosure
{
public Object doCall(Object it)
{
CallSite acallsite[] = $getCallSiteArray();
acallsite[0].callCurrent(this, "Hello");
return acallsite[1].callCurrent(this, "Hello2");
}
public Object doCall()
{
CallSite acallsite[] = $getCallSiteArray();
return doCall(null);
}
protected MetaClass $getStaticMetaClass()
{
if(getClass() != Test$_run_closure1)
return ScriptBytecodeAdapter.initMetaClass(this);
ClassInfo classinfo = $staticClassInfo;
if(classinfo == null)
$staticClassInfo = classinfo = ClassInfo.getClassInfo(getClass());
return classinfo.getMetaClass();
}
public static void __$swapInit()
{
CallSite acallsite[] = $getCallSiteArray();
$callSiteArray = null;
}
private static void $createCallSiteArray_1(String as[])
{
as[0] = "println";
as[1] = "println";
}
private static CallSiteArray $createCallSiteArray()
{
String as[] = new String[2];
$createCallSiteArray_1(as);
return new CallSiteArray(Test$_run_closure1, as);
}
private static CallSite[] $getCallSiteArray()
{
CallSiteArray callsitearray;
if($callSiteArray == null || (callsitearray = (CallSiteArray)$callSiteArray.get()) == null)
{
callsitearray = $createCallSiteArray();
$callSiteArray = new SoftReference(callsitearray);
}
return callsitearray.array;
}
static Class _mthclass$(String s)
{
try
{
return Class.forName(s);
}
catch(ClassNotFoundException classnotfoundexception)
{
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
private static ClassInfo $staticClassInfo;
public static transient boolean __$stMC;
private static SoftReference $callSiteArray;
static
{
__$swapInit();
}
public Test$_run_closure1(Object _outerInstance, Object _thisObject)
{
CallSite acallsite[] = $getCallSiteArray();
super(_outerInstance, _thisObject);
}
}
It ends up calling one of the Closure.call methods (in this case the one with no args)
There's more info about this in the documentation