Bound Properties

In the previous example, property changes can take place when one of the setStocks() methods are invoked. This results in the state of the WatchList being changed. What would happen if two objects were using the WatchList and one of them changed the Stocks property? It’s possible that users of the WatchList will want to know if its properties are changed.

A Bean component can provide change notifications for its properties. These notifications take the form of events, and they conform to the event model described in the earlier chapters. Properties that support change notifications are known as bound properties, because other objects can bind themselves to changes in their value.

Non-Specific Property Binding

An object can bind itself to non-specific property changes. In this case the source object sends notifications to the bound object whenever the value of one of its bound properties has changed. The notification takes the form of a java.beans.PropertyChangeEvent. The public methods for this class are shown below, without their implementations:

public class java.beans.PropertyChangeEvent
   extends java.util.EventObject
{
   // contructor
   PropertyChangeEvent(Object source,        // the source of the event
                       String propertyName,  // the property name
                       Object oldValue,      // the old value
                       Object newValue);     // the new value

   // get the new value of the property
   public Object getNewValue();

   // get the old value of the property
   public Object getOldValue();

   // get the event propagation id
   public Object getPropagationId();

   // get the name of the property that changed
   public String getPropertyName();

   // set the event propagation id
   public void setPropagationId();
}

The java.beans.PropertyChangeEvent class treats old and new values as instances of class Object. If the property that changed uses one of the primitive data types like int or float, you’ll have to use the object version of the type here, such as java.lang.Integer or java.lang.Float. It is possible that when the event is fired the old or new values are not known, or that multiple values have changed. In this case null can be returned from the getOldValue() or getNewValue() method. This is also true of the property name. If multiple properties have changed, the getPropertyName() method may return null. The PropertyChangeEvent also contains something called a propagation ID. This is reserved for future use. If you receive a PropertyChangeEvent and then in turn generate and fire another PropertyChangeEvent, you must propagate the propagation ID as well.

A PropertyChangeEvent for a bound property should only be fired after its internal state has been updated. This means that the event signifies a change that has already taken place. If an object supports bound properties, it will provide the following methods for registering and unregistering the associated event listeners:

public void addPropertyChangeListener(PropertyChangeListener p);
public void removePropertyChangeListener(PropertyChangelistener p);

The java.beans.PropertyChangeListener interface extends the base Java class java.util.EventListener. This interface supports a single method called propertyChange(), which takes only one parameter as an argument, of type java.beans.PropertyChangeEvent. If an object wants to receive notifications of bound property changes, it would implement the java.beans.PropertyChangeListener interface and register itself with the source object by calling its addPropertyChangeListener() method. A diagram of this interaction is shown in Figure 4.1.

Notifying a listener that a property has changed

Figure 4-1. Notifying a listener that a property has changed

Let’s go back to our Temperature class. Earlier we added a read-only property to this class named CurrentTemperature. If we wanted to implement this as a bound property, we would implement the addPropertyChangeListener() and removePropertyChangeListener() methods. Then whenever CurrentTemperature changed, we would fire a PropertyChangeEvent to all listeners. Originally we designed the Temperature class to fire a TempChangedEvent to listeners that implement the TempChangeListener interface. For now we’ll remove the code related to the firing of TempChangeEvent events. Now that we understand properties, let’s modify the Temperature class to fire the PropertyChangeEvent instead. Because the object does not keep track of the previous temperature, the oldValue parameter of the PropertyChangeEvent constructor is set to null. The code would now look like this:

package BeansBook.Simulator;

import java.beans.*;
import java.util.Vector;

public class Temperature
{
   // the current temperature in Celsius
   protected double currentTemp = 22.2;

   // the collection of objects listening for property changes
   protected Vector propChangeListeners = new Vector();

   // the constructors
   public Temperature(double startingTemp)
   {
      this();
      currentTemp = startingTemp;
   }

   public Temperature()
   {
   }

   // the get method for property CurrentTemperature
   public double getCurrentTemperature()
   {
      return currentTemp;
   }

   // add a property change listener
   public synchronized void
   addPropertyChangeListener(PropertyChangeListener l)
   {
      // add a listener if it is not already registered
      if (!propChangeListeners.contains(l))
      {
         propChangeListeners.addElement(l);
      }
   }

