Wednesday, January 9, 2013

Customizing Jython ProxyMaker

I just got a set of patches under "Customizable ProxyMaker" umbrella committed into Jython 2.7. In this post i will try to explain a bit of what Jython's ProxyMaker is and what benefits the ability to customize/override brings for interop with Java.

What is ProxyMaker?

To answer that I need to explain what happens when you subclass a Java class in Jython.

Here's an example Jython code that subclasses a Java class (taken from jython.org):
from java import awt

class SpamListener(awt.event.ActionListener):
    def actionPerformed(self,event):
        if event.getActionCommand() == "Spam":
     print 'Spam and eggs!'

f = awt.Frame("Subclassing Example")
b = awt.Button("Spam")
b.addActionListener(SpamListener())
f.add(b, "Center")
f.pack()
f.setVisible(1)
This will create a new window with a button and use our Jython subclass of ActionListener that we passed back to awt. What's interesting here is that we have created a subclass of a Java class in Python, overrode a method and passed it back to Java! If you create an instance of SpamListener and print it's repr, you'll see an odd looking string org.python.proxies.__main__$SpamListener$0@4fd20361. After digging through Jython code you will soon find that org.python.proxies leads to a class called org.python.compiler.ProxyMaker. It seems that a new Java class is generated at runtime, which is a subclass of ActionListener, that is somehow able to call Jython code from Java.

To understand how it works, i have enabled proxy debugging like this:
import org.python.core.Options
org.python.core.Options.proxyDebugDirectory = '/tmp'
After you run the code above, any new Jython proxy classes will be saved to /tmp. As i'll show below, this is very useful for understanding and debugging behavior of our proxy subclasses. If you're curious, you can see the output of javap -c decompilation in this gist.

What you'll find is that the generated class either extends or implements the Java class, but it overrides the constructor and the methods declared in our Jython code (in this case actionPerformed).

What's in the constructor? Not much interesting really. First it initializes the superclass then calls org.python.core.Py:initProxy()which instantiates the Python version of the class and crossreferences Jython and Java instances. Basically at the end of initialization there's two instances in two different worlds that know of each other.

It's a bit more interesting to look at the decompiled actionPerformed method. First thing it does is call org.python.compiler.ProxyMaker:findPython()which is not trying to find the snake, but the method that was associated with the Python instance of the class. After that, our original Java method calls org.python.core.PyObject:_jcall() , which does a few nifty things... It converts all the arguments from Java types to Python types, then calls the Python version instance of the class. Next, it either throws an exception (if something went wrong), or returns PyObject (the result of our Python version call) back to our Java version method. What's left, is to cast our resulting PyObject to the Java type that was declared by the original Java method (in case of ActionListener, void).

Why should we customize?

Why did i have to tell you all this mumbo jumbo? In part, to (re)educate myself on how the ProxyMaker works, in part to document it, but more importantly to illustrate how awesome it is!

It is a mechanism by which you can subclass almost any Java class, override it's methods and offer it back to JVM by writing 0 lines of Java code!

Which leads me, finally, to the reason for the patch set to be able to extend ProxyMaker. ProxyMaker is wonderful at what it does, but the underlying technology can do so much more... If extended, it could further improve Python/Java interop. It's always been easy to use Java code in your Jython code base, but the opposite is much more involving. Imagine if you could write new classes in Jython, then simply import the resulting work straight from your Java code!

Well, that's what I'm working on in a related project called Clamp. I'll write about it in the next post, hopefully not too long from now...

In the meantime, please consider joining the Jython project. There's a TON of interesting things to do and not enough heads working on them.

Thanks for reading!