Skip to main content

Exploring GWT 3: The Proposed Binding Solution

Posted by mjeffw on September 21, 2008 at 8:43 AM PDT

If you recall from my last post, what I want to do in GWT code is take a class like this:

class Person 
{
   private String firstName;
   private String middleName;
   private String lastName;

   public String getFirstName() { return firstName; }
   public void setFirstName(String name) { firstName = name; }

   public String getMiddleName() { return middleName; }
   public void setMiddleName(String name) { middleName = name; }

   public String getLastName() { return lastName; }
   public void setLastName(String name) { lastName = name; }
}

...and wrap it with a "property adapter"...

   Person person = new Person();
  
   // create a Property adapter from the class
   PropertyAdapter<Person> adapter = PropertyFactory.create(person);

...and use the adapter to register listeners and generate events:

    String fname = "Robert";

   // bind the "firstName" property to fname
   adapter.get("firstName").bind(new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
               fname = (String) evt.getNewValue();
            }});

   // use the adapter to modify the state of the firstName property...
   adapter.set("Bob");

   // ...and this should update the value of fname
   assert fname.equals("Bob");

As I mentioned in my last post, GWT doesn't support reflection, byte code generation, dynamic proxies, or aspect-oriented programming. So how can you generically add code to generate property events and bindings from a plain old java object without those?

GWTs solution to this is something called deferred binding. I won't go into a long explanation of deferred binding here, but would like to point you to Google's explanation. The important thing to note is that deferred binding works at the source code level -- that's right, it generates Java source code at compile time that can get compiled into JavaScript (or Java, in hosted mode).

Having accepted that we need to use deferred binding, we have to figure out what source code to generate to solve our problem. Ultimately this will be a combination of regular Java source code and compile-time generated Java source. The obvious thing to me was to write the classes as regular Java source code first, then determine what code could remain as regular Java source files, and which needed to be generated by GWT at compile time.

The Property framework

First order of business was to create a property framework: the classes that represent properties and fire events when they change. After some thinking and some test-driven development, I came up with this design:

/**
* A Property wraps a named value such that it can generate
* PropertyChangeEvents. Since a common use-case for Property is to bind them
* so that a change of one Property causes the update of another, Property is
* also an PropertyChangeListener which calls the set() method if it gets an
* update event.
*
* @param <T> type of field that Property wraps.
*/
public class Property<T> implements PropertyChangeListener<T>
{
   private PropertyChangeSupport<T> changes = new PropertyChangeSupport<T>(this);
   private String name;
   private Accessor<T> accessor;

   /**
    * A helper method that binds the source to the target, and vice-versa, so
    * that a change either results in the same change to the other. A side
    * effect of the binding is that the target's value is set to the source's
    * value.
    *
    * @param source
    * @param target
    */
   public static void bindTwoWay(Property<?> source, Property<?> target)
   {
      target.bind(source);
      source.bind(target);
   }

   /**
    * Constructor.
    *
    * @param name String name for this property
    * @param accessor to wrap underlying field
    */
   public Property(String name, Accessor<T> accessor)
   {
      this.name = name;
      this.accessor = accessor;
   }

   public String getName()
   {
      return name;
   }

   public T get()
   {
      return accessor.get();
   }

   /**
    * The semantic of the set method should be to change the wrapped value ONLY
    * if the newValue is different from the current value, and to fire a
    * PropertyChangeEvent if the wrapped value is updated.
    *
    * @param newValue
    */
   public void set(T newValue)
   {
      if (accessor.get() == null || !accessor.get().equals(newValue))
      {
         T oldValue = accessor.get();
         accessor.set(newValue);
         firePropertyChangeEvent(oldValue, newValue);
      }
   }

   /**
    * Binds this Property to changes from the passed Property. As a side effect,
    * this Property's value is set to the passed Property's value.
    *
    * @param source
    */
   @SuppressWarnings("unchecked")
   public void bind(Property source)
   {
      set((T) source.get());
      source.addPropertyChangeListener(this);
   }

   public void addPropertyChangeListener(PropertyChangeListener<T> listener)
   {
      changes.addPropertyChangeListener(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener<T> listener)
   {
      changes.removePropertyChangeListener(listener);
   }

   public void propertyChanged(PropertyChangeEvent<T> changeEvent)
   {
      set(changeEvent.getNewValue());
   }

   protected void firePropertyChangeEvent(T oldValue, T newValue)
   {
      changes.firePropertyChange(oldValue, newValue);
   }

   @Override
   public String toString()
   {
      return "Property[" + getName() + ":" + get() + "]";
   }

   /**
    * @return true if the value of this Property is the same as the value of the
    *         passed Property
    */
   @SuppressWarnings("unchecked")
   @Override
   public boolean equals(Object obj)
   {
      if (obj instanceof Property)
      {
         Property<T> other = (Property<T>) obj;
         return get().equals(other.get());
      }
      return false;
   }
}


/**
* A wrapper around the getters and setters of an object's fields
*
* @param <T> the type of the field
*/
public interface Accessor<T>
{
   public T get();
   public void set(T newValue);
}


/**
* A utility class to make the registration and removal of property listeners,
* and the firing of property events, easier.
*
* @param <T> parameterized type of Property
*/
public class PropertyChangeSupport<T>
{
   private Property<T> source;
   private List<PropertyChangeListener<T>> globalListeners = new ArrayList<PropertyChangeListener<T>>();

   public PropertyChangeSupport(Property<T> source)
   {
      this.source = source;
   }

   public void addPropertyChangeListener(PropertyChangeListener<T> listener)
   {
      globalListeners.add(listener);
   }

   public void removePropertyChangeListener(PropertyChangeListener<T> listener)
   {
      globalListeners.remove(listener);
   }

   public void firePropertyChange(T oldValue, T newValue)
   {
      for (PropertyChangeListener<T> listener : globalListeners)
      {
         listener.propertyChanged(new PropertyChangeEvent<T>(source, oldValue, newValue));
      }
   }
}


/**
* The event that is fired when a Property changes.
*
* @param <T> the class type of the Property
*/
public class PropertyChangeEvent<T>
{
   private Property<T> source;
   private T oldValue;
   private T newValue;

   public PropertyChangeEvent(Property<T> source, T oldValue, T newValue)
   {
      this.source = source;
      this.oldValue = oldValue;
      this.newValue = newValue;
   }

   public Property<T> getSource()
   {
      return source;
   }

   public T getNewValue()
   {
      return newValue;
   }

   public T getOldValue()
   {
      return oldValue;
   }
}


/**
* The interface that is implemented by any class that needs to listen to
* property events. This is just like java.beans.PropertyChangeListener,
* except it uses Generics.
*
* @param <T> the type of the property
*/
public interface PropertyChangeListener<T>
{
   public void propertyChanged(PropertyChangeEvent<T> changeEvent);
}

To use these classes to wrap an object's field with property events, you'd do the following (for simplicity, I'm only declaring a single field in this version of the Person example):

public class Person
{
   private String firstName;

   public String getFirstName()
   {
      return firstName;
   }

   public void setFirstName(String firstName)
   {
      this.firstName = firstName;
   }
}

The following JUnit testcase wraps the firstName field in a Property, so that changes to it can generate PropertyChangeEvents:

public class PersonTest extends TestCase
{
   // field used for testing the property binding
   private String copyOfFirstName = "";

   // test our property binding code
   public void testPropertyBinding() throws Exception
   {
      final Person person = new Person();
      person.setFirstName("Robert");

      // declare an accessor for the firstName field
      Accessor<String> accessor = new Accessor<String>() {
         public String get()
         {
            return person.getFirstName();
         }

         public void set(String newValue)
         {
            person.setFirstName(newValue);
         }
      };

      // declare a Property to wrap the firstName field
      Property<String> property = new Property<String>("firstName", accessor);

      assertEquals("", copyOfFirstName);

      // bind copyOfFirstName to the property
      property.addPropertyChangeListener(new PropertyChangeListener<String>() {
         public void propertyChanged(PropertyChangeEvent<String> changeEvent)
         {
            copyOfFirstName = changeEvent.getNewValue();
         }
      });

      // test that changes to Person.firstName are reflected in copyOfFirstName
      property.set("Bob");
      assertEquals("Bob", copyOfFirstName);
   }
}

Whew! That's a good bit of code to include in this entry! I hope you're able to follow it okay.

At this point, I knew that I needed to identify code that needed to be generated by GWT's deferred binding system, and figure out how to hook it into that system. We'll pick that up in my next blog entry.

Related Topics >>