   // remove a property change listener
   public synchronized void 
   removePropertyChangeListener(PropertyChangeListener l)
   {
      // remove it if it is registered
      if (propChangeListeners.contains(l))
      {
         propChangeListeners.removeElement(l);
      }  
   }

   // notify listening objects of CurrentTemperature property changes
   protected void notifyTemperatureChange()
   {
      // create the event object
      PropertyChangeEvent evt = 
          new PropertyChangeEvent(this, "CurrentTemperature",
                                   null, new Double(currentTemp));

      // make a copy of the listener object vector so that it cannot
      // be changed while we are firing events
      Vector v;
      synchronized(this)
      {
         v = (Vector) propChangeListeners.clone();
      }

      // fire the event to all listeners
      int cnt = v.size();
      for (int i = 0; i < cnt; i++)
      {
         PropertyChangeListener client =
                       (PropertyChangeListener)v.elementAt(i);
         client.propertyChange(evt);
      }
   }
}

With this change in place, we have to change the implementation of the Thermometer class as well. Instead of implementing the TempChangeListener interface it will implement the PropertyChangeListener interface. We remove the tempChanged() method and replace it with a propertyChange() method. For the sake of simplicity, let’s revert to the case where the Thermometer works with a single Temperature object named theTemperature instead of the two that we worked with previously. The code for the changed constructor and propertyChange() method is shown below:

// constructor
Thermometer(Temperature temperature)
{
   theTemperature = temperature;

   // register for property change events
   theTemperature.addPropertyChangeListener(this);
}

// handle the property change events
public void propertyChange(PropertyChangeEvent evt)
{
   // determine if the CurrentTemperature property of the temperature
   // object is the one that changed
   if (evt.getSource() == theTemperature &&
       evt.getPropertyName() == "CurrentTemperature")
   {
      Temperature t = (Temperature)evt.getSource();

      // get the new value object
      Object o = evt.getNewValue();

      double newTemperature;

      if (o == null)
      {

         // go back to the object to get the temperature
         newTemperature = t.getCurrentTemperature();
      }
      else
      {
         // get the new temperature value
         newTemperature = ((Double)o).doubleValue();
      }
   }
}

The propertyChange() method first determines if the source of the event was its instance of the Temperature class, and if the property that changed is called CurrentTemperature. If these two things are true, the source is cast to type Temperature and the new value object is retrieved from the event object. Since the new value object can be null, there is a check for that condition. If it is null, it goes back to the source to get the value of the CurrentTemperature property. If it isn’t null, the value is retrieved directly from the new value object.

A support class called java.beans.PropertyChangeSupport can be used to fire property change events to the registered listeners. You can either inherit from this class, or directly use an instance of it. PropertyChangeSupport implements the java.io.Serializable interface, which we discuss in Chapter 5. The method signatures of this class are as follows:

public class java.beans.PropertyChangeSupport
    implements java.io.Serializable
{
   // construct the object 
   public PropertyChangeSupport(Object source);

   // add a property change listener
   public synchronized void
   addPropertyChangeListener(PropertyChangeListener l);

   // fire a property change event to any listeners
   public void firePropertyChange(String propertyName, Object oldValue,
                                  Object newValue);

   // remove a property change listener
   public synchronized void
   removePropertyChangeListener(PropertyChangeListener l);
}

We could reimplement the Temperature class by inheriting from the PropertyChangeSupport class. This would eliminate the code that deals with the property change events. Note that the version of the constructor that takes no parameters calls the superclass constructor to make itself the event source. Although we aren’t going to keep the Temperature class like this, here’s what the code would look like if we did:

package BeansBook.Simulator;

import java.beans.*;
import java.util.Vector;

public class Temperature extends PropertyChangeSupport
{
   // the current temperature in Celsius
   protected double currentTemp = 22.2;

   // the constructors
   public Temperature(double startingTemp)
   {
      this();
      currentTemp = startingTemp;
   }

   public Temperature()
   {
      super(this);
   }

   // the get method for property CurrentTemperature
   public double getCurrentTemperature()
   {
      return currentTemp;
   }

   // notify listening objects of CurrentTemperature property changes
   protected void notifyTemperatureChange()
   {
      // fire the event
      firePropertyChange("CurrentTemperature",
                       null, new Double(currentTemp));
   }
}
               

Get Developing Java Beans now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.