HexaBinding does fast dynamic binding between values, DTOs, Widgets (with the GWT add-on artifact), and any other objects in Java applications. It is open and extensible with the possibility to add new property adapters to the binding engine. The library supports standard Java and also has a version for GWT.
It might also work on Android and with JavaFX, although it wasn't tested on it. Any feedback is appreciated !
Suppose you have two classes A
and B
, one having a name
field and the other having a fullName
. Imagine you have two instances a
and b
of those classes. You can write :
Binder.bind( a, "name" ).to( b, "fullName" );
With this one line of code, you have bound the two fields dynamically in a two-way data binding mode.
Now imagine, you have a Person
and a Workplace
classes. You can write :
Binder.bind( person, "workplace.address" ).to( form, "address" );
This will bind the person.getWorkplace().getAddress() value to the form.getAddress() value. Still in a two-way fashion.
Another example. In a UI code you will typically use the data binding library like this :
Binder.bind( listBox ).mapTo( personForm );
This will build a one way data binding between the listBox
and the personForm
which displays and edits the selected person. Then, each matching fields between the selected object and the form will be two-way data bound. Isn't that cool ?!
Inside a GWT application you can write :
Binder.bind( personDto, "category.color" ).to( widget, "style.borderColor" );
to bind the person's category's color to the widget's border color.
But there's more, there are plenty of options you can use !
Here is a short step by step guide to use the HexaBinding library in a Java project. For a GWT application, check the GWT Quick start, it is written later in the document.
First create a Java project. Then add this dependency in your pom.xml :
<dependency>
<groupId>fr.lteconsulting</groupId>
<artifactId>hexa.binding</artifactId>
<version>1.2</version>
</dependency>
Then, specify at least the Java 6 language level :
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
In your application, create one pojo class all by hand
import fr.lteconsulting.hexa.databinding.properties.Properties;
public class Person {
private String name;
public void setName(String name) {
this.name = name;
Properties.notify(this, "name");
}
public String getName() {
return this.name;
}
}
Then in the application's main, bind two instances of those classes
Person a = new Person();
Person b = new Person();
// bind the name property
Binder.bind( a, "name" ).to( b, "name" );
a.setName( 'jack' );
// will print 'jack'
System.out.println( b.getName() );
b.setName( 'paul' );
// will print 'paul'
System.out.println( a.getName() );
That's really a first step. You can do many other things ! Now, you can read the other part of the manual or investigate into the samples :
- Sample 1, a simple data binding hellow world in pure Java
- Sample 2, a simple data binding master/detail view in Java with GWT
There are three phases when constructing a data binding :
- specifying the source,
- specifying options,
- and specifying the destination
This is done through the fluent Binder API.
There are three possible methods to specify the value source :
public static Binder bind( Object source, String propertyPath )
The first parameter (source) is the object on which properties are watched. The second parameter (propertyPath) is the path to the property value, starting from the source.
public static Binder bindObject( Object source )
When using this method, the source data of the binding will be fixed and will be the source object itself.
public static Binder bind( HasValue<?> widget )
This one is used with GWT. HasValue
is a standard interface of this framework. When using a HasValue as a data source, the binding system will use the standard GWT mechanism to get and subscribe to the value (that is, the addValueChangeHandler method).
public static Binder bind( PropertyAdapter source )
This is the more general way to specify a data source. With this method, you have to implement the PropertyAdapter
by yourself. Note that several implementations are available for common use cases (WriteOnlyPropertyAdapter, DTOMapperPropertyAdapter, ...).
After specifying the source, you have an opportunity to customize the options on the data binding.
public Binder mode( Mode mode )
With this you specify the data binding mode. There are three values : OneWay
, TwoWay
and OneWayToSource
.
public Binder log( String prefix )
The Log method accepts a prefix. When an event will occur on this binding, it will be logged using this prefix so you can identify easily when things go wrong !
public Binder withConverter( Converter converter )
Sometimes, you need to convert values between the source and the destination. This is done by calling this method, to which you provide an implementation of the Converter
interface. You will able to implement the backward and forward conversions.
public Binder deferActivate()
By default, the data binding is synchronous. For whatever reason, you may want it to be deferred. In that case, you just have to call this method (it just works with GWT right now).
At the end of the fluent call, you specify the destination of the data binding. You have several possibilities :
public DataBinding to( Object destination, String propertyPath )
As for the source, this specifies an object and a property path to walk in order to get or set the value.
public DataBinding to( HasValue<?> widget )
When your destination is an instance of the HasValue
interface (GWT applications), you can use this method.
public DataBinding to( PropertyAdapter destination )
Once again, you can provide your own PropertyAdapter
for the destination of the binding.
public DataBinding mapTo( Object destination )
The MapTo method will use the source value of the data binding to create a new two-way data binding between each field of the source and each field of the destination.
There is one left method in the Binder that will create a two-way data binding between all matching properties of the objects on the two sides of the data binding. Here is the method :
public DataBinding map( Object source, Object destination )
To free all resources associated with a data binding, you just have to call the terminate()
method on it :
DataBinding binding = Binder.bind(...)...to(...);
...
binding.terminate();
This will desactivate the binding and unregister all the data handlers. That's a good practice to check whether an application leaks memory because it will run faster and smoother if less resource is consumed. Refer to the statistics paragraph to see how it is possible to detect resource leaks with HexaBinding.
The binding system bases itself on two things :
- The concept of Property,
- And the notification system.
A Property is just a simplified way to look at Java objects' methods and fields. It is a named value belonging to an object, that can be read, written and subscribed for.
The HexaBinding library can use the getters and setters of the objects. It can also directly use the object's field if no getter or getter is found. It will even create virtual fields if there is no field in the class with the same name as the property. This adds a very dynamic aspect to the data binding. It is very useful to enhance existing classes dynamically, and allow to attach virtual properties to arbitrary objects. For example one can add the "selected" property to a java.util.ArrayList
object.
To set a virtual property value, you just have to call :
Properties.setValue( object, "propertyName", value );
If the object does not already have a "propertyName" property, a virtual one will be created.
To get its value, you call :
String value = Properties.getValue( object, "propertyName" );
It is quite usual to maintain a list of objects together with the currently selected object in the list. The selected object is then often edited by the user in some UI component.
The standard java.util.ArrayList
class does not have the concept of a "selected item". That's OK, because we will use the dynamic property functionality of HexaBinding :
// A normal Java list creation
java.util.List<MyPojo> list = new ArrayList<>();
...
// We set the "selected" property of the list instance
Properties.setProperty( list, "selected", value );
// We bind (two-way) each field of the selected value to each field of the editing view
Binder.bind( list, "selected" ).mapTo( view );
That may seem a little, and that's really a little written code for a lot of things done !
The notification system allows to register for object property value changes and to notify clients when a change occurs on them.
To register for value changes from a property on an object :
Properties.registerPropertyChangedEvent( source, "propertyName", new PropertyChangedHandler()
{
public void onPropertyChanged( PropertyChangedEvent event )
{
// here is the object on which the property changed
Object sender = event.getSender();
// here is the name of the property that changed
String propertyName = event.getPropertyName();
}
} )
To register on all properties of an object, "*"
can be passed instead of the property name.
To notify a change of the property value :
// Notifies that the "propertyName" value
// in the 'this' object was just modified
Properties.notify( this, "propertyName" );
This will update all registered clients for that property of that object. This method should be called by your application's objects in order for the data binding system to work. That's typically the method you will have call in your DTO's setters.
Any object of any class can be used with the data binding.
If your object has a setter method, then it should calls the notify(...)
method. Like this :
// Setter in a java class
public void setName( String name )
{
this.name = name;
Properties.notify( this, "name" );
}
If you cannot modify the object's class, you can always call the notify
method after calling its setter :
pojo.setName( value );
Properties.notify( pojo, "name" );
If the object has no getter, you can call :
Properties.setValue( pojo, "name", value );
But for performance reasons, you may prefer to just write :
pojo.name = value;
Properties.notify( pojo, "name" );
Appart from that there is no special things to call to have the data binding library to work with your classes.
The HexaBinding library can create observable POJOs for you. This is done by defining a very reduced specification of the desired POJO, with the @Observable annotation on the class. For example :
@Observable
class MyPojo
{
String name;
int weight;
double value;
}
The HexaBinding annotation processor will process the class and generate a ObservableMyPojo
class inheriting from MyPojo
with all the correct getters and setters, along with the constructors calling the super class one's. When the annotated class name ends with 'Internal', the generated class name will be the annotated class name without the 'Internal' suffix. This allows you to choose your naming schema.
Note : For your project to work with the Java annotation processing, it must be at least Java 6 and your IDE needs to be correctly configured.
The Property class can also help you reducing the amount of boilerplate code. It stores a value and exposes a getter and a setter which calls the notify(...)
method.
An example :
class MyPojo
{
Property<String> name = new Property( ... );
}
MyPojo pojo = new MyPojo();
Binder.bind( pojo, "name" ).to( otherObject, pptyPath );
It is possible to forget to unregister a binding when it is not used anymore. In that case, the application leaks resources and after a sufficiently long execution, the whole thing could crash.
In order to help the developper in the search of the code which causes binding leaks, one can call the getStatistics()
method :
Window.alert( Properties.getStatistics() );
This will show a message looking like this :
PropertyChanges stats :
# registered handlers : 6
# notifications : 2
# dispatches : 2
selected@ArrayList => 5 (diff: 2)
borderColor@JavaScriptObject$ => 1
First there is three stats numbers : the total number of registered handlers (used to subscribe to property changes), the total number of notifications of property changes that happened so far, and the total number of dispatches that were executed.
Next, comes the number of handlers for each type of property that's watched. In the above example, the 6 registered handlers are in fact 5 handlers on the selected
property of the ArrayList
class (that's a virtual property, because the ArrayList class does not have a 'selected' property), and 1 handler on a borderColor
property of a JavaScriptObject
object.
The (diff: 2)
text means that the number of those handlers increased by 2 since the last call of the getStatistics
method. That's very useful to check whether a part of an application leaks or not. Call the method before opening a view and just after closing it. Inspect the differences and it will give you a clue about which part of the code forgot to call the terminate()
method and on wich data binding.
The data binding library is built upon an internal introspection system which allows runtime type information availability. To ensure minimum generated code size, the introspection system needs to know on which classes it needs to work on at compile time. This is done by adding this glue code :
// to be declared somewhere :
interface MyClassBundle extends ClazzBundle
{
// list of classes with introspection
@ReflectedClasses( classes = {
ArrayList.class,
WatchableCollection.class,
Article.class } )
void register();
}
...
// introspection bundle registration
MyClassBundle bundle = GWT.create( MyClassBundle.class );
bundle.register();
From there onwards, you can use the data binding on objects of those classes !
Note that you can create ClazzBundle
s at several places in the code, the set of reflected classes will just grow accordingly.
Note also that you don't have to declare subclasses of the JavaScriptObject classes, they are managed automatically. But if you need to use a JavaScriptObject class or subclass, then you need to register the JavaScriptObject class.
The @Observable annotation can be used instead of @Observable in GWT projects because it generates also the ClazzBundle
glue code for the
annotated class. Refer to the @Observable paragraph to learn more about POJO generation.
GWT is a very good tool to write web applications in Java. Luckily the HexaBinding library is compatible with GWT and leverage the compiler architecture to produce efficient data binding.
Here is a quick start guide to create a GWT application using HexaBinding. The code that is discussed here can be found here.
First create a Java GWT project. Then add those dependencies in your pom.xml :
<dependency>
<groupId>fr.lteconsulting</groupId>
<artifactId>hexa.binding.gwt</artifactId>
<version>1.2</version>
<scope>compile</scope>
</dependency>
Then, specify at least the Java 6 language level :
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
Our project will be using the @Observable
annotation and thus annotation processing. To give the generated classes to the gwt maven compiler plugin, you will need to add that :
<properties>
<generated-sources>${project.build.directory}/generated-sources/annotations</generated-sources>
</properties>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<generatedSourcesDirectory>${generated-sources}</generatedSourcesDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>1.9.1</version>
<executions>
<execution>
<id>add-source</id>
<phase>process-classes</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${generated-sources}</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
In your gwt module file, add this line :
<inherits name='fr.lteconsulting.hexa.HexaBinding'/>
We will begin with a very simple binding between two text boxes and a label. This will go like this :
Label label = new Label();
TextBox textBox = new TextBox();
TextBox textBox2 = new TextBox();
RootPanel.get().add( textBox );
RootPanel.get().add( textBox2 );
RootPanel.get().add( label );
Binder.bind( textBox ).to( label, "text" );
Binder.bind( textBox ).to( textBox2 );
The binding system needs to get one information from you : the classes of the objects that you will use at runtime with the data binding. This will allow HexaBinding to optimize the generated code at compile time. Here the classes used in the data binding are Label and TextBox. We have to create an interface and call the register
method on it. Here is the interface :
// Declares the classes used with data binding
interface MyClassBundle extends ClazzBundle
{
@ReflectedClasses( classes = {
Label.class,
TextBox.class
} )
void register();
}
Then before using those classes, we have to register the Label and TextBox types runtime information, like this :
((MyClassBundle) GWT.create( MyClassBundle.class )).register();
You can now start the application. Typing a value in one textbox should be replicated in the other text box and in the label.
In the second sample, we will bind a TextBox and a Label to a field of a Java POJO.
First, let's define the POJO :
public class Person
{
private String name;
public void setName( String name )
{
this.name = name;
Properties.notify( this, "name" );
. }
public String getName()
{
return this.name;
}
}
That's a really simple POJO. The only thing asking explanation is the line Properties.notify( this, "name" );
. It is located in the setter after the modification of the "name" field. It notifies the binding system that the property value has changed. This is required if you want the binding system to be able to watch your values. If you don't call this method, the binding will work, but won't react on your property changes.
To help your productivity, the @Observable
annotation can be used to generate the Person class. Instead of writing the Person class by hand, you could write a reduced PersonInternal class annotated with @Observable :
@Observable
class PersonInternal
{
String name;
}
And the Person class will be generated automatically. This feature uses the annotation processing mechanism, so if you want to use that, you should have correctly configured your IDE.
You don't need to add this POJO class in the set of @ReflectedClasses because the generated implementation does that for you.
In the main code, we first create the pojo, the widgets and add them into the DOM :
Person p = new Person();
HTML html = new HTML();
TextBox nameBox = new TextBox();
RootPanel.get().add( html );
RootPanel.get().add( nameBox );
Then we bind things together. First let's bind the TextBox value to the person's name :
Binder.bind( p, "name" ).to( nameBox );
Then, let's bind the person's name to the html widget, adding a converter in order to format the string :
Binder.bind( p, "name" ).withConverter( new OneWayConverter()
{
@Override
public Object convert( Object value )
{
return "You are editing person <b>'" + value + "'</b>";
}
} ).to( html, "HTML" );
The converter accepts the value that is currently transferring between the two sides of the binding and must return the converted value. Here the value
parameter receives the person's name and returns the String that will be set as the HTML of the html widget.
Now, launch the application and look at how things go...
The third example is much simpler. We are going to bind a TextBox's value to the body's background color :
TextBox colorBox = new TextBox();
Binder.bind( colorBox ).to( Document.get(), "body.style.backgroundColor" );
RootPanel.get().add( colorBox );
For this to work, we have to add the JavaScript
object in the set of reflected classes. That's because all the Document instance, and the getBody() and getStyle() methods return a subclass of the JavaScriptObject class. With the JavaScriptObject classes, you don't have to register
subclasses but at least the JavaScriptObject class.
Now let's try a last thing. What if we want to bind the text box value to the window's title ? Since the Window class in GWT has only static methods, we cannot use an instance of set property values on. So we need to create a custom adapter :
Binder.bind( colorBox ).to( new WriteOnlyPropertyAdapter()
{
@Override
public void setValue( Object object )
{
Window.setTitle( (String) object );
}
} );
Each time the colorBox value changes, the binding system propagates this change and calls the setValue( Object object )
method with the propagated value. Here we use the abstract class WriteOnlyPropertAdapter
which allows us to only implement the setValue.
To go further, you can also checkout the Sample 2, which is a very basic application showing a master detail edition view. It is full of comments so it's easy to use it as a first step guide.
If you have questions, remarks or ideas, you can push a pull request or a comment !
Thanks !
Hexa Tools is developped with love by LTE Consulting. If you want to contribute, have suggestions or ideas, please feel free to submit pull requests or to contact me.
(c) LTE Consulting - 2015