Thursday, November 15, 2012

Active Annotations Explained - JavaFX Properties

Today I'd like to give a more detailed example of how active annotations can be used to solve real world problems. Active annotations are an upcoming language feature for Eclipse Xtend, which allow you to participate in the translation step from Xtend code to Java using annotations.

JavaFX Properties

JavaFX comes with a very nice binding and properties framework, which allows for easy connection of model properties and widgets. It even has support for expressions, like String concatenations, which are reevaluated automatically, when a property changes.

I have used it in a login screen, such that the welcome message get's updated when you type in the user name.

Unfortunately defining JavaFX beans is not so nice, as it requires a lot of boilerplate. Michael Heinrichs, technical lead of JavaFX Properties, recommends the following pattern in his blog:

// Java boilerplate
public class Login {

  private String userName = "";

  private SimpleStringProperty userNameProperty;

  public String getUserName() {
    return (this.userNameProperty != null)? this.userNameProperty.get() : this.userName;
  }

  public void setUserName(final String userName) {
    if (userNameProperty != null) {
      this.userNameProperty.set(userName);
    } else {
      this.userName = userName;
    }
  }

  public SimpleStringProperty userNameProperty() {
    if (this.userNameProperty == null) { 
      this.userNameProperty = new SimpleStringProperty(this, "userName", this.userName);
    }
    return this.userNameProperty;
  }

  // ... same pattern for the password goes here

}
  
That's a lot of code. Given that we usually have to map a lot of properties to the UI, you don't want to write and maintain that.

Code generation to the rescue?

The typical solution in those scenarios is to come up with a little DSL and a code generator. In Java land we really know how to do that and there is easy to use technology available to build ... yet another entity DSL. And it's definitely a much better approach than maintaining above's code by hand, but ...

The big advantage of frameworks such as Xtext is the flexibility they provide. You can build all kind of tooling and choose the right syntax for the problem. No compromises. However, there are certain classes of DSLs which really don't need or use the syntactic flexibility since they are close to class-like structures : Entities, Services, etc.

In addition building an external DSL introduces a bit of an extra complexity in the development turnarounds: You'll have to maintain an Eclipse Plug-In and deploy it synchronously with the rest of your framework to all developers in your team. And just like everything else in your project, the DSL and the code generation evolve over time. So you have to keep everything in sync and deploy and install new Eclipse plug-ins every time your DSL enhances.

Don't get me wrong there are many situations where the additional flexibility of Xtext is extremely useful, but for a certain class of DSLs you are better off with active annotations.

JavaFX Beans with Active Annotations

So let's see how we can replace JavaFX beans like the one shown above with a more readable and maintainable variant.

First we replace the Java class with the following Xtend class:

@FXBean class Login {
  String userName
  String password
}

Next we define the referenced annotation, like so:

@FXBean for class {

  process each {
    inDerivedJavaClass(it) [
      for (f : declaredFields.toSet) {
        //TODO create property field
        //TODO create getter
        //TODO create setter
        //TODO create property accessor
      }
    ]
  }
}

The syntax is a bit different from Java's since we are convinced that reusing the keyword interface for an annotation, as Java does, is surprising at least. Also the annotation target (class in this case) is a first class syntax construct and is not encoded in another annotation.

The important part is the 'process'-hook, which will be called by the Xtend compiler, for every class that is annotated with @FXBean. Therein we state that we want to iterate over the declaredFields in the context of the derived Java class.

In the following we are going to replace the individual TODOs to make the compiler create a real JavaFX property from just a single field declaration.

We start by declaring a couple of local variables we'll use in the following:

process each {
  inDerivedJavaClass(it) [
    for (f : declaredFields.toSet) {
      val fieldName = f.simpleName
      val fieldType = f.type
      val propName = f.simpleName+'Property'
      val propType = f.type.toPropertyType(this)

  ]
}

The extension method toPropertyType() is not shown here, but it's really just a small utility method I built, which will return the JavaFX property type for the field's type. E.g. when asked with the type String the extension method returns the type SimpleStringProperty. Such utility methods simply reside on the classpath just like the annotation itself.

Let's now create the field.

process each {
  inDerivedJavaClass(it) [
    for (f : declaredFields.toSet) {
      val fieldName = f.simpleName
      val fieldType = f.type
      val propName = f.simpleName+'Property'
      val propType = f.type.toPropertyType(this)
      
      // create a field for the JavaFX property type
      field(propName, propType)
    }
  ]
}

The method field(String, Type) adds a field to the class in context using the given name and type. We could also change existing Java information here, like visibility and so on and also have an initializer or add annotations. You can even create new Java classes interfaces, etc.. Basically everything you can do in Java, can be done here.

Next up we create a 'getter'-method:

process each {
  inDerivedJavaClass(it) [
    for (f : declaredFields.toSet) {
      val fieldName = f.simpleName
      val fieldType = f.type
      val propName = f.simpleName+'Property'
      val propType = f.type.toPropertyType(this)
      
      // create a field for the JavaFX property type
      field(propName, propType)
      
      // getter
      method ('get'+fieldName.toFirstUpper, fieldType) [
        body = 
          'return (this.'+propName+' != null)? 
               this.'+ propName +'.get() : this.'+fieldName+';'
      ]
    }
  ]
}

Nothing special here. The square brackets are a lambda expression wherein you can customize the created method. We assign a body in this case.

The other two methods are created similarly:

@FXBean for class {
  
  process each {
    inDerivedJavaClass(it) [
      for (f : declaredFields.toSet) {
        val fieldName = f.simpleName
        val fieldType = f.type
        val propName = f.simpleName+'Property'
        val propType = f.type.toPropertyType(this)
        
        field(propName, propType)
        
        // getter
        method ('get'+fieldName.toFirstUpper, fieldType) [
          body = 
            'return (this.'+propName+' != null)?
                 this.'+ propName +'.get() : this.'+fieldName+';'
        ]
        
        // setter
        method ('set'+fieldName.toFirstUpper, type('void')) [
          param(fieldName, fieldType)
          body = 
            'if ('+propName+' != null) {
               this.'+propName+'.set('+fieldName+');
             } else {
               this.'+fieldName+' = '+fieldName+';
             }'
        ]
        
        // property accessor
        method (fieldName+'Property', propType) [
          body = 
            'if (this.'+propName+' == null) { 
               this.'+propName+' = 
                 new '+propType.identifier+'(this,"'+fieldName+'",this.'+fieldName+');
             }
             return this.'+propName+';'
        ]
      }
    ]
  }
}

And that's it. What you can't see here, is that everything gets updated and recompiled instantaneously on safe. Also important to note, is that you don't need any additional compiler configuration. Just have your active annotation on the classpath (e.g. distributed via jar or in the some project) is enough to use it and have the compiler applying it. And this of course works wherever you compile Xtend code (Command Line, Eclipse, Ant, Maven, etc.).

Naturally the IDE and the compiler is aware of what you do in the processing so, you'll for instance get the expected content assist proposals, ca use the typical navigation features and see the synthetically derived methods and fields in the outline view.

The following screen cast shows everything in action: