Reputation: 1705
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:
overrides
overrides
but not in `thisMethods in this
should be called when:
overrides
Object
, GroovyObject
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
Reputation: 42272
Try simplifying GroovyObject.invokeMethod(String name, Object args)
method implementation. You could achieve this by implementing that method in following way:
overrides
has method with that name and invoke it if it does existinvokeMissingMethod
otherwiseI 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