Ann: A domain-specific language for the effective design and validation of Java annotations Irene Córdoba-Sánchez, Juan de Lara Computer Science Department, Universidad Autónoma de Madrid (Spain)
Abstract This paper describes a new modelling language for the effective design and validation of Java annotations. Since their inclusion in the 5th edition of Java, annotations have grown from a useful tool for the addition of metadata to play a central role in many popular software projects. Usually they are not conceived in isolation, but in groups, with dependency and integrity constraints between them. However, the native support provided by Java for expressing this design is very limited. To overcome its deficiencies and make explicit the rich conceptual model which lies behind a set of annotations, we propose a domain-specific modelling language. The proposal has been implemented as an Eclipse plug-in, including an editor and an integrated code generator that synthesises annotation processors. The environment also integrates a model finder, able to detect unsatisfiable constraints between different annotations, and to provide examples of correct annotation usages for validation. The language has been tested using a real set of annotations from the Java Persistence API (JPA). Within this subset we have found enough rich semantics expressible with Ann and omitted nowadays by the Java language, which shows the benefits of Ann in a relevant field of application. Keywords: Model Driven Engineering, Domain-Specific Languages, Code generation, Java annotations, Model Finders
Email addresses: (Juan de Lara)
[email protected]
(Irene Córdoba-Sánchez),
[email protected]
Preprint submitted to Computer Languages, Systems and Structures
January 22, 2016
1. Introduction In 2004 the possibility of adding custom meta-data to programs was added to the Java language in the form of annotations 1 . Predefined annotations were available previously for very specific tasks, however, the huge amount of boilerplate code that many Application Programming Interfaces (APIs) required motivated their establishment as another general tool in the language. Schildt [33] suggests other reasons as motivation to the appearance of annotations in Java: the increasingly growing tendency of including the meta-data associated with a program within the program itself instead of keeping it in separate files; and the pressure from other programming languages which already included similar features, like C#. Since their introduction in the language, annotations have become a success and are widely used in many important projects within the software development scene. We find them in frameworks like Seam2 and Spring3 , in the Object Relation Mapping of Hibernate4 , and also in proper Sun Java standards such as the Java Persistence API (JPA)5 . However, despite this success, the native support that Java provides for their construction is very poor. This is so as Java annotations are not defined using a specialized syntax, but reusing the syntax to create interfaces. This lack of specialized syntax greatly limits the ability to specify the elements where an annotation can be placed and further correctness conditions. Moreover, annotations are rarely conceived in an isolated way; instead they are usually part of a set with dependencies and integrity constraints. Currently there is no effective way in Java for making explicit the constraints underlying a set of annotations at design time, and validate that they are not conflicting. Instead, the usual path taken to overcome these deficiencies is to develop an extension to the Java compiler (called annotation processor) to ensure that such constraints are complied with, and rely on extensive manual testing of such processor. As a first step towards the alleviation of this situation, we propose Ann, a textual Domain-Specific Language (DSL) [36] aiming to provide a more 1
http://docs.oracle.com/javase/1.5.0/docs/guide/language/annotations.html
2
http://docs.jboss.org/seam/latest/reference/html/annotations.html
3
http://spring.io/
4
http://hibernate.org/orm/
5
http://www.oracle.com/technetwork/java/javaee/tech/persistence-jsp-140049.html
2
expressive and suitable syntactic support for the design of sets of annotations and their associated integrity constraints. We have developed an Integrated Development Environment (IDE) as an Eclipse plug-in, which integrates seamlessly with the Java IDE. The environment includes a code generator to translate the design and constraints expressed using Ann into Java code, which can be fully integrated into projects in such language. Moreover, Ann makes use of a constraint solver over models (a model finder [25]), which is able to detect whether the constraints posed by a set of annotations are unsatisfiable, and provide examples (annotated class mock-ups) of usages of the annotations. These examples could be used by designers to validate whether the encoded integrity constraints defined with Ann are according to their intentions. Ann has been tested using a real set of annotations from JPA, demonstrating that it can capture a wide set of the constraints in its specification, and showing advantages with respect to other approaches with similar goals in the state of the art. More information and the source code of the project can be found at http://irenecordoba.github.io/Ann. This paper is an extended version of [6, 7], where we have added validation mechanisms based on constraint solving, and integrated such mechanisms with the environment. Additionally, all sections have been enlarged with additional explanations and more details. The rest of the paper is organised as follows. Section 2 analyses related work. Section 3 gives a more detailed overview on the current limitations of Java annotations. Section 4 introduces the main concepts of Model-Driven Engineering (MDE) and the different choices when building a DSL. Section 5 provides an overview of our approach. Section 6 describes the proposed DSL, Ann. Section 7 explains our approach to validate annotations. Section 8 contains the evaluation of Ann, including a detailed real case study and an evaluation of the efficiency of the model finder for annotation validation. Finally, section 9 summarises the conclusions and future development. Two appendices detail the description of the textual concrete syntax of Ann, and the generated OCL invariants for the JPA case study. 2. Related research Some research has been made in order to improve and expand the functionality of Java annotations. For example, Phillips in [28] aims at conciliating object oriented principles with the design of annotations by the introduction of a new one: composite. With it, he manages to support composition, 3
allowing encapsulation and polymorphism of annotations. A Java extension, @Java, is proposed by Cazzola and Vacchi [4] in order to expand the range of application of an annotation to code blocks and expressions, although some improvement in this respect has also been made natively in the latest version of Java6 . The expressiveness limitations of Java annotations are recognised in [5], where a proposal is made to embed DSLs into Java, with a more natural and flexible syntax. JUMP [1] is a tool to reverse engineer Java programs (with annotations) into profiled UML class diagrams. Although the aforementioned approaches expand the features of Java annotations, they do not facilitate their design, nor address the limitations with respect to expressing integrity constraints within an annotation or between the annotations in an annotation set, which is the main goal of our work. Just a few works are aimed at improving the design of annotations. Darwin [11] suggests a DSL, called AnnaBot, based on claims about a set of existing annotations, with a concrete syntax very similar to Java. With these claims, interdependency constraints can be expressed within a set of annotations. However, there is no possibility of characterising the valid targets of an annotation type (i.e., the valid elements in a program where the annotation can be placed). Moreover, no improvement is made with respect to the syntax for defining annotations in Java, given its heavy focus on existing sets of annotations and constraints between them, and not on isolated ones. Finally, the approach uses reflection to check the statements of its claims, which could and should be avoided. Another approach is AVal [27], a set of meta-annotations (annotations which are placed at other annotations) to add integrity constraints at the definition of the annotation type. The drawback of this approach is that its expressiveness is rather restricted, given the limited flexibility which the structural characteristics of meta-annotations provide. Pluggable type systems [2], provide a way to support improved analysis of programs by ensuring stronger type checking. Implementation of these systems exist for Java, like the JavaCOP framework [26]. Java 8 improves the support for such systems via type annotations. Pluggable type systems provide a customized semantics to sets of programming elements via the use of annotation sets. However, they normally do not provide a unified 6
Java 8 at the time of writing
4
way to describe and validate the syntax and integrity constraints of a set of annotations. Regarding constraint validation, constraint solving strategies are extensively used in connection with program testing [13]. Many times, constraint solving is used as a means to generate interesting test data for programs, perhaps derived from specifications [19], or making the program execute a certain path [8]. In [21] constraint solving is used to generate both test cases and mock-up classes, considering advanced features like reflection and annotations. Hence, while they can generate mock-up programs with annotations, they need to derive the needed constraints from the analysis of reflective Java code (i.e., at runtime), with the consequent loss of precision and drawbacks of runtime checking. Instead, for our purposes, it would be more desirable to have a language making explicit the annotation constraints at design time, which then can be analysed before such annotations are used. Hence, as we have observed, there is currently a need for: (i) better syntactical support for the specification of Java annotations; (ii) explicit, high-level means to describe constraints for an annotation (e.g., regarding its valid targets), and between a set of annotations (e.g., expressing their dependencies); and (iii) ways to analyse such constraints to find inconsistencies, at design time. For this purpose, in the rest of the paper, we describe Ann, a DSL directed to describe both the syntax and well-formedness constraints of annotation families, to validate the correctness of the annotation constraints and to make explicit the design of such annotation set, allowing their immediate use on Java projects thanks to the code generation facility. 3. Java annotations To help understanding the current limitations of Java annotations, in this section we describe how they are defined in Java (subsection 3.1), their usage and limitations (subsection 3.2) and how their correct use is checked (subsection 3.3). 3.1. Defining Java annotations Java annotations do not constitute a type of their own. Instead, they are defined as special interfaces. There are many differences between annotations and interfaces, however we will only review those necessary to understand the design of Ann.
5
1
package examples;
2 3
import java.lang.annotation.*;
4 5
@Target(ElementType.TYPE)
6
public @interface Person {
7
String name() default "Mary";
8
int age() default 21; float weight() default 52.3f;
9 10
}
Listing 1: Annotation
Person
defined in Java.
Listing 1 shows an example of the definition and usage of a simple annotation called Person. Such annotation definition is called an annnotation type. As it can be noticed, the special nature of annotations is indicated by the @ character before the interface keyword (line 7). The zero-argument methods inside the container (lines 7-9) are the fields (the parameters) of the annotation. To assign a default value to those fields, the keyword default must be used. An annotation can have an arbitrary number of fields, which can be of primitive type, Class, String, an annotation type, or an array of the previous types. Since the goal of annotations is to add meta-data to Java programs by being placed at certain elements, it is important to know which elements are eligible as their targets. Annotations can be employed in the declaration of many constructions in Java; however with Ann we have focused on the most usual ones, namely types (classes, interfaces, enumerates, annotations), constructors, methods and fields7 . Another important characteristic of annotations is that they can have three different levels of retention, which depends on the phase where they will be used: in the source code (they are discarded by the compiler); compiled but ignored by the Java Virtual Machine (JVM); and compiled and read by the JVM when the type that contains them is loaded. The last ones are accessible by the Java Reflection API at runtime, which can check the values 7
Ann was started before the official release of Java 8, and hence some of its feature regarding annotations, like the possibility of defining type uses as targets is not currently supported, but left for future work.
6
1
import examples.Person;
2 3
@Person(name="Peter", age=43)
4
class Filter {
5
...
6
}
Listing 2: Usage of the annotation
Person.
in their fields. Both the targets allowed for an annotation and its desired level of retention can be specified at design time by using the standard Java metaannotations Target and Retention, respectively. As we will see in the next subsection, the former is very poor regarding its expressive power with respect to real and common use cases where annotations are used. 3.2. Annotation usage and limitations Listing 2 shows the use of the defined Person annotation. Annotations are considered as modifiers when using them on a target in Java. This is why, although in Listing 2, line 3, annotation Person appears above the class, it could perfectly be merged with the rest of the modifiers, but the former is the usual syntax. Pairs key = value are used in order to specify the values of the fields of the annotation. It is mandatory to set a value for all the fields that do not have a default value predefined on the annotation type, and the order of the fields does not matter. Traditionally only one instance of a particular annotation type could annotate a target; however with Java 8 it is possible to use several such instances if the corresponding annotation type is properly marked on its definition. Line 5 of Listing 1 shows another example of an annotation being used: Target. In this case, the value is directly specified because the annotation has only one field and it is named value. By using the value TYPE of the enumeration ElementType, Person is restricted to be applied to classes, interfaces (including annotation types) and enumerations. However, there is no way to e.g., restrict its applicability to classes only. We have presented a very simple example of an annotation type, but if we take a look at the JPA documentation, particularly the extensively used
7
annotation, we find that it can only be applied to classes meeting the following more elaborated requirements8 :
Entity
• They must have a public or protected constructor. • They must not be final. • They must not have any final method. • Their persistent fields must be declared private, protected or packageprivate. None of these constraints can be expressed nowadays with the syntax available for the definition of annotation types. What is more, when designing annotation sets, it is common to have constraints involving several annotations, because the annotations are usually inter-related. For example, the JPA annotation Id is only allowed in attributes within classes annotated with Entity. We call such constraints the static semantics or integrity constraints of an annotation, or an annotation set. Given a large and complex set of requirements for an annotation set, it is easy to make mistakes, by requiring conflicting features from the different annotations, specially at design time. Just for the sake of illustration, if we require Id to be applicable on a public attribute, then it would make the Id annotation to be in conflict with Entity (as the latter requires non-public attributes), and hence inapplicable. Therefore, what can be done to ensure the compliance of such outlined constraints and ensure their validity? The only remaining choices are to write a guiding comment for its use and signal an error at runtime. In addition, it is possible to develop extensions to the Java compiler, known as annotation processors, and rely on their extensive manual testing to validate that the annotation requirements are met by their implementation in the processor. In the next subsection we review these annotation processors, since they are one of the key components of Ann. 3.3. Annotation processors The Java package javax.annotation.processing provides a set of elements for processing annotations at compile time. An annotation processor is invoked by the compiler, and it can check the annotations attached to any 8
http://docs.oracle.com/javaee/7/api/
8
2
@SupportedAnnotationTypes("Person") // annotation to be checked @SupportedSourceVersion(SourceVersion.RELEASE_6)
3
public class PersonProcessor extends AbstractProcessor
4
{
1
5
@Override
6
public boolean process(Set