A life committed to learning.

Where’s my Java reflection?

Some people like it, others don’t. I like it, it’s so cool to make generic code, but as I ‘ve been experimenting with GTW to know it’s capabilities I found that GWT does not support Reflection. It uses instead a mechanism called Deffered Binding to overcome the lack of reflection.

When doing some kind of framework code with GWT, soon you’ll realize that you need reflection, as I did. Bad luck, you’ll have to dig into the underground features of GWT, namely deferred bindings and generators. You can learn the theoretical traits of Deferred Bindings in the documentation: http://code.google.com/webtoolkit/doc/latest/DevGuideCodingBasicsDeferred.html

Let me explain my scenario.

I do all client/server communication using JSON, and the form field errors are returned from the server in JSON like this:

{"errors": {"name": ["Error 1 on field name",  "Error 1 on field name"], "email:  ["Error 1 on field email",  "Error 1 on field email"]}, "_errorMessage": "Could not save the record due validation errors"}

My form editor at the GWT side, has some kind of Editors for each field of the form that implements an interface like this:

public interface Errorable {
	public void setError();
	public void setError(String message);
	public void setErrors(List<String> messages);
	public void clearErrors();
	public boolean hasErrors();
}

My form component has something like this:

public class UserEditor extends Composite implements CompositeEditor<User> {

	@UiField
	TextBoxEditor name;

	@UiField
	TextBoxEditor email;
         //Rest of the code

	public void setFieldErrors(Map<String, List<String>> fieldErrors) {

		//Bind error fields
	}

}

TextEditor class implements the interface Errorable and the implementation of setErrors(List messages) simply shows the validation errors underneath the text box, the usual stuff…

The UserEditor::setFieldErrors(Map> fieldErrors) get a Map produced from the JSON response, with the name of the field as key and a list of Strings representing each error in the field, and show the errors for the corresponding field in the UserEditor

My problem is with the implementation of UserEditor::setFieldErrors(Map> fieldErrors) , I did’n want to do something like this:

	public void setFieldErrors(Map<String, List<String>> fieldErrors) {

		//Bind error fields
		if ( fieldErrors.containsKey("name")) {
			name.setErrors(fieldErrors.get("name"));
		}

		if ( fieldErrors.containsKey("email")) {
			name.setErrors(fieldErrors.get("email"));
		}

	}

This could be a small amount of code now, but for a bigger application this is too much code I have to write by hand. So, here was where I needed the Java reflection. I needed to go through each field of FormEditor and find the members that implements the Erroable interface and check if the Map has a key with the same name and the call the setErros(List) on that members… you get it, right?

This could be done in runtime if I have access to Java reflection features in GWT, but I don’t and what I can have in GWT is code generation at compile time, so the solution to the problem can be slightly different than doing it with Java Reflection, but If I can generate that boilerplate code it would be great.

From here I digged into GWT code generation, and borrow some ideas from the UIBinder.

My UserEditor.java has now something like this:

public class UserEditor extends Composite implements CompositeEditor<User> {

private static UserEditorErrorBinder errorBinder = GWT
			.create(UserEditorErrorBinder.class);

interface UserEditorErrorBinder extends ErrorBinder<UserEditor> {

	}
}

The ErrorBinder interface is defined as:

public interface ErrorBinder<T> {
	public void bindErrors(T editor, Map<String, List<String>> errors);
}

Now I have to implement one generator that generate the code that implements the method bindErrors with my desired logic.

After solving many puzzles, I came out with the this generator code that you can borrow to do your own generators in GWT:

public class ErrorBinderGenerator extends Generator {

	private String implPackageName;

	private String implTypeName;
	private JClassType parameterizedType;

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

		TypeOracle typeOracle = context.getTypeOracle();

		JClassType objectType = typeOracle.findType(requestedClass);
		if (objectType == null) {
			logger.log(TreeLogger.ERROR, "Could not find type: "
					+ requestedClass);
			throw new UnableToCompleteException();
		}

		implTypeName = objectType.getSimpleSourceName() + "Impl";

		implPackageName = objectType.getPackage().getName();

		JClassType[] implementedTypes = objectType.getImplementedInterfaces();

		// Can only implement one interface
		if (implementedTypes == null
				|| implementedTypes.length != 1
				|| !implementedTypes[0].getQualifiedSourceName().equals(
						ErrorBinder.class.getName())) {
			logger.log(TreeLogger.ERROR, "The type: " + requestedClass
					+ " Must implement only one interface: "
					+ ErrorBinder.class.getName());
			throw new UnableToCompleteException();
		}

		// Get parameterized type
		JParameterizedType parameterType = implementedTypes[0]
				.isParameterized();
		if (parameterType == null) {
			logger.log(TreeLogger.ERROR, "The type: " + requestedClass
					+ " Must implement only one parameterized interface: "
					+ ErrorBinder.class.getName());
			throw new UnableToCompleteException();
		}

		if (parameterType.getTypeArgs() == null

		|| parameterType.getTypeArgs().length != 1) {
			logger.log(TreeLogger.ERROR, "The type: " + requestedClass
					+ " Must implement only one parameterized interface: "
					+ ErrorBinder.class.getName() + " with only onde Parameter");
			throw new UnableToCompleteException();

		}

		parameterizedType = parameterType.getTypeArgs()[0];

		ClassSourceFileComposerFactory composerFactory = new ClassSourceFileComposerFactory(
				implPackageName, implTypeName);

		composerFactory.addImport(Map.class.getCanonicalName());
		composerFactory.addImport(List.class.getCanonicalName());
		composerFactory.addImplementedInterface(objectType
				.getQualifiedSourceName());

		PrintWriter printWriter = context.tryCreate(logger, implPackageName,
				implTypeName);
		if (printWriter != null) {

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

			composeBindErrorsMethod(sourceWriter);
			sourceWriter.commit(logger);

		}
		return implPackageName + "." + implTypeName;
	}

	private void composeBindErrorsMethod(SourceWriter sourceWriter) {

		sourceWriter.print("public void bindErrors("
				+ parameterizedType.getQualifiedSourceName()
				+ " erroable, Map<String, List<String>> errors) {");
		sourceWriter.print("  System.out.println(\"Implement it now:)\");");
		sourceWriter.print("}");

	}
}

Now I have only to generate the code for bindErrors and implement the setFiledErrors method in my UserEditor like this:

	public void setFieldErrors(Map<String, List<String>> fieldErrors) {
		errorBinder.bindErrors(this, fieldErrors);
	}

I hope this can help someone to bootstrap their own GWT generators.

jpereira

http://jpereira.eu

View more posts from this author
3 thoughts on “Where’s my Java reflection?
  1. Colin Alworth

    Nice – I would suggest looking into the print(String, Object…) overloads on SourceWriter to make your code a little easier to read/write. Additionally, using SourceWriter.indent(), SourceWriter.outdent() and println instead of print can make the generated code easier to read and debug. Line 83 to 87 could look like this:

    sourceWriter.println("public void bindErrors(%s errorable, Map<String, List<String>> errors) {", parameterizedType.getQualifiedSourceName());
    sourceWriter.indent();
    sourceWriter.println("System.out.println("Implement it now: ")");
    sourceWriter.outdent();
    sourceWriter.println("}");
    

    Edited for generics/tags

     
  2. Thomas Broyer

    …and you’ll soon (GWT 2.3 ?) be able to use a setConstraintViolations() on the SimpleBeanEditorDriver and take advantage of the HasEditorErrors.

    That being said, maybe you should extend the AbstractEditorDriverGenerator (or maybe you could extend the SimpleBeanEditorDriverGenerator) to do you own setErrors on a XxxEditorGenerator of your own (or one that extends SimpleBeanEditorDriver)

     

Leave a Reply

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