Groovy - bind properties from one object to another - groovy

Is there a way to bind properties from one instance of a class to the properties of an instance of another class (the common fields between the two). See the example below:
class One {
String foo
String bar
}
class Two {
String foo
String bar
String baz
}
def one = new One(foo:'one-foo', bar:'one-bar')
def two = new Two()
two.properties = one.properties
assert "one-foo" == two.foo
assert "one-bar" == two.bar
assert !two.baz
The result is an error: Cannot set readonly property: properties for class: Two

I would choose InvokerHelper.setProperties as I suggesed here.
use(InvokerHelper) {
two.setProperties(one.properties)
}

The problem is that for every object, .properties includes two built-in Groovy-defined properties, these are the metaClass and class. What you want to do is only set the user-defined properties. You can easily do this using code such as that shown below:
class One {
String foo
String bar
}
class Two {
String foo
String bar
String baz
}
def one = new One(foo:'one-foo', bar:'one-bar')
// You'll probably want to define a helper method that does the following 3 lines for any Groovy object
def propsMap = one.properties
propsMap.remove('metaClass')
propsMap.remove('class')
def two = new Two(propsMap)
assert "one-foo" == two.foo
assert "one-bar" == two.bar
assert !two.baz

Related

How to make snakeyaml and GStrings work together

When I'm trying to use snakeyaml to dump out Yaml out of Groovy interpolated strings, it ends up printing a class name instead.
For example:
#Grab(group='org.yaml', module='snakeyaml', version='1.16')
import org.yaml.snakeyaml.Yaml
Yaml yaml = new Yaml();
def a = "a"
def list = ["$a"]
def s = yaml.dump(list)
Prints:
- !!org.codehaus.groovy.runtime.GStringImpl
metaClass: !!groovy.lang.MetaClassImpl {}
I'm guessing it has something to do with the fact that GStrings get transformed to Strings when they used and I suspect snakeyaml uses some sort of introspection to determine the class of the object.
Is there a better solution than calling toString() on all GStrings?
Try to create a new Representer :
public class GroovyRepresenter extends Representer {
public GroovyRepresenter() {
this.representers.put(GString.class, new StringRepresenter());
}
}
Yaml yaml = new Yaml(new GroovyRepresenter())
...
You could add type info to your variables
Yaml yaml = new Yaml();
def a = "a"
String aStr = "$a"
def list = [aStr]
def s = yaml.dump(list)

Is there a way to call a class method inside an eval?

I'm writing Groovy scripts that are pasted into a web-based system to be run. There is a
class available to scripts run in this environment which I'll call BrokenClass. It has a
bug where it will only accept a string literal as its first parameter, but not a variable
with a string in it. So, this will work (it returns a list):
BrokenClass.reflist('something', 'name')
However, if I try to use a variable as the first parameter I get an error:
list_name = 'something'
BrokenClass.reflist(list_name, 'name')
This produces the message Metadata RefList[something] cannot be accessed.
I don't have any control over BrokenClass (aside from filing a bug on it). I tried to work
around the problem with something like this:
list_name = "foo"
list_call = "BrokenClass.reflist(${list_name}, 'name')"
list_values = Eval.me(list_call)
However, that produces an error:
groovy.lang.MissingPropertyException: No such property: BrokenClass for class: Script1
I tried adding an import to my string, but then I get unable to resolve class BrokenClass.
Is there a way to use BrokenClass inside the eval'd string? Or some other way I haven't
considered to work around the bug in BrokenClass.reflist? A really long switch block
is out, because the possible list names change.
The method signature for BrokenClass.reflist is:
public static List<Object> reflist(String reflistName, String field);
I have a suspicion that BrokenClass.reflist() is directly or indirectly doing an improper String comparison by using the == operator rather than String.equals(). See this article for an explanation of the difference.
The problem
Here's a demonstration of the problem:
def a = 'whatever'
def b = 'what' + 'ever'
assert doSomething('whatever') == 'OK'
assert doSomething(a) == 'OK'
assert doSomething(b) == 'ERROR'
def doSomething(String value) {
if(value.is('whatever')) { // In Java this would be: value == "whatever"
'OK'
} else {
'ERROR'
}
}
Because it's using reference equality, which in Groovy is done by the Object.is(Object) method, BrokenClass.reflist() was inadvertently coded to work only with String literals: all String literals with the same value refer to the same String instance, resulting in an evaluation of True. A String composed at run time with the same value of a literal does not refer to the same String instance.
Work around
Obviously BrokenClass.reflist() should be fixed. But you can work around the problem by using an interned String.
def b = 'what' + 'ever'
assert doSomething(b.intern()) == 'OK'
def doSomething(String value) {
if(value.is('whatever')) {
'OK'
} else {
'ERROR'
}
}
If the variable's value matches that of a String literal, then variable.intern() will return the same String instance as the matching literal. This would allow the Java == operator to work as you need it to.

How to convert a #ToString generated string back to object with Groovy

For example, instance of the following following class produces string A(x:7, values:[hello, world])
#ToString( includeNames=true )
class A {
def x
def values = []
}
How can I transform this String back to an instance of the class?
Based on the comments by #cfrick, this is not possible.
(Answering my own question for the sake of closing this topic.)

Test Groovy class that uses System.console()

I have a groovy script that asks some questions from the user via a java.io.console object using the readline method. In addition, I use it to ask for a password (for setting up HTTPS). How might I use Spock to Unit Test this code? Currently it complains that the object is a Java final object and can not be tested. Obviously, I'm not the first one trying this, so thought I would ask.
A sketch of the code would look something like:
class MyClass {
def cons
MyClass() {
cons = System.console()
}
def getInput = { prompt, defValue ->
def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
def inputTest = input?.toLowerCase()
input
}
}
I would like Unit Tests to test that some mock response can be returned and that the default value can be returned. Note: this is simplified so I can figure out how to do the Unit Tests, there is more code in the getInput method that needs to be tested too, but once I clear this hurdle that should be no problem.
EDITED PER ANSWER BY akhikhl
Following the suggestion, I made a simple interface:
interface TestConsole {
String readLine(String fmt, Object ... args)
String readLine()
char[] readPassword(String fmt, Object ... args)
char[] readPassword()
}
Then I tried a test like this:
def "Verify get input method mocking works"() {
def consoleMock = GroovyMock(TestConsole)
1 * consoleMock.readLine(_) >> 'validResponse'
inputMethods = new MyClass()
inputMethods.cons = consoleMock
when:
def testResult = inputMethods.getInput('testPrompt', 'testDefaultValue')
then:
testResult == 'validResponse'
}
I opted to not alter the constructor as I don't like having to alter my actual code just to test it. Fortunately, Groovy let me define the console with just a 'def' so what I did worked fine.
The problem is that the above does not work!!! I can't resist - this is NOT LOGICAL! Spock gets 'Lost' in GroovyMockMetaClass somewhere. If I change one line in the code and one line in the test it works.
Code change:
From:
def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
To: (add the null param)
def input = (cons.readLine(prompt, null).trim()?:defValue)?.toString()
Test change:
From:
1 * consoleMock.readLine(_) >> 'validResponse'
To: (again, add a null param)
1 * consoleMock.readLine(_, null) >> 'validResponse'
Then the test finally works. Is this a bug in Spock or am I just out in left field? I don't mind needing to do whatever might be required in the test harness, but having to modify the code to make this work is really, really bad.
You are right: since Console class is final, it could not be extended. So, the solution should go in another direction:
Create new class MockConsole, not inherited from Console, but having the same methods.
Change the constructor of MyClass this way:
MyClass(cons = null) {
this.cons = cons ?: System.console()
}
Instantiate MockConsole in spock test and pass it to MyClass constructor.
update-201312272156
I played with spock a little bit. The problem with mocking "readLine(String fmt, Object ... args)" seems to be specific to varargs (or to last arg being a list, which is the same to groovy). I managed to reduce a problem to the following scenario:
Define an interface:
interface IConsole {
String readLine(String fmt, Object ... args)
}
Define test:
class TestInputMethods extends Specification {
def 'test console input'() {
setup:
def consoleMock = GroovyMock(IConsole)
1 * consoleMock.readLine(_) >> 'validResponse'
when:
// here we get exception "wrong number of arguments":
def testResult = consoleMock.readLine('testPrompt')
then:
testResult == 'validResponse'
}
}
this variant of test fails with exception "wrong number of arguments". Particularly, spock thinks that readLine accepts 2 arguments and ignores the fact that second argument is vararg. Proof: if we remove "Object ... args" from IConsole.readLine, the test completes successfully.
Here is Workaround for this (hopefully temporary) problem: change the call to readLine to:
def testResult = consoleMock.readLine('testPrompt', [] as Object[])
then test completes successfully.
I also tried the same code against spock 1.0-groovy-2.0-SNAPSHOT - the problem is the same.
update-201312280030
The problem with varargs is solved! Many thanks to #charlesg, who answered my related question at: Spock: mock a method with varargs
The solution is the following: replace GroovyMock with Mock, then varargs are properly interpreted.

Passing variable to be evaluated in groovy gstring

I am wondering if I can pass variable to be evaluated as String inside gstring evaluation.
simplest example will be some thing like
def var ='person.lName'
def value = "${var}"
println(value)
I am looking to get output the value of lastName in the person instance. As a last resort I can use reflection, but wondering there should be some thing simpler in groovy, that I am not aware of.
Can you try:
def var = Eval.me( 'new Date()' )
In place of the first line in your example.
The Eval class is documented here
edit
I am guessing (from your updated question) that you have a person variable, and then people are passing in a String like person.lName , and you want to return the lName property of that class?
Can you try something like this using GroovyShell?
// Assuming we have a Person class
class Person {
String fName
String lName
}
// And a variable 'person' stored in the binding of the script
person = new Person( fName:'tim', lName:'yates' )
// And given a command string to execute
def commandString = 'person.lName'
GroovyShell shell = new GroovyShell( binding )
def result = shell.evaluate( commandString )
Or this, using direct string parsing and property access
// Assuming we have a Person class
class Person {
String fName
String lName
}
// And a variable 'person' stored in the binding of the script
person = new Person( fName:'tim', lName:'yates' )
// And given a command string to execute
def commandString = 'person.lName'
// Split the command string into a list based on '.', and inject starting with null
def result = commandString.split( /\./ ).inject( null ) { curr, prop ->
// if curr is null, then return the property from the binding
// Otherwise try to get the given property from the curr object
curr?."$prop" ?: binding[ prop ]
}

Resources