Groovy MetaProgramming - intercept all method even missing ones - groovy

I'd like to intercept all method (instance and static) of a class even the missing ones.
Let say that :
class SomeClass {
def methodMissing(String name, args) {
if(name == "unknownMethod"){
return "result from unknownMethod"
}
else throw new MissingMethodException(name, delegate, args)
}
}
SomeClass.metaClass.static.invokeMethod = { methodName, args ->
println "Before"
def result = delegate.metaClass.invokeMethod(delegate, methodName, *args)
println "After"
return result
}
new SomeClass().with{ sc ->
sc.unknownMethod() //throw the MissingMethodExcept
}
This works well for method that are implemented by the class but when it's a method handled by methodMissing, I get a MissingMethodException...
How would you do that?
Thanks in advance

I think you need to catch the non static invokeMethod as well
Also, you need to go through getMetaMethod to call the original method or else you run the risk of stackoverflows.
Given the following:
class SomeClass {
String name
static String joinWithCommas( a, b, c ) {
[ a, b, c ].join( ',' )
}
String joinAfterName( a, b, c ) {
"$name : ${SomeClass.joinWithCommas( a, b, c )}"
}
def methodMissing(String name, args) {
if(name == "unknownMethod"){
return "result from unknownMethod"
}
else {
throw new MissingMethodException( name, SomeClass, args )
}
}
}
// Return a closure for invoke handler for a class
// with a given title (for the logging)
def invokeHandler = { clazz, title ->
{ String methodName, args ->
println "Before $methodName ($title)"
def method = clazz.metaClass.getMetaMethod( methodName, args )
def result = method == null ?
clazz.metaClass.invokeMissingMethod( delegate, methodName, args ) :
method.invoke( delegate, args )
println "After $methodName result = $result"
result
}
}
SomeClass.metaClass.invokeMethod = invokeHandler( SomeClass, 'instance' )
SomeClass.metaClass.static.invokeMethod = invokeHandler( SomeClass, 'static' )
new SomeClass( name:'tim' ).with { sc ->
sc.joinAfterName( 'a', 'b', 'c' )
sc.unknownMethod( 'woo', 'yay' )
sc.cheese( 'balls' )
}
I get the output:
Before with (instance)
Before joinAfterName (instance)
Before joinWithCommas (static)
After joinWithCommas result = a,b,c
After joinAfterName result = tim : a,b,c
Before unknownMethod (instance)
After unknownMethod result = result from unknownMethod
Before cheese (instance)
Exception thrown
groovy.lang.MissingMethodException: No signature of method: SomeClass.cheese() is applicable for argument types: (java.lang.String) values: [balls]

Related

Creation of custom comparator for map in groovy

I have class in groovy
class WhsDBFile {
String name
String path
String svnUrl
String lastRevision
String lastMessage
String lastAuthor
}
and map object
def installFiles = [:]
that filled in loop by
WhsDBFile dbFile = new WhsDBFile()
installFiles[svnDiffStatus.getPath()] = dbFile
now i try to sort this with custom Comparator
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return 1
} else if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
but get this error java.lang.String cannot be cast to WhsDBFile
Any idea how to fix this? I need to use custom comparator, cause it will be much more complex in the future.
p.s. full source of sample gradle task (description of WhsDBFile class is above):
project.task('sample') << {
def installFiles = [:]
WhsDBFile dbFile = new WhsDBFile()
installFiles['sample_path'] = dbFile
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (o1.name > o2.name) {
return 1
} else if (o1.name > o2.name) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
}
You can try to sort the entrySet() :
def sortedEntries = installFiles.entrySet().sort { entry1, entry2 ->
entry1.value <=> entry2.value
}
you will have a collection of Map.Entry with this invocation. In order to have a map, you can then collectEntries() the result :
def sortedMap = installFiles.entrySet().sort { entry1, entry2 ->
...
}.collectEntries()
sort can also take a closure as parameter which coerces to a Comparator's compare() method as below. Usage of toUpper() method just mimics the implementation of FilenameUtils.getBaseName().
installFiles.sort { a, b ->
toUpper(a.value.name) <=> toUpper(b.value.name)
}
// Replicating implementation of FilenameUtils.getBaseName()
// This can be customized according to requirement
String toUpper(String a) {
a.toUpperCase()
}

Is there a way to list arguments to a method as a collection?

If I have a method such as this:
void someMethod(int one, int two, int three)
{
log.debug("One = ${one}, two = ${two}, three = ${three}")
}
Is there a way to avoid listing each param in the debit message? I want to print out values for all the parameters without necessarily listing each one separately.
You can just use varargs:
void someMethod( int... args ) {
println "Args = $args"
}
Worried about more than 3 arguments getting used while calling method? Then make sure you only deal with args[0] till args[2] inside the method.
You may also use interceptor construct:
class Sample {
void someMethod(int one, int two, int three) {
// println("One = ${one}, two = ${two}, three = ${three}")
}
}
class SampleInterceptor implements Interceptor {
boolean doInvoke() {
true
}
Object beforeInvoke(Object obj, String name, Object[] args) {
if(name == "someMethod")
println args
}
Object afterInvoke(Object obj, String name, Object[] args, Object result) {
result
}
}
def proxy = ProxyMetaClass.getInstance(Sample)
def interceptor = new SampleInterceptor()
proxy.interceptor = interceptor
proxy.use {
def h = new Sample()
h.someMethod(1,2,3)
}
Have a look at the sample.

