Is it possible to add a property or a method to an object dynamically in Groovy? This is what I have tried so far:
class Greet {
def name
Greet(who) { name = who[0].toUpperCase() + [1..-1] }
def salute() { println "Hello $name!" }
}
g = new Greet('world') // create object
g.salute() // Output "Hello World!"
g.bye = { println "Goodbye, $name" }
g.bye()
But I get the following exception:
Hello World!
Caught: groovy.lang.MissingPropertyException: No such property: bye for class: Greet
Possible solutions: name
at test.run(greet.groovy:11)
If you just want to add the bye() method to the single instance g of the class Greet, you need to do:
g.metaClass.bye = { println "Goodbye, $name" }
g.bye()
Otherwise, to add bye() to all instance of Greet (from now on), call
Greet.metaClass.bye = { println "Goodbye, $name" }
But you'd need to do this before you create an instance of the Greet class
Here is a page on the per-instance metaClass
And here is the page on MetaClasses in general
Also, there's a bug in your constructor. You're missing who from infront of your [1..-1] and if the constructor is passed a String of less than 2 characters in length, it will throw an exception
A better version might be:
Greet( String who ) {
name = who.inject( '' ) { String s, String c ->
s += s ? c.toLowerCase() : c.toUpperCase()
}
}
As metioned in the comments,
Greet( String who ) {
name = who.capitalize()
}
is the proper way
Related
I'm trying to find laconic and efficient way to replace placeholders with values at the Groovy Strings. But I can't find convenient solution for 2 cases:
When String with the placeholder and the value are defined at different classes.
When the String is passed as argument to a method, and should be replaced with local's variable value. Here is the illustration of 2 approaches I have tried:
class A {
static def strPlaceHolder = 'token = ${tokenValue}';
static def strRefPlaceHolder = "token = ${->tokenRef}";
}
class B {
def tokenRef = "token reference as field";
void parseGString(GString str) {
println str; //fails here. No property tokenRef for class: A. Though I've expected that "this" is B
}
void parseString(String str) {
def tokenValue = "token value as local variable";
println str; //I know why it doesn't work as required. But how to make something similar
}
}
new B().parseString(A.strPlaceHolder); //token = ${tokenValue}
new B().parseGString(A.strRefPlaceHolder); //fails,
You could replace your GString fields with closures and pass those closures to your methods. e.g.:
class A {
static def strPlaceHolder = { token -> "token = ${token}" }
}
class B {
def tokenRef = "token reference as field";
void parseGString(def closure) {
println closure(tokenRef)
}
void parseString(def closure) {
def tokenValue = "token value as local variable"
println closure(tokenValue)
}
}
new B().parseString(A.strPlaceHolder);
new B().parseGString(A.strPlaceHolder);
I find one use case today with groovy like this:
manager.build.#result = hudson.model.Result.SUCCESS
It adds a # before the attribute, what is the purpose for that?
I test it in my local place, and I don't find big diff between we have # and without #.
My example is as follows:
class Person {
private String hello;
}
def person = new Person()
person.hello = "hello world"
println person.#hello
Br,
Tim
It's used to access the field directly (without getter), see:
class Person {
private String hello
public String getHello() {
"lol $hello"
}
}
def person = new Person()
person.hello = "hello world"
assert person.#hello == 'hello world'
assert person.hello == 'lol hello world'
I want to use either a value of expected property or a specified default.
How to achieve this in groovy?
Let's look at the example:
def printName(object) {
//if object has initialized property 'name' - print 'name', otherwise print ToString
if (object<some code here>name && object.name) {
print object.name
} else {
print object
}
}
You can use hasProperty. Example:
if (object.hasProperty('name') && object.name) {
println object.name
} else {
println object
}
If you're using a variable for the property name, you can use this:
String propName = 'name'
if (object.hasProperty(propName) && object."$propName") {
...
}
Assuming your object is a Groovy class, you can use hasProperty in the object metaClass like so:
def printName( o ) {
if( o.metaClass.hasProperty( o, 'name' ) && o.name ) {
println "Printing Name : $o.name"
}
else {
println o
}
}
So, then given two classes:
class Named {
String name
int age
String toString() { "toString Named:$name/$age" }
}
class Unnamed {
int age
String toString() { "toString Unnamed:$age" }
}
You can create instance of them, and test:
def a = new Named( name: 'tim', age: 21 )
def b = new Unnamed( age: 32 )
printName( a )
printName( b )
Which should output:
Printing Name : tim
toString Unnamed:32
You can write your own method via meta-programming:
class Foo {
def name = "Mozart"
}
def f = new Foo()
Object.metaClass.getPropertyOrElse = { prop, defaultVal ->
delegate.hasProperty(prop) ? delegate."${prop}" : defaultVal
}
assert "Mozart" == f.getPropertyOrElse("name", "")
assert "Salzburg" == f.getPropertyOrElse("city", "Salzburg")
If I simply want to assert that an object has some property, I just test the following:
assertNotNull(myObject.hasProperty('myProperty').name)
If myObject does not have myProperty the assertion will fail with a null pointer exception:
java.lang.NullPointerException: Cannot get property 'name' on null object
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
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.