Home » Featured, Headline, Web Development, Web Experiments

A Simple GWT Generator Example

9 April 2010 No Comment
A Simple GWT Generator Example

Generators allow the GWT coder to generate Java code at compile time and have it then be compiled along with the rest of the project into JavaScript. They are a sort of similar to T4 Templates in the .NET world. I don’t recommend Generators because you end up writing a lot of code in printLn()s which is sort of the opposite of “maintainability”. However, there were so few examples available I decided to post a simple one here just for the sake of it.

My Use Case is I want to generate a screen off of metadata and have it hooked to events written in Java code.

First you’ll need an interface that will be used to trigger the Generator. Anytime compiler sees this interface it’ll swap in a generated class.

package com.crap.crapGWT.client;

import com.google.gwt.user.client.ui.RootPanel;

public interface ScreenObject {
	void PaintScreen(RootPanel rp);
}

Next you need an implementor of that interface

package com.crap.crapGWT.client;

import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.RootPanel;

public class ExampleScreen implements ScreenObject {

	@Override
	public void PaintScreen(RootPanel rp) {
             // Leave this empty
	}
}

Next you need a flag in the main GWT XML module file:

<generate-with class="com.crap.crapGWT.server.WidgetGenerator">
	<when-type-assignable class="com.crap.crapGWT.client.ScreenObject" />		
</generate-with>  

The above tells the compiler to trigger the generator (WidgetGenerator). This needs to go in your server code, not the client or shared.

package com.crap.crapGWT.server;

import java.io.PrintWriter;

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.NotFoundException;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;

public class WidgetGenerator extends Generator {

	@Override
	public String generate(TreeLogger logger, GeneratorContext context,
			String typeName) throws UnableToCompleteException {
		// Build a new class, that implements a "paintScreen" method
		JClassType classType;
		
		try {
			classType = context.getTypeOracle().getType(typeName);
			
			// Here you would retrieve the metadata based on typeName for this Screen
			SourceWriter src = getSourceWriter(classType, context, logger);
			src.println("public void PaintScreen(RootPanel rp) {");		
			
			// Here you would iterate through the metadata adding elements to the code
			src.println("Button myButton = new Button(\"Click Me\");");
			
			// Now hook up the events, in this case the naming convention is <Class>Events so
			// "ExampleScreen" becomes "ExampleScreenEvents" 				
			src.println("myButton.addClickHandler(new " + typeName + "Events());");			
			src.println("rp.get().add(myButton);");
			
			src.println("}");
			src.commit(logger);
			
			System.out.println("Generating for: " + typeName);
			return typeName + "Generated";
			
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	public SourceWriter getSourceWriter(JClassType classType,
			GeneratorContext context, TreeLogger logger) {
		String packageName = classType.getPackage().getName();
		String simpleName = classType.getSimpleSourceName() + "Generated";
		ClassSourceFileComposerFactory composer = new ClassSourceFileComposerFactory(
				packageName, simpleName);
		composer.setSuperclass(classType.getName());		
		composer.addImplementedInterface("com.crap.crapGWT.client.ScreenObject");
		
		// Need to add whatever imports your generated class needs. 
		composer.addImport("com.crap.crapGWT.client.ScreenObject");
		composer.addImport("com.google.gwt.user.client.Window");
		composer.addImport("com.google.gwt.user.client.rpc.AsyncCallback");
		composer.addImport("com.google.gwt.user.client.ui.Button");
		composer.addImport("com.google.gwt.user.client.ui.RootPanel");
		
		PrintWriter printWriter = context.tryCreate(logger, packageName,
				simpleName);
		if (printWriter == null) {
			return null;
		} else {
			SourceWriter sw = composer.createSourceWriter(context, printWriter);
			return sw;
		}
	}
}

This is the meat and potatoes of the business. You’ll see the code is doing a println to print out the actual Java that’ll then be coupled with the rest of the project. Very messy stuff.

You’ll also notice I’ve tied the button above to an event Handler implemented in “” + “Events” so in this case ExampleScreenEvents.java:


package com.crap.crapGWT.client;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;

public class ExampleScreenEvents implements ClickHandler {

	@Override
	public void onClick(ClickEvent event) {
		Window.alert("Hello from onClick of MyExampleScreenEvent Handler");
	}
}

Finally you need to create the screen, in onModuleLoad just add the following:

ScreenObject s = GWT.create(ExampleScreen.class);
s.PaintScreen(RootPanel.get());

Presto, it works. Run the sample, you’ll see a button added to the form, click it and you’ll get the hello world message.

There are a lot of issues with this. If you make a mistake, your code will break at compile time which is good, but since it’s breaking on a generated file, you’ll need to go digging in the TEMP folder to find the actual Java code that’s broken. For me this was located in C:\Documents and Settings\fs11782\Local Settings\Temp\ExampleScreenWrapper4091248644661218253.java
Exactly like T4 templates. Very messy.

The code is written out using strings, that makes it tough to maintain. It was very often necessary to import the generated file into the project to see what the issue was, then adjust the generator and then run it again.

Every time I did this Eclipse failed to recognize the changes and would only pick them up after a shutdown/restart of the IDE.

Generators in GWT: not recommended but there you are.

Let’s take a step back; GWT is supposed to solve the “problem” (false premise) of writing JavaScript. JavaScript is “bad” because it’s a dynamic language, loosely typed, interpreted and so on. Yet those are also advantages. Thinking back a few years I seem to recall a similar discussion between Lisp programmers and C++. By introducing Generators, I’ve actually lost all the perceived benefits of GWT, writing code out as a println() for example. I do get to write my code in Java but at a fairly high cost and high level of complexity with multiple layers of abstraction introduced into the solution.

Leave your response!

Add your comment below, or trackback from your own site. You can also subscribe to these comments via RSS.

Be nice. Keep it clean. Stay on topic. No spam.

You can use these tags:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

This is a Gravatar-enabled weblog. To get your own globally-recognized-avatar, please register at Gravatar.