Skip to main content

Synchronizing Properties with Beans Binding (JSR 295)

March 20, 2008

{cs.r.title}



Beans Binding is a Java platform library defined by "http://jcp.org/en/jsr/detail?id=295">Java Specification Request
295
. JSR 295 is an open source project that tries to simplify
how developers synchronize two properties, usually in two different
objects. The JSR has a reference implementation available as the
Beans Binding
project
on java.net. Additionally, "http://netbeans.org/">NetBeans IDE 6 includes the latest
release of the Beans Binding library, making it easy for NetBeans
users to try out the new library.

Of course, you can use the Beans Binding library regardless of
your IDE. Download the
reference implementation, compile the source, and package the Java
Archive (JAR) file directly with your own projects. Alternatively,
you can download compiled libraries with source and documentation
packages from the host site. This article will show you how to get
the reference implementation, include it in your projects, and use
its libraries to connect and synchronize properties from different
objects.

Synchronization Without Beans Binding

To appreciate the problem that Beans Binding tries to solve, you
should first consider how developers usually synchronize two
properties. AWT and Swing user interface components already
have the ability to add event listeners, and they fire events when
specific properties change values. However, when you create your
own Java Bean components, you may have to create all those basic
abilities yourself. In other words, depending on what type of bean
you create, you may have to write your own methods to add event
listeners, store those listeners, and fire property change events
when necessary. You may also need to create new event listener
interfaces and implement those as "glue" code that connects two or
more components.

Writing this glue code is often tedious. Programmers often
duplicate this code to create change events and propagate them to
listeners. In a large application, you might duplicate some of
these same design patterns dozens or even hundreds of times.
Fortunately, you can use the
java.beans.PropertyChangeSupport class to reduce the
effort. However, to support custom beans and their properties, you
still have to add the typical addSomePropertyListener
and removeSomePropertyListener methods to your
beans.

The following code sample shows a simple
PersonWithPropertySupport class that provides its own
property change support. The PropertyChangeSupport
class provides most of property support, as shown here:

[prettify]
public class PersonWithPropertySupport {

    private String name;
    private int age;
    private PropertyChangeSupport props = new PropertyChangeSupport(this);

    public PersonWithPropertySupport() {
    }

