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'
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.
With groovy .& operator one can create references to static methods, as in
def static xyz( name='Joe' ) {
println "Hello ${name}"
}
// NOTE: ConsoleScript number part varies
def ref = ConsoleScript52.&xyz
And can be easilly called without params, as
ref() // prints "Hello "
But how can this method be called with params? ref('John') gives an error groovy.lang.MissingMethodException: No signature of method: ConsoleScript52.xyz() is applicable for argument types: (java.lang.String) values: [John]
Note that Groovy does not even use the default value of name param in above example when static method is called with ref.
You are probably using a ConsoleScript instance where you did not define that method with parameters.
In the code below, in the 8th execution in my console (ConsoleScript8) y defined the method hello() without parameters (added the "script number" to make it explicit):
println this.getClass().getName()
println ''
def static hello() {
println "(8) Hello"
}
def h8 = ConsoleScript8.&hello
h8()
h8('Joe')
And it yields the following in the next execution:
ConsoleScript9
(8) Hello
Exception thrown
groovy.lang.MissingMethodException: No signature of method: ConsoleScript9.hello() is applicable for argument types: (java.lang.String) values: [Joe]
And in the 10th execution (ConsoleScript10) I modified it adding the default parameter:
println this.getClass().getName()
println ''
def static hello(name='amigo') {
println "(10) Hello ${name}"
}
def h10 = ConsoleScript10.&hello
h10()
h10('Joe')
println '--'
def h8 = ConsoleScript8.&hello
h8()
h8('Joe')
And it yields:
ConsoleScript12
(10) Hello amigo
(10) Hello Joe
--
(8) Hello
Exception thrown
groovy.lang.MissingMethodException: No signature of method: ConsoleScript8.hello() is applicable for argument types: (java.lang.String) values: [Joe]
It is easier if you use an explicit class:
class Greeter {
def static hello(name='Joe') {
"Hello ${name}"
}
}
def hi = Greeter.&hello
assert hi() == 'Hello Joe'
assert hi('Tom') == 'Hello Tom'
What version of Groovy? This works with Groovy 2.4.5:
class Test {
static greet(name = 'tim') {
"Hello ${name.capitalize()}"
}
}
// regular static calls
assert Test.greet() == 'Hello Tim'
assert Test.greet('kaskelotti') == 'Hello Kaskelotti'
// Create a reference to the static method
def ref = Test.&greet
// Calling the reference works as well
assert ref() == 'Hello Tim'
assert ref('kaskelotti') == 'Hello Kaskelotti'
I need to intercept method calls on predefined Java classes. For example, lets say I need to intercept String class split method, how do I do this?
I tried this which works, but I doesn’t want end user to change their code by wrapping their calls in with proxy block.
Is there any way this can be achieved with Groovy?
If what you want to do is intercept a call to a specific method you can do something like this...
// intercept calls to the split method on java.lang.String
String.metaClass.split = { String arg ->
// do whatever you want to do
}
If what you want to do is intercept a call to a specific method and do some stuff in addition to invoking the original (like to wrap the real method with some of your own logic) you can do something like this:
// get a reference to the original method...
def originalSplit = String.metaClass.getMetaMethod('split', [String] as Class[])
// now add your own version of the method to the meta class...
String.metaClass.split = { String arg ->
// do something before invoking the original...
// invoke the original...
def result = originalSplit.invoke(delegate, arg)
// do something after invoking the original...
// return the result of invoking the original
result
}
I hope that helps.
you want to use MetaClass for that see doc
ExpandoMetaClass.enableGlobally()
//call 'enableGlobally' method before adding to supplied class
String.metaClass.split = { regex ->
println "calling split from $delegate with $regex"
delegate.split regex, 22
}
To intercept all method calls in a class override Groovy's invokeMethod. Example:
class Test {}
Test.metaClass.foo = {"foo() called"}
Test.metaClass.static.bar = {"bar() called"}
Test.metaClass.invokeMethod = { name, args ->
handleInterception(name, args, delegate, false)
}
Test.metaClass.static.invokeMethod = { name, args ->
handleInterception(name, args, delegate, true)
}
def handleInterception(name, args, delegate, isStatic) {
def effDelegate = isStatic ? delegate : delegate.class
println ">> Entering ${delegate.class.name}.$name() with args: $args"
def metaMethod = effDelegate.metaClass.getMetaMethod(name, args)
if (!metaMethod) {
println "-- Method not found: $name($args)"
return
}
try {
def result = metaMethod.invoke(delegate, args)
println "<< Leaving ${delegate.class.name}.$name() with result: $result"
return result
} catch (ex) {
println "-- Exception occurred in $name: $ex.message"
throw ex
}
}
new Test().foo("1", 2)
Test.bar(2)
new Test().onTheFly(3)
Code taken from Roshan Dawrani's post at groovyconsole.appspot.com.
Output:
>> Entering Test.foo() with args: [1, 2]
-- Method not found: foo([1, 2])
>> Entering java.lang.Class.bar() with args: [2]
<< Leaving java.lang.Class.bar() with result: bar() called
>> Entering Test.onTheFly() with args: [3]
-- Method not found: onTheFly([3])
Other options:
Custom MetaClass implementing invokeMethod
Implementing the Interceptor Interface. Read more in this tutorial
I've written a method that I can use ClassName.methodName(args).
How can I make it so I can use methodName(args).
I tried monkey patching Object like so:
class Object {
def methodName(args) {
// method definition
}
}
Update:
I tried what dmahapatro said.
import static groovy.json.JsonOutput.*
Object.metaClass.outputJson = {
return println(prettyPrint(toJson(it)))
}
outputJson([:])
Return:
Caught: groovy.lang.MissingMethodException: No signature of method: Object.outputJson() is applicable for argument types: (java.util.LinkedHashMap) values: [[:]]
Possible solutions: outputJson(), outputJson(java.lang.Object)
groovy.lang.MissingMethodException: No signature of method: Object.outputJson() is applicable for argument types: (java.util.LinkedHashMap) values: [[:]]
Possible solutions: outputJson(), outputJson(java.lang.Object)
at Object.run(Object.groovy:7)
[Finished in 2.1s]
The issue created by the edit was because Object.groovy conflicted with Groovy's Object.java. Once I renamed it to ObjectMeta (or any other non conflicting name, it worked).
Using ExpandoMetaClass on Object
Object.metaClass.printsHello = {
return it
}
assert "Hello" == printsHello("Hello")
assert "Hello" == 'ABC'.printsHello("Hello")
assert "Hello" == 123.printsHello("Hello")
assert "Hello" == new Object().printsHello("Hello")
class A{
Integer a
}
assert "Hello" == new A(a: 10).printsHello("Hello")
This can also be achieved by using #Category as below
#Category(Object) class CustomizedObject{
def printsHello(String str){
return str
}
}
String.mixin CustomizedObject
assert 'Hello' == 'ABC'.printsHello('Hello')
Integer.mixin CustomizedObject
assert 'Hello' == 123.printsHello('Hello')
BigInteger.mixin CustomizedObject
assert 'Hello' == 123G.printsHello('Hello')
#Mixin(CustomizedObject) //Compile Time Mixin
class A{
}
assert 'Hello' == new A().printsHello('Hello')
If you want to distribute the #Category in a jar, then include CustomizedObject in that jar and use it wherever needed.
import static groovy.json.JsonOutput.*
Object.metaClass.outputJson = {
return prettyPrint(toJson(it))
}
println outputJson([a: 1, b: 2, c: 3])
//Prints:
{
"a": 1,
"b": 2,
"c": 3
}
Note:-
One thing to catch here is, we are using metaClass on Object directly which can be pivotal sometimes, you should clear the metaClass from object once you are done with it.
There are several possibilities that you can do. The simplest one is to use categories. In your main method or script do something like this:
use(ObjectExtender) {
startProgram();
}
And then create the ObjectExtender class like this:
class ObjectExtender {
static def methodName(Object self, Map args) {
...
}
}
As long as you are inside the control flow of the use call, you will be able to call methodName on any object. There are other possibilities, like creating a new metaClass for object, but I'd probably go with categories.
I'm implementing Groovy step definitions for Cucumber-JVM and I want a step to be able store itself so that the next step can repeat it n times.
Given(~'the (\\S+) is in the blender') { String thing ->
// do stuff...
context.repeatable = self.curry(thing)
}
What should "self" be in the above code?
I can't use "this" as that refers to the enclosing object (whatever that is in this case, maybe the script).
Since curry is a method of the Closure class, directly invoking curry applies to the closure, both if it is named:
def context
test = { String thing ->
context = curry(thing)
thing.toUpperCase()
}
test('hello')
println context.call()
test('world')
println context.call()
=>
HELLO
WORLD
or anonymous:
def context
['test'].each { String thing ->
context = curry(thing)
thing.toUpperCase()
}
println context.call()
=>
TEST
You can try using unreferenced curry method passing the received parameters:
clos = { String text ->
if (text) {
println "text=$text"
a = curry null
a()
} else {
println "done"
}
}
clos "closure text"
Will print:
text=closure text
done
Update
You can also use clone():
closure = {
def clone = clone()
}