caffeine_inquisitor
caffeine_inquisitor

Reputation: 727

Groovy missingMethod

I'm trying to add a new method to my Groovy class dynamically, following the documentation. So here is my class which implements the methodMissing method:

class AlexTest {

    def methodMissing(String name, args){
        println "Method missing is called"

        def cachedMethod = { Object[] varArgs ->
            println "Hi! ${varArgs}"
        }

        AlexTest.metaClass."${name}" = cachedMethod
        return cachedMethod(args)
    }
}

and here is another groovy script which uses my AlexTest class:

def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")

I expect the "Method missing is called" to be called only once - as the method "hi" would have been 'introduced' inside the methodMissing. But, that methodMissing is being called twice, as if the "hi" method never gets introduced to the AlexTest class.

I have also tried to do it differently:

class AlexTest {

    AlexTest() {
        def mc = new ExpandoMetaClass(AlexTest, false, true)
        mc.initialize()
        this.metaClass = mc
    }

    def methodMissing(String name, args){
        println "Method missing is called"

        def cachedMethod = { Object[] varArgs ->
            println "Hi! ${varArgs}"
        }

        // note that this is calling metaClass inside the instance
        this.metaClass."${name}" = cachedMethod
        return cachedMethod(args)
    }
}

which sort of works, but only for that single instance. So if I create two instances of AlexTest, and call the 'hi' method, I will get two "Method missing is called" message. Can anyone point me to the documentation which explains this behaviour?

Thanks in advance!

Regards,

Alex

Upvotes: 1

Views: 5360

Answers (3)

Maicon Mauricio
Maicon Mauricio

Reputation: 3021

Another solution is caching the method closures in a Map.

class AlexTest {
    static Map methods = [:]

    def methodMissing(String name, args){
        if (!methods[name]) {
            println "Method is not cached"
            methods[name] = { Object[] varArgs ->
                println "Hi! ${varArgs}"
            }
        }
        return methods[name](args)
    }
}

def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")

Output

Method is not cached
Hi! [Alex]
Hi! [John]

Upvotes: 0

dmahapatro
dmahapatro

Reputation: 50265

How about this in the script? This makes sure the methodMissing is implemented for the Class reference and is applied to all of the instances referring to it.

class AlexTest {

}

mc = AlexTest.metaClass
mc.methodMissing = {String name, args=[:] ->
        println "Method missing is called"
        def cachedMethod = { Object[] varArgs ->
            println "Hi! ${varArgs}"
        }
        mc."${name}" = cachedMethod
        cachedMethod(args)
}

def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")

def myTest = new AlexTest()
myTest.hi("Walter")
myTest.hi("Hank")

//Prints
Method missing is called
Hi! [Alex]
Hi! [John]
Hi! [Walter]
Hi! [Hank]

Although enabling ExpandoMetaClass globally works as well with a cost of little extra memory. :)

Upvotes: 1

Michael Easter
Michael Easter

Reputation: 24498

Add the following to the beginning of your first attempt:

ExpandoMetaClass.enableGlobally()

It works for me with V 2.0.1

If you prefer to enable on a class instance basis, this post illustrates one way to do it:

class AlexTest {
    AlexTest() {
        def mc = new ExpandoMetaClass(AlexTest, false, true)
        mc.initialize()
        this.metaClass = mc        
    }

    def methodMissing(String name, args){
        println "Method missing is called"

        def cachedMethod = { Object[] varArgs ->
            println "Hi! ${varArgs}"
        }

        this.metaClass."${name}" = cachedMethod
        return cachedMethod(args)
    }
}

def alexTest = new AlexTest()
alexTest.hi("Alex")
alexTest.hi("John")

Upvotes: 2

Related Questions