    public PersonWithPropertySupport(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void setName(String newName) {
        String oldName = this.name;
        this.name = newName;
        props.firePropertyChange("name", oldName, newName);
    }

    public String getName() {
        return name;
    }

    public void setAge(int newAge) {
        int oldAge = this.age;
        this.age = newAge;
        props.firePropertyChange("age", oldAge, newAge);
    }

    public int getAge() {
        return age;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        props.addPropertyChangeListener(l);
    }

    public void addPropertyChangeListener(String propName, PropertyChangeListener l) {
        props.addPropertyChangeListener(propName, l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        props.removePropertyChangeListener(l);
    }

    public void removePropertyChangeListener(String propName, PropertyChangeListener l) {
        props.removePropertyChangeListener(propName, l);
    }
}
[/prettify]

Note that the basic class is quite simple. A large portion of
the code supports the registration of event listeners and the
firing of appropriate events at the correct time. As classes and
their relationships become more complex, the management of
properties, change events, and event listeners can easily represent
the majority of a bean's source code.

The Beans Binding libraries help this situation by factoring out
the common design patterns and activities involved in keeping
properties synchronized. The new library helps you quickly connect
properties from different objects, and even helps you avoid writing
the same boilerplate code over and over again.

Beans Binding Setup

The Beans Binding API and
libraries are available from the "https://beansbinding.dev.java.net/">Beans Binding project. If
you select the "https://beansbinding.dev.java.net/servlets/ProjectDocumentList">Documents
& Files
link on the page, you will see the the latest
version of the source, documentation, and precompiled binaries, as
seen in Figure 1. The project has reached version 1.2.1, and most
of the API is stable at this point.

Beans Binding hosted files. align="left" width="404" height="356" border="0" />
"left" />
Figure 1. The Beans Binding project hosts the binaries, source, and
documentation for the library

Getting the prepackaged binaries and source bundles is
convenient and easy. However, you can get the source code directly
from the Subversion repository too. Access to the source repository
requires a "https://www.dev.java.net/servlets/Join">registered username on
the project's website. If you have the Subversion tool in your
shell's command path, the following command will check out a
current version of the project:

[prettify]
svn checkout https://beansbinding.dev.java.net/svn/beansbinding/trunk beansbinding --username <username>
[/prettify]

The project is Ant-based, so you will also need a current copy
of the Ant build tool if you
want to build the project yourself. Additionally, you should have
Java SE 5
or later
. To build the JAR and Javadoc targets, use the
following command:

[prettify]
ant jar javadoc
[/prettify]

You may also be able to use the project's Ant configuration
files in your IDE. For example, you can access all the project's
build targets from either the "http://www.netbeans.org/">NetBeans IDE or "http://www.eclipse.org/">Eclipse. Other IDEs should work as
well.

Once you have the library JAR file, you should include this file
in your compiler and environment classpath. The "http://java.sun.com/javase/6/docs/technotes/tools/index.html">technical
notes
for the Java SE Development Kit (JDK) have more
information about the Java platform tools and utilities, including
information about setting the classpath.

The Beans Binding authors hope to include the API in a future
version of the JDK. At this point, however, you must download the
API separately. For now, the API has the
org.jdesktop.beansbinding package name, but expect
that to change when the API becomes a standard addition to the
JDK.

Properties

The Beans Binding API defines an abstract Property
class. This Property class defines a uniform way to
set or get a property value. A concrete Property class
implementation lets you represent a specific property in a source
object. All methods of this class require a source
argument because the methods can access this same property across
multiple different objects.

The concrete implementations of this abstract class include the
following:

  • org.jdesktop.beansbinding.ObjectProperty
  • org.jdesktop.beansbinding.BeanProperty
  • org.jdesktop.beansbinding.ELProperty

An ObjectProperty instance represents an immutable,
read-only property. A BeanProperty instance is useful
for both readable and writable properties; it also provides support
for change event listeners. An ELProperty instance
allows you to create dynamic or synthetic properties using
Expression Language (EL) syntax originating from the GlassFish
project. Examples of this language will be provided later in this
article.

In the following example code, a simplified Person
class has name and age properties with
the appropriate getter and setter methods that are typical of a
Java Bean component. Notice that the class does
not explicitly support bound properties and event
listeners.

[prettify]
public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age ) {
        this.name = name;
        this.age = age;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }
}
[/prettify]

You can use the BeanProperty class to represent the
name and age properties. Even though the
Person class doesn't directly provide support for
bound properties, you can use the BeanProperty class
to add that functionality. In the following example, the
PersonPropertyTest class creates a Person
object and uses a BeanProperty instance to represent
the age property. Additionally, the example shows how
to add a property state listener for the age property.
The PropertyStateListener and
PropertyStateEvent classes are also part of the Beans
Binding API.

[prettify]
import org.jdesktop.beansbinding.*;

public class PersonPropertyTest {

  private Person person;

  public PersonPropertyTest() {
    person = new Person("John", 21);
  }

  void run() {
    Property ageProperty = BeanProperty.create("age");
    ageProperty.addPropertyStateListener(person, new PropertyStateListener() {

      public void propertyStateChanged(PropertyStateEvent pse) {
        Person p = (Person) pse.getSourceObject();
        System.out.printf("Happy Birthday, %s! " +
                "You're %d years old.\n", p.getName(), p.getAge());
      }
    });
    for (int x = 35; x < 45; x++) {
      ageProperty.setValue(person, x);
    }
  }

  public static void main(String[] args) {
    PersonPropertyTest test = new PersonPropertyTest();
    test.run();
  }
}
[/prettify]

Notice that the BeanProperty class has a static
create method. Use this create method to
instantiate BeanProperty instances. You can now use
this example's ageProperty variable to access an
age property in any object that actually provides that
property and conforms to the access method naming conventions for
JavaBeans.

