I'm developing a Groovy library providing variable binding/syncronization with simple syntax and rich event handler.
Firstly I'm aiming to archive running the following:
def a = 1
def b = 1
def c = 1
a bind { b + c }
assert a==2 & b==1 & c==1
b = 3
assert a==4 & b==3 & c==1
c = -1
assert a==2 & b==3 & c==-1
I'd like avoid Java FX approach of providing additional classes but rather enhance objects with this functionality.
I'm stuck at finding some tracking of objects. Groovy doesn't feature overload of assignment expression.
Currently I see solution in heavy AST transformation (maybe dsld might help).
Maybe there is some object tracking, events or whatever, I've missed?
I'm presuming you are writing your code using scripts. Did what you wanted with the following ideas:
Instead of binding values to keys using the Script.binding, it is better if you create a Variable class which represents both the value and the variable name;
Every method called on the Variable object gets delegated to its value;
A Listener class which encapsulates a reference to the updating operation AND the Variable object which needs to be updated;
An updateListeners() method, to, well, update the listeners;
A bind(closure) method that just bypasses the closure to the variable object. It could pretty much be dropped and written as a { b + c };
class Listener { Closure operation; Variable variable }
class Variable implements GroovyInterceptable {
String name; def value
def invokeMethod(String method, args) {
def var = args[0]
var.class == Variable ?
value."$method"(var.value) : value."$method"(var)
}
}
Variable.metaClass.call = { Closure operation ->
binding['listeners'] <<
new Listener(operation: operation, variable: delegate)
updateListeners()
}
def bind(Closure operation) { operation }
def updateListeners() {
binding.listeners.each {
it.variable.value = it.operation()
}
}
void setProperty(String prop, value) {
if (!binding.hasVariable('listeners')) binding['listeners'] = []
binding[prop] = new Variable(name: prop, value: value)
updateListeners()
}
And your tests worked like a charm.
Related
I have a global shared library on Jenkins implicitly loaded on all pipelines, then my Jenkinsfile is like that:
new com.company.Pipeline()()
And then the shared library has on directory src/com/company some files, below the Pipeline.groovy class:
package com.company
import static Utils.*
def call() {
// some stuff here...
}
The problem is, this way I have to static declare all methods, thus I lose the context and cannot access jenkins' methods easly without the Pipeline class' instance. As you can see here they passing this to the method mvn.
Thinking of avoid this I was wondering about dynamically add all methods as closures by calling Utils.install this instead of using import static Utils.*, then my Utils.groovy is something like that:
package com.company
private Utils() {}
static def install(def instance) {
def utils = new Utils()
// Some extra check needed here I know, but it is not the problem now
for (def method in (utils.metaClass.methods*.name as Set) - (instance.metaClass.methods*.name as Set)) {
def closure = utils.&"$method"
closure.delegate = instance
instance.metaClass."$method" = closure
}
}
def someMethod() {
// here I want to use sh(), tool(), and other stuff freely.
}
But it raises an GStringImpl cannot be cast to String error, I believe .& do not work with variables, how can I convert a method into closure having the method name on a variable? I have the MetaMethod mostly being a CachedMethod instance, if it were possible to turn it a ClosureMetaMethod instance maybe the problem can be solved, but whenever I search for method to closure conversion for groovy I just found the .& solution!
If I use instance.metaClass.someMethod = utils.&someMethod it do work, but I want it to be dinamic as I add new methods without needing to worry about sharing it.
There is a way to do it dynamically. Notation utils.&someMethod returns a MethodClosure object that can be simply instantiated with its constructor:
MethodClosure(Object owner, String method)
Consider following example:
class Utils {
def foo() {
println "Hello, Foo!"
}
def bar() {
println "Hello, Bar!"
}
}
class Consumer {
}
def instance = new Consumer()
def utils = new Utils()
(utils.metaClass.methods*.name - instance.metaClass.methods*.name).each { method ->
def closure = new MethodClosure(utils, method)
closure.delegate = instance
instance.metaClass."$method" = closure
}
instance.foo() // Prints "Hello, Foo!"
instance.bar() // Prints "Hello, Bar!"
In this example I use def closure = new MethodClosure(utils, method) to get object method reference and then add this method to instance object. I hope it helps.
I'm trying to construct a convenient shared library for my scripted pipelines.
I want to be able to use an object from my Jenkinsfile that has several functional methods, but also stores some initial variables that are referenced by several methods.
So, I've defined a file in "vars" with a bunch of methods for functionality, but also a handful of get/set methods for the properties I want to store in the object for reference by several methods.
My initial testing is just to set some of the variables (not all of them) and call the "toString()" method which I've defined in the file (just for convention, obviously).
In my test Jenkinsfile, If I do set all of the variables and then call "toString()", it works fine and completes.
However, if I try commenting out one of the variable initializations, when it hits the line in the "toString" method that constructs the return value, Jenkins just hangs forever. I eventually just kill the job.
I've been able to avoid the hang by qualifying each reference with 'if binding.variables.containsKey("foo")) {', only referencing the variable if that is true. I made this a tiny bit cleaner by putting the binding check in each of the getter methods instead.
I don't really like this workaround. It just seems odd that I would have to do this.
I've tried several variations, but I don't set the variable and I try to reference it in a gstring, the job hangs forever, every time.
This is a brief excerpt from the "vars" file:
def setJobName(value) { jobName = value; }
//def getJobName() { return jobName }
def getJobName() { return (binding.variables.containsKey("jobName") ? jobName : "") }
def setMechIdCredentials(value) { mechIdCredentials = value; }
//def getMechIdCredentials() { return mechIdCredentials }
def getMechIdCredentials() { return (binding.variables.containsKey("mechIdCredentials") ? mechIdCredentials : "") }
... more get/set methods
String toString() {
echo "Inside uslutils.toString()."
This is an excerpt from the Jenkinsfile that uses this:
uslutils.currentBuild = currentBuild
uslutils.jobName = env.JOB_NAME
uslutils.buildURL = env.BUILD_URL
//uslutils.mechIdCredentials = "abc"
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " +
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"codeURL[${codeURL}] buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] QBotUserID[${QBotUserID}] " +
"QBotPassword[${QBotPassword}]]"
}
So, for instance, if I swapped the two variations of "getMechIdCredentials", leaving the "plain" one, this combination of samples will hang, until I click on the red X on the "Progress" indicator in Jenkins.
Update:
So, based on feedback, I defined variables in the file and changed my getters/setters to use the "this.#var" syntax. As a result, the build fails with "No such field: var for class: uslutils".
I imagine my syntax for defining the field isn't correct, but here's an excerpt of what I have:
def currentBuild = ""
String jobName = ""
String buildURL = ""
def mechIdCredentials = ""
def setCurrentBuild(value) { this.#currentBuild = value; }
def getCurrentBuild() { return this.#currentBuild }
def setJobName(value) { this.#jobName = value; }
def getJobName() { return this.#jobName }
def setBuildURL(value) { this.#buildURL = value; }
def getBuildURL() { return this.#buildURL }
def setMechIdCredentials(value) { this.#mechIdCredentials = value; }
def getMechIdCredentials() { return this.#mechIdCredentials }
Note that this is NOT within a "class" declaration, it's in a Groovy script file called "uslutils.groovy".
To be clear, here is an excerpt of the stack trace I get:
groovy.lang.MissingFieldException: No such field: mechIdCredentials for class: uslutils
at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:2823)
at groovy.lang.MetaClassImpl.getAttribute(MetaClassImpl.java:3759)
at org.codehaus.groovy.runtime.InvokerHelper.getAttribute(InvokerHelper.java:145)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getField(ScriptBytecodeAdapter.java:306)
at com.cloudbees.groovy.cps.sandbox.DefaultInvoker.getAttribute(DefaultInvoker.java:42)
at com.cloudbees.groovy.cps.impl.AttributeAccessBlock.rawGet(AttributeAccessBlock.java:20)
at uslutils.getMechIdCredentials(/opt/app/jenkins/sdt-usl/data/jobs/uslutils-tests/builds/33/libs/usl-pipeline-library/vars/uslutils.groovy:197)
at uslutils.toString(/opt/app/jenkins/sdt-usl/data/jobs/uslutils-tests/builds/33/libs/usl-pipeline-library/vars/uslutils.groovy:242)
at WorkflowScript.run(WorkflowScript:13)
Line 242 is shown here:
String toString() {
echo "Inside uslutils.toString()x."
return "[currentBuild[${currentBuild}] mechIdCredentials[${mechIdCredentials}] " + // line 242
"baseStashURL[${baseStashURL}] jobName[${jobName}] codeBranch[${codeBranch}] " +
"codeURL[${codeURL}] buildURL[${buildURL}] pullRequestURL[${pullRequestURL}] QBotUserID[${QBotUserID}] " +
"QBotPassword[${QBotPassword}]]"
}
Ran into this while doing a refactor. Calls to getProperties() were causing our CPU usage to spike. What we discovered is that if you have a getter without an associated attribute, when you make a call to getProperties() that getter is called over 1000 times. The fix/workaround is obvious and we know it has something to do with metaprogramming but why is this happening (what point in the groovy source)? See groovy script code below:
class tester {
int count = 0
public getVar() {
println count++ + " getVar() called!"
return var
}
}
def t = new tester()
t.getProperties()
println "done!"
You should see getVar() called over 1000 times. 1068 to be exact for us.
The question has probably already been answered in the comments but I dug a little deeper to also answer the "what point in the groovy source" part.
When you call getProperties() on the instance of tester Groovy will do its magic and finally call DefaultGroovyMethods#getProperties(Object) which (in Groovy 2.4.7) looks like this:
public static Map getProperties(Object self) {
List<PropertyValue> metaProps = getMetaPropertyValues(self); // 1
Map<String, Object> props = new LinkedHashMap<String, Object>(metaProps.size());
for (PropertyValue mp : metaProps) {
try {
props.put(mp.getName(), mp.getValue()); // 2
} catch (Exception e) {
LOG.throwing(self.getClass().getName(), "getProperty(" + mp.getName() + ")", e);
}
}
return props;
}
First, Groovy determines the meta properties of the given object (see 1). This will return three properties:
var: getter only (getVar()), no setter, no field
class: getter only (inherited from Object), no setter, no field
count: getter, setter (both generated by Groovy) and field
You can easily verify this by calling t.getMetaPropertyValues().
Next, Groovy tries to get the current value of each property and puts it in a map (see 2). When it reaches var, it remembers that var has a getter (namely getVar()) and calls it. getVar() however, returns var again. For Groovy, this is the exact same property as determined in the first step. Once again, it calls its getter getVar() and the endless loop begins.
At some point, depending on the JVM, this results in a StackOverflowError, which is exactly what this site is all about :-D
I don't appear to be able to override a getter within my grails controller. The sample code I've created to illustrate this is provided below:
class MyController extends RestfulController<MyDomainObj> {
def field
def getField(){
field += 1
}
def index(MyCommand command) {
field = 1
// in a controller this prints 1, but in my class it prints 2
println('field' + field)
}
}
If I create a Groovy class and override the getter then it works.
class X {
public static void main(String[] args){
def x = new X()
x.field = 1
println x.field
}
def field
def getField(){
field += 1
}
}
Am I doing something wrong in the Controller or is this feature not supported in controllers? If it isn't supported, then does anyone know why? What magic is going on that would cause this feature not to work?
For attributes within a class, Groovy uses the generated private variable directly:
See http://groovy.codehaus.org/Groovy+Beans:
If you access a property from within the class the property is defined
in at compile time with implicit or explicit this (for example
this.foo, or simply foo), Groovy will access the field directly
instead of going though the getter and setter.
Example:
class C {
def prop
def getProp() {
println "getter"
prop
}
def dostuff() {
prop = "Y"
println prop
println getProp()
}
}
new C().dostuff()
results in
Y
getter
Y
When I run this java code, i am able to get the values of variables define outside the function, but i am unable to get the values of variable define inside the function. how to access these variable values?
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
public class JSFunctionTest {
public static void main(String args[]) {
String code = "var name='nc',global_a = 'jill'; " + "\n"+
"function myfunc(b) { " + "\n"+
"var local_a = 1;" + "\n"+
"global_a = 'jack';" + "\n"+
" return b;" + "\n"+
"}";
Context context = Context.enter();
context.setGeneratingDebug(true);
context.setOptimizationLevel(-1);
Scriptable scope = context.initStandardObjects();
context.evaluateString(scope, code, "code", 1, null);
//getting values of varables
System.out.println("var name:"+scope.get("name", scope));
System.out.println("var global_a:"+scope.get("global_a", scope));
System.out.println("var local_a:"+scope.get("local_a", scope));//not found becase function wasnt run
//calling the function.
Object fObj = scope.get("myfunc", scope);
if (!(fObj instanceof Function)) {
System.out.println("myfunc is undefined or not a function.");
} else {
Object functionArgs[] = { "nc" };
Function f = (Function)fObj;
Object r = f.call(context, scope, scope, functionArgs);
String report = "myfunc('nc') = " + Context.toString(r);
//trying to access global and local a after calling function
System.out.println("var global_a:"+scope.get("global_a", scope));//values is changed, because this is defined out side the function.
System.out.println("var local_a:"+scope.get("local_a", scope));// still not found,after running the function.
System.out.println(report);
}
}
}
I was able to resolve this, by implementing a debugger using the debug API in Rhino.
Implement your own debugger and debugframe classes.
Hook them to your context using setDebugger() method
in the DebugFrame class, I have implemented. cache the scope object in the onEnter method.
in the onLineChange method, you can get the local variables using ScriptableObject.getProperty() passing the cache scope object and giving the names
In ECMAScript, functions create their own scope. Outside the global scope, it's the only way to create new ones. See Rhino Scopes and Contexts for an example similar to yours.
The thing is that ECMAScript is a dynamic language (especially when optimization level is set to interpreted mode). This means the interpreter does not know in advance what it will encounter. The function's scope is only created/evaluated when the code is actually executed. So you can't evaluate a piece of code and query for variables inside a scope that isn't executed.
The question is why would you want to do that in practice? For debugging, you can step into the code and inspect the scopes, you should be able to see it.