Navigation Menu

Skip to content

DavidShepherdson/bindgen

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

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.

Gotchas

  • 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

Todo

  • 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 null employer could have get() return null and not NPE and set() could create a new Employer() to then call setName() 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 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)

Packages

No packages published

Languages

  • Java 100.0%