A PropertyStateEvent object represents a change in
property state for a specific object. A
PropertyStateListener defines the interface you should
implement to receive notification of property state changes.

The simple test application iterates through a range of integers
and repeatedly updates the age property for a specific
Person instance. Fortunately, annual age updates for
people in real life don't occur as rapidly. Each time the code
calls the property's setValue method, the property
state listener reports the new value:

[prettify]
Happy Birthday, John! You're 35 years old.
Happy Birthday, John! You're 36 years old.
Happy Birthday, John! You're 37 years old.
...
[/prettify]

The ELProperty class allows you to create
properties using "http://java.sun.com/products/jsp/reference/techart/unifiedEL.html">
EL
syntax. The EL definition is part of the "http://java.sun.com/products/jsp/">JavaServer Pages 2.1
specification. This language allows you to create more complex,
synthetic properties even when those actual properties don't exist
in a target object. For example, you can create a "middle-aged"
property for the Person class using the following code
and expression:

[prettify]
ELProperty middleAgedProperty = ELProperty.create("${age > 45}");
[/prettify]

The middleAgedProperty variable now represents a
Boolean property that is true whenever a bean's age
property is greater than 45. Of course, your definition of
"middle age" may vary. The online "http://www.bartleby.com/61/0/M0280000.html">American Heritage
Dictionary of the English Language
suggests that middle
age is that period of life between ages 40 and 60. The "http://www.collinslanguage.com/results.aspx?js=on&dictionary=cedm&text=middle+age">
Oxford English Dictionary
claims this period of life starts at
age 45. This author will take sides with the youthful British
for this particular example.

In order to fully use the ELProperty class, you
must ensure that your Java Bean class supports property change
listeners for all referenced properties in the expression. In this
case, the expression uses the bean's age property to
determine whether someone is middle-aged. Since the
ELProperty instance references the age
property, you must modify the Person class to support
change listeners. The following
PersonWithPropertySupport class adds change listener
support for both the name and age
properties:

[prettify]
import java.beans.*;

public class PersonWithPropertySupport extends Person {

  private PropertyChangeSupport props = new PropertyChangeSupport(this);

  public PersonWithPropertySupport() {
  }

  public PersonWithPropertySupport(String name, int age) {
    super(name, age);
  }

  @Override
  public void setName(String newName) {
    String oldName = getName();
    super.setName(newName);
    props.firePropertyChange("name", oldName, newName);
  }

  @Override
  public void setAge(int newAge) {
    int oldAge = getAge();
    super.setAge(newAge);
    props.firePropertyChange("age", oldAge, newAge);
  }

  public void addPropertyChangeListener(PropertyChangeListener l) {
    props.addPropertyChangeListener(l);
  }

  public void addPropertyChangeListener(String propName, PropertyChangeListener l) {
    props.addPropertyChangeListener(propName, l);
  }

  public void removePropertyChangeListener(PropertyChangeListener l) {
    props.removePropertyChangeListener(l);
  }

  public void removePropertyChangeListener(String propName, PropertyChangeListener l) {
    props.removePropertyChangeListener(propName, l);
  }
}
[/prettify]

Now you can use the synthetic middleAgedProperty to
determine when a person reaches middle age. The following code uses
an ELProperty instance and a listener to alert you
when a Person instance reaches "middle-aged"
status:

[prettify]
import org.jdesktop.beansbinding.*;
public class PersonELPropertyTest {

  private Person person;
  private ELProperty middleAgedProperty;
  private BeanProperty ageProperty;

  public PersonELPropertyTest() {
    person = new PersonWithPropertySupport("John", 40);
    ageProperty = BeanProperty.create("age");
    middleAgedProperty = ELProperty.create("${age > 45}");
  }

