Goal
Using GWT generator to find out the timestamp of when the GWT project was compiled. You can use this as a very simplified build ID.
Steps
Step1: Define an interface. The implementation of this interface will be generated at the compile time by the GWT generator. In my case, the interface is as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.afzal.gentest.client; | |
public interface BuildInfo { | |
public String getBuildTimestamp(); | |
} |
Step2: Write a generator that will generate a class which implements this interface. In my case, the generator is as follows:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.afzal.gentest.rebind; | |
import java.io.PrintWriter; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
import com.afzal.gentest.client.BuildInfo; | |
import com.google.gwt.core.ext.Generator; | |
import com.google.gwt.core.ext.GeneratorContext; | |
import com.google.gwt.core.ext.TreeLogger; | |
import com.google.gwt.core.ext.UnableToCompleteException; | |
import com.google.gwt.core.ext.typeinfo.JClassType; | |
import com.google.gwt.core.ext.typeinfo.TypeOracle; | |
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory; | |
import com.google.gwt.user.rebind.SourceWriter; | |
public class BuildInfoGenerator extends Generator { | |
@Override | |
public String generate(TreeLogger logger, GeneratorContext context, | |
String typeName) throws UnableToCompleteException { | |
TypeOracle typeOracle = context.getTypeOracle(); | |
assert (typeOracle != null); | |
JClassType classType = typeOracle.findType(typeName); | |
if (classType == null) { | |
logger.log(TreeLogger.ERROR, "Unable to find metadata for type '" + typeName + "'", null); | |
throw new UnableToCompleteException(); | |
} | |
String packageName = classType.getPackage().getName(); | |
String simpleName = classType.getSimpleSourceName() + "Impl"; | |
String qualifiedClassName = packageName + "." + simpleName; | |
ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory( | |
packageName, simpleName); | |
composerFactory.addImport(BuildInfo.class.getCanonicalName()); | |
composerFactory.addImplementedInterface(BuildInfo.class.getName()); | |
PrintWriter printWriter = context.tryCreate(logger, packageName, simpleName); | |
if (printWriter == null) | |
return qualifiedClassName; | |
SourceWriter sourceWriter = composerFactory.createSourceWriter(context, printWriter); | |
if (sourceWriter == null) | |
return qualifiedClassName; | |
String buildTimestamp = getCurrentTimestampString(); | |
// write the method body of getBuildTimestamp | |
sourceWriter.println("public String getBuildTimestamp() {"); | |
sourceWriter.println(" return \"" + buildTimestamp + "\";"); | |
sourceWriter.println("}"); | |
// method body ends | |
sourceWriter.commit(logger); | |
return packageName + "." + simpleName; | |
} | |
private String getCurrentTimestampString() { | |
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmm"); | |
return format.format(new Date()); | |
} | |
} |
Step3: In the module xml, tell GWT compiler to generate any instance of BuildInfo interface with the above generator. In my case that section
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<generate-with class="com.afzal.gentest.rebind.BuildInfoGenerator"> | |
<when-type-assignable class="com.afzal.gentest.client.BuildInfo" /> | |
</generate-with> |
Step4: Instantiate the BuildInfo interface through GWT.create(). Since I already have told the GWT compiler to generate any such case with the generator, the returned class will be actually generated with the above generator. After that simply use the returned class.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.afzal.gentest.client; | |
import com.google.gwt.core.client.EntryPoint; | |
import com.google.gwt.core.shared.GWT; | |
import com.google.gwt.user.client.Window; | |
public class Gwt_generator_test implements EntryPoint { | |
public void onModuleLoad() { | |
BuildInfo buildInfo = GWT.create(BuildInfo.class); | |
Window.alert("Build Timestamp = " + buildInfo.getBuildTimestamp()); | |
} | |
} |
Step5: Compile and run the app in production mode. Or just run the app in development mode. You will be greeted with a popup alert, which will state something like below:
The timestamp in the above screenshot was actually generated by the BuildInfoGenerator, which kind of hard coded it when the web app was compiled.
That's it, that was a fully working example of GWT generator generating a class. Even though this specific generator does not add much functionality, this should give you the basic steps required of GWT generators.
If you want to download the complete Eclipse project for the above code, you can download it from the dropbox:
Download complete Eclipse GWT project as ZIP file
Few important points
- Instead of passing GWT.create() an interface, you can pass a concrete implementation too. If you return null from the generate() method, it will use the original class that was used as the argument of GWT.create(). This could be a good way only to generate the code when some special conditions are true, and not always. If the argument was an interface (as in my case), that will raise a compile time error.
- It's very important to understand, the generate method of the generator class (in my case BuildInfoGenerator) will be invoked more than once, one for each compile permutation.
- GeneratorContext.tryCreate() method (line 40) will return a PrintWriter if one already does not exist. Since generate method is called more than once, it means after the first time, tryCreate() will return null. It's very important that instead of returning null in such cases, you return the fully qualified class name (see line 42). Otherwise code generations will not work for these compile permutations, and you will be facing some very unintuitive behavior.
The Java code emitting as above is not very intuitive. I discussed some basic ways to debug this process here. If you are facing any problems, like compile errors or something else, do have a look.
No comments:
Post a Comment