GWT: generating the factory

Controllers galore

In my current GWT project, we make heavy use of MVC, which means we have controllers and views all over the place. To link menu items and other actions to screens, the action item only keeps a reference to the controller class. This implies that we have to be able to instantiate the controller when the action is triggered.

No problem, we have a controllerClass, so we call controllerClass.newInstance(), right?

Well, not in the GWT world.

When you’re running the web version of your GWT application, due to the amount of meta-data that would have to be carried together with your code, Java reflection capabilities are not available. Which means that, even though the Class class exists, you can’t use it to list methods or fields, and you definitely can’t use it to obtain an instance of that class. This is a classic trap, because it might appear to work in hosted mode, simply because hosted mode runs in a standard JVM… but when you try to compile and run your application in web mode, you can forget it.

The poor man’s dynamic instantiation

Basically, the only option you have when you want to instantiate a class from a class reference, is to compare it with known classes, and when one is recognized, to call “new”, like this:

if (controllerClass == Controller1.class) {
  return new Controller1();
}
if (controllerClass == Controller2.class) {
  return new Controller2();
}
// (many lines omitted...)
throw new IllegalArgumentException("The class " +
 controllerClass.getName() + " is unknown in this factory.");

You can see how this is painful:

  • whenever you create a new controller, you have to remember to add it to this method, otherwise instantiation will fail during runtime
  • this method rapidly grows and becomes unmaintainable

To make things as clean as possible, this should be wrapped in a controller factory: a simple class that implements an interface with a single method

Controller createController(Class<? extends Controller> controllerClass) throws IllegalArgumentException;

While this hides the “details” of the implementation, it does not remove the burden of maintaining the factory method.

This is when GWT’s deferred binding mechanism can be useful. To compensate for the lack of runtime introspection, the GWT compiler can dynamically (during compilation) provide the best-suited implementation of an interface, or subclass of a given class. The client requests an implementation of this class by calling GWT.create(MyInterface.class).

The replacement class can be provided in one of two ways:

  • by substitution: under certain conditions, the class is substituted with an existing implementation. This is how GWT internally handles differences between browsers.
  • by generation: the implementation is constructed at compile-time by a Generator.

So why not use the second option and let the GWT compiler generate the factory class for us? It’s simple enough, we just need to generate an if statement for each known controller. So let’s try to do that.

Declaring the generator

First we need to tell the GWT compiler to invoke our generator, and when. Suppose our factory method is contained in an interface named ControllerFactory, then we just need to add the following lines to the GWT module file:

    <generate-with class="oge.gwt.rebind.ControllerFactoryGenerator">
         <when-type-assignable class="oge.gwt.client.ControllerFactory"/>
    </generate-with>

This translates to “when the client calls GWT.create() with a parameter class that is assignable to ControllerFactory, use ControllerFactoryGenerator to build the implementation class and return an instance of this class”. The interface must reside in the client package, but the generator must be somewhere else. The common practice is to put all generators in a package named “rebind”.

Next the generator itself.

Building the generator

The generator must implement the interface com.google.gwt.core.ext.Generator, which defines a single method:

generate(TreeLogger logger, GeneratorContext context, java.lang.String typeName)

where:

  • logger is used to log debug/info messages
  • context is used to obtain context information, and helpers to write the generated class
  • typeName is the name of the type being substitued; in our case this should always be “oge.gwt.client.ControllerFactory”

This method is invoked by the GWT compiler whenever the generate-with clause of the module is activated. The generated class must be written as full java source like you would write by hand; luckily some convenience methods exist.

The first thing the generator needs to decide is what the name of the generated class will be. The common practice is to add the “Impl” suffix to the interface name (we use “GeneratedImpl” to distinguish it from our hand-coded implementation).

This is how we do it:

public String generate(TreeLogger logger, GeneratorContext context, String typeName) throws UnableToCompleteException {

 logger.log(TreeLogger.Type.INFO, "Generating " + typeName);

 this.typeName = typeName;
 TypeOracle typeOracle = context.getTypeOracle();

 try {
 // obtain JClassType instance for target type and construct target class name
 JClassType classType = typeOracle.getType(typeName);
 this.packageName = classType.getPackage().getName();
 this.className = classType.getSimpleSourceName() + "GeneratedImpl";

 // generate class source code
 generateClass(logger, context);

 } catch (Exception e) {
 logger.log(TreeLogger.ERROR, "Failed to generate implementation for " + typeName, e);
 }

 // return the fully qualifed name of the generated class
 return this.packageName + "." + this.className;
 }

Things to note:

  • we use TypeOracle (from the context) to obtain a JClassType instance for the type being substituted. TypeOracle and the associated classes (JClassType, JPackage, JField, JConstructor, JMethod, etc.) provide a powerful tool to query Java source and meta-information. It can be seen as the equivalent of native Java introspection. Once we have our JClassType instance, we can easily obtain the package name and class name.
  • if the generation succeeds, the method must return the fully qualified name of the generated class

Generating the class

The class generation itself requires the following steps:

  1. Begin source generation by calling context.tryCreate(logger, packageName, className). This method returns a PrintWriter, which we will use to write the source code. If it returns null, it means that the target class has already been generated and we can safely skip the generation.
  2. Instantiate a ClassSourceFileComposerFactory with the package name and class name. This is a utility class that helps building the target class.
  3. If necessary, call addImport(),  addImplementedInterface(), setSuperclass() on the ClassSourceFileComposerFactory instance.
  4. Obtain a SourceWriter by calling createSourceWriter(context, printWriter) on the instance of ClassSourceFileComposerFactory.
  5. Use methods of SourceWriter (println(), indent(), outdent(), …) to write the meat of the class.
  6. When you’re done, commit the generated source by calling context.commit(logger, printWriter).

