Table Of Contents

Previous topic

jclasspath – Modify Java classpath from Jython

Next topic

jcompile – Java 6 Compiler API Wrapper

This Page

jannotation – Jython annotations

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.

Classes

class jannotations.annotation(anno_cls[, arg = ""])

Creates a new annotation object from a Java annotation class.

Parameters:
  • anno_cls – class of type java.lang.annotation.Annotation.
  • arg

    annotation arguments in string form. If ‘A’ is the name of the Java annotation class, then the annotation object is represented as

    '@A'+arg
    

Recommendation

Don’t construct annotation objects directly but use the extract() factory function instead.


__call__(obj) → obj

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.

classmethod extract(*annoclasses) → decorators

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.

  1. The annotation is parameter less. It can then be used like

    from java.lang import Override
    
    Override = annotation.extract(Override)
    
    @Override
    def foo(self):
        ...
    
  2. 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.


class jannotations.signature(sig_description[, overload = False])

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);
    }

    ...
}
__call__(obj) → obj

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) {
    ...
}

class jannotations.jproperty( type_description [, transfer = None] [, initializer = None] )

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:
  • transfer

    one can pass an optional transfer function. If a transfer function F is passed and F is annotated with Java annotations,those annotations will be transferred to the jproperty

    jproperty.java_annotations = F.java_annotations[:]
    F.java_annotations = []
    
  • initializer

    an optional initializer string can be passed. Setting an initializer a type declaration is turned into a definition.

    Examples

    1) x = jproperty("private int", initializer = "7")
    
            -->
    
       private int x = 7;
    
    2) v = jproperty("private Vector", initializer = "new Vector()")
    
            -->
    
       private Vector v = new Vector();

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;
}

Functions

jannotations.JavaClass(jython_class) → jython_class

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

jannotations.bean_property(type_info) → decorator function
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;
    }
}

jannotations.getAnnotations(obj) → list

Returns list of Java annotations for obj. If no Java annotations are avilable the list is empty.

Example

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"

Appendix A

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 ) * )]  [ ( ',' )] '}'