The jannotations module implements a mechanism for automatic Java class generation from Jython classes which are equipped with Java annotations defined in Jython. We want to call those annotations Jython annotations. It is important to keep in mind that Jython annotations are not a feature of the Python language. They are a Java feature expressed in Python.
Creates a new annotation object from a Java annotation class.
Parameters: |
|
---|
Recommendation
Don’t construct annotation objects directly but use the extract() factory function instead.
All annotations of an object obj can be read from a java_annotation attribute which has list type. If obj is passed to annotation.__call__() the annotation will be added to obj.java_annotation or if java_annotation doesn’t exist yet it will be created
obj.java_annotation = [self]
annotation objects can decorate Python methods and classes.
The return value is usually the same object that was passed in. An exceptional case are signature objects. If a signature object is passed the signature is added to the annotation via the java_signature attribute and the annotation itself will be returned.
The extract classmethod keeps one or more Java annotation classes, extracts parameter and type information from them and returns a decorator for each of them which can be used as a Jython annotation. For annotation applications a few use cases have to be distinguished.
The annotation is parameter less. It can then be used like
from java.lang import Override
Override = annotation.extract(Override)
@Override
def foo(self):
...
An annotation takes parameters, then we write
from javax.persistence import Column
Column = annotation.extract(Column)
@Column(name = "FOO", nullable = False)
def getFoo(self):
...
Parentheses are always omitted when a Jython annotation doesn’t take parameters
from org.junit import Test
@Test
def test_foo(self):
...
@Test(timeout=100):
def test_bar(self):
...
This closely reflects how annotations are used in Java.
Creates a new signature object from a Java method header description. signature objects are used to decorate Python methods.
Parameters: | overload – Supports method overloading. @JavaClass
class Foo(Object):
@signature("S _(A)", overload = True)
def f(self, x):
pass
@signature("S _(B)", overload = True)
def f(self, x):
pass
...
New Python functions f_0, f_1, ... will be generated for different versions of f. On the Java side a single overloaded function f will be created which invokes the f_i functions accordingly: public class Foo extends Object {
public S f(A x) {
return jyobject.invoke("f_0", x);
}
public S f(B x) {
return jyobject.invoke("f_1", x);
}
...
}
|
---|
Method used to create a java_signature attribute at the passed obj parameter.
One can compose annotation and signature objects i.e. pass a signature to an annotation and vice versa. The result is a decorator.
Example
@signature("int _(int, int)")
def add(self, x, y):
return x+y
This function will be translated into a Java function int add(int arg0, int arg1) {...}. If it gets decorated with an annotation object as follows
@Override
@signature("int _(int, int)")
def add(self, x, y):
return x+y
the result will look like
@Override
int add(int arg0, int arg1) {
...
}
Creates a new Python descriptor of type jproperty. jproperty descriptors work like simple Python properties but they don’t bind values to private attributes but to Java member variables. The type_description value passed to the jproperty constructor contains the declaration types of a Java member.
Parameters: |
|
---|
When Jythons reads/sets the value of the jproperty x then the x member of the Java class will be read/set.
Example 1
class A(Object):
x = jproperty("private int")
Here x is defined as a jproperty descriptor. The corresponding generated Java class will look like
public class A extends Object {
private int x;
}
Example 2
class A(Object):
@X
@y
def x(self):
pass
x = jproperty("private int", x)
If X, Y are annotation objects they will be stripped from the method x and attached to the jproperty x. In the translated source code @X, @Y are annotations of the private member x.
public class A extends Object {
@X @Y private int x;
}
The JavaClass function creates a new Java class from a Jython class, compiles and loads the Java class and returns the result as a new Jython class.
The JavaClass function is usually used as a class decorator as in
@JavaClass
class TestClass(Object):
@Test
def test_some_property(self):
assertEqual(...)
A Jython class that can be translated by JavaClass must be derived from a Java class. In the above example it is java.lang.Object.
Monkey Patching
The generated Java class delegates method calls to an instance of the Jython class being extracted. The Java class holds a jyobject member. It is not possible to monkey patch the Java class and add new attributes on the fly but one can do this on the jyobject
class JyClass(Object): # Jython class
def foo(self):
print self.x # x not defined at compile time
JaClass = JavaClass(JyClass) # Extract JyClass and create a Java class
ja = JaClass()
ja.x = 42 # ERROR! ja cannot be patched
ja.jyobject.x = 42 # o.k.
ja.foo() # 42
Parameters: | type_info – Java type. The bean_property function accepts reflected Java classes e.g. String or the same class in quotes: “String”. The calls bean_property(String) and bean_property("String") have the same effect. |
---|
The @bean_property is used to reduce boilerplate code when defining Java beans and getters / setters in particular.
The application of the @bean_property decorator has following effect:
Example
@JavaClass
class A(Object):
@bean_property(String)
def foo(_):_
The translation will be
public class A extends Object {
...
private String s;
public String getS() {
return s;
}
public String setS(String value) {
s = value;
}
}
Returns list of Java annotations for obj. If no Java annotations are avilable the list is empty.
The Java Compiler API allows custom annotation processors which are executed at compile time. These processors have to inherit from the AbstractProcessor class and specify some class annotations. The following example shows a simple processor called CodeAnalyzer
from java.lang import Override
from java.util import List
from javax.lang.model import SourceVersion
from javax.annotation.processing import SupportedAnnotationTypes
from javax.annotation.processing import SupportedSourceVersion
from javax.annotation.processing import AbstractProcessor
from jynx.jannotations import*
Override = annotation.extract(Override)
SupportedSourceVersion = annotation.extract(SupportedSourceVersion)
SupportedAnnotationTypes = annotation.extract(SupportedAnnotationTypes)
@JavaClass
@SupportedAnnotationTypes(value = "*")
@SupportedSourceVersion(value = SourceVersion.RELEASE_6)
class CodeAnalyzer(AbstractProcessor):
elements = jproperty("private List")
@Override
def process(self, annotations, roundEnvironment):
for e in roundEnvironment.getRootElements():
self.elements.append("Element is "+ str(e.getSimpleName()))
return True
Let’s compile a Java class now with a CodeAnalyzer instance being passed to the Java compiler
code = '''
public class Simple {
}
'''
analyzer = CodeAnalyzer()
analyzer.elements = []
JavaCompiler(processors = [analyzer]).createClass("Simple", code)
assert analyzer.elements[0] == "Element is Simple"
The elements attribute can be initialized directly at the analyzer because it is defined as a jproperty. If we uncomment the elements definition in CodeAnalyzer we need to modify the execution protocol
...
analyzer.jyobject.elements = []
...
assert analyzer.jyobject.elements[0] == "Element is Simple"
The following EBNF rules describe an annotation in Java
annotation: '@' annotationName [ ( '(' [ ( elementValuePairs | elementValue )] ')' )]
annotationName: Identifier( '.' Identifier ) *
elementValuePairs: elementValuePair ( ',' elementValuePair ) *
elementValuePair: Identifier '=' elementValue
elementValue: expression | annotation | elementValueArrayInitializer
elementValueArrayInitializer: '{' [ ( elementValue( ',' elementValue ) * )] [ ( ',' )] '}'