  void run() {
    ageProperty.addPropertyStateListener(person, new PropertyStateListener() {
     public void propertyStateChanged(PropertyStateEvent pse) {
        Person p = (Person) pse.getSourceObject();
        System.out.printf("%s is %d years old.\n", p.getName(), p.getAge());
      }
    });

    middleAgedProperty.addPropertyStateListener(person, new PropertyStateListener() {
      public void propertyStateChanged(PropertyStateEvent pse) {
        Person p = (Person) pse.getSourceObject();
        System.out.printf("%s is middle-aged. Sigh...\n", p.getName());
      }
    });

    int age = person.getAge();
    for (int x = age; x < age + 10; x++) {
      ageProperty.setValue(person, x);
    }
  }

  public static void main(String[] args) {
    PersonELPropertyTest test = new PersonELPropertyTest();
    test.run();
  }
}
[/prettify]

The PersonELPropertyTest prints the following:

[prettify]
...
John is 44 years old.
John is 45 years old.
John is middle-aged. Sigh...
John is 46 years old.
John is 47 years old.
...
[/prettify]

The middleAgedProperty fires once in this example.
Clearly, the property itself defines a boolean
synthetic property that is true when a person's age is
greater than 45. Why doesn't the property fire repeatedly as the
age increments through 46, 47, and above? Although the property
itself is true, the property state doesn't change
after its initial change at age 45. The property becomes true at
age 45 and remains true thereafter. Since the state doesn't change,
no additional PropertyStateEvent objects are
needed.

Binding

To bind one property to another means that you
synchronize the properties in some way: a change in one property
affects the other. For an example, imagine that a slider component
is connected to a colored panel so that changing the slider values
will also change the panel's color tint. In other words, the
slider's value and the panel's tint are synchronized. To create
this binding yourself, you must ensure that both the slider and
panel components listen and respond to the correct change events.
You must create code that listens to changes in the slider and
propagates those changes to the panel. Depending on the component,
you might even create your own custom events, listener interfaces,
and glue code between the components.

The Beans Binding API simplifies this binding task. The
following example demonstrates how to use the binding functionality
to synchronize a Swing JSlider component's value with
a custom TintedPanel component's background color. For
simplicity, the only custom feature of the TintedPanel
class is a tint property. The TintedPanel
class has the typical getter and setter methods that are common for
a bean. In this case, those methods are setTint and
getTint. The tint can range in values from 0 through
100, representing a color tint percentage. The demo configures the
slider with values from 0 though 100 to represent percentage
values. As you move the slider, the colored panel should change
tint to match the slider setting. Figure 2 shows the panel and
slider beans that are bound together.

"412" height="209" border="0" id="graphics2" alt="The colored panel tint" />

Figure 2. The colored panel tint is synchronized to the slider
value through the binding API

This Tint Bind Demo contains two beans: a minimally
customized TintedPanel component and an unmodified
JSlider component. The TintedPanel code is shown here
in part:

[prettify]
public class TintedPanel extends javax.swing.JPanel {

    /** Creates new form TintedPanel */
    public TintedPanel() {
        initComponents();
        setTint(0);
    }

    public void setTint(int tint) {
        assert(tint >= 0 && tint <= 100);
        this.tint = tint;
        int colorValue = 255 - (tint*255/100);
        this.setBackground(new Color(colorValue, colorValue, 255));
    }

    public int getTint() {
        return tint;
    }

    private void initComponents() {
    // ...
    }

    private int tint = 0;
}
[/prettify]

The following TintBindingTest class creates
JSlider and TintedPanel instances and
binds them. The code uses the BeanProperty.create
method to create new properties. The Bindings class
provides the factory method for creating a binding between the
properties: tint and value. The
tintValue variable represents the panel's
tint property. The slideValue represents
the slider's value property.

[prettify]
public class TintBindingTest extends javax.swing.JFrame {
  private Binding tintBinding;
  private Property slideValue;
  private Property tintValue;

  /** Creates new form TintBindingTest */
  public TintBindingTest() {
    initComponents();

    slideValue = BeanProperty.create("value");
    tintValue = BeanProperty.create("tint");
    tintBinding = Bindings.createAutoBinding(UpdateStrategy.READ,
            tintSlider, slideValue, tintedPanel, tintValue);
    tintBinding.bind();
    tintSlider.setValue(0);
  }

