Search code examples
groovyexpandometaclass

Result of Replaced/Overriden Parameterless Method Not Passed to the Constructor


I'm trying to change the behaviour of the constructor of a groovy class by replacing a method in that class which is used to set a property but the properties are not getting set with the expected values.

class TestClass {

    def noParam
    def withParam

    TestClass() {
        noParam = noParam()
        withParam = withParam('second test')
    }

    def noParam() {
        return 'first test'
    }

    def withParam(param) {
        return param
    }

}

TestClass.metaClass.withParam =  { param -> 'pass' }
TestClass.metaClass.noParam = {-> 'pass' }

def test = new TestClass()

assert test.withParam('dummy') == 'pass' //passes
assert test.withParam == 'pass' // fails
assert test.noParam() == 'pass' // passes
assert test.noParam == 'pass' // fails

Solution

  • Groovy is not using your metaclass overrides in the TestClass constructor when it executes the noParam and withParam methods. In fact, if you type the parameter in your withParam method, the 2nd assert will also fail.

    class TestClass {
    
        def noParam
        def withParam
    
        TestClass() {
            noParam = noParam()
            withParam = withParam('second test')
            println "in the constructor: noParam = $noParam, withParam = $withParam"
        }
    
        def noParam() {
            return 'first test'
        }
    
        def withParam(String param) {
            return param
        }
    }
    
    TestClass.metaClass.withParam = { String param -> 'pass' }
    TestClass.metaClass.noParam = {-> 'pass' }
    
    def test = new TestClass()
    
    assert test.withParam('dummy') == 'pass'
    assert test.withParam  == 'pass' // this fails now too!
    assert test.noParam() == 'pass'
    assert test.noParam == 'pass' // this fails
    

    Here's the output: in the constructor: noParam = first test, withParam = second test

    test.withParam and test.noParam are actually calling test.getWithParam() and test.getNoParam() - they are returning the property values set in the constructor.

    test.withParam('dummy') and test.noParam() are calling your metaclass method and returning "pass".

    As far as why groovy does not use the metaclass method in your constructor, I'm not sure... I couldn't find anything in the metaclass documentation...

    Maybe you could use static methods instead?

    class TestClass {
    
        def noParam
        def withParam
    
        TestClass() {
            noParam = TestClass.noParam()
            withParam = TestClass.withParam('second test')
            println "in the constructor: noParam = $noParam, withParam = $withParam"
        }
    
        static def noParam() {
            return 'first test'
        }
    
        static def withParam(String param) {
            return param
        }
    }
    
    TestClass.metaClass.'static'.withParam = { String param -> 'pass' }
    TestClass.metaClass.'static'.noParam = {-> 'pass' }
    
    def test = new TestClass()
    
    assert test.withParam('dummy') == 'pass'
    assert test.withParam  == 'pass' // passes!
    assert test.noParam() == 'pass'
    assert test.noParam == 'pass' // passes!
    

    Here's the output: in the constructor: noParam = pass, withParam = pass