Putting Proxy-o-Matic to work
It all started a couple of weeks ago when Alex Tkachman submitted the following code to the Groovy dev list
as(MouseListener) {
mouseClicked{
println "Clicked: $it"
}
mousePressed {
println "Pressed: $it"
}
...
}This little piece of code should create an instance of something (an unknown class) that implements the MouseListener interface, in other words, it creates a proxy of MouseListener. But before we get into topic let's revisit what Groovy already offers: the ability to create a proxy from some predefined structure, in this case a Map or a Closure, given the following rules
- you must use the as keyword
- when coercing a Closure, the target interface should define one method only
- when coercing a Map, the target may be an interface, an abstract or concrete class
Those rules allow you to create a proxy easily and though maps let you proxy 3 different targets they come with two drawbacks
- maps must hold a unique key per method name, you can't proxy overloaded method definitions
- you can't call a proxied method within another proxied method
Fortunately Groovy includes a very handy alternative to dynamic beans: Expandos. If you haven't seen expandos before think of them as JSON objects. Yes, I meant exactly that. You can add properties at will to an Expando, if a value of a property happens to be a closure then that property will be treated as a method definition, let's look at the following example
def bean = new Expando()
bean.foo = {-> "foo" }
bean.bar = {-> "bar" }
bean.foobar = {-> foo()+bar() }
assert bean.foo() == "foo"
assert bean.bar() == "bar"
assert bean.foobar() == "foobar"
Alright, expandos can overcome restriction #2 on maps, but as they are also map based restriction #1 still applies, and sadly expandos do not come with a proxy friendly mechanism wired through the as keyword (which actually works by calling asType(Class) on the target).
Enter Proxy-o-Matic.
Proxy-o-Matic's main class (aptly named ProxyOMatic) exposes a very simple API to homogeneoslycreate proxies from the three sources we have outlined: Closures, Maps and Expandos, and provides features that overcome some of the limitations that the aforementioned rules can't break. Proxies create with ProxyOMatic.proxy() share the following features
- call proxied methods from within other proxied methods
- may proxy more than one interface at the same time
Proxies created from closures may
- define method implementations with syntax similar to how classes are defined
- define method implementations for overloaded methods
Proxies created from maps may
- define method implementations for overloaded methodsprovided they use ProxyMethodKey as keys instead of Strings
Expandos don't gain much other that the shared features so far. But enough theory, code should show you a better picture, let's take the following interface definitions for example
interface Foo { String foo() }
interface Bar { String bar() }
interface FooBar extends Foo, Bar {
String foobar()
}
interface Fooz extends Foo {
String foo( String n )
}Creating proxies from closures and Maps for the first three is as simple as
import static org.kordamp.groovy.util.ProxyOMatic.proxy
def fc = proxy( Foo ) {
foo { -> "Foo" }
}
def fbc = proxy( FooBar ) {
foo { -> "Foo" }
bar { -> "Bar" }
foobar { -> foo() + bar() }
}
def fm = proxy( Foo, [
foo: { -> "Foo" }
])
def fbm = proxy( FooBar, [
foo: { -> "Foo" },
bar: { -> "Bar" },
foobar: { -> foo() + bar() }
])
// assert proxies are of the required type
assert fc instanceof Foo
assert fm instanceof Foo
assert fbc instanceof FooBar
assert fbm instanceof Foobar
// assert methods return expected results
assert [fc.foo(),fm.foo()] == ["Foo","Foo"]
assert [fbc.bar(),fbm.bar()] == ["Bar","Bar"]
assert [fbc.foobar(),fbm.foobar()] == ["FooBar","FooBar"]
You are probably wondering about the overloaded method version, without further ado
// don't forget to add the following line
// import static org.kordamp.groovy.util.ProxyOMatic.methodKey
def fzc = proxy( Fooz ) {
foo { -> "Foo" }
foo { String n -> "Foo" + n }
}
def map = [:]
map[methodKey("foo")] = { -> "Foo" }
map[methodKey("foo",[String])] = { String n -> "Foo" + n }
def fzm = proxy( Fooz, map )
assert fzc.foo() == "Foo"
assert fzc.foo("Groovy") == "FooGroovy"
assert fzm.foo() == "Foo"
assert fzm.foo("Groovy") == "FooGroovy"
A word of caution, methodKey is only available in the current development version (0.6-SNAPSHOT), while we are at it, let me show you what is in store for the soon to be released version. You probably have figured out by now that these proxies rely on java.lang.reflect.Proxy to do their thing, so they are bound by the laws that govern Proxy, but these guys want to be Groovy, so they automaticall implement the GroovyObject interface, which gives them access to the following groovylicious methods:
- getProperty
- setProperty
- methodMissing
- propertyMissing (get mode)
- propertyMissing (set mode)
- getMetaClass
- setMetaClass (read-only, can't change it)
By convention if a method implementation has not been given the proxy will call methodMissing, which by default throws an UnsupportedOperationException, this means the following code passes the green test
import static org.kordamp.groovy.util.ProxyOMatic.proxy
def shouldFail = { Class ex, code ->
try {
code()
throw new RuntimeException()
}catch( Exception x ) {
assert x.class == ex
}
}
interface Foo { String foo() }
def f1 = proxy( Foo ) { }
shouldFail( UnsupportedOperationException ) {
f1.foo()
}
What happens if you call a method for which not even a definition has been made in any of the proxied interfaces?
import static org.kordamp.groovy.util.ProxyOMatic.proxy
interface Foo { String foo() }
def f2 = proxy( Foo ) {
foo { -> bar() }
methodMissing { String name, value -> "oops" }
}
assert f2.foo() == "oops"
assert f2.bar() == "oops"
Properties work very much alike, for instance here is how you may expose a local variable as a property on the proxy
import static org.kordamp.groovy.util.ProxyOMatic.proxy
interface Foo { String foo() }
def f3 = proxy( Foo ) {
def count = 0
foo { -> count++ }
propertyMissing { String name -> count }
}
assert f3.foo() == 0
assert f3.foo() == 1
assert f3.count == 2
But you may define a whole set of properties by using a special properties node
import static org.kordamp.groovy.util.ProxyOMatic.proxy
interface Foo { String foo() }
def f4 = proxy( Foo ) {
foo { -> "${id}:${name}".toString() }
properties {
name()
id(1)
}
}
f4.name = "Duke"
assert f4.foo() == "1:Duke"
The properties node works for the 3 sources (Closures, Maps and Expandos). If you define a value for a property definition, then that value will be assigned as the initial value of the property, as you can atest by looking at how the id property was defined. You may intercept property access by defining setProperty/getProperty but there is a catch. You can't call them directly (at least not while testing inside a script) reason being that the enclosing script defines its own getProperty/setProperty, so whenever the closure that calls any of those method is called, it willactually call the script's methods, not the ones you defined. Regular classes don't have that problem, and if it is ambiguous you may qualify the call with this.Clearly that won't work as well (using this) so what now? proxies expose a read-only property named self, which pretty much works as this.
import static org.kordamp.groovy.util.ProxyOMatic.proxy
interface Foo { String foo( String n ) }
def f5 = proxy( Foo ) {
def props = [:]
foo { String n -> self.setProperty(n,n); n }
getProperty { String name -> props[name] }
setProperty { String name, value -> props[name] = value }
}
assert f5.name == null
assert f5.foo("name") == "name"
assert f5.name == "name"
You are probably thinking by now "proxies are great, but why go over all this hassle?" well here are some reasons
- You may pass an Expando or even a Map to a Groovy class that favors duck typing, sadly you can't do the same for a Java class that expects an specific type
- Current proxy creation mechanisms in Groovy do not support overloaded methods
- Groovy doesn't support inner class definitions (yet, anonymous inner classes are schedule for 1.7)
While we wait for inner class support, proxies are our best alternative to anonymous inner classes. Of course you can code a top level class instead, in order to avoid creating a proxy, but then your namespace may get cluttered by tiny classes meant for an specific point in your code.
Going back to Alex's suggestions, Proxy-o-Matic is very close. Now to figure out how to create proxies from abstract and concrete classes.
- Login or register to post comments
- 896 reads
- Flag as offensive
- Email this Story
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)







Comments
Raphaël replied on Thu, 2008/06/26 - 2:57pm
Hi,
I tried to use it to proxy MouseListener, but i was answered that
GroovyObject was not found in the classloader.
Full stack trace available if you want.
This seems an astonishing way to implement swing listeners
Thanks,
Raphaêl
Andres Almiray replied on Thu, 2008/06/26 - 4:35pm
in response to: rafale