Stitches of a flea language - defining Java annotations in Jython
Posted in Java, Jython on June 30th, 2009 by kay – 9 CommentsJython annotations - anyone?
The last few days I tried to figure out how to create Jython annotations. A Jython annotation is defined here as a Java annotation lifted from Jython to Java. So one essentially defines a Java annotation in a Jython class. A Jython annotation shall not be confused with a decorator. A Python ( or Jython ) decorator is just a higher order function ( or callable ). It might create attributes in Jython objects but those are not the same as Java annotations reflected by the Java runtime. Without Jython annotations Jython is right now essentially stuck in a pre version 1.5 Javaverse and Jython classes are disconnected from modern Java frameworks and cannot be plugged.
Jython annotations in Jython 2.5 don’t work out of the box. It is not much known yet about how or when Jython annotations will be supported by core Jython. The lead Jython developer Frank Wierzbicki announced something along the lines in his 2008 PyCon conference talk but this is now about 16 months ago. I could temper my impatience if Jython annotations were just around the corner but what can we expect after those 16 months?
In this article I introduce a library that enables lifting of meta-data from Jython to Java and loading Java back into Jython. One key element is Java code generation and dynamic compilation using the Java 6 Compilation API. Another one is interface extraction of Jython classes using the rich reflection API provided by core Jython.
Lifting up Jython classes
For every Jython class JyClass one can generate a Java class JaFromJyClass by means of interface extraction. We assume JyClass to be a subclass of a Java class, e.g. Object, and translate the Jython class
class JyClass(Object): def foo(self, *args): print "foo", args
into a corresponding Java class
public class JaFromJyClass extends Object{ PyObject jyobject; public PyObject foo(PyObject[] args) { return jyobject.invoke("foo", args); } }
This class is basically a proxy for the jyobject member variable of type PyObject which is a Jython API type. Once we have generated the Java code from Jython we can dynamically compile and load the Java code into Jython:
JaFromJyClass = createJavaClass("JaFromJyClass", source) jainstance = JaFromJyClass() jainstance.jyobject = JyClass() jainstance.foo(9) # prints 'foo 9'
This was straightforward and hints on our translation strategy. Next we review the Jython to Java translations in more detail.
Jython to Java translations
PyObject Injections
We cannot be glad with the way the jyobject was assigned to the jainstance in the previous example. The particular assignment protocol implies that the Jython script has always control over the instantiation of Jython classes. But once we plug the class into a framework the framework takes over. A better solution is to inject the PyObject using a factory mechanism.
public JaFromJyClass() { super(); jyobject = JyGateway.newInstance("JyClass", this, null); jaobject = (Object)jyobject.__tojava__(Object.class); }
The factory is called JyGateway. The JyGateway is a Java class which defines HashMap called registry
public static HashMap<String, PyDictionary> registry = new HashMap<String, PyDictionary>();
The keys of the Java HashMap are Strings that represent class names. The PyDictionary is a dictionary of Jython functions. Right now two functions are defined: newInstance and callStatic. Both of them correspond to static methods of JyGateway. If JyGateway.newInstance(”JyClass”, this, null) is called the newInstance Jython function is fetched from the registry using “JyClass” as a key. The third argument of JyGateway.newInstance contains an array of Java objects passed as arguments to the newInstance function which returns a new PyObject. If the constructor doesn’t take an argument null is passed as in the example. The particular JyClass will never be exposed to Java code.
Overrdiding superclass methods
Aside from jyobject we have also defined jaobject in the JaFromJyClass constructor which has the type Object. Here Object is just the superclass of both JaFromJyClass and JyClass. The jaobjectis defined for the purpose of overriding superclass methods: we cannot simply change the signature of superclass methods in particular not the return value.
If public void foo(int x) is a method defined in the superclass of JyClass, the Java method generated from JyClass is
public void foo(int arg) { jaobject.foo(arg) }
The method foo called from jaobject is still the method implemented in Jython. The Jython method is just called with Java arguments and returns a Java value ( if any ) that gets converted back to Jython.
Calling static methods
Calling static or classmethods of Jython objects from Java is similar to calling JyGateway.newInstance:
public static PyObject bar(PyObject args[]) { return JyGateway.callStatic("JyClass", "bar", args); }
Defining Jython metadata
There are three kinds of meta-data which can be added to Jython classes which are extracted for dynamic Java generation. Those are called jproperty, annotation and signature. They serve different purposes.
signature
A signature decorator is defined to assign Java argument and the return types to a Jython method. Without the signature decorator a default translation is applied:
def foo(self, arg): ...
—–>
public PyObject foo(PyObject[] args) { return jyobject.invoke("foo", args); }
If we decorate foo with the following signature decorator
@signature("public int _(char)") def foo(self, arg): ...
we get the translation
public int foo(char arg0){ PyObject args[] = new PyObject[1]; for(int i=0;i<1;i++) { args[0] = Py.java2py(arg0); } return (Integer)jyobject.invoke("foo", args).__tojava__(int.class); }
The name of the function in the signature declaration string is of no relevance. That’s why we have used a single underscore.
annotation
The annotation decorator applies to methods and classes. We have to wait for Jython 2.6 for proper class decorator syntax but the semantics is the same when we write
cls = annotation(value)(cls)
The value passed to annotation is a string which must conform Java annotation syntax with the leading @ character being stripped.
@annotation("Override") def foo(self, arg): ...
is a valid annotation which corresponds to the Java method
@Override public PyObject foo(PyObject[] args) { return jyobject.invoke("foo", args); }
Annotations can be stacked and also combined with the signature decorator. So we can define three new decorators
test = annotation("Test")(signature("public void _()")) setUp = annotation("Before")(signature("public void _()")) beforeClass = annotation("BeforeClass")(signature("public static void _()"))
and use them within e.g. JUnit 4
from org.junit import* from org.junit.Assert import* class TestClass(Object): @beforeClass def start(self): print "Run 'TestClass' tests ..." @test def test_epoweripi(self): from math import e, pi assertTrue( abs( e**(pi*1j) + 1 ) < 10**-10 )
jproperty
The jproperty object is a descriptor which assigns a Java type and zero or more annotations to a Jython attribute.
class JyClass(Object): x = jproperty("private int", "X", "Y")
This is how it translates
pubic class JyClassBase(Object) { @Y @X private int x; }
A JyClass instance reads ( and sets ) jproperty values from the corresponding Java class instances it was assigned to. Remember that the jyobject instance construction looked like this
jyobject = JyGateway.newInstance("JyClass", this, null);
With this an instance of the Java class was passed to the newInstance factory function. Not only holds a javaobject a jyobject but also a jyobject holds a javaobject. Reading / writing jproperty values is the primary reason for this cyclic dependence.
Class import heuristics
Whenever a Java class gets compiled, names have to be imported from classes/packages using the import-statement. Jython applies some heuristics to extract class and package names from annotations/Jython code and creates Java import statements accordingly. An important precondition for a class/package to be found is that it has been imported in Jython code. This isn’t particularly cumbersome. When you define an annotation
annotation('SupportedAnnotationTypes("*")')
the name SupportedAnnotationTypes has to be made public using a normal Jython import:
from javax.annotation.processing import SupportedAnnotationTypes
This is not much different from considering an evaluation of the parameter string using Pythons eval.
The annotation class has a class attribute namespace which is used for Jython class and annotation extraction. If the heuristics fails to extract a class the namespace can be manually updated:
annotation.namespace.update(locals())
Jynx
Within the next few days I’ll launch a new project on code.google.com called jynx which will contain tools particularly suited for Jython utilization of modern Java frameworks and APIs. The relevant source code for this article can be found here and you can examine and play with it.
