Groovy MetaClass: Overriding a method whilst using the old implementation

The need to add some functionality an existing method, but avoiding cutting and pasting the old implementation has come up a few times over the last week.

Sometimes, creating a subclass isn’t feasible, often because you don’t control all of the places where that class gets used.

Calling super() without the subclass

All you need to do is to get a reference to the old method. Getting methods from the metaClass is easy, if there’s only one method signature for that method, just use the “&” operator:

def oldToOctalString = Long.metaClass.&toOctalString

If there are multiple method signatures for a method, you’ll need to use the getMetaMethod method and pass in the signature of the method you want. This is probably the safest way to ensure that you’re getting the method that you think you’re getting.

println Long.metaClass.methods.findAll { it.name == "compareTo" }.join("\n")
// prints:
// public int java.lang.Long.compareTo(java.lang.Long)
// public int java.lang.Long.compareTo(java.lang.Object)

def oldCompareTo = Long.metaClass.getMetaMethod("compareTo", [java.lang.Long] as Class[]) 

Now that you know how to get a reference to the method you want, you can redefine the method, while still letting the old method’s implementation do all the heavy lifting for you.

Here, we redefine the “plus” method on Integer to always work with absolute values, without actually getting our hands dirty in all that messy arithmetic:

def oldPlus = Integer.metaClass.getMetaMethod("plus", [Integer] as Class[])

Integer.metaClass.plus = { Integer n ->
    return oldPlus.invoke( Math.abs(delegate), Math.abs(n) )
}

assert 4 == 2 + 2
assert 4 == -2 + -2	

If you don’t save the old method (or completely re-implement the logic for plus), you’ll end up in an infinite loop and throw a java.lang.StackOverflowError:

Integer.metaClass.plus = { Integer n ->
    return Math.abs(delegate) + Math.abs(n)
}

// DOESN'T WORK THROWS java.lang.StackOverflowError
assert 4 == -2 + -2	// FAILS!!!

This same kind of thing can be done with AOP, but that often feels more heavy than a little metaClass manipulation.

7 Responses to “Groovy MetaClass: Overriding a method whilst using the old implementation”

  1. Josh Reed

    Thanks, Ted. I was just trying to figure out how to do this the other day, so this is quite timely.

    Cheers,
    Josh

  2. Arvind

    But the same is not happening for methods which doesn’t take parameters.
    For example :

    String.metaClass.concat = { String s ->
    println ‘Coming herere >>>>>>>>>>>>> ‘
    return 1
    }
    String.metaClass.length = {
    println ‘Coming herere >>>>>>>>>>>>> ‘
    return 1
    }

    println “Foo”.concat(“bar”)
    println “Foo”.length()

    Expected result :

    Coming herere >>>>>>>>>>>>>
    1
    Coming herere >>>>>>>>>>>>>
    1

    But result produced is :

    Coming herere >>>>>>>>>>>>>
    1
    3

    Do you have any idea how to override methods that doesn’t take parameters ?

  3. tednaleid

    @arvind normally you need to be explicit about the closure being zero length when overriding it, so you’d need to do this:

    String.metaClass.length = {-> return 1}
    

    I say “normally” as there’s actually a weird bug/issue in groovy that affects this particular test case to not make it work (that I wasn’t previously aware of). Apparently, in the 1.7 versions of groovy, there’s a bug that causes any method that is part of an interface to not be overridden by the metaClass. Most groovy stuff doesn’t use interfaces so it’s normally not that big of a deal, but it is a big deal with Java stuff.

    So I’m able to override this zero param length method with no problem because it isn’t part of an interface:

    class Bar {
        int length() {
            return 3
        }
    }
    
    def b = new Bar()
    
    assert 3 == b.length()
    
    b.metaClass.length = {-> return 1}
    
    assert 1 == b.length()
    

    But String.length() gets put onto the String class because length() is an implementation of a method from the CharSequence interface.

  4. Trevor Butler

    Ted,

    Thanks for this article. I think the information is very helpful. I have a system classpath for my application that includes Cobertura, among other things. I am working out a way to use another library that includes a different version of Cobertura than the one my application requires. Obviously, I cannot just add the new library jar to my classpath as the two Cobertura versions will conflict. The way I am going about it is to load the class I need from the new library dynamically using the GroovyClassLoader, and then use your technique to override methods and add some new behavior.

    I have a question though… In your example, you override a method on Integer:

    def oldPlus = Integer.metaClass.getMetaMethod(“plus”, [Integer] as Class[])

    Integer.metaClass.plus = { Integer n ->
    return oldPlus.invoke( Math.abs(delegate), Math.abs(n) )
    }

    In my case, I have a simple execute() method without any arguments and a method that takes a String argument to override (note the following is a simplified example based on the real code in question):

    public void org.sas.JavaClass.execute() throws org.apache.tools.ant.BuildException
    public void org.sas.JavaClass.setStr(java.lang.String)

    So, I did this:

    // Grab current implementations of methods we want to override
    def superSetStr = javaClass.metaClass.getMetaMethod(“setStr”, [java.lang.String] as Class[])
    def superExecute = javaClass.metaClass.getMetaMethod(“execute”, [] as Class[])

    // Override setStr()
    javaClass.metaClass.setStr = { java.lang.String str ->
    // Do something before calling super
    println(“Override: Do before setStr…”)

    superSetStr.invoke(delegate, str)

    // Do something after calling super
    println(“Override: Do after setStr…”)
    }

    // Override execute()
    javaClass.metaClass.execute = { -> // there are no args
    // Do something before calling super
    println(“Override: Do before execute…”)

    superExecute.invoke(delegate)

    // Do something after calling super
    println(“Override: Do after execute…”)
    }

    The above code appears to work just fine. Now that I have shared the code, here is my question. It took me a while to figure out how to call the invoke() method on the MetaMethod class. At first I was just calling it with the same arguments as the method it was overriding, but when I did that, I got an IllegalArgument exception. Looking at the JavaDoc showed that invoke() expects an Object as the first parameter, followed by the argument list:

    abstract Object invoke(Object object, Object[] arguments)

    The JavaDoc does not really give any example or explain what should be provided for the first parameter. However, as you can see, I guessed from your example that “delegate” should be used here. I did so, and it seems to work. I just wanted clarification on what the “delegate” keyword means and if I am using it properly in my case.

    Thanks…

Leave a Reply