Groovy DSL - shortcut for creating objects - groovy

is there a way in Groovy to replace some code like the below:
Task a = new Task('a')
Process p = new Process('p')
with something easier, like:
task a
process p
where task and process can be method calls that create the object and return it or add it to the script Map.
The main problem I currently have is that I cannot use a because it is not defined.

To create objects and name them without assigning to a variable, you can use a binding. Create and keep a reference to the a closure's binding, and have the utility methods task and process associate the new instance with the name. For example:
def scriptBinding = new Binding()
def task = { String name ->
scriptBinding[name] = new Task(name)
}
def process = { String name ->
scriptBinding[name] = new Process(name)
}
def script = {
task 'a'
process 'b'
println a
println b
}
script.binding = scriptBinding
script()
Note that you have to quote a and b so they are interpreted as strings instead of undefined variables. If you'd like to omit quotes, you can use a custom Binding object that evaluates undefined symbols as their string representation like this:
class SymbolAsStringBinding extends Binding {
Object getVariable(String name) {
try {
super.getVariable(name)
} catch (MissingPropertyException e) {
name
}
}
boolean hasVariable(String name) {
true
}
}
With that addition, you can write the script as:
def script = {
task a
process b
println a
println b
}

Try this:
class Task {
String name
Task(name) { this.name = name }
String toString() { "task: $name".toString() }
}
def propertyMissing(String name) { this.getBinding()[name] = name }
def task(name) { this.getBinding()[name] = new Task(name) }
task a
println a
this will produce:
task: a
essentially when you reach the
task a
statement the propertyMissing() will put in the binding a variable named a with content it's name.
Later the task() function will replace the variable a in the binding with the new Task passing as name the name of the missing varible a

Related

Skip MissingPropertyException while using GroovyShell.evaluate

Is there a way to skip MissingPropertyException while using GroovyShell.evaluate?
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate("a=5; b=1") // works fine
// How to not get MissingPropertyException, or silently ignore property 'a'
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
I know about the Expando solution, is there a way to do it without defining a class?
A very minimal approach would be to override Binding.getVariable.
Note that this is very straight forward: "all exceptions" are ignored - you might want to have better logging or more accurate error handling.
import groovy.lang.*
class NoOpBinding extends Binding {
#Override
Object getVariable(String name) {
try {
return super.getVariable(name)
}
catch (Throwable t) {
println "Ignoring variable=`$name`"
return null
}
}
}
def shell = new GroovyShell(new NoOpBinding())
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
// → Ignoring variable=`a`
println shell.getVariable('b')
// → 1
You could do something like this:
def binding = [:].withDefault { }
def shell = new GroovyShell(binding as Binding)
shell.evaluate 'a; b=1'

groovy print environments from groovy.config

how do I print available environments from a config file? What is the form of the ojbect ConfigSlurper creates?
I tried
def config2 = new ConfigSlurper().parse(new File('obieeadmincfg.groovy').toURL())
config2.config.environments.each { println ${it} }
and
println prettyPrint(toJson(config2))
and
for ( i in 0 ..config2.config.environments.size()-1)
println config2.config.environments[i]
groovy.config
//Config3.groovy
obieadmin {
//default values
serverurl = "http://default.mycompany.com"
}
environments {
pldev01 {
obieeadmin {
serverurl = 'devgdwobi03.x.com'
}
}
plsbx02 {
obieeadmin {
serverurl = 'devgdwobi03.x.com'
}
}
}
I'm afraid you can't do this out-of box.
But using a bit of Groovy Metaprogramming it's achievable.
Groovy config slurper parses proper Groovy file, and you can do the same with GroovyShell. You can catch call to environment method providing closure in binding. In that closure you have to collect all top-level method calls(with same methodMissing).
Providing base script with property and method missing handlers, you can suppress runtime errors, and execute script without much care to other properties.
Not the nicest path, but it works.
package test
import org.codehaus.groovy.control.CompilerConfiguration
class A extends Script {
def propertyMissing(String name) { null }
def propertyMissing(String name, def arg) {}
def methodMissing(String name, def args) {}
#Override Object run() { null }
}
class NameCollector {
def envs = []
def methodMissing(String name, def args) { envs << name }
}
// configure interceptors.
def configuration = new CompilerConfiguration()
configuration.scriptBaseClass = 'test.A'
def nc = new NameCollector()
def environments = { Closure it ->
it.delegate = nc;
it.resolveStrategy = Closure.DELEGATE_ONLY
it()
}
// execute config script
new GroovyShell([environments: environments] as Binding, configuration).evaluate(new File("config.groovy"))
nc.envs // Return, print them.
Not sure if this is going to work forever but currently you can do it by overriding the setting for the 'environments' conditional block, like this:
def config = """environments {
dev {
foo = "bar"
}
prod {
foo = "baz"
}
}"""
def configSlurper = new ConfigSlurper()
configSlurper.registerConditionalBlock('environments', null)
assert configSlurper.parse(config).environments.keySet() == ['dev', 'prod'].toSet()

