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:

15 comments:

  1. Good stuff! I especially like the live nature of the annotations as it brings true meta-programming to Java and it caters nicely for the situations where you don't need a full-blown DSL that targets multiple layers and aspects of your application architecture.

    One quick&easy question: in the syntax method [ body = 'yadda' ], can one also set body to a CharSequence so we can use Xtend's rich strings?

    ReplyDelete
    Replies
    1. sure, also we are working on allowing writing xbase expressions, which are translated to its corresponding AST (similar to LINQ).
      Having an AST instead of just Java code allows for using control flow analysis (e.g. unused private fields).

      Delete
  2. Perfect - just one minor note. The exposed property normally is not the SimpleStringProperty but StringProperty or ReadOnlyStringProperty

    ReplyDelete
    Replies
    1. Unfortunately that one doesn't have the constructor I use. So it would involve a bit more special handling, which I wanted to avoid for the sake of simplicity.

      Delete
    2. Right, those 2 are the base classes so they are shown in the method signature only. So you'd have to calculate 2 types the one you create an instance of and the one you return from the property method. Anyways very cool

      Delete
    3. Ah, I see. We of course should do it that way in our "Xtended JavaFX" project :-)

      Delete
  3. Great!
    When will it be available to the general public?
    Is there an update site from which we can install and test it?

    ReplyDelete
    Replies
    1. The next release is going to come with a first beta (working but API changes afterwards expected). We hoped to be able to release to this year, but that's unlikely as it looks now. We hope for end of January, early February. It's in the nightly update site, but there's no documentation and it's in a really rough state atm. I'd suggest to wait a bit. sorry.

      Delete
    2. This comment has been removed by the author.

      Delete
  4. Do you plan to publish this example on github?
    https://github.com/svenefftinge

    ReplyDelete
  5. Yes, the plan is to start a little project together with Tom Schindl about special JavaFX support for Xtend. But no concrete dates yet.

    ReplyDelete
  6. In the demo video you said that this feature is currently not available, but in the comments you state that it's in the nightly update site. What is the current state? Am I able to get a running environment from "anywhere" ;-)?
    I tried the FXBean example with the current plugins fromo nightly update site, but I get compile errors ("Couldn't resolve reference to JvmIdentifiableElement 'inDerivedJavaClass'").

    ReplyDelete
  7. Oh this is awesome! Thank you very much, can't wait till you release it.

    ReplyDelete
  8. Where can I found information about the current state? Are there any news when this feature will be stable and released?

    ReplyDelete
    Replies
    1. An experimental version will be part of the next release, which we hope to get out during February. We'll post a concrete date within the next two weeks using the typical channels (xtend website, blogs, twitter, google+).

      Delete