Bart Robeyns
Bart Robeyns

Reputation: 581

Groovy's missingMethod is not called on a Closure's Delegate for implicit calls to non-GroovyObjects

A closure with a delegate that is not a groovy-object (e.g. coming from a normal java-library), will never call the 'methodMissing' object added to that delegate using it's metaclass, if the call is made 'implicit' (i.e. not calling it explicitly on 'delegate' within the closure.)

The code below does an explicit and an implicit call to a non-existing method; it does so on a Groovy-class instance, a GString and a non-groovy object. The only one that fails is the implicit call to a non-groovy-object (i.c. ArrayList).

(You can see and run the same code online: https://groovyconsole.appspot.com/edit/5200829376102400)

Not sure if this is a bug or a limitation - references to methodMissing defined through metaClass are pretty scarce. Any insightful comments would be welcome.

class ClosureDelegate {
    def testMissingMethod(def someObject) {
        someObject.metaClass.methodMissing = { String name, args ->
            println name
        }
        def closure = {
            delegate.anything()
            anything() // this one fails on non-groovyclasses
        }
        closure.delegate = someObject
        closure.resolveStrategy = Closure.DELEGATE_ONLY
        closure()
    }
}

class TestObject {}

println "testing with TestObject"
new ClosureDelegate().testMissingMethod(new TestObject())
println "testing with GString"
new ClosureDelegate().testMissingMethod("${new Date()}")
println "testing with ArrayList"
new ClosureDelegate().testMissingMethod(new ArrayList())
testing with TestObject
anything
anything
testing with GString
anything
anything
testing with ArrayList
anything
Caught: groovy.lang.MissingMethodException: No signature of method: ClosureDelegate$_testMissingMethod_closure2.anything() is applicable for argument types: () values: []
Possible solutions: toString(), toString(), any(), any()

Upvotes: 2

Views: 633

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42184

According to the ClosureMetaClass implementation, this behavior is expected. Take a look at the following part that starts at line 275 in that file:

    switch (resolveStrategy) {
        case Closure.TO_SELF:
            break;
        case Closure.DELEGATE_ONLY:
            method = getDelegateMethod(closure, delegate, methodName, argClasses);
            callObject = delegate;
            if (method == null) {
                invokeOnDelegate = delegate != closure && (delegate instanceof GroovyObject);
            }
            break;

Source: src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java#L275-L284

The invokeOnDelegate boolean flag verifies if the delegate object extends GroovyObject (the default parent class for all Groovy classes.) When you execute your code with Groovy classes, this flag is set to true and the anything method from the delegate object gets invoked. In the case of non-Groovy classes, MethodMissingException is thrown.

You can debug this behavior by setting the breakpoint in AbstractCallSite.callCurrent(GroovyObject receiver) line 160. You will see that in all cases the method anything is not found in the closure, but in the first two cases invokeOnDelegate is evaluated to true, and the invokeMethod on delegate object is executed. It does not happen in the third case, because ArrayList is not an instance of GroovyObject.

Upvotes: 1

Related Questions