Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to replace groovy method for existing object?

Tags:

The following code tried to replace an existing method in a Groovy class:

class A {
  void abc()  {
     println "original"
  }
} 

x= new A()
x.abc()
A.metaClass.abc={-> println "new" }
x.abc()
A.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

new A().abc()

And it results in the following output:

original
original
Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@103074e[name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()
new

Does this mean that when modify the metaclass by setting it to closure, it doesn't really replace it but just adds another method it can call, thus resulting in metaclass having two methods? Is it possible to truly replace the method so the second line of output prints "new"?

When trying to figure it out, I found that DelegatingMetaClass might help - is that the most Groovy way to do this?

like image 543
Jean Barmash Avatar asked Mar 15 '10 03:03

Jean Barmash


3 Answers

You can use the per-instance metaClass to change the value in the existing object like so:

x= new A()
x.abc()
x.metaClass.abc={-> println "new" }
x.abc()
x.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

But as you have seen, x will have two methods associated with it (well, in actual fact, a method and the closure you added

If you change the definition of A so that the method becomes a closure definition like so:

class A {
  def abc = { ->
     println "original"  
  }
} 

Then you will only get a single closure in the metaClass and no method after the alteration

like image 92
tim_yates Avatar answered Sep 19 '22 15:09

tim_yates


I fully agree with @tim_yates on this. But, there is a way around, if you want to avoid modifying the original class use MethodClosure instead, as shown below:

class A {
  void abc()  {
     println "original"
  }
} 

x = new A()

//Create a Method Closure or Method pointer
pointer = x.&abc

//Replace Original call with Method pointer
//x.abc()
pointer()

//Meta classed
A.metaClass.abc={-> println "new" }

//Call method pointer instead of original again
//x.abc()
pointer()

A.metaClass.methods.findAll{it.name=="abc"}.each { println "Method $it"}

new A().abc()

You should get what is expected:

original
new
Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@43094309
  [name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()
new

Based on Groovy 2.2.1. The question is too old though.

like image 21
dmahapatro Avatar answered Sep 20 '22 15:09

dmahapatro


I'm sure your still having this issue ;) but... I'm running Groovy version 1.8.7 on Fedora 17. I've found that have you have to do this combination:

A.metaClass.abc = {-> println it}
A obj = new A()
obj.metaClass.abc = {-> println it}
obj.abc

From there on out it will act as you want. When you look for the methods, you'll still get two:

Method org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod@103074e[name: abc params: [] returns: class java.lang.Object owner: class A]
Method public void A.abc()

But at least you don't have to change your public void declaration.
Not sure if this is a bug or what.

like image 45
Dan Avatar answered Sep 22 '22 15:09

Dan