groovy evaluate string as the function which exists in the same script - groovy

I am trying to evaluate string as code in groovy and it is failing with groovy.lang.MissingMethodException exception even though method exists in the same script. As I understood groovy runs new instance every time it tries to evaluate the code, but is there any way to inject current script into Eval.me or GroovyShell().evaluate() so that it can find the method and runs it ?
Below is sample code snippet,
def justSayHello(){
return "hello"
}
def my_str = "justSayHello()"
//Eval.me(my_func_str)
new GroovyShell().evaluate(my_func_str)
Both Eval and GroovyShell().evaluate() are throwing below exception
Caught: groovy.lang.MissingMethodException: No signature of method: Script1.justSayHello() is applicable for argument types: () values: []
groovy.lang.MissingMethodException: No signature of method: Script1.justSayHello() is applicable for argument types: () values: []
at Script1.run(Script1.groovy:1)
at string_split.run(string_split.groovy:35)

The following code:
justSayHello = {
println "hello"
}
def my_str = "justSayHello()"
new GroovyShell(binding).evaluate(my_str)
prints out hello when run. Here we have changed justSayHello from a method (on an implicit class that you can not see but which the groovy compiler generates around your script) to a closure. Further we are not doing def justSayHello as that would be defining it as a field on the implicit surrounding class (again which you can't see, but it's there), but rather just defining the variable without any modifiers which puts it in the global binding of the script.
We then send in the binding to the GroovyShell so that it can find the variable.
Result:
─➤ groovy solution.groovy
hello
A more generic variant is to do something like this:
def justSayHello() {
println "hello"
}
def someOtherMethod() {
println "hello again"
}
def methods = this.class.declaredMethods.findResults { m ->
if (m.name.startsWith('$') || m.name in ['main', 'run']) return null
[m.name, this.&"${m.name}"]
}.collectEntries { it }
// just for debugging, print the methods
methods.each { k, v ->
println "method: $k"
}
def my_str = "justSayHello()"
new GroovyShell(new Binding(methods)).evaluate(my_str)
which prints:
─➤ groovy solution.groovy
method: justSayHello
method: someOtherMethod
hello
here we find all the declared methods in the implicit class generated by groovy, remove some stuff added by the groovy compiler (namely main, run and methods starting with a $) and then send the resulting map as the binding for the GroovyShell constructor.
I suspect there might be a more elegant way of accomplishing this, so any groovy gurus - feel free to correct me here.
For an explanation of the implicit enclosing class for a groovy script, see for example this stackoverflow answer.

Related

How can I use Arrow-kt's Some() in Groovy code?

I'm trying to test my Kotlin code, which has Arrow-kt types, using Spock in Groovy. However, I'm not able to use Arrow-kt's additions such as Some. For example, I have a test as follows:
#Unroll
def "add returns #expected for queryRecord #queryRecord"() {
given:
def ip = "ip"
def rule = "rule"
when:
def result = unit.add(ip, rule)
then:
1 * dynamoDBMapperMock.load(ActionRecord.class, ip) >> queryRecord
result == expected
where:
queryRecord | expected
new ActionRecord() | None.INSTANCE
null | Some(new ActionInternal("ip"))
}
While the first data row succeeds with no problems, the second one fails with the following error:
groovy.lang.MissingMethodException: No signature of method: package.name.EventSpec.Some() is applicable for argument types: (package.name.ActionInternal) values: [ActionInternal(dropletIp=null)]
Possible solutions: Mock(), Spy(), Stub(), dump(), Mock(groovy.lang.Closure), Mock(java.lang.Class)
I've tried .some() as well, but not to avail. Apparently Groovy can't access Kotlin extensions, but Some is simply a data class[1], so I'm not sure why I cannot use it in Groovy.
Yes, you can use Arrow Datatypes in Groovy, the result is not as idiomatic as in Kotlin because the library heavily depends on extension functions and functions in the companion object
Example
import arrow.core.Option
import static arrow.core.OptionKt.getOrElse
static main(args){
println 'What is your name?'
def name = Option.#Companion.fromNullable(System.in.newReader().readLine())
.filterNot { it.isEmpty() }
.map { it.toUpperCase() }
println("Welcome ${getOrElse(name) { 'Anonymous' }}")
}
Output
'Welcome JOHN' (or 'Welcome Anonymous' if the provided name is null or empty)
As you can see, to be able to use getOrElse extension function, we need to import it as a static method
Hint
Do not use Some directly unless you are absolutely sure the value is not null, otherwise, you should rely on Option.fromNullable to safely lift the value to the Option context (i.e create Some or None depending if the value is null or not)

Groovy MissingMethodException with signature when accessing getter and NoSuchMethodError on inheritance

