John Mercier
John Mercier

Reputation: 1705

Runtime delegation in groovy

I'm trying to figure out something for my own project so I made this example to better explain it. Class Thing has a delegate Type called overrides. Methods in overrides should be called when:

Methods in this should be called when:

I have considered using @Delegate but I need to change overrides at runtime. Here is the code.

class Thing implements GroovyInterceptable {
    Type overrides = new BigThing()

    Object invokeMethod(String name, Object args) {
        MetaMethod thingMethod = metaClass.getMetaMethod('findDeclaredMethod', [this, name, args] as Object[])
        .invoke(this, name, args)
        MetaMethod overrideMethod = findDeclaredMethod(overrides, name, args)
        if(overrideMethod) {
            overrideMethod.invoke(overrides, args)
            //maybe invoke missingMethod on overrides catch MissingMethodException and call thingMethod
        } else if(thingMethod) {
            thingMethod.invoke(this, args)
        } else {
            this.metaClass.invokeMissingMethod(this, name, args)
        }
    }

    MetaMethod findDeclaredMethod(Object obj, String name, Object args) {
        MetaMethod m = this.metaClass.getMetaMethod(name, args)
        if(m && m.declaringClass.theClass != Thing.getClass()) {
            m = null
        }
        return m
    }

    String method1() {
        return "from Thing method1() ${makeString()}"
    }
    String method2() {
        return "from Thing method2() ${makeString()}"
    }
}

interface Type {
    String makeString()
}

class BigThing implements Type {
    String makeString() {
        return "makeString()"
    }

    String method2() {
        return "from BigThing method2() ${makeString()}"
    }
}

assert 'from Thing method1() makeString()' == new Thing().method1()
assert 'from BigThing method2() makeString()' == new Thing().method2()
new Thing().with {
    assert 'from Thing method1() makeString()' == method1()
    assert 'from BigThing method2() makeString()' == method2()
}

Note: this currently doesn't work but I think it explains the idea well enough.

Is there an already established pattern for something like this in groovy?

Update:

With this I am part way there. It fails on calling method2() in a with closure.

class Thing implements GroovyInterceptable {
    Type overrides = new BigThing()

    Object invokeMethod(String name, Object args) {
        MetaMethod method = overrides.metaClass.getMetaMethod(name, args)
        if(method != null) {
            System.out.println("$method.name class is $method.declaringClass.theClass")
            System.out.println("$method.declaringClass.theClass interfaces contains Type or is type ${method.declaringClass.theClass == Type || method.declaringClass.theClass.interfaces.contains(Type)}")
        }
        if (method != null && (method.declaringClass.theClass == Type || method.declaringClass.theClass.interfaces.contains(Type))) {
            return method.invoke(overrides, args)
        }

        method = this.metaClass.getMetaMethod(name, args)
        if (method != null) {
            return method.invoke(this, args)
        }

        return this.metaClass.invokeMissingMethod(this, name, args)
    }

    String method1() {
        return "from Thing method1() ${makeString()}"
    }
    String method2() {
        return "from Thing method2() ${makeString()}"
    }
}

interface Type {
    String makeString()
}

class BigThing implements Type {
    String makeString() {
        return "makeString()"
    }

    String method2() {
        return "from BigThing method2() ${makeString()}"
    }
}

assert 'from Thing method1() makeString()' == new Thing().method1()
assert 'from BigThing method2() makeString()' == new Thing().method2()
new Thing().with {
    assert 'from Thing method1() makeString()' == method1()
    assert 'from BigThing method2() makeString()' == method2()
}

Upvotes: 1

Views: 114

Answers (1)

Szymon Stepniak
Szymon Stepniak

Reputation: 42272

Try simplifying GroovyObject.invokeMethod(String name, Object args) method implementation. You could achieve this by implementing that method in following way:

  • firstly check if overrides has method with that name and invoke it if it does exist
  • check if current object has method with that name and invoke it
  • invoke invokeMissingMethod otherwise

I have checked following implementation and it worked fine:

class Thing implements GroovyInterceptable {
    Type overrides = new BigThing()

    Object invokeMethod(String name, Object args) {
        MetaMethod method = overrides.metaClass.getMetaMethod(name, args)
        if (method != null) {
            return method.invoke(overrides, args)
        }

        method = this.metaClass.getMetaMethod(name, args)
        if (method != null) {
            return method.invoke(this, args)
        }

        return this.metaClass.invokeMissingMethod(this, name, args)
    }

    String method1() {
        return "from Thing method1() ${makeString()}"
    }
    String method2() {
        return "from Thing method2() ${makeString()}"
    }
}

interface Type {
    String makeString()
}

class BigThing implements Type {
    String makeString() {
        return "makeString()"
    }

    String method2() {
        return "from BigThing method2() ${makeString()}"
    }
}

assert 'from Thing method1() makeString()' == new Thing().method1()
assert 'from BigThing method2() makeString()' == new Thing().method2()

I hope it helps.

Upvotes: 1

Related Questions