How to compose variable name dynamically?

I need to generate a list,and name it's items based on for-loop index
number, like this:
for(int i=0;i<someNumber;i++){
Model m_{$i}=Mock() //but this doesn't work
......
models.add(i,m_{$i})
}
then they can be distinguished by name when debugging test code(shame to tell this) within eclipse,but it doesn't work, so how to make it work?
update:add image to tell why I want to append for-loop index to variable name
You can also add some property to your Mock class at runtime thanks to Groovy's MetaClass. Take a look at this sample snippet:
class myClass {
String someProperty
}
def models = []
10.times { it ->
def instance = new myClass(someProperty: "something")
instance.metaClass.testId = it
models.add(instance)
}
// delete some
println "Removing object with testId = " + models.remove(4).testId
println "Removing object with testId = " + models.remove(7).testId
def identifiersOfObjectsAfterRemoves = models.collect { it.testId }
def removedObjectsIdentifiers = (0..9) - identifiersOfObjectsAfterRemoves
println "Identifiers of removed objects: " + removedObjectsIdentifiers

jenkins extended parameter plugin groovy script

The website for the plugin says that you can create a groovy script to run to determine the parameter list.
how is this resolved though? The instructions don't say anything.
In what context is the script run?
What am i supposed to return from the script?
What directory is the cwd of the script? is it the environment variable WORKSPACE?
there is an extra field called variable bindings. How is this used?
I had to dig into the source code to find the answer to these questions so i hope this helps everyone else.
1. In what context is the script run?
The script is run inside a groovy.lang.GroovyShell. This class is currently from the Groovy 1.8.5 library. here is an excerpt from the code:
// line 419 - 443 of the ExtendedChoiceParamaterDefinition
else if(!StringUtils.isBlank(groovyScript)) {
try {
GroovyShell groovyShell = new GroovyShell();
setBindings(groovyShell, bindings);
Object groovyValue = groovyShell.evaluate(groovyScript);
String processedGroovyValue = processGroovyValue(isDefault, groovyValue);
return processedGroovyValue;
}
catch(Exception e) {
}
}
else if(!StringUtils.isBlank(groovyScriptFile)) {
try {
GroovyShell groovyShell = new GroovyShell();
setBindings(groovyShell, bindings);
groovyScript = Util.loadFile(new File(groovyScriptFile));
Object groovyValue = groovyShell.evaluate(groovyScript);
String processedGroovyValue = processGroovyValue(isDefault, groovyValue);
return processedGroovyValue;
}
catch(Exception e) {
}
}
2. What am i supposed to return from the script?
As the above code demonstrates, the script should return a string with whatever delimiter you have specified in the paramater or a String[] array. here is a snippet of the function that processes the value returned from the script:
// line 450 - 465 of ExtendedChoiceParameterDefinition
private String processGroovyValue(boolean isDefault, Object groovyValue) {
String value = null;
if(groovyValue instanceof String[]) {
String[] groovyValues = (String[])groovyValue;
if(!isDefault) {
value = StringUtils.join((String[])groovyValue, multiSelectDelimiter);
}
else if(groovyValues.length > 0) {
value = groovyValues[0];
}
}
else if(groovyValue instanceof String) {
value = (String)groovyValue;
}
return value;
}
3. What directory is the cwd of the script? is it the environment variable WORKSPACE?
Does it matter? You can access the environment variable WORKSPACE from within the script using
Map<String, String> props = System.getenv();
def currentDir = props.get('WORKSPACE');
4. there is an extra field called variable bindings. How is this used?
This is a property file formatted key=value file. these names are then resolvable in the groovy script.
e.g.
key1=foo
prop2=bar
For parse json object (from parametres) to groovy object - Parsing and producing JSON
import groovy.json.JsonSlurper
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText('{ "myList": [4, 8, 15, 16, 23, 42] }')
println(object.myList)

Dynamically added groovy property is not unique per-instance

If I dynamically add a property to a class, each instance of the class is initialized with a reference to the same value (even though the properties are correctly at different addresses, I don't want them to share the same reference value):
Here's an example:
class SolarSystem {
Planets planets = new Planets()
static main(args) {
SolarSystem.metaClass.dynamicPlanets = new Planets()
// Infinite loop
// SolarSystem.metaClass.getDynamicPlanets = {
// if (!delegate.dynamicPlanets.initialized) {
// delegate.dynamicPlanets = new Planets(initialized: true)
// }
//
// delegate.dynamicPlanets
// }
// No such field: dynamicPlanets for class: my.SolarSystem
// SolarSystem.metaClass.getDynamicPlanets = {
// if (!delegate.#dynamicPlanets.initialized) {
// delegate.#dynamicPlanets = new Planets(initialized: true)
// }
//
// delegate.#dynamicPlanets
// }
SolarSystem.metaClass.getUniqueDynamicPlanets = {
if (!delegate.dynamicPlanets.initialized) {
delegate.dynamicPlanets = new Planets(initialized: true)
}
delegate.dynamicPlanets
}
// SolarSystem.metaClass.getDynamicPlanets = {
// throw new RuntimeException("direct access not allowed")
// }
def solarSystem1 = new SolarSystem()
println "a ${solarSystem1.planets}"
println "b ${solarSystem1.dynamicPlanets}"
println "c ${solarSystem1.uniqueDynamicPlanets}"
println "d ${solarSystem1.dynamicPlanets}"
println ''
def solarSystem2= new SolarSystem()
println "a ${solarSystem2.planets}"
println "b ${solarSystem2.dynamicPlanets}"
println "c ${solarSystem2.uniqueDynamicPlanets}"
println "d ${solarSystem2.dynamicPlanets}"
}
}
In a separate file:
class Planets {
boolean initialized = false
}
When this runs, you see something like this:
a my.Planets#4979935d
b my.Planets#66100363
c my.Planets#5e0feb48
d my.Planets#5e0feb48
a my.Planets#671ff436
b my.Planets#66100363
c my.Planets#651dba45
d my.Planets#651dba45
Notice how for solarSystem2, the 'normal' member variable planets has a different address when the two objects are created. However, the dynamically added dynamicPlanets points to the same object that solarSystem1 pointed to (in this case, at address 66100363).
I can reassign them in my dynamic getter (getUniqueDynamicPlanets), and that fixes the problem.
However, I cannot override the getDynamicPlanets getter, because I either get an infinite loop, or I cannot get direct access to the dynamically-added property.
Is there a way to directly access the dynamically-added property so I could handle this in the getDynamicPlanets getter? Is there a better strategy for this altogether? Sorry if I missed it, I've looked a bunch...
Thanks
I'm not 100% sure I understand your question, but if I do, did you try setting the getDynamicPlanets closure to have explicitly 0 parameters, so:
SolarSystem.metaClass.getDynamicPlanets = {-> ... }
If you don't have the -> with no args before it, there is an implicit it parameter that's assigned and it's not a zero arg method, so doesn't adhere to the javabean getter/setter pattern.

Resources