How to verify if an object has certain property?

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

Unexpected behavior with overloaded methods

I'm a bit confused about groovys method overloading behavior: Given the class
and tests below, I am pretty okay with testAStringNull and testBStringNull
throwing ambiguous method call exceptions, but why is that not the case for
testANull and testBNull then?
And, much more importantly: why does testBNull(null)
call String foo(A arg)? I guess the object doesn't know about the type of the variable it's bound to, but why is that call not ambiguous to groovy while the others are?
(I hope I explained well enough, my head hurts from generating this minimal
example.)
class Foo {
static class A {}
static class B {}
String foo(A arg) { return 'a' }
String foo(String s, A a) { return 'a' }
String foo(B arg) { return 'b' }
String foo(String s, B b) { return 'b' }
}
Tests:
import org.junit.Test
import Foo.A
import Foo.B
class FooTest {
Foo foo = new Foo()
#Test
void testA() {
A a = new A()
assert foo.foo(a) == 'a'
}
#Test
void testAString() {
A a = new A()
assert foo.foo('foo', a) == 'a'
}
#Test()
void testANull() {
A a = null
assert foo.foo(a) == 'a'
}
#Test
void testAStringNull() {
A a = null
assert foo.foo('foo', a) == 'a'
}
#Test
void testB() {
B b = new B()
assert foo.foo(b) == 'b'
}
#Test
void testBString() {
B b = new B()
assert foo.foo('foo', b) == 'b'
}
#Test
void testBNull() {
B b = null
assert foo.foo(b) == 'b'
}
#Test
void testBStringNull() {
B b = null
assert foo.foo('foo', b) == 'b'
}
}
It's a (somewhat little-known) oddity of Groovy's multi-dispatch mechanism, which as attempting to invoke the "most appropriate" method, in combination with the fact that the provided static type (in your case A or B) is not used as part of the dispatch mechanism. When you declare A a = null, what you get is not a null reference of type A, but a reference to NullObject.
Ultimately, to safely handle possibly null parameters to overloaded methods, the caller must cast the argument, as in
A a = null
assert foo.foo('foo', a as A) == 'a'
This discussion on "Groovy Isn't A Superset of Java" may shed some light on the issue.

invokeMethod from Groovy with no parameters

I have some Java code that seemed to work fine:
/**
* Helper method
* 1. Specify args as Object[] for convenience
* 2. No error if method not implemented
* (GOAL: Groovy scripts as simple as possible)
*
* #param name
* #param args
* #return
*/
Object invokeGroovyScriptMethod(String name, Object[] args) {
Object result = null;
try {
result = groovyObject.invokeMethod(name, args);
} catch (exception) { // THIS HAS BEEN GROVIED...
if (exception instanceof MissingMethodException) {
if (log.isDebugEnabled()) {
log.debug("invokeGroovyScriptMethod: ", exception);
}
} else {
rethrow exception;
}
}
return result;
}
Object invokeGroovyScriptMethod(String name) {
return invokeGroovyScriptMethod(name, [ null ] as Object[]);
}
Object invokeGroovyScriptMethod(String name, Object arg0) {
return invokeGroovyScriptMethod(name, [ arg0 ] as Object[]);
}
Object invokeGroovyScriptMethod(String name, Object arg0, Object arg1) {
return invokeGroovyScriptMethod(name, [ arg0, arg1 ] as Object[]);
}
but I am having problems with the method:
Object invokeGroovyScriptMethod(String name) {
return invokeGroovyScriptMethod(name, [ null ] as Object[]);
}
groovy.lang.MissingMethodException: No signature of method: MyClass.getDescription() is applicable for argument types: (null) values: [null]
Possible solutions: getDescription(), setDescription(java.lang.Object)
Any hints?
Thank you
Misha
I had a quick go (getting rid of the log bit and replacing it with a println as I didn't have the logs set up in my tests), and I came up with this that doesn't require the overloaded versions of invokeGroovyScriptMethod:
Object invokeGroovyScriptMethod( String name, Object... args = null ) {
try {
args ? groovyObject."$name"( args.flatten() ) : groovyObject."$name"()
} catch( exception ) {
if( exception instanceof MissingMethodException ) {
println "invokeGroovyScriptMethod: $exception.message"
} else {
throw exception;
}
}
}
groovyObject = 'hi'
assert 'HI' == invokeGroovyScriptMethod( 'toUpperCase' )
assert 'i' == invokeGroovyScriptMethod( 'getAt', 1 )
assert '***hi' == invokeGroovyScriptMethod( 'padLeft', 5, '*' )
// Assert will pass (as we catch the exception, print the error and return null)
assert null == invokeGroovyScriptMethod( 'shouldFail' )
edit
Just read the question again, and you say this is a Java class? But then the catch seems to point to this being Groovy code...
I fear I may have sent you down the wrong path if this is Java...

Resources