Can you explain why sometimes groovy throws MissingMethodException when java code calls a getter and there's a property with the same name?
Secondary question:
Can you explain why my first work-around is invalid for the 2nd use case?
The following script works because I added methodMissing
#!/usr/bin/env groovy
#Grapes([
#Grab(group='org.jvnet.hudson', module='xstream', version='1.4.7-jenkins-1'),
])
def a
println 'xstream'
com.thoughtworks.xstream.XStream s = new com.thoughtworks.xstream.XStream()
println s
def reg1 = s.converterRegistry
println "using property: $reg1"
com.thoughtworks.xstream.XStream.metaClass.methodMissing = { String name, def args ->
println "missing $name"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
def reg2 = s.getConverterRegistry()
println "using getter : $reg2"
println "ok"
This script prints:
com.thoughtworks.xstream.XStream#6c45ee6e
using property: com.thoughtworks.xstream.XStream$2#2e8e8225
missing getConverterRegistry
using getter : com.thoughtworks.xstream.XStream$2#2e8e8225
ok
The method getConverterRegistry exists in XStream but if you comment out the methodMissing you get :
groovy.lang.MissingMethodException: No signature of method: com.thoughtworks.xstream.XStream.getConverterRegistry() is applicable for argument types: () values: []
at TestXStream.run(TestXStream.groovy:24)
I was full of hope when making this work (although it's not pretty) but my issue continues because my code is actually using jenkins library and the following code does not work:
#!/usr/bin/env groovy
import hudson.util.XStream2
import com.thoughtworks.xstream.XStream
#Grapes([
#Grab(group='org.jvnet.hudson', module='xstream', version='1.4.7-jenkins-1'),
#Grab(group='org.jenkins-ci.main', module='jenkins-core', version='1.642.3', transitive=false),
])
def a
XStream.metaClass.methodMissing = { String name, def args ->
println "missing $name for XStream"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
def reg2 = new XStream().getConverterRegistry()
println reg2
XStream2.metaClass.methodMissing = { String name, def args ->
println "missing $name for XStream2"
if (name=="getConverterRegistry") {
return delegate.converterRegistry
}
}
println 'xstream2'
XStream2 s2 = new XStream2() // internal call to this.getConverterRegistry()
println "ok"
And the output:
missing getConverterRegistry for XStream
com.thoughtworks.xstream.XStream$2#c2db68f
xstream2
Caught: java.lang.NoSuchMethodError: hudson.util.XStream2.getConverterRegistry()Lcom/thoughtworks/xstream/converters/ConverterRegistry;
java.lang.NoSuchMethodError: hudson.util.XStream2.getConverterRegistry()Lcom/thoughtworks/xstream/converters/ConverterRegistry;
at hudson.util.XStream2.wrapMapper(XStream2.java:188)
at com.thoughtworks.xstream.XStream.buildMapper(XStream.java:610)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:568)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:496)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:465)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:411)
at com.thoughtworks.xstream.XStream.<init>(XStream.java:350)
at hudson.util.XStream2.<init>(XStream2.java:89)
at TestXStream2.run(TestXStream2.groovy:33)
Class XStream contains a property converterRegistry and its getter.
XStream2 extends XStream and the getter is inherited.
Note that when I run this from eclipse it's working fine and when using CLI I have this issue; possibly because eclipse would change this code more than the compiler.
Any clues?
I dropped the issue by switching back to plain Java for the main launcher.
I use the same über jar as a dependency as when I used groovy and grape.
I don't know wether it's related to groovy or grape (I suspect groovy) but I worked around it.

groovy MissingMethodException - parsing through ConfigSlurper

I am trying to parse through some properties file using configslurper.
ENT.adminserver.nodenumber=1
ENT.managedserver.1.host=vserver04
ENT.managedserver.2.host=vserver05
ENT.managedserver.3.host=vserver08
ENT.managedserver.4.host=vserver07
Said properties file. I am trying to read the host names from the properties.
Properties properties = new Properties()
File propertiesFile = new File('DomainBuild.properties')
propertiesFile.withInputStream {properties.load(it)}
def config = new ConfigSlurper().parse(properties)
def domainname="ENT" //will be passed through paremeters
def domain = config.get(domainname)
def managedServerFlow= {
println domain.managedserver
println domain.managedserver.keySet()
domain.managedserver.each {
println it.getClass()
println it.get("1")
}
for (server in domain.managedserver) {
println server.getClass()
println server
}
}
}
the it.get("1") is causing the following error.
No signature of method: java.util.LinkedHashMap$Entry.get() is applicable for argument types: (java.lang.String) values: [1]
Possible solutions: getAt(java.lang.String), grep(), grep(java.lang.Object), wait(), getKey(), any()
I looked through the java and groovy doc and spent few hours without resolution. Please help.
Instead of
println it.get("1")
Try
println it.'1'
Or
println it.getAt("1") // as the exception shows you
Think about what types you are working with. config is a ConfigObject, which you can treat like a map. Its sub-objects domain and domain.managedserver are also ConfigObjects. When you call each on domain.managedserver and pass it a closure that takes no parameters, it gives you a set of Entries. Therefore you can't call it.get("1") because an Entry doesn't have a property called "1". It has key and value. So you can either println "$it.key: $it.value" or
domain.managedserver.each { key, value ->
println value.getClass()
println "$key: $value"
}
or if you want to get the value for key "1" directly:
println domain.managedserver.'1'

Groovy collect returning GString in a Jenkins Workflow script

It seems that in the following piece of code:
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
at least sometimes formattedPaths evaluates to a GString instead of a List. This piece of code is a fragment of a larger Jenkins Workflow script, something like:
node {
currentBuild.rawBuild.changeSets[0].collect {
"""<b>${it.user}</b> # rev. ${it.revision}: ${it.msg}
${affectedFilesLog(it.affectedFiles)}"""
}
}
def affectedFilesLog(affectedFiles) {
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
println "formattedPaths [${formattedPaths.class}]: $formattedPaths"
formatItemList(formattedPaths)
}
def formatItemList(list) {
if (list) {
return list.join('\n')
}
return '(none)'
}
Running this script in Jenkins produces output:
Running: Print Message
Affected files [class java.util.ArrayList]: [hudson.scm.SubversionChangeLogSet$Path#5030a7d8]
Running: Print Message
formattedPaths [class org.codehaus.groovy.runtime.GStringImpl]: edit my/path/flow.groovy
(...)
groovy.lang.MissingMethodException: No signature of method: java.lang.String.join() is applicable for argument types: (java.lang.String) values: [
]
And this makes me believe that in the code:
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def formattedPaths = affectedFiles.collect {
"${it.editType.name} ${it.path}"
}
println "formattedPaths [${formattedPaths.class}]: $formattedPaths"
affectedFiles is ArrayList (script output Affected files [class java.util.ArrayList]: [hudson.scm.SubversionChangeLogSet$Path#5030a7d8] in the output)
but result of running collect method on it - assigned to formattedPaths - is a GString (output: formattedPaths [class org.codehaus.groovy.runtime.GStringImpl]: edit my/path/flow.groovy)
Shouldn't the collect method always return a List?
After the discussion in the comments pointing that it may be some side effects done by the Jenkins Workflow plugin, I decided to use a plain for-each loop:
def affectedFilesLog(affectedFiles) {
println "Affected files [${affectedFiles.class}]: $affectedFiles"
def ret = ""
for (Object affectedFile : affectedFiles) {
ret += affectedFile.path + '\n'
}
println("affectedFilesLog ret [${ret.class}]: $ret")
if (!ret) {
return '(brak)'
}
return ret;
}
EDIT 19/11/2015:
Jenkins workflow plugin mishandles functions taking Closures, see https://issues.jenkins-ci.org/browse/JENKINS-26481 and its duplicates. So rewriting the code to a plain Java for-each loop was the best solution.
You cannot currently use the collect method. JENKINS-26481
I guess your code is not thread safe. If you pass some objects as paramters to some other functions, do not change this. Always create and return a new changed object. Do not manipulate the original data. You should check where your objects live. Is it just inside a function or in global scope?

Properties in Groovy base scripts

I have a DSL where, if present, a closure called before will be called before every command.
In my setup I have 3 files: The script itself - Script, a ScriptBase, that is 'attached' to the script via a CompilerConfiguration, and a Handler.
In the script I may or may not have a closure called before.
before = {
//Do stuff.
}
Notice the lack of a type declaration, or def. If I understand Groovy correctly, this means that before is a in the binding, and accessible from outside code when evaluated with GroovyShell.evaluate().
In the ScriptBase I do the following:
class ProductSpecificationBase extends Script {
def before = null
}
This script base may or may not be overridden later on.
Then, in the Handler, I'm doing a check for whether a before closure is defined in the script:
def config = new CompilerConfiguration()
config.setScriptBaseClass(ScriptBase.class.name)
def shell = GroovyShell()
evaluatedScript = shell.evaluate(new File(thePathToScript))
if (evaluatedScript.before) {
theEvaluationOfMyScript.before()
}
The code works as expected if the script does contain a before closure, but if it doesn't it returns a MissingPropertyException. I've had a look at what this means, and it seems that my before in the ScriptBase isn't considered a property, and all the examples of using these ScriptBases I've found on the internet give examples of using methods. This is not feasible for my use case I'm afraid. How can I ensure that the closure in the ScriptBase is considered a property instead of a field(as I am assuming it is now).
To be paraphrase: I would like my code to not execute the if block if the script does not contain a before closure as well as not having been overridden in an extension of the ScriptBase. However, I would like the evaluation of evaluatedScript.before to be false as it is an empty/null Closure (i.e. it went all the way up to ScriptBase, and found the null closure)
I like to avoid a try/catch approach if possible.
in your example you would basically call the getter for the before property. To check, if there is a method with the name (and params) check with respondsTo. To see, if there is a property at all with that name use hasProperty (Thanks #dmahapatro for pointing this out)
class X {
void before() { println 'x' }
}
class Y { }
class Z {
def before = { println 'z' }
}
def x = new X()
def y = new Y()
def z = new Z()
assert x.respondsTo('before', null)
assert !y.respondsTo('before', null)
assert !z.respondsTo('before', null)
assert !x.hasProperty('before')
assert !y.hasProperty('before')
assert z.hasProperty('before')
x.before()
z.before()

Resources