With the following modification 3rd party widget libraries are enabled to leverage
GWT's new declarative ui binding feature. The goal is to add custom element parsers
to the GWT framework. These parsers are responsible for generating java code from
xml representations of layouts based on GWT widgets. The ability to add custom
parsers is the key to ensure correct java code generation of (complex) custom
widgets.
Currently the parsers are held in a private variable in UiBinderWriter. There's
no way to access it, so UiBinderWriter has to be modified in some way. Rewriting
UiBinderWriter and overwriting it in the corresponding jar (gwt-user.jar) is a brutal
way which I don't recommend. The main reason is that all GWT applications based on
that jar are affected. The following how-to describes a non-intrusive method which
applies the modification at runtime (in memory).
Table of contents:
- Hook into UiBinder
- Create custom Generator
- Modify UiBinderWriter
- Note
Hook into UiBinder
GWT generates an implementation of UiBinder at runtime which is specific for
the root widget of the .ui.xml and the java class which is aware of the widget
instance. Here's the well known code which triggers the code generation:
The code generator UiBinderGenerator instantiates UiBinderWriter which is aware
of the mentioned element parsers. So replacing UiBinderGenerator is a way to
also replace UiBinderWriter. Because GWT offers the ability to hook in custom
generators within the GWT module configuration, com.google.gwt.uibinder.rebind.UiBinderGenerator
can be overwritten with a custom one (here: cafebab3.rebind.custom.UiBinderGenerator).
The GWT.create(...) method is taking care of this.
Corresponding snippet of the main .gwt.xml:
As a result GWT binds every .ui.xml via cafebab3.rebind.custom.UiBinderGenerator.
Create custom Generator
I didn't want to re-implement the whole UiBinderGenerator. Instead I use
delegation where the delegate is the original UiBinderGenerator. The 'trick'
is to load the delegate with a custom ClassLoader which returns a modified
UiBinderWriter class if it is asked for the original one. We know it is
asked eventually because the original UiBinderGenerator instantiates one.
Here's the (one hell of a) ClassLoader. It needs the modified UiBinderWriter.class
to be renamed to UiBinderWriter.bytecode and placed in the same package.
UPDATE: Fixed an issue with requesting specific classes several times.
The *magic* Bytecode class used in the UiBinderClassLoader is needed to locate all
classes, fields and methods with default visibility (= package private visibility)
(or which are protected) and make them public (= unlock them).
This is necessary because two classes A and B with default visiblity, which are
in the same package, don't see each other if they are loaded by different
ClassLoaders (internally the ClassLoader is part of the namespace in addition to
the package).
I thought about using an existing bytecode manipulation library like
ASM
but these libraries are huge (asm.jar > 40 mb) and so I decided to implement my
own one.
The result is a tiny class which only modifies access flags. This 'unlocking'
of bytecode takes round about 10 ms when called first time, and 0 to 1 ms when
called subsequently (tested on my notebook).
Caution: Bytecode manipulation should be considered as a hack. Changing the visibility
of classes, methods or fields may also be a change of the semantic of the java program
(if it uses reflection for example).
Modify UiBinderWriter
All the steps above are nice but not necessary if the modified UiBinderWriter is
copied directly into the corresponding jar (gwt-user.jar) which I decided to be
no good idea because it is intrusive.
Recalling that the goal is to supply custom element parsers.
At compile time the custom 3rd party widget parsers are not known by the GWT framework.
My preferred solution is to annotate widgets with a hint which parser to use.
When initializing the pool of parsers, all packages have to be searched for Widgets.
Finding such an annotation containing a parser hint this hint will be taken into account.
The implementation of the UiBinderWriter modification will be shown in my next post.
Additionally I will give an example for ui binding GXT widgets. Stay tuned!
Note
Manually copying the modified UiBinderWriter.class is crude.
A simple way to provide the file UiBinderWriter.bytecode is to create an
additional project (next to the GWT project), including the GWT libraries
and the modified UiBinderWriter (in the package com.google.gwt.uibinder.rebind).
Deployment to the GWT project can be done like this:
Don't forget to refresh the workspace when manually copying files into it!