  private void initComponents() {
    // ...
    );

  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new TintBindingTest().setVisible(true);
      }
    });
  }

  private javax.swing.JSlider tintSlider;
  private com.joconner.bindingdemo.TintedPanel tintedPanel;
}
[/prettify]

When you create a binding between properties, you must associate
an update strategy. The strategy will determine how the properties
will synchronize. You have three choices:

  • UpdateStrategy.READ_ONCE
  • UpdateStrategy.READ
  • UpdateStrategy.READ_WRITE

The READ_ONCE strategy tells the binding to update
the target property only once. The READ strategy tells
the binding to update the target whenever the source property
changes. The READ_WRITE strategy tells the binding to
update both properties when either changes.

Binding Conversions

Sometimes synchronized properties don't have matching types.
Consider how you might synchronize a JToggleButton and
a JLabel component. The button has a
selected property that is a boolean. If
you want to bind this property to the label's
background color, you will need to convert from one
property type to another. Figure 3 shows these two components used
as a simple light switch and a light bulb. The button represents
the switch; the label represents the bulb.

<br "converterdemo" />

Figure 3. Sometimes you may have to convert property types in a
binding

The Converter Demo code shows how to bind these
properties with a converter:

public class ConverterDemo extends JFrame {

    JLabel lightBulb;
    JToggleButton lightSwitch;
    BindingGroup bindingGroup;
    Converter converter;

    /** Creates new form ButtonBinding */
    public ConverterDemo() {
        converter = new Converter() {
            @Override
            public Color convertForward(Boolean isSelected) {
                return isSelected ? Color.YELLOW : Color.BLACK;
            }
            @Override
            public Boolean convertReverse(Color arg0) {
                return arg0 == Color.YELLOW;
            }
        };
        initComponents();
    }

    private void initComponents() {
        lightBulb = new javax.swing.JLabel();
        lightSwitch = new javax.swing.JToggleButton();

        bindingGroup = new BindingGroup();
        Binding binding =
            Bindings.createAutoBinding(AutoBinding.UpdateStrategy.READ_WRITE,
            lightSwitch, ELProperty.create("${selected}"),
            lightBulb, BeanProperty.create("background"));
        binding.setConverter(converter);
        bindingGroup.addBinding(binding);
        bindingGroup.bind();
        // ...
    }
    //...
}

As you can see in the anonymous inner class, you create a
Converter by implementing the abstract
Converter class. You will override two methods:

  • convertForward
  • convertReverse

As you might imagine, the convertForward method
will convert from a source property to a target property. The
convertReverse method does the reverse; it converts
from a target property to a source property. This demo class
implements the two methods by converting to and from a
Color type value and a Boolean type
value. The Boolean value comes from the button's
selected property. The Color value comes
from the label's background property.

Summary

You can use the Beans Binding API to quickly and easily
synchronize properties in different objects. The API allows you to
add and remove property change listeners, create synthetic
properties, and to even bind Swing components to database result
sets.

Sometimes your bindings must convert values from one type to
another type. In that situation, you should implement a custom
Converter class to convert to and from dissimilar
properties. By implementing your own Converter class,
you allow a Binding instance to convert from one
property type to another.

Consider using the Beans Binding API in your next project. You
may be able to save considerable time and effort binding and
synchronizing properties between two different Java Bean
components. The API allows you all the benefits of synchronized
properties but minimizes the amount of code you write for event
listening and propagation.

Resources

The following resources will be helpful as you research and use
this API:


width="1" height="1" border="0" alt=" " />
John O'Conner is a software architect, consultant, author, and speaker.
Related Topics >> JSP   |   Programming   |   Swing   |   

Comments

Sorry to bother you with this John but your article on Using ...

Sorry to bother you with this John but your article on Using Java DB in Desktop Applications has a broken reference to download the project files.
I'm starting with a project of that kind and it'll be very helpful to have it.
Is there anywhere I can find it?
Thanks!