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
Related
Is there a way to skip MissingPropertyException while using GroovyShell.evaluate?
def sharedData = new Binding()
def shell = new GroovyShell(sharedData)
shell.evaluate("a=5; b=1") // works fine
// How to not get MissingPropertyException, or silently ignore property 'a'
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
I know about the Expando solution, is there a way to do it without defining a class?
A very minimal approach would be to override Binding.getVariable.
Note that this is very straight forward: "all exceptions" are ignored - you might want to have better logging or more accurate error handling.
import groovy.lang.*
class NoOpBinding extends Binding {
#Override
Object getVariable(String name) {
try {
return super.getVariable(name)
}
catch (Throwable t) {
println "Ignoring variable=`$name`"
return null
}
}
}
def shell = new GroovyShell(new NoOpBinding())
shell.evaluate("a; b=1") // MissingPropertyException for 'a'
// → Ignoring variable=`a`
println shell.getVariable('b')
// → 1
You could do something like this:
def binding = [:].withDefault { }
def shell = new GroovyShell(binding as Binding)
shell.evaluate 'a; b=1'
As the title says, if I define a closure that takes one argument can I pass it with an argument to be executed.
For example:
def print = { String NAME -> println "$NAME"}
And then pass it to another function for execution like this:
otherFunction(print("Jeff"))
If the other function has signature:
otherFunction(Closure clos):
clos.call()
Thanks!
I have figured out where I went wrong, I need my function to return a closure with my variable interpolated.
For example:
def Closure print (String NAME){
{name -> println name}
}
Then make a call to generate a custom closure and pass that:
otherFunction(print("Jeff"))
Answered my own question please close.
The problem with the call otherFunction( print("Jeff") ) is that you pass to it the return value of print("Jeff"), which is null because println does not return anything (void function).
Instead of this, you must pass the closure object which is layzy called with the method call(). You figured out yourself this, but my approach is more straightforward. Other solution would be to use function composition:
def print = { println it }
def otherFunction(Closure clos) {
clos.call()
}
// this is equivalent with otherFunction(null)
//otherFunction( print("Jeff") )
// pass a closure object
otherFunction { print("Jeff") }
// using function composition
def printJeff = print << { "Jeff" }
printJeff()
I want to invoke an externally provided closure in the context of some script, but I'd like it to be able to access an object scoped to the method from which the closure is being invoked:
def method1(Map config) {
...
with config.some_closure //here I'd like to pass the config map as a param.
}
//now invoke:
method1 {
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
}
My current workaround is to assign my object to a script-scoped variable so that the provided closure can access it:
globalConfig = null
def method1(Map config) {
globalConfig = config
...
with config.some_closure //here I'd like to pass the config map as a param.
}
//now invoke:
method1 {
some_value: 1,
some_closure: {
//access global here, eg:
globalConfig.some_value = 2
}
}
Is there a better way?
I think currying is what you are looking for:
def method1(Map config) {
with config.some_closure.curry(config) // this is a new closure taking no arguments, where param1 has been fixed.
}
//now invoke:
method1 ([
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
])
I think that using delegate is a valid alternative to with() that permits parameter passing:
def method1(Map config) {
...
config.some_closure.delegate = this // retain access to this object's methods
config.some_closure(config) //pass config explicitly
}
//now invoke:
method1 {
some_value: 1,
some_closure: { def param1 ->
//access param here, eg:
param1.some_value = 2
}
}
I am doing some integration tests with Spock with 3rd party apps. Now I am struggling with a problem that I am not sure wether I am approaching the issue properly or not.
In one of the tests I am connecting to a 3rd party service to get some information in an array. Then each of these items are passed to another method to process them individually.
def get3rdPartyItems = {
[item1, item2, item3]
}
def processItem = { item ->
//do something with item
}
get3rdPartyItems.each {
processItem(it)
}
Then I have a test that connects to real 3rd party service using the method get3rdPartyItems() in which I am testing that processItem is called as many times as items has returned the method get3rdPartyItems().
What I am trying to do is to save one of the items as #Shared variable to write another test to know that the item is processed properly as I don't want to mock the content retrieved from the 3rd party service as I want real data.
Basically, this is what I am doing:
#Shared def globalItem
MyClass.metaClass.processItem = { i ->
if (!globalItem)
globalItem = i
//And now I would need to call the original method processItem
}
Any clue how to achieve this? I am probably overheading too much so I am open to change the solution.
Not sure if this is what you want, as it's hard to see your existing structure from the code and the code isn't runnable as-is, but given this class:
class MyClass {
def get3rdPartyItems = {
['item1', 'item2', 'item3']
}
def processItem( item ) {
println item
//do something with item
}
def run() {
get3rdPartyItems().each {
processItem( it )
}
}
}
You can do this:
def globalItem
def oldProcessItem = MyClass.metaClass.getMetaMethod("processItem", Object)
MyClass.metaClass.processItem = { item ->
if (!globalItem) {
println "Setting global item to $item"
globalItem = item
}
oldProcessItem.invoke( delegate, item )
}
def mc = new MyClass()
new MyClass().run()
Just as a matter of concision, that should be the way of passing the parameters to the metamethod in case you pass multiple parameters:
def globalItem
def oldProcessItem = MyClass.metaClass.getMetaMethod("processItem", ["",[:]] as Object[])
MyClass.metaClass.processItem = { String p1, Map p2 ->
if (!globalItem) {
println "Setting global item to $item"
globalItem = p2
}
oldProcessItem.invoke( delegate, [p1,p2] as Object[] )
}
def mc = new MyClass()
new MyClass().run()
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'