The postings on this site are the contributor's and don’t necessarily represent IBM’s positions, strategies or opinions.
August 10th, 2008 Alex Moffat Posted in GWT | 5 Comments »
In the Google Web Toolkit Generators form part of the Deferred Binding mechanism. They are one way of producing different code for different browsers or locales. When you write
Type myObj = GWT.create(Type.class)
a Generator is invoked at GWT compile time, before the conversion from Java to JavaScript, to produce the concrete Java code that implements Type. Ray Cromwell from Timepedia has written a three part series (one, two, three) showing how to write a Generator. I think I also have a use for the Generator mechanism, here’s my take on how to write one.
For GWT to invoke the right Generator when it sees the GWT.create(Type.class) you need to define a generate-with element in a module xml file. The one below says that whenever GWT encounters a GWT.create(Type.class) statement and instances of Type can be assigned to variables declared as GenerateMe then the ExampleGenerator should be used.
<generate-with class="com.lombardi.gwtexample.generator.rebind.ExampleGenerator">
<when-type-assignable class="com.lombardi.gwtexample.generator.client.GenerateMe"/>
</generate-with>
Typically GenerateMe will be an interface and Type will implement that interface.
Generally you put the GenerateMe interface and the various classes that implement the Generator in their own module and add the generate-with element to that module’s module xml file. The GenerateMe interface goes in the client subpackage of the module and by convention the Generator itself goes in a rebind subpackage. Then users of the Generator just need to inherit the module to be able to use the generator.
<inherits name="com.lombardi.gwtexample.generator.ExampleGenerator"/>
After inheriting the right module I can define an interface called GenerateExample that extends GenerateMe like this
public interface GenerateExample extends GenerateMe {
public String getText();
}
and then use it in a simple program
public class Main implements EntryPoint {
private GenerateExample example = GWT.create(GenerateExample.class);
public void onModuleLoad() {
Window.alert(example.getText());
}
}
so that the ExampleGenerator will be called to produce an implementation of GenerateExample when the GWT.create statement is reached.
To create the ExampleGenerator itself you need to extend Generator and implement a single method,
public String generate(TreeLogger logger, GeneratorContext ctx, String requestedClass)
throws UnableToCompleteException
Here’s my first, and non functional, implementation.
public String generate(TreeLogger logger, GeneratorContext ctx, String requestedClass)
throws UnableToCompleteException {
TypeOracle typeOracle = ctx.getTypeOracle();
System.out.println("classType " + typeOracle.findType(requestedClass));
PropertyOracle propertyOracle = ctx.getPropertyOracle();
try {
System.out.println("user.agent " + propertyOracle.getPropertyValue(logger, "user.agent"));
} catch (BadPropertyValueException e) {
System.out.println("No user.agent property");
}
return null;
}
There are three points to notice. First, the TypeOracle. This gives you access to the types defined in the files being compiled. In the example code I find the type for the class that the ExampleGenerator was asked to generate and print it. Second, the PropertyOracle. This let’s you find the values for deferred binding properties. In the example I print the value of the user.agent property. Finally there is the value returned from the method. This is name of the class to use in place of the requested class. You are allowed to return null, in which case the requested class itself is used. Of course this won’t work in this case as GenerateExample is an interface.
However, I can try to compile the code, the result is interesting.
CompileGWTModule:
[java] Compiling module com.lombardi.gwtexample.generatoruser.GeneratorUser
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko1_8
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent ie6
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent opera
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent safari
[java] [ERROR] Errors in 'file:/Users/amoffat/Projects/GWTExamples/src/main/java/com/lombardi/...
[java] [ERROR] Line 40: Rebind result 'com.lombardi.gwtexample.generatoruser.client....
[java] [ERROR] Cannot proceed due to previous errors
[java] [ERROR] Build failed
You can see that the generator is called for all the different values of the user.agent property. As a quick hack to see what would happen if ExampleGenerator actually generated something I manually created an implementation of the GenerateExample interface called GenerateExampleImpl.
public class GenerateExampleImpl implements GenerateExample {
public String getText() {
return "Hello World";
}
}
and changed ExampleGenerator to return the fully qualified name of this class instead of null. Now when I try to compile I get
CompileGWTModule:
[java] Compiling module com.lombardi.gwtexample.generatoruser.GeneratorUser
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko1_8
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent ie6
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent opera
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent safari
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent gecko1_8
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent ie6
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent opera
[java] classType interface com.lombardi.gwtexample.generatoruser.client.GenerateExample
[java] user.agent safari
[java] Compilation succeeded
[java] Linking compilation into src/main/webapp/com.lombardi.gwtexample.generatoruser.GeneratorUser
You can see that it looks like the ExampleGenerator is being called twice for each value of user.agent. I’m not sure why this is. I used the manifest linker I created in an earlier post to look at all the property values in use.
Property Name Property Value -------------------------------------------------------- gwt.enforceRPCTypeVersioning true gwt.suppressNonStaticFinalFieldWarnings false locale default user.agent <varies>
I doesn’t appear that the value of anything other than user.agent is changing. I’m going to look at how to actually output some code in the next post.
August 17th, 2008 at 10:19 am
[...] GWT Generator Experiments Alex Moffat has written a nice, easy to follow article discussing GWT Generators. [...]
July 18th, 2009 at 12:22 am
[...] Continued prevalence of heavy BeanModel-based models and stores and expensive run-time massaging of data; someone should really teach them about generators [...]
August 4th, 2009 at 9:05 am
Good article. But anybody know what to do with warnings like
[ERROR] Line 6: No source code is available for type com.google.gwt.core.ext.Generator; did you forget to inherit a required module?
What should I inherit I have not found any module .xml file with this definition in gwt-linux-devel.jar ?
October 31st, 2009 at 5:40 pm
=( i’ve the same problem with source
May 2nd, 2010 at 3:02 pm
“Because the Generator stuff is all at compile time you can’t have your
Generator classes in packages under the client source tree (unless you specifically exclude them in a source tag).
So to anyone who is starting to work with the compile-time code generation facilities, be sure to keep your compile-time code out of your client package tree, or you will get the dreaded ‘No source code is available for type’ errors.”