I am having trouble understanding how the GroovyClassLoader cache works with expando Methods.
In particular, I expected the following code to give a different output for each call to Number.test(), yet this is not the case.
int i = 1
Number.metaClass.test = {
return "A string"
}
println i.test()
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
Number.metaClass = null
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
Number.metaClass.test = {
return "Another String"
}
println i.test()
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
println GroovySystem.metaClassRegistry.getMetaClass(Number.class)
Number.metaClass.test = {
return "Yet another String"
}
println i.test()
Output:
A string
groovy.lang.ExpandoMetaClass#3571b748[class java.lang.Number]
groovy.lang.MetaClassImpl#7748410a[class java.lang.Number]
A string
groovy.lang.ExpandoMetaClass#37f1104d[class java.lang.Number]
groovy.lang.MetaClassImpl#55740540[class java.lang.Number]
A string
Edit:
I tried some more things. It looks like my system is acting quite strangely. Given the following code:
def i = 1
i.class
Number.metaClass.test = {
return "A string"
}
println i.test()
Number.metaClass = null
Number.metaClass.test = {
return "Another String"
}
println i.test()
GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
Number.metaClass.test = {
return "Yet another String"
}
println i.test()
When I execute it on my system, it works as expected (getting 3 different strings in output) but it fails (3 x A string) if I comment the supposedly useless i.class.
However, if I execute it on groovy-console.appspot.com, it gives me the expected output with or without that line...
I'm kind of at a loss.
When you created the int i = 9, it created an object with the current Number.metaClass. It accepts creating a new method (read Number.metaClass.test = { "A string" }), but won't allow overload already existing ones. You can change the object's metaclass instead:
int i = 1
Number.metaClass.test = { "A string" }
assert i.test() == "A string"
Number.metaClass = null
i.metaClass.test = { "Another String" }
assert i.test() == "Another String"
GroovySystem.metaClassRegistry.removeMetaClass(Number.class)
assert i.test() == "Another String"
i.metaClass.test = { "Yet another String" }
assert i.test() == "Yet another String"
Needing to invoke i.class looks like a bug to me... Taking a look at jira I saw some unresolved and opened bugs. I thought yours looked similar to these ones:
Overriding methods on a Java class metaClass doesn't take effect until instance metaClass is changed: https://issues.apache.org/jira/browse/GROOVY-5065
Using metaClass to override methods in class hierarchy does not work as expected: https://issues.apache.org/jira/browse/GROOVY-3942
Overriding methods via .metaClass doesn't behave consistently: https://issues.apache.org/jira/browse/GROOVY-6847
Removing a metaClass method: https://issues.apache.org/jira/browse/GROOVY-4189
The first part of my answer works if you consider a concrete class:
class Echo {}
Echo.metaClass.test = { "A string" }
def i = new Echo()
assert i.test() == "A string"
Echo.metaClass = null
Echo.metaClass.test = { "Another String" }
assert i.test() == "A string"
i.metaClass.test = { "Another String" }
assert i.test() == "Another String"
i.metaClass.test = { "Yet another String" }
assert i.test() == "Yet another String"
Related
I am trying to make my own Dsl and was playing around with different styles of closures in groovy.
I've stumbled upon follwing snippet:
myClosure {
testProperty: "hello!"
}
But can't figure out if this a valid code and how can I access then this testProperty. Is it valid? How can I read "hello!" value?
For now, lets put aside closures, consider the following code:
def f1() {
testProperty : 5
}
def f2() {
testProperty : "Hello"
}
println f1()
println f1().getClass()
println f2()
println f2().getClass()
This compiles (therefor the syntax is valid) and prints:
5
class java.lang.Integer
Hello
class java.lang.String
So what you see here is just a labeled statement (groovy supports labels see here)
And bottom line the code of f1 (just like f2) is:
def f1() {
return 5 // and return is optional, so we don't have to write it
}
With closures its just the same from this standpoint:
def method(Closure c) {
def result = c.call()
println result
println result.getClass()
}
method {
test : "hello"
}
This prints
hello
class java.lang.String
as expected
Usually in DSL you have either this:
mySomething {
a = 42
b = 84
}
which corresponds to property setting
or this:
mySomething( a:42, b:84 ){
somethingElse{}
}
which is a method call with Map-literal.
The code you shown is not used as #mark-bramnik explained.
I'm looking for a way to avoid having to check for the property first with hasProperty().
Ideally I would like to have something like
def a = myobj.getPropertyOrElse("mypropname", "defaultvalueifpropertymissing')
I see that gradle has a findProperty() but I can't find a similar thing for plain groovy.
The hasProperty method returns a MetaProperty instance that you can use to retrieve the value by passing the original instance:
def a = myobj.hasProperty('mypropname')?.getProperty(myobj) ?:
'defaultvalueifpropertymissing'
And then use the Safe navigation operator(?.) and Elvis operator (?:) to avoid the if/else.
The shortest version I could think of is
def a = if (a.hasProperty("mypropname")) a.getProperty("mypropname") else "defaultvalueifmissing"
which obviously repeats the property name twice. Creating your own method is possible but it's limited to your current class.
class MyClass {
String name = "the name"
}
def a = new MyClass()
def getProperty(Object theInstance, String propName, Object defaultValue) {
if (theInstance.hasProperty(propName)) theInstance.getProperty(propName) else defaultValue
}
assert "the name" == getProperty(a, "name", "")
assert "default value" == getProperty(a, "method", "default value")
One can use getProperties() of MetaClass or getProperty() of GroovyObject:
class Test {
String field1
String field2
}
def test = new Test(field1: "value1", field2: null)
// using MetaClass.getProperties()
println test.properties["field1"] // value1
println test.properties["field2"] // null
println "field2" in test.properties.keySet() // true
println test.properties["field3"] // null
println "field3" in test.properties.keySet() // false
// using GroovyObject.getProperty()
println test.getProperty("field1") // value1
println test.getProperty("field2") // null
println test.getProperty("field3") // groovy.lang.MissingPropertyException
Would someone help me figure out why this added "get" method works with one class(String) but not the other class(Node)?
String.metaClass.getFoo = { "string foo" }
s = "test"
println s.foo // WORKS: get "string foo"
Node.metaClass.getFoo = { "node foo" }
xml = "<test><body>test</body></test>"
nodes = new XmlParser().parseText(xml)
println nodes.foo // NOT WORK: gets []
How do I make calling the "foo" resulting the same as getFoo() for class Node?
nodes.foo will try to find an element in the parsed tree of nodes. Directly using getFoo() would be the only option AFAIK.
In groovy 1.8.6, I was trying to do something like this:
class Greeter {
def sayHello() {
this.metaClass.greeting = { System.out.println "Hello!" }
greeting()
}
}
new Greeter().sayHello()
This didn't work:
groovy.lang.MissingPropertyException: No such property: greeting for class: groovy.lang.MetaClassImpl
After a bit of trying, I found that passing a reference to self to the method did work. So, basically what I came up with was this:
class Greeter {
def sayHello(self) {
assert this == self
// assert this.metaClass == self.metaClass
self.metaClass.greeting = { System.out.println "Hello!" }
greeting()
}
}
def greeter = new Greeter()
greeter.sayHello(greeter)
The strangest thing is that the assert this == self actually passes, which means they are the same instance... right? The default toString also seems to confirm this.
On the other hand, the assert this.metaClass == self.metaClass fails:
assert this.metaClass == self.metaClass
| | | |
| | | org.codehaus.groovy.runtime.HandleMetaClass#50c69133[groovy.lang.MetaClassImpl#50c69133[class Greeter]]
| | Greeter#1c66d4b3
| false
groovy.lang.MetaClassImpl#50c69133[class Greeter]
Why is self.metaClass wrapped in a HandleMetaClass, while this.metaClass isn't? Also, how can the first example be made to work without passing in a reference to self?
I figured out the 2 questions:
groovy.lang.MissingPropertyException: No such property: greeting for class: groovy.lang.MetaClassImpl
why this.metaClass == self.metaClass
See this link: https://stackoverflow.com/a/45407488/42769
You can implement methodMissing in the class as below to answer your last question:
class Greeter {
def sayHello() {
//this.metaClass.greeting = { System.out.println "Hello!" }
greeting()
goodNight()
}
def methodMissing(String name, args){
if(name == 'greeting'){
println "Hello!"
} else
println "Good Night"
}
}
new Greeter().sayHello()
Also note that == in groovy actually means equals() (that is value comparison) if you want to compare identity then is() can be used like
a.is(b) //Corresponds to == in Java
a == b //Corresponds to equals() in Java
UPDATE
Can use metaClass as below
Greeter.metaClass.greeting = { println "Hello"}
def greet = new Greeter()
//or
//greet.metaClass.greeting = { println "Hello"}
greet.sayHello()
In Groovy, I can make an object invokable like a function by monkey-patching the metaclass' call method:
myObject.metaClass.call = { "hello world" }
println myObject() // prints "hello world"
patching call only allows me to invoke the object with no arguments. Is there a way of allowing objects to be invoked with arguments using standard function-like syntax?
edit: one answer is exactly as tim_yates suggests, although it's worth noting from ataylor's comment that you can simply override call without explicit metaprogramming:
class MyType {
def call(...args) {
"args were: $args"
}
}
def myObject = new MyType()
println myObject("foo", "bar") // prints 'args were ["foo", "bar"]'
Apparently the trick is the variadic signature using ...args.
You could do:
myObject.metaClass.call = { ...args -> "hello $args" }
assert myObject( 'one', 'two', 'three' ) == 'hello [one, two, three]'
(as you can see, args is an array of Objects)
Or for one parameter:
myObject.metaClass.call = { who -> "hello $who" }
Or if you want that single parameter as an optional param, you could do:
myObject.metaClass.call = { who = null -> "hello ${who ?: 'world'}" }
assert myObject( 'tim' ) == 'hello tim'
assert myObject() == 'hello world'