Active annotations use cases

Active Annotations is a language feature of Xtend to

So active annotations are an addition and in some cases even an alternative to the classic approach of defining domain specific languages and writing code generators for these DSLs.

This is especially true when your DSL tend to evolve to a full blown programming language with some domain specific customizations. In such a case you should consider reusing a general purpose language (GPL) like Xtend and customize it with active annotations to your needs. So you avoid the overhead of reimplementing a complete IDE infrastructure for a DSL.

So the active annotations mechanism isn’t a simple code generator (although you can use it in that way, too) but a transformation working on the Java model AST where you can add new fields and methods. After editor save these members are then immediately visible (in scoping, type computation and content assistance) when further editing the Xtend file.

In this blog post I want present you two use cases how active annotations eases programming.

Message bundles

In programming you should try to avoid situation where you have to keep things in sync manually. One such common situation is the handling of message bundles. It is always a good idea to extract and centralize messages so there is one place to adapt them or even add internationalized messages. These message strings may contain wildcards that can be bound from outside. In Eclipse OSGi there is already an abstract class, NLS, in place that enables handling of message bundles. Despite of that it is still up to the programmer to keep the keys in the message bundle manually in sync with the static String constants in the Java class.

Sven already blogged about how to externalize strings to a properties file and even derive methods to bind type safe the wild card parameters. I want to show you the other way round:

message.properties:

INVALID_TYPE_NAME=Entity name
{0} should start with a capital.INVALID_FEATURE_NAME=Feature name {0} in {1}
should start with a lowercase.EMPTY_PACKAGE_NAME=Package name cannot be empty.INVALID_PACKAGE_NAME=Invalid package name {0}.MISSING_TYPE=Missing {0} type {1}.  

IssueCodes.xtend:  

import de.abg.jreichert.activeanno.nls.NLS

@NLS(propertyFileName="messages")
class IssueCodes {

}

DomainmodelJavaValidator.java:

public class DomainmodelJavaValidator extends XbaseJavaValidator {

   @Check
   public void checkTypeNameStartsWithCapital(Entity entity) {
      if(!Character.isUpperCase(entity.getName().charAt(0))) {
         warning(
             IssueCodes.getMessageForINVALID_TYPE_NAME(entity.getName()),
             DomainmodelPackage.Literals.ABSTRACT_ELEMENT__NAME,           
             ValidationMessageAcceptor.INSIGNIFICANT_INDEX,
             IssueCodes.INVALID_TYPE_NAME,
             entity.getName()
         );
      }
   }
   ...
}

You see that for each key in messages.properties a static String constant of same name is created. Moreover a method prefixed with getMessageFor[KEY_NAME] is derived for each key taking exactly so many parameters as place holders appearing in the message for this key in messages.properties.

The complete example can be found here, including the active annotation processor.

So every time you change messages.properties, code referencing then non existing keys or passing an invalid count of parameters will get error markers in IDE.

As properties file changes usually doesn’t trigger the Java builder there is an ANT builder added to the project that touches IssueCodes.xtend when messages.properties has been changed.

Some details about the implementation of the NLS active annotation:

Since Xtend 2.5 it is possible to write

initializer = '''
   new «Function0»<«String»>() {
      public «string» apply() {
         «NLS /* this is the class literal of org.eclipse.osgi.util.NLS */».initializeMessages(«annotatedClass.findDeclaredField(BUNDLE_NAME_FIELD).simpleName», 
              «annotatedClass».class
         );
         return "";
      }
   }.apply();
'''

All class literals inside the rich string assigned to the initializer are now wrapped with toJava automatically. Compare this with the old notation used in the code on GitHub. This is much more readable now.

Hibernate Criteria Builder

In the second example I want to handle the problem of building type safe SQL queries. Hibernate ORM defines a fluent API to create criteria queries, a programmatic, type-safe way to express a database query. It depends on a static meta model that enables static access to the meta data of the entities contained in the domain model. This meta model have to be generated by the JPA Static Metamodel Generator, a annotation processor. It requires a class defining volatile static fields corresponding to the attributes of the entity as input.

An alternative approach is JOOQ, but here you also have an extra generation step.

The third approach, Sculptor, is a generator framework to describe 3-tier enterprise applications following the domain driven design approach. It uses Xtext based DSLs to define entities, repositories, services and front end. Out of the DSL artifacts code for well established frameworks like JPA, Hibernate, Spring and Java EE is generated. Sculptor itself also ships with some useful static framework classes then called by the generated code. Similar to the before mentioned JPA Static Metamodel Generator Sculptor generates attribute accessor classes for every entity defined in the DSL to be used for a self defined critera query builder.

Wouldn’t it be nice to see immediately which queries break when you change your domain model?

In the following the static framework classes of Sculptor will be reused. But instead using the Sculptor DSL and generator these classes are combined with active annotations. Find the complete example here and in particular the the active annotation processing class.

So with annotating a class with @Entity generates an id field and derives the classes later to use when creating type safe database queries. With annotating a class only with @EntityLiteral will leave off adding the id field. The @Property annotation will add getter and setter method for the annotated field.

Having the domain model for a P2 repository structure (see Database.xtend for the complete entity model)

Location <>— * Unit <>— * Version

and each entity is annotated with @Entity the following typed queries are now possible (copied from LocationManager.xtend):

def Set getLocationURLsContainingUnitWithVersion(String unit, String version) {
   val urls = newHashSet
   val session = SessionManager::currentSession
   val unitFindByCondition = new CustomJpaHibFindByConditionAccessImpl(Unit, session)
   var unitCriteriaRoot = ConditionalCriteriaBuilder.criteriaFor(Unit)
   unitCriteriaRoot = unitCriteriaRoot.withProperty(UnitLiterals.name()).eq(unit).and().withProperty(UnitLiterals.versions().name()).eq(version)
   unitFindByCondition.addCondition(unitCriteriaRoot.buildSingle())
   unitFindByCondition.performExecute
   val unitIds = unitFindByCondition.getResult().map[id]
   val locationFindByCondition = new CustomJpaHibFindByConditionAccessImpl(Location, session)
   var locationCriteriaRoot = ConditionalCriteriaBuilder.criteriaFor(Location)
   locationCriteriaRoot = locationCriteriaRoot.withProperty(
   LocationLiterals.units().id()).in(unitIds)
   locationFindByCondition.addCondition(locationCriteriaRoot.buildSingle())
   locationFindByCondition.performExecute
   val result = toLocationList(locationFindByCondition.getResult())
   result.forEach[urls.add(url)]
   urls
}

The method above will return the URLs of those locations that contain a unit with the given name and version.

The nice thing about the active annotation here is that if you rename version’s attribute name to id and save this change will immediately produce an error marker in LocationManager.xtend as now the access .name() in the query isn’t valid anymore.

Some implementation details about the active annotation here as well:

Other use cases

Besides the both use cases described above there are several other examples of how to use the power of active annotations:

Summary

I hope I was able to give you a good impression, what you can achieve with active annotations. If you want to start writing your own active annotation processors have a look at the official documentation and at this best practices guide as well. Also don’t hesitate to ask questions in the Xtend forum and filling feature requests or bugs here.