Showing posts with label macros. Show all posts
Showing posts with label macros. Show all posts

Wednesday, September 04, 2013

Better I18n in Java

Internationalization is not the most loved topic for Java developers. This is merely because it makes code less readable and introduces the need to work with property files and constants. As a result there is no static typing in that area and we are in need to get supported by tools, such as IDEs. In this blog post I want to show how this can be elegantly solved with Xtend.

I18n - The JDK Way

The JDK offers well working facilities for externalizing messages and formatting values such as dates and currencies in a proper localized way. For externalizing messages the class ResourceBundle is your friend, as it not only reads and parses property files but also supports flexible way of how property values are looked up based on Locales. The other interesting bit is the MessageFormat hierarchy which contains strategies to turn data types into localized strings.

The provided functionality is very powerful but enforces a cumbersome programming model. The standard way is to have a bunch of property files and whenever you need to get the contained values you use a ResourceBundle which has to be created like this:

ResourceBundle messages = ResourceBundle.getBundle("MyMessages", localeToUse);

Now you obtain the messages using the property keys, which are just strings. To minimize very likely spelling problems and it is good practice to have a declaring all the keys as constants. Then you only have to declare them once in the code and once for each property file.

public class MyMessagesKeys {
  public final static GREETING = "greeting";
  ...
}

Clients can access the values using the constants :

messages.getString(MyMessagesKeys.MY_KEY);

A common requirement and feature is to have variables in the messages which should provided by the program. These arguments can even be of different types, where the MessageFormat types come in. But again the Java compiler doesn't know if there are any arguments, nor how many and of which types they are. So you are back in untyped land and will learn about your errors only at runtime.

Let's see how we can improve the API for the JDK classes, such that we get rid of all the duplication and weakly typed access.

DRY With Xtend

Xtend is not a language which is meant to be used instead of Java, but is an extension to Java. You should use it in places where Java just doesn't cut it, like in this example.

In the following we're going to built a statically typed API to be used from Java by means of an active annotation. The basic idea is, that we make the compiler turn a constant containing a message into a Java facade and a corresponding properties file. Consider the following declaration of messages:

@Externalized class MyMessages {
  val GREETING = "Hello {0}!"
  val DATE_AND_LOCATION = "Today is {0,date} and you are in {1}"
}

This is all the developer should need to declare. Now we implement an active annotation, that turns the field declarations into static methods. It even adds typed arguments for the placeholders in the messages. That is the generated Java provides the following sigatures:

// Generated Java signatures

/**
 * Hello {0}!
 */
public static String GREETING(String arg0) { ... }

/**
 * Today is {0,date} and you are in {1}
 */
public static String DATE_AND_LOCATION(Date arg0, String arg1) { ... }

The actual values are written to a *.properties file and the implementation of the static methods use a ResourceBundler to look them up. So in case you need a locale-specific translation, you only need to add an additional *.properties file. Even that could be easily validated for miss-spelled or non declared keys. But I'll leave this feature as an exercise for other people :-).

Java clients can now access the messages in a statically typed way, and if you are working within Eclipse you even get the default message displayed when hovering over a property.

Building The @Externalized Annotation

Developing the active annotation is relatively easy. First you need to create a separate Java project. Although an active annotation is just library, it has to sit in an upstream project or jar, i.e. cannot be used within the same project. We would run into chicken 'n egg problems during compilation if that was allowed.

In that project create a new Xtend file and name it Externalized.xtend. We first declare the annotation and annotate it with @Active so the compiler is aware of it being an active annotation. We also have to provide a processor class which is executed by the compiler:

@Active(ExternalizedProcessor) annotation Externalized {}

class ExternalizedProcessor extends AbstractClassProcessor {

  override doTransform(MutableClassDeclaration clazz, extension TransformationContext ctx) {
    // To be implemented ...
  }

  override doGenerateCode(ClassDeclaration clazz, extension CodeGenerationContext ctx) {
    // To be implemented ...
  }

}

As you can see two callback methods have been overridden, which correspond to certain phases in the compiler. The first phase we participate in is doTransform in which the annotated classes can be mutated. The second phase doGenerateCode allows us to write to the file system. We will later write the properties files during this phase. The doGenerateCode phase has been introduced in version 2.4.3, so make sure you have updated accordingly if you want to use this.

Step 1: Transforming The Fields Into Methods

We want to turn all field declarations into static methods, so we first iterate over the declared fields of the given class.

override doTransform(MutableClassDeclaration clazz, extension TransformationContext ctx) {

  for (field : clazz.declaredFields) {

    // get the actual value of the field
    val initializer = field.initializerAsString

    // create a message format object for that value
    val msgFormat = try {
      new MessageFormat(initializer)
    } catch(IllegalArgumentException e) {
      field.initializer.addError("invalid format : " + e.message)
      new MessageFormat("")
    }

    // check the syntax and report back in case of problems
    val formats = msgFormat.formatsByArgumentIndex
    if(msgFormat.formats.length != formats.length) {
      field.initializer.addWarning('Unused placeholders. They should start at index 0.')
    }

    // add a method using the field's name
    clazz.addMethod(field.simpleName) [

      // return type is always string and the method is static
      returnType = string
      static = true

      // add parameters for the given arguments in the message format object
      formats.forEach [ format, idx |
        addParameter("arg" + idx,
          switch format {
            NumberFormat: primitiveInt
            DateFormat: Date.newTypeReference()
            default: string
          })
      ]
      
      // add the value as a comment, for documentation purpose
      docComment = initializer
      
      // add the actual body. It's generated Java code.
      val params = parameters
      body = [
        '''
          try {
            String msg = RESOURCE_BUNDLE.getString("«field.simpleName»");
            «IF formats.length > 0»
              msg = «toJavaCode(MessageFormat.newTypeReference)»
                    .format(msg,«params.map[simpleName].join(",")»);
            «ENDIF»
            return msg;
          } catch («toJavaCode(MissingResourceException.newTypeReference)» e) {
            // TODO error logging
            return "«initializer»";
          }
        ''']
    ]
  }

  // now that we have the methods we can just remove the fields, 
  // as they are no longer needed
  clazz.declaredFields.forEach[remove]

  // add a static ResourceBundle to be used from the methods we just created.
  clazz.addField("RESOURCE_BUNDLE") [
    static = true
    final = true
    type = ResourceBundle.newTypeReference
    initializer = ['''ResourceBundle.getBundle("«clazz.qualifiedName»")''']
  ]
}

Now we have successfully transformed an @Externalized class written in Xtend into a Java class containing corresponding static methods.

Step 2: Generating The Properties File

The last thing we need to do now is to write the values of the fields into the properties file. For this we participate in the code generation phase by overriding doGenerateCode. While during doTransform we got a mutable Java representation of the original sources, in this phase we get the unmodifiable original source passed in.

override doGenerateCode(ClassDeclaration clazz, extension CodeGenerationContext ctx) {
    
  // obtain the target folder for the given compilation unit
  val targetFolder = clazz.compilationUnit.filePath.targetFolder
    
  // compute the path for the properties file
  val file = targetFolder.append(clazz.qualifiedName.replace('.', '/') + ".properties")

  // write the contents to the file
  file.contents = '''
    «FOR field : clazz.declaredFields»
      «field.simpleName» = «field.initializerAsString»
    «ENDFOR»
  '''
}

Check Out The New Release

The described example is included in the "active annotations examples" coming with latest release.

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: