A data binding framework that generates type-safe binding classes.
Or, OGNL with no strings.
Originally built for the joist web framework. See joist's bindgen page for more docs.
A test case:
public void testEmployerThroughEmployee() {
Employer er = new Employer();
er.name = "at&t";
Employee ee = new Employee();
ee.name = "bob";
ee.employer = er;
EmployeeBinding eb = new EmployeeBinding(ee); // EmployeeBinding is generated by bindgen
eb.name(); // name() returns a StringBinding instance instead of the actual name String
// During rendering TextBox calls StringBinding.get()
Assert.assertEquals("bob", new TextBox(eb.name()).toString());
Assert.assertEquals("at&t", new TextBox(eb.employer().name()).toString());
// During POST procesing TextBox calls StringBinding.set()
new TextBox(eb.name()).set("newBob");
new TextBox(eb.employer().name()).set("newAt&t");
Assert.assertEquals("newBob", ee.name);
Assert.assertEquals("newAt&t", er.name);
}
The point being that eb.employer().name()
does not immediately return the value of name
, but instead returns a StringBinding
that the web framework can bind values into/out of as it serves the request.
Bindgen is implemented as JDK6 annotation processor. When configured in your IDE (e.g. with project-specific settings in Eclipse), as soon as you add a @Bindable
annotation to a class Foo
, and hit save, the IDE immediately invokes the Processor behind the scenes and FooBinding
is created.
This is a spike from a Click-like web framework I'm hacking around on:
@Bindable
public class HomePage extends AbstractPage {
public Form form = new Form("Login");
public String username = "blah";
public String password;
@Override
public void onInit() {
HomePageBinding b = bind(this); // static import of BindKeyword.bind
this.form.add(new TextField(b.username()));
this.form.add(new TextField(b.password()));
this.form.add(new SubmitField(b.submit()));
}
public void submit() {
// do stuff with this.username and this.password
}
}
The HomePageBinding
class is auto-generated because of the @Bindable
annotation on the HomePage
class.
When the form POSTs, the TextFields call the Binding.set
methods with their form values, which populates the this.username
and this.password
fields.
Fun things like type conversion using Binding.getType()
method to go from strings -> whatever would be possible too.
Stateless bindings allows a single Binding
instance to be evaluated against multiple roots in a thread-safe manner.
For example:
// Make just once instance of PersonBinding/StringBindingPath
PersonBinding p = new PersonBinding();
// Get a binding to their first name, through the Demographics object
StringBindingPath<Person> b = p.demographics().firstName();
// thread1
b.getWithRoot(bob); // returns Bob
b.setWithRoob(bob, "Bobby"); // changes bob
// thread2
b.getWithRoot(fred); // returns Fred
None of the getWithRoot
/setWithRoot
invocations will step on each other's toes if running concurrently.
For more examples, see MethodExampleStatelessTest.
- Eclipse: 3.5 works best--3.4 has several bugs that were fixed (see 263985)
- Eclipse: Annotating packages does not work
- Eclipse: Must be run on a JDK6 JVM--for Macs, this means 3.5 64-bit on the Apple 64-bit JDK6
- IntelliJ: Has mediocre support for annotation processors (last I checked)
javac
: Does not properly re-use already-generated classes, so pass-AskipExistingBindingCheck=true
to re-generate all of the binding classes each time
- Support extension methods, e.g. StringBinding could have extra methods like
length()
,substring()
, etc., ideally configurable - done. - Optional null-safe get/set, e.g.
eb.employer().name()
with a nullemployer
could haveget()
returnnull
and not NPE andset()
could create anew Employer()
to then callsetName()
on to again avoid the NPE - Document options,
fixRawTypes
,bindgen.log
, etc. - Package a
bindgen-profiled
that has post-processed/something basic wall clock timing for performance analysis - Make
Util.resolveTypeVarIfPossible
go away in favor ofTypes.memberOf
(if possible)- Looks like not--
Types.getMemberOf
doesn't resolve the generic insetFoo(T foo)
when inherited by aChild extends Parent<String>
- Probably needs the type
Parent<String>
passed to it, which would mean remembering which super-type we're on instead of usinggetAllMembers
- Perhaps this would be solved by having child bindings inherit from the parent, e.g.
ChildBindingPath extends ParentBindingPath<String>
- Looks like not--
- Move most
Binding
methods behind aasBound
method so that generated bindings don't have a polluted name space - Add
Binding.getTag/setTag
for attaching metadata to binding instances (like gwt-mpv properties)