:mod:`jannotation` -- Jython annotations ==================================================== .. module:: jannotations :synopsis: Defines Jython annotations that can be lifted to Java classes. .. moduleauthor:: Kay Schluehr 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:: annotation( anno_cls [, arg = ""] ) Creates a new ``annotation`` object from a Java annotation class. :param anno_cls: class of type ``java.lang.annotation.Annotation``. :param 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 :meth:`extract` factory function instead. | .. method:: __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:: signature( sig_description , [overload = False]) Creates a new ``signature`` object from a Java method header description. ``signature`` objects are used to decorate Python methods. :param overload: Supports method overloading. .. sourcecode:: jython @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: .. sourcecode:: java 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:: __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 .. sourcecode:: java @Override int add(int arg0, int arg1) { ... } | .. class:: 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. :param 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 = [] :param 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 .. sourcecode:: java 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``. .. sourcecode:: java public class A extends Object { @X @Y private int x; } Functions --------- .. function :: 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 .. sourcecode:: jython @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`` .. sourcecode:: jython 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 | .. function:: bean_property( type_info ) -> decorator function :param 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** .. sourcecode:: jython @JavaClass class A(Object): @bean_property(String) def foo(_):_ The translation will be .. sourcecode:: java public class A extends Object { ... private String s; public String getS() { return s; } public String setS(String value) { s = value; } } | .. function :: 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`` .. sourcecode:: jython 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 ) * )] [ ( ',' )] '}' External Links -------------- | `Specification of Java annotations `_ | `Blog article about Jython annotations `_