How to "include" common methods in groovys - groovy

I have developed a number of groovys used as plugins by Serviio.
Many of the methods used by these plugins are common, but when changes are made, each plugin needs to be updated. Therefore I want to "include" those methods in each plugin from a tools.groovy. I have tried 2 different approaches suggested in other posts.
I tried using
evaluate(new File("C:\\Program Files\\Serviio\\plugins\\tools.groovy"))
at the start of each plugin where tools.groovy just has
class Tools{method1{return}method2{return}}
but when executing the plugin I get
Caught: groovy.lang.MissingMethodException: No signature of method: Tools.main() is applicable for argument types: () values: []
If I then add
void main(args) { }
to class Tools, the error goes away but that Tools.main is run instead of the plugin.main and I get no output.
My second approach as suggested was to use
def script = new GroovyScriptEngine( '.' ).with {
loadScriptByName( 'C:\\Program Files\\Serviio\\plugins\\tools.groovy' )
}
this.metaClass.mixin script
This however gives the error
unexpected token: this # line 55, column 2.
this.metaClass.mixin script
Any suggestions on how to make either of these solutions work would be appreciated.

Did you try defining a common base script and giving it as a compiler configuration.
http://groovy.codehaus.org/Embedding+Groovy
From the groovy documentation...
class ScriptBaseTest {
#Test
void extend_groovy_script() {
def compiler = new CompilerConfiguration()
compiler.setScriptBaseClass("ScriptBaseTestScript")
def shell = new GroovyShell(this.class.classLoader, new Binding(), compiler)
assertEquals shell.evaluate("foo()"), "this is foo"
}
}
abstract class ScriptBaseTestScript extends Script {
def foo() {
"this is foo"
}
}

Related

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.

How to use closures of Jenkins Job DSL Plugin within groovy classes

I'm new to Job DSL Plugin and even Groovy.
Given the following script:
class MyClass {
def create() {
folder('test') {
}
}
}
new MyClass().create()
I'm getting the following error:
javaposse.jobdsl.dsl.DslScriptException: (script, line 3) No signature of method: MyClass.folder() is applicable for argument types: (java.lang.String, MyClass$_create_closure1) values: [test, MyClass$_create_closure1#62591600]
Possible solutions: find(), collect()
Ok, clear. Groovy does not find a method called "folder" in my class. But this isn't a method. It is a Job DSL command. How can I use them within my classes?
You need to pass the script reference into your class, see the Job DSL wiki.
class MyClass {
def create(def dslFactory) {
dslFactory.folder('test') {
}
}
}
new MyClass().create(this)

Groovy - Type check AST generated code

I have a Groovy application that can be custimized by a small Groovy DSL I wrote. On startup, the application loads several Groovy scripts, applies some AST transformations and finally executes whatever was specified in the scripts.
One of the AST transformations inserts a couple of lines of code into certain methods. That works fine and I can see the different behavior during runtime. However, sometimes the generated code is not correct. Although I load the scripts with the TypeChecked customizer in place, my generated code is never checked for soundness.
To show my problem, I constructed an extreme example. I have the following script:
int test = 10
println test // prints 10 when executed without AST
I load this script and insert a new line of code between the declaration of test and println:
public void visitBlockStatement(BlockStatement block) {
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant 15
}
}).first()
def newStmt = new ExpressionStatement(assignment)
newStmt.setSourcePosition(block.statements[1])
block.statements.add(2, newStmt)
super.visitBlockStatement(block)
}
After applying this AST, the script prints 15. When I use AstNodeToScriptVisitor to print the Groovy code of the resulting script, I can see the new assignment added to the code.
However, if I change the value of the assignment to a String value:
// ...
def assignment = (new AstBuilder().buildFromSpec {
binary {
variable "test"
token "="
constant "some value"
}
}).first()
// ...
I get a GroovyCastExcpetion at runtime. Although the resulting script looks like this:
int test = 10
test = "some value" // no compile error but a GroovyCastException at runtime here. WHY?
println test
no error is raised by TypeChecked. I read in this mailing list, that you need to set the source position for generated code to be checked, but I'm doing that an it still doesn't work. Can anyone provide some feedback of what I am doing wrong? Thank you very much!
Update
I call the AST by attaching it to the GroovyShell like this:
def config = new CompilerConfiguration()
config.addCompilationCustomizers(
new ASTTransformationCustomizer(TypeChecked)
)
config.addCompilationCustomizers(
new ASTTransformationCustomizer(new AddAssignmentAST())
)
def shell = new GroovyShell(config)
shell.evaluate(new File("./path/to/file.groovy"))
The class for the AST itself looks like this:
#GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
class AddAssignmentAST implements ASTTransformation {
#Override
public void visit(ASTNode[] nodes, SourceUnit source) {
def transformer = new AddAssignmentTransformer()
source.getAST().getStatementBlock().visit(transformer)
}
private class AddAssignmentTransformer extends CodeVisitorSupport {
#Override
public void visitBlockStatement(BlockStatement block) {
// already posted above
}
}
}
Since my Groovy script only consists of one block (for this small example) the visitBlockStatement method is called exactly once, adds the assignment (which I can verify since the output changes) but does not ever throw a compile-time error.