This is how we do it for our controller factory:

private void generateClass(TreeLogger logger, GeneratorContext context) throws Exception {

 // get a PrintWriter to write source code to
 PrintWriter printWriter = context.tryCreate(logger, packageName, className);
 if (printWriter == null) {
 // source code has already been generated, abort
 return;
 }

 ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(packageName, className);
 composer.addImport("oge.gwt.client.mvc.Controller");
 composer.addImplementedInterface(typeName);

 SourceWriter sourceWriter = composer.createSourceWriter(context, printWriter);

 generateCreateControllerMethod(sourceWriter, context.getTypeOracle());

 sourceWriter.println("}");

 // commit source generation
 context.commit(logger, printWriter);
 }

Almost done… now we only need to implement generateCreateControllerMethod(), which will generate the body of the method “createController()”. To do this, we need to generate a block like the following for each controller class that might be instantiated dynamically at runtime:

if (controllerClass == ControllerXXX.class) {
  return new ControllerXXX();
}

To do this, we wil simply iterate on all known classes by using getTypes() from TypeOracle, and retain only those that:

  • implement our substituted interface (ControllerFactory)
  • are public
  • are not abstract
  • have a public no-args constructor

These conditions are checked by using various methods on JClassType.

This is the result:

private void generateCreateControllerMethod(SourceWriter sourceWriter, TypeOracle typeOracle) throws Exception {

 // start method source generation
 sourceWriter.println("public Controller createController(Class<? extends Controller> controllerClass) throws IllegalArgumentException {");
 sourceWriter.indent();

 JClassType simpleViewClassType = typeOracle.getType("oge.gwt.client.mvc.Controller");

 JClassType classTypes[] = typeOracle.getTypes();
 for (JClassType classType : classTypes) {

 // we're only interested in subclasses of simpleViewClassType that are public and not abstract
 if (!classType.isAssignableTo(simpleViewClassType)
 || !classType.isPublic()
 || classType.isAbstract()) {
 continue;
 }

 // check that we have a no-arg constructor
 try {
 classType.getConstructor(new JType[] {});
 } catch (NotFoundException nfe) {
 continue;
 }

 System.out.println("Found controller: " + classType);
 sourceWriter.println("if (controllerClass == " + classType.getQualifiedSourceName() + ".class) {");
 sourceWriter.println("  return new " + classType.getQualifiedSourceName() + "();");
 sourceWriter.println("}");
 }

 sourceWriter.println("throw new IllegalArgumentException(\"The class \" + controllerClass.getName() + \" is unknown in this factory.\");");

 // end methods source generation
 sourceWriter.outdent();
 sourceWriter.println("}");

 }

Using the generated factory

Finally, we need to replace our old manual instantiation of the hand-coded ControllerFactoryImpl with a call to GWT.create() so that the generator mechanism is triggered:

//controllerFactory = new SimpleViewControllerFactoryImpl();
controllerFactory = GWT.create(SimpleViewControllerFactory.class);

Now when we GWT-compile the application, we will see our log message indicating that the generator is being called:

 Computing all possible rebind results for 'oge.gwt.client.ControllerFactory'
 Rebinding oge.gwt.client.ControllerFactory
 Invoking <generate-with class='oge.gwt.rebind.ControllerFactoryGenerator'/>
 Generating oge.gwt.client.ControllerFactory

The application should work exactly as before, since we only substituted a hand-written implementation of ControllerFactory with a computer generated version, which does exactly the same.

You will notice that the generator also gets called when you reload the hosted mode window… this makes sense, because you could very well have added a new Controller , in which case the factory needs to be updated. This is the only downside of this approach, but in my opinion the benefits are well worth the added overhead.


10 thoughts on “GWT: generating the factory

    1. Mostly because I wanted to learn how to use GWT generators 🙂
      I considered Gin but it seemed overkill for what I wanted to do.

  1. Thank you so much for this blog post. I always wanted to know about Generators and thanks to your insightful introduction I finally understood what they are all about.

  2. Nice article.

    One quick question that you may be able to help me with:

    If the generated output does not compile then logs will point to a java file in your temp folder but if everything compiles OK but doesn’t do exactly what it should how (or where) can you see to generated coded?

    Thanks,
    Dave

    1. Sure..

      If you set the GWT compiler’s log level to DEBUG (warning: it generates an insane amount of messages!), somewhere in the junk you’ll see this sequence:
      Invoking <generate-with class='your-generator-class'/>
      Generating substituted-target-class
      (your own log messages...)

      and after it’s done:
      Generator returned class 'your-generated-class'
      Finished in 35 ms
      Assimilating generated source
      Generated source files...
      file:/path/to/generated/source.java

      That’s where you will find the generated source file.

  3. I have almost no experience in GWT but isn’t the simplest solution to your “generator problem” to have a single class ControllerResolver with a static resolve(Class clazz) method as follows:

    public static native Object resolve(Class c) /*-{
    return eval(“@”+c.@java.lang.Class::getName()+”::new()()”);
    }-*/;

    or something along those lines (have not tested for correctness, just trying to give the idea)

    1. That would be nice, but unfortunately it doesn’t work: eval chokes on the “new” syntax.
      I believe you can only use this construct when you have a literal class name, not inside an eval string.

  4. Or maybe a better class/method name combination is GWTInstanciator for the class and newInstance for the method.

  5. Thanks a lot for the write-up – just what I needed. It’s a nice and clear intro to generators.

Leave a Reply

Your email address will not be published. Required fields are marked *