Skip to content

apidae-tourisme/bindgen

Repository files navigation

Intro

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.

Annotations

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.

Another Example

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

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.

Todo

  • 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 of Types.memberOf (if possible)
    • Looks like not--Types.getMemberOf doesn't resolve the generic in setFoo(T foo) when inherited by a Child extends Parent<String>
    • Probably needs the type Parent<String> passed to it, which would mean remembering which super-type we're on instead of using getAllMembers
    • Perhaps this would be solved by having child bindings inherit from the parent, e.g. ChildBindingPath extends ParentBindingPath<String>
  • Move most Binding methods behind a asBound 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)