Scope of Groovy's metaClass?

I have an application which can run scripts to automate certain tasks. I'd like to use meta programming in these scripts to optimize code size and readability. So instead of:
try {
def res = factory.allocate();
... do something with res ...
} finally {
res.close()
}
I'd like to
Factory.metaClass.withResource = { c ->
try {
def res = factory.allocate();
c(res)
} finally {
res.close()
}
}
so the script writers can write:
factory.withResource { res ->
... do something with res ...
}
(and I could do proper error handling, etc).
Now I wonder when and how I can/should implement this. I could attach the manipulation of the meta class in a header which I prepend to every script but I'm worried what would happen if two users ran the script at the same time (concurrent access to the meta class).
What is the scope of the meta class? The compiler? The script environment? The Java VM? The classloader which loaded Groovy?
My reasoning is that if Groovy meta classes have VM scope, then I could run a setup script once during startup.
Metaclasses exist per classloader [citation needed]:
File /tmp/Qwop.groovy:
class Qwop { }
File /tmp/Loader.groovy:
Qwop.metaClass.bar = { }
qwop1 = new Qwop()
assert qwop1.respondsTo('bar')
loader = new GroovyClassLoader()
clazz = loader.parseClass new File("/tmp/Qwop.groovy")
clazz.metaClass.brap = { 'oh my' }
qwop = clazz.newInstance()
assert !qwop.respondsTo('bar')
assert qwop1.respondsTo('bar')
assert qwop.brap() == "oh my"
assert !qwop1.respondsTo('brap')
// here be custom classloaders
new GroovyShell(loader).evaluate new File('/tmp/QwopTest.groovy')
And a script to test the scoped metaclass (/tmp/QwopTest.groovy):
assert !new Qwop().respondsTo('bar')
assert new Qwop().respondsTo('brap')
Execution:
$ groovy Loaders.groovy
$
If you have a set of well defined classes you could apply metaprogramming on top of the classes loaded by your classloader, as per the brap method added.
Another option for this sort of thing which is better for a lot of scenarios is to use an extension module.
package demo
class FactoryExtension {
static withResource(Factory instance, Closure c) {
def res
try {
res = instance.allocate()
c(res)
} finally {
res?.close()
}
}
}
Compile that and put it in a jar file which contains a file at META-INF/services/org.codehaus.groovy.runtime.ExtensionModule that contains something like this...
moduleName=factory-extension-module
moduleVersion=1.0
extensionClasses=demo.FactoryExtension
Then in order for someone to use your extension they just need to put that jar file on their CLASSPATH. With all of that in place, a user could do something like this...
factoryInstance.withResource { res ->
... do something with res ...
}
More information on extension modules is available at http://docs.groovy-lang.org/docs/groovy-2.3.6/html/documentation/#_extension_modules.

The Cucumber JVM's (Groovy) "World (Hooks)" object mixin and Intellij navigation

I am developing cucumber scenarios using Cucumber JVM (Groovy) in intellij. Things are much better than doing the same in eclipse I must say.
I'd like to resolve one small problem to make things even better for my team. But since I am new to Intellij, I need help with the following please:
When I am in a step def file (groovy), Intellij can't seem to see variables and methods defined in the cucumber "World" object. So don't get IDE support (auto-complete, etc) for those which is a bit annoying. How can I fix that?
IntelliJ IDEA expects an object inside World closure. So, you could define the folloing block in EACH step definitions file:
World {
new MockedTestWorld()
}
MockedTestWorld.groovy:
class MockedTestWorld implements TestWorld {
#Lazy
#Delegate
#SuppressWarnings("GroovyAssignabilityCheck")
private TestWorld delegate = {
throw new UnsupportedOperationException("" +
"Test world mock is used ONLY for syntax highlighting in IDE" +
" and must be overridden by concrete 'InitSteps.groovy' implementation.")
}()
}
To cleanup duplicated world definitions we use last-glue initializers and a bit of copy-paste:
real/InitSteps.groovy
def world
GroovyBackend.instance.#worldClosures.clear()
GroovyBackend.instance.registerWorld {
return world ?: (world = new RealTestWorld1()) // Actual initialization is much longer
}
legacy/InitSteps.groovy
def world
GroovyBackend.instance.#worldClosures.clear()
GroovyBackend.instance.registerWorld {
return world ?: (world = new LegacyTestWorld1())
}
Finally, run configurations would be like this (with different glues):
// Real setup
glue = { "classpath:test/steps", "classpath:test/real", },
// Legacy setup
glue = { "classpath:test/steps", "classpath:test/legacy", },

Resources