Groovy cast behaves inconsistently - groovy

I ran into a bad chunk of code today that equates to this:
[["asdf"]].each {String str -> println str}
println (["asdf"] as String)
Put this into groovy console (groovysh) and you'll see this come out:
asdf
[asdf]
Can anyone explain why there's a difference in the outputs?

Groovy will unwrap a List if it means it will fit into the types you have defined in your closure.
If you don't define the type (or set it as List), it will behave as you expect:
// Both print '[a]'
[['a']].each { it -> println it }
[['a']].each { List it -> println it }
If however you define a type, it will attempt to unwrap the list and apply the contents to the defined parameters, so:
// Prints 'a'
[['a']].each { String it -> println it }
Think of it like varargs in java, or calling the closure with closure( *it ) in Groovy
It actually comes in pretty useful as you can do things like:
// Tokenize the strings into 2 element lists, then call each with these
// elements in separate variables
['a=b', 'b=c']*.tokenize( '=' )
.each { key, value ->
println "$key = $value"
}

The first output
I don't know for sure, but it seems as though Groovy will try to apply the closure to a single element of a list, iff there is only one.
Note that this:
[["asdf"]].each {String str -> println str }
is equivalent to this:
Closure c = { String s -> println s }
c(["asdf"])
and that these tests suggest the empirical evidence:
Closure c = { String s -> println s }
println "c test"
try { c(["asdf","def"]) } catch(Exception ex) { println "no" }
try { c(123) } catch(Exception ex) { println "no" }
try { c([]) } catch(Exception ex) { println "no" }
// this works:
try { c(["asdf"]) } catch(Exception ex) { println "no" }
Closure d = { Integer i -> println i }
println "d test"
try { d([22,33]) } catch(Exception ex) { println "no" }
try { d("abc") } catch(Exception ex) { println "no" }
try { d([]) } catch(Exception ex) { println "no" }
// this works:
try { d([22]) } catch(Exception ex) { println "no" }
The second output
Simply note that this:
println (["asdf"] as String)
is likely equivalent to this:
List<String> list = ["asdf"]
println list.toString()

Related

Vala, string to enum

Is there a way to convert a string to an enum in vala:
string foo = "Enum1";
MY_ENUM theEnum = MY_ENUM.get_value_by_name(foo);
enum MY_ENUM {
Enum1,
Enum2,
Enum3
}
So in this example "theEnum" would have the value: MY_ENUM.Enum1
It is possible using the runtime type system provided by GLib's GObject library. There are EnumClass and EnumValue. These provide introspection at runtime and allow an enum to be initialised from a string.
The syntax is a bit complex at present, although it may be possible for someone to modify the Vala compiler to make it easier, but that is a significant piece of work.
An example:
void main () {
try {
MyEnum? the_enum_value;
the_enum_value = MyEnum.parse ("FIRST");
print (#"$(the_enum_value)\n");
} catch (EnumError error) {
print (#"$(error.message)\n");
}
}
errordomain EnumError {
UNKNOWN_VALUE
}
enum MyEnum {
FIRST,
SECOND,
THIRD;
public static MyEnum parse (string value) throws EnumError {
EnumValue? a;
a = ((EnumClass)typeof (MyEnum).class_ref ()).get_value_by_name ("MY_ENUM_" + value);
if (a == null) {
throw new EnumError.UNKNOWN_VALUE (#"String $(value) is not a valid value for $(typeof(MyEnum).name())");
}
return (MyEnum)a.value;
}
}

Catching an exception when loading a groovy file

I have these 3 groovy files:
A.groovy:
// ...
stage("Test")
{
throw new Exception("This script fails")
}
B.groovy:
// ...
stage("Test")
{
// Nothing, want this to pass
}
main.groovy:
// ...
m = [:]
status = [:]
scripts = ["A", "B"]
for script in scripts
{
m["${script}"] =
{
stage("${script}")
{
try
{
load "${script}.groovy"
status["${script}"] = true
}
catch (Exception e)
{
status["${script}"] = false
}
}
}
}
parallel m
for result_iterator in status:
print "${result_iterator.key} resulted in ${result_iterator.value}"
The code above is a sketch of the real code =)
When I run main.groovy to see the results in the status dictionary, I only get to see B. A threw an exception and thus it didn't add itself to the dictionary. Is there a way I can catch A's exception somehow?
Problem 1
You got bitten by the way Groovy closures capture variables outside of their scope. The references to script inside the closure will receive the value of script at the time when the closure is run, which will be the last value of script. So both closures actually load "B.groovy" and the exception is never thrown!
The fix is simple, just create a local variable inside of the loop that captures the current value of script:
for( script in scripts ) {
def theScript = script
// Now only use theScript inside of the closure!
}
Problem 2
You are not serializing write access to the status variable from parallel threads. This could result in very nasty, hard-to-reproduce bugs.
The fix is to use Map.asSynchronized() to create a synchronized map:
status = [:].asSynchronized()
Complete 'main.groovy' fix
m = [:]
status = [:].asSynchronized() // because multiple threads write to this variable
scripts = ["A", "B"]
for( script in scripts ) {
def theScript = script // Important to pass the current value into the closure!
m[ theScript ] = {
stage( theScript ) {
try {
load "${theScript}.groovy"
status[ theScript ] = true
}
catch (Exception e) {
status[ theScript ] = false
}
}
}
}
parallel m
for( result_iterator in status ) {
print "${result_iterator.key} resulted in ${result_iterator.value}"
}
Note: I have also removed the unnecessary string interpolations.

Clone of list still correct the original list

In groovy the original value get overwritten when I change values in a clone list. Does anyone know if I am doing it wrong or it is a bug older groovy?
I am doing something like this:
List<Foo> myFooList = fooList.newFoos.findAll { it.type == "Types}
List<Foo> newFoo = fooList.oldFoos.findAll { it.type == "Types}.clone()
newFoo.each {
it.value = "neeeew value"
}
Foo fooOne = newFoo.each { foooo ->
fooTwo = fooList.oldFoos.find { it.id == foooo.id}
if(fooTwo.value != foooo.value) {
//Here it should go... but it turns out that fooTwo.value == foooo.value
}
}
the clone method called on list produces a new list but with the same objects in it.
you want to build new list with new objects. here is an example:
#groovy.transform.ToString
class Foo{
String type
String value
}
def fooList = [
new Foo(type:"Types", value:'old value1'),
new Foo(type:"Not", value:'old value2'),
new Foo(type:"Types", value:'old value3'),
new Foo(type:"Not", value:'old value4'),
]
def newFooList = fooList.
findAll{it.type=='Types'}.
collect{ new Foo(type:it.type, value:"new value") } //build new array with transformed elements
//check the original list
fooList.each{assert it.value!='new value'}
//check new list
newFooList.each{assert it.value=='new value'}
assert newFooList.size()==2
println fooList
println newFooList
I solved the issue by adding clone of the element as well, any way it became to much of cowboy fix:
List<Foo> myFooList = fooList.newFoos.findAll { it.type == "Types}
List<Foo> newFoo = fooList.oldFoos.findAll { it.type == "Types}.collect {it.clone()}
newFoo.each {
it.value = "neeeew value"
}
Foo fooOne = newFoo.each { foooo ->
fooTwo = fooList.oldFoos.find { it.id == foooo.id}
if(fooTwo.value != foooo.value) {
//Here it should go... but it turns out that fooTwo.value == foooo.value
}
}

Groovy: How to invoke methods when closure is one of the parameter

This is my closure method and i am looking for different ways to invoke this groovy method
myMethod(Closure c, def val) {
if(c)
c.call()
println val
}
Things i tried:
myMethod({/*code*/},"print something")
Is there a way i can skip braces or a better way to do the same?
Put the Closure last in the definition:
def myMethod(val, Closure c) {
if(c) c.call()
println val
}
Then you can do:
myMethod("print something") { -> println "closure!" }
Edit
Think you're going to need 2 methods:
def myMethod(Closure c) {
myMethod('default', c)
}
def myMethod(val, Closure c) {
if(c) c.call()
println val
}
Then you can do:
myMethod('tim') { println 'woo' }
or
myMethod { println 'woo' }

groovy method calls and parameters - no signature of method?

I'm trying to understand what is happening when I get errors like "groovy.lang.MissingMethodException: No signature of method: Three.method() is applicable for argument types: "
b = "Tea"
class Three
{
String myVar1, myVar2, myVar3, myVar4, myVar5, myVar6
def method(myVar1,myVar2,myVar3,myVar4,myVar5,myVar6)
{
try {
println myVar1, myVar2, myVar3, myVar4, myVar5, myVar6
} catch (groovy.lang.MissingPropertyException e) {
println "error caught"
}
}
}
try {
new Three().method(myVar1:b);
} catch (groovy.lang.MissingPropertyException e) {
println "error caught"
}
try {
new Three().method(myVar1=b);
} catch (groovy.lang.MissingPropertyException e) {
println "error caught"
}
try {
new Three().method(b);
} catch (groovy.lang.MissingPropertyException e) {
println "error caught"
}
I think that you're mixing different concepts... by default groovy classes has two default constructor, the default with no params and the map based constructor which works as follows:
def three = new Three(myVar1:'a',myVar2:'b',myVar3:'c')
println three.myVar1 // prints a
println three.myVar2 // prints b
println three.myVar3 // prints c
However in the case of the methods there isn't this default behavior and due you can not use this kind of invocation and you've to fit the signature of the method, in your case the method expects 6 arguments and you're trying to invoke it passing a map this is why you're getting a missingMethodException, because there is no method with this signature inside your class.
In your case you only have one method method() with 6 parameters with not implicit type so you've to invoke it like this:
three.method(1,'kdk','asasd',2323,'rrr',['a','b'])
// or like this
three.method(1,'kdk','asasd',2323,'rrr','b')
// or like this
three.method(1,2,3,4,5,6)
// ...
Note that in your code there is another error, you are invoking println erroneously inside method()... use this:
println "$myVar1 $myVar2 $myVar3 $myVar4 $myVar5 $myVar6"
instead of:
println myVar1, myVar2, myVar3, myVar4, myVar5, myVar6
Hope this helps,

Resources