Javafu01 - Javabeans Revisited

From Monkeys @ Keyboards
Jump to: navigation, search

Javabeans Revisited

Previous TOC Next
Javafu01 - Component-Based Programming Java Fu Javafu01 - Case Study 1 - Tip O' The Day Bean

Introduction

In this chapter we're going to revisit the idea of JavaBeans and look a bit closer at how we can properly implement them in a way that allows us to make the best use of the component definition. We'll also be looking at the idea of serialization, which is a concept that is not specific to JavaBeans - any object you write is a candidate for serialization.

Our bean from the previous chapter implemented events to a degree, but these were entirely internalised - it wasn't possible for the user of the bean to gain access to either the event handler object or the event originator. In order to make a genuinely useful component, it is important that there is a framework in place for allowing messages to be passed to and from the bean.

JavaBean Communication

In order to implement a genuinely interchangeable component framework, it is necessary for components to be able to communicate, both directly and indirectly. Consider a Java application that makes use of two JavaBeans... one Bean is just a container for a JButton, another is a container for a JLabel:

2.1: Javabean Communications

Communication from bean to bean via a container application is known as indirect communication, whereas communication directly from bean to bean is direct communication. Both techniques have a role to play in software component design.

Let's assume that the application is designed to change the state of the LabelBean every time the ButtonBean is pressed. The Application could listen for ActionEvents on the ButtonBean, and when they are triggered it could then change the appropriate property in the LabelBean. This is an example of indirect communication.

It's also possible for the ButtonBean to alter the property of the LabelBean without going through the application - this is an example of direct communication.

It is vital in any component framework that communication of these types is permitted, and JavaBeans provide us with the tools for ensuring proper communication between components.

Bound Properties

In the last chapter we looked at setting up simple properties - these were implemented purely as a pair of accessor methods. We can specialise a property so that it behaves as a bound property - this is a property that has the capability of notifying other components when it has been changed.

Beans communicate via events - in the case of a property being changed, it triggers what is known as a PropertyChange event. To implement the event structure, you incorporate the proper event triggering code in your property methods, and then have Beans register their interest in the changes.

First, we need to import a new package - this is the java.beans package which defines a range of support classes for manipulating and implementing JavaBeans.

Let's start by setting up an entirely new Bean - we'll start developing a Countdown Clock, like the kind made famous by James Bond movies.

We'll set up a bean with three properties - the hours, the minutes, and the seconds. Allowing for further configuration is left as an exercise for the reader, but this shouldn't be particularly taxing - define a property for what you want to be able to change, and then change it. Simple!

Our bean will set up five labels - one for each of the number properties, and two for colons. The colons will flash between each tick of the clock... all of this is handled by a Timer control. It is somewhat more complicated than the simple bean we looked at in the last chapter, but there is nothing new in this bean from what you will be familiar with from the Javanomicon:

import java.awt.*;
import javax.swing.JPanel;
import java.beans.*;
import java.io.*;
import javax.swing.*;
import java.awt.event.*;
 
public class CountdownClockBean extends JPanel implements Serializable, ActionListener {
 
    private int seconds;
    private int minutes;
    private int hours;
    private JLabel hoursLabel;
    private JLabel minutesLabel;
    private JLabel secondsLabel;
    private JLabel colon1;
    private JLabel colon2;
    private Timer myTimer;
    boolean tick;
 
    public CountdownClockBean() {
        setLayout(new GridLayout(1, 5));
        Font myFont = new Font("LCD-BOLD", Font.BOLD, 24);
        colon1 = new JLabel(":");
        colon1.setFont(myFont);
        colon2 = new JLabel(":");
        colon2.setFont(myFont);
        hoursLabel = new JLabel();
        hoursLabel.setFont(myFont);
        minutesLabel = new JLabel();
        minutesLabel.setFont(myFont);
        secondsLabel = new JLabel();
        secondsLabel.setFont(myFont);
        add(hoursLabel);
        add(colon1);
        add(minutesLabel);
        add(colon2);
        add(secondsLabel);
        myTimer = new Timer(500, this);
        myTimer.start();
        tick = true;
        setHours(0);
        setMinutes(1);
        setSeconds(10);
    }
 
    public int getSeconds() {
        return seconds;
    }
 
    public void setSeconds(int value) {
        seconds = value;
        secondsLabel.setText("" + seconds);
    }
 
    public int getMinutes() {
        return minutes;
    }
 
    public void setMinutes(int value) {
        minutes = value;
        minutesLabel.setText("" + minutes);
    }
 
    public int getHours() {
        return hours;
    }
 
    public void setHours(int value) {
        hours = value;
        hoursLabel.setText("" + hours);
    }
 
    public void updateClock() {
        int newSeconds;
        int newMinutes;
        int newHours;
        tick = !tick;
        if (tick == false) {
            colon1.setText("");
            colon2.setText("");
        } else {
            colon1.setText(":");
            colon2.setText(":");
            newSeconds = getSeconds();
            newMinutes = getMinutes();
            newHours = getHours();
            newSeconds -= 1;
            if (newSeconds == 0) {
                if (newHours == 0 && newMinutes == 0) {
                    myTimer.stop();
                } else {
                    newSeconds = 59;
                    if (newMinutes > 0) {
                        newMinutes -= 1;
                    } else if (newHours > 0) {
                        newHours -= 1;
                    }
 
                }
 
            }
 
            setSeconds(newSeconds);
            setMinutes(newMinutes);
            setHours(newHours);
        }
 
    }
 
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == myTimer) {
            updateClock();
        }
 
    }
}

We can now drag and drop our countdown clock onto any application in a builder environment, and we'll get a rough and ready countdown clock to meet all of our needs! We can even setup the initial values for hours, minutes and seconds to ensure that it is Super Relevant. Huzzah for us!

But alas, it's still not very useful except as a clock in itself. It's a bean, but it's not particularly easy to integrate into another application. We need more... more I say. We need to be able to set alarms to go off at particular times. We need to be able to trigger other actions in our application when the clock has finished counting down. We need to be able to start and stop the clock based on external considerations. In short, we need to be able to communicate with our bean.

Implementing Some Bound Properties

The first thing we need to do is add a new attribute to our bean... the attribute will be an instance of a new class called PropertyChangeSupport:

private PropertyChangeSupport boundPropertiesListeners = new PropertyChangeSupport (this);

The object created from the class is a collection of objects that have indicated their interest in property changes - you still need to write the methods to provide an interface for this, but it is the PropertyChangeSupport object that handles all the hard work.

Once you've instantiated the object, you need to define two methods in your bean - one that allows an interest in a property change to be registered, and one that allows an interest to be removed. Both of these methods will take a single parameter - a PropertyChangeListener object:

    public void addPropertyChangeListener(PropertyChangeListener ob) {
        boundPropertiesListeners.addPropertyChangeListener(ob);
    }
 
    public void removePropertyChangeListener(PropertyChangeListener ob) {
        boundPropertiesListeners.removePropertyChangeListener(ob);
    }

This provides the framework for setting up a bound property - other objects can register and deregister their interest, but at the moment nothing will happen. There's one more piece of functionality we need to set up - we need to trigger the appropriate event at the appropriate juncture.

There's a method in the PropertyChangeSupport class called firePropertyChange that deals with informing each of the registered listener objects. All you need to do is call with the correct parameters.

There are three parameters for the firePropertyChange method. The first is the name of the property being changed (as a String). The second is the old value of the property (as an Object), and the third is the new value of the property (as an Object). Since the second and third parameters are objects, it is necessary to use a wrapper class to pass any primitive variable types - which is exactly what we're going to be doing with our setSeconds method:

    public void setSeconds(int value) {
 
        int oldVal = getSeconds();
 
        seconds = value;
        secondsLabel.setText("" + seconds);
 
        boundPropertiesListeners.firePropertyChange("seconds", new Integer(oldVal), new Integer(value));
 
    }

A similar process can be followed with each of the other properties to trigger property change events when they are altered:

    public void setMinutes(int value) {
        int oldVal = getMinutes();
        minutes = value;
        minutesLabel.setText("" + minutes);
        boundPropertiesListeners.firePropertyChange("minutes", new Integer(oldVal), new Integer(value));
    }
 
    public int getHours() {
        return hours;
    }
 
    public void setHours(int value) {
        int oldVal = getHours();
        hours = value;
        hoursLabel.setText("" + hours);
        boundPropertiesListeners.firePropertyChange("hours", new Integer(oldVal), new Integer(value));
    }

And with that, we've implemented three bound properties. Most excellent!

But still, we're not done yet - we need to create a new bean... one that will listen for these events and let us do something with them. We'll set up an AlarmBean that will trigger a sound when the CountdownClockBean times reach a certain value.

Setting up a ProperyChangeListener object is a familiar process - one that is almost identical to the way we set up any other listener object. First we need to implement the appropriate interface - in this case, PropertyChangeListener (natch):

import java.beans.*;
public class AlarmBean extends JPanel implements PropertyChangeListener  {

Second, we need to include the appropriate handler method, which is called propertyChange and takes a parameter of type PropertyChangeEvent:

public void propertyChange (PropertyChangeEvent e) {
}

And we include the relevant code in there. Our AlarmBean at this point looks like this:

import javax.swing.*;
import java.beans.*;
 
public class AlarmBean extends JPanel implements PropertyChangeListener {
 
    private int hour;
 
    public int getHour() {
        return hour;
    }
 
    public void setHour(int value) {
        hour = value;
    }
 
    public AlarmBean() {
    }
 
    public void propertyChange(PropertyChangeEvent e) {
    }
}

The PropertyChangeEvent object that is passed as a parameter contains all the information we included as a parameter to our firePropertyChange method. The method getPropertyName will get the name of the property, and the methods getOldValue and getNewValue will get the previous and current values of the property, respectively.

We can combine these to implement our alarm code:

    public void propertyChange(PropertyChangeEvent e) {
        Integer value;
        if (e.getPropertyName().equals("hours")) {
            value = (Integer) e.getNewValue();
            if (value.intValue() == getHour()) {
                triggerAlarm();
            }
        }
    }

And then in our triggerAlarm method, we play the sound we want:

    public void setupSound() {
        if (Alarm != null) {
            return;
        }
        String baseDir = System.getProperty("user.dir");
        String filename = "attention.wav";
        String fullpath = "file:" + baseDir + "\\" + filename;
        try {
            URL myURL = new URL(fullpath);
            Alarm = Applet.newAudioClip(myURL);
        } catch (MalformedURLException ex) {
        }
    }
 
    public void triggerAlarm() {
        setupSound();
        Alarm.play();
    }

Finally, we need to add a way to set the time the alarm should trigger - we do this with a simple JTextField:

    public AlarmBean() {
        triggerAt = new JTextField(10);
        add(triggerAt, BorderLayout.CENTER);
        triggerAt.addActionListener(this);
    }
 
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == triggerAt) {
            setHour(Integer.parseInt(triggerAt.getText()));
        }
    }

Finally, we need to include our AlarmBean in our main application, and link it to the CountdownClockBean... all we need to do is call the addPropertyChangeListener method on the CountdownClockBean and pass our AlarmBean as a parameter, and viola! Two beans communicating via the magic of Bound Properties! Isn't that cool? I think it's pretty cool.

More Javabean Events

It's probably becoming apparent by now that JavaBeans don't involve much in the way of 'new' code... in fact, it's just using familiar code (although perhaps, unfamiliar objects) in unusual ways. This underpins one of the key concepts of component based programming - it's not about making use of new tools, it's about combining existing tools into a particular kind of software element.

There are some aspects of Bean design that are new (at least to us), and we'll get to those as the module progresses... but for now, let's look at making use of a particular kind of object model to allow for our components to act as sources of other kinds of events.

One of the big 'theoretical' areas of Object Oriented programming is the field of 'design patterns', where standard templates for objects are designed, refined and then made available as generic solutions to certain kinds of programming problems. We won't have cause to explore this topic in any depth during this module, but we will touch on it very briefly in this section of the book.

The event model used within Java is known as the delegation model. In this model, objects are broken into two categories - event dispatchers and event handlers. The code that happens when an event has been caught is dealt with by event handler objects. The event dispatcher objects are responsible for ensuring that all interested event handlers are informed when an event has been triggered.

This is of course familiar - we dealt with event handlers all the way through second year. Above, we saw an example of writing an event dispatcher object for bound properties. We can apply this same model to dispatching other kinds of event. Let's say, for example, that we want to trigger an ActionEvent within our CountdownClock - this event will be triggered whenever the clock has reached 0 hours, 0 minutes and 0 seconds.

First, we need to provide a way to register ActionListeners for our clock... we provide a pair of add and remove methods to deal with this as above. However, we don't have an equivalent of PropertyChangeSupport for our ActionListeners - we must handle this functionality ourselves.

We'll just use a standard ArrayList to maintain our list of Listener objects - we don't need to do anything more complicated than this. We do however need to ensure that our add and remove listener methods are thread-safe - we do this through the use of the synchronized keyword. You may remember this from the Javanomicon

First, our add and remove methods:

    ArrayList<ActionListener> myActionListeners;
 
    public synchronized void addActionListener(ActionListener ob) {
        myActionListeners.add(ob);
    }
 
    public synchronized void removeActionListener(ActionListener ob) {
        myActionListeners.remove(ob);
    }

And then we need a method for triggering an event. We need to create an appropriate ActionEvent object that will be passed as a parameter to each listener object... there are three arguments that need to be passed into the constructor method of the ActionEvent class, but only one of these is important - the first. The first parameter relates to the object which spawned the event - it's what gets returned by the getSource method of the object.

The second parameter is a numerical ID - ignore this. The third is a command string. Ignore this too. We'll pass these as 0 and null respectively.

Once we've constructed a suitable ActionEvent object, we step over every element in our ArrayList and call actionPerformed on each, passing as a parameter our ActionEvent object. Simple!

    public void handleActionEvents() {
        ActionEvent ex = new ActionEvent(this, 0, null);
        ActionListener temp;
        for (int i = 0; i < myActionListeners.size(); i++) {
            temp = (ActionListener) myActionListeners.get(i);
            temp.actionPerformed(ex);
        }
    }

Now all we need is to register an ActionListener on the CountdownClockBean in the same way we registered a PropertyListener, and we'll be able to setup an alarm that occurs as the properties change, as well as an alarm that occurs when the clock has reached 0 in all fields. Most excellent, I'm sure you'll agree.

Constrained Properties

A complement to the idea of bound properties is that of the constrained property - a constrained property is one that can be 'vetoed' by an external object or component. This can be used to ensure that no component can set the internal variables of another component into an invalid state.

In general, if a property is to be constrained, it should also be bound - it's just good practise.

First of all, we need to instantiate an object to deal with the hard work for us - this works in the same way as the PropertyChangeSupport object we discussed earlier. The class we need for implementing constrained properties is called VetoableChangeSupport:

VetoableChangeSupport myVetoableListeners = new VetoableChangeSupport (this);

We need to implement add and remove listeners for our new object:

    public void addVetoableChangeListener(VetoableChangeListener ob) {
        myVetoableListeners.addVetoableChangeListener(ob);
    }
 
    public void removeVetoableChangeListener(VetoableChangeListener l) {
        myVetoableListeners.removeVetoableChangeListener(l);
    }

And then whenever we want to throw out an event, we use the fireVetoableChange method of the VetoableChangeSupport object:

        myVetoableListeners.fireVetoableChange("property name", new Integer(old_value), new Integer(new_value));

We can handle this event in other components by implementing the VetoableChangeListener interface, which requires a method called vetoableChange. This method takes a parameter of type PropertyChangeEvent.

So far, it's all very similar to a PropertyChangeListener - except that because a listener object can veto the change, it needs a way of communicating this to the dispatch object. It does this through the use of exceptions. If a property is going to be set to an invalid state, then the vetoableChange method must throw a PropertyVetoException:

    public void vetoableChange(PropertyChangeEvent ex) {
        if (ex.getNewValue() > 60) {
            throw new PropertyVetoException("Bad monkey", ex);
        }
    }

Introspection

There needs to be a way for Java development environments to identify each of the properties, methods and events that a JavaBean supports - this is done through a process called introspection. Introspection allows for Java tools (particularly builders, since it is with builders that JavaBeans are primarily associated) to query a software component directly for its characteristics, without requiring a separate specification or API documentation.

Partially this is done through the enforced naming conventions we have discussed throughout - if we have methods getBing and setBing then we know, because of the Bean convention, that these constitute a property called Bing.

However, there is also a technique called reflection that allows for a more generic approach to determining the various elements of a JavaBean - this is handled within the java.lang.reflect package (it's all done automatically - we're only setting the scene here).

Reflection is a very CPU intensive process, but allows you to programmatically query the methods provided by a class, the attributes of a class, and what methods it provides.

Reflection allows for each of the methods to be queried, and the naming convention allows the environment to work out how each of these methods fit into the JavaBean structure.

For example, let's look at a very simple reflection class that will display all of the methods that have been declared in our CountdownClockBean (declared means that it will not show those that have been inherited):

import java.lang.reflect.*;
 
public class ReflectionClass {
 
    CountdownClockBean bing;
 
    public ReflectionClass() {
        bing = new CountdownClockBean();
        Class myClass = bing.getClass();
        Method[] methods = myClass.getDeclaredMethods();
        for (int i = 0; i < methods.length; i++) {
            System.out.println("" + methods[i].getName());
        }
    }
 
    public static void main(String args[]) {
        ReflectionClass mainWindow = new ReflectionClass();
    }
}

This simple program outputs the following:

addActionListener
removeActionListener
handleActionEvents
getSeconds
setSeconds
getMinutes
setMinutes
getHours
setHours
updateClock
actionPerformed
addPropertyChangeListener
removePropertyChangeListener

The naming convention allows for each of these to be assessed in terms of the software component definition... each of these can be slotted into one of three categories:

  • If the method name begins with set or get, then it is a property. The text that follows the set or get indicates the name of the property.
  • If the method name begins with add and ends with Listener, then the JavaBean acts as an originator for events of the specified type.
  • If the method doesn't fall into either of these categories, it's just a utility method.

By following this, the methods in our bean can be broken up into separate categories:

Properties Seconds (R/W)
Hours (R/W)
Minutes (R/W)
Events PropertyChange
Action
Utility Methods actionPerformed
updateClock
handleActionEvents


This should hopefully give some indication of why it is necessary to follow the naming convention for JavaBeans.

Serialization

The final thing we are going to discuss in this chapter is the idea of serialization. This is a technique for converting an object (of any type, not just a JavaBean) into a stream of bytes suitable for transmission over a network, or storing on a given storage medium.

The process is called 'serialization' because each object is assigned a serial number on the stream - this is so it can later be accessed. Later, if the same object is written again to the stream, only its serial number is stored.

Serialization is extremely useful, and allows for very powerful storage routines that store objects, as well as all the objects that those objects reference. This is done with a mere flick of our Java wrists - it does not require much in the way of additional coding.

In order for an object to be a candidate for serialization, it must implement the Serializable interface... all the functionality for actually handling the serialization is already done for you.

The JavaBeans standard requires that all JavaBeans be serializable. We'll talk more about this when we discuss XML in the next chapter. We don't need to write file IO routines, but we'll look at how we can do that anyway. As has already been stated, serialization is not something unique to JavaBeans... you can serialize any object you have written for easy storage and retrieval.

All of the classes we discuss in this section may be found in the java.io package.

First of all, we need to make a connection to a file on disk (or indeed, on a network - we'll look at disk access at the moment). We do this by creating a FileOutputStream around the relevant file:

FileOutputStream myFile = new FileOutputStream ("bing.dat");

Then, we create an ObjectOutputStream object around this FileOutputStream:

ObjectOutputStream out = new ObjectOutputStream (myFile);

Then, we use the writeObject method of ObjectOutputStream to dump the object to the file:

out.writeObject (bing);

And that's it!

Reading the object back is just as easy. First we create a FileInputStream around a file:

FileInputStream myFile = new FileInputStream ("bing.dat");

Then we create an ObjectInputStream around that FileInputStream:

ObjectInputStream in = new ObjectInputStream (myFile);

Then, we call readObject on the ObjectInputStream to load the file. It gets returned as an Object, so it must be cast:

bing =  ( ExampleClass )in.readObject ();

The readObject method can throw a ClassCastException - this is a mandatory acknowledgement exception that must be dealt with, on pain of pain!

So, that's pretty cool - an easy way to write out objects to a file. What makes serialization really exciting is that it will write out all serializable objects that an object has a reference to - if you write out a HashMap to a stream, you'll get the HashMap structure as well as all the objects stored within! That's insanely cool.

Here's the code for the ExampleClass - as you can see, it's trivial:

import java.io.*;
 
public class ExampleClass implements Serializable {
 
    public String bing;
    public String bong;
 
    public ExampleClass(String b1, String b2) {
        bing = b1;
        bong = b2;
    }
 
    public String toString() {
        return bing + " " + bong;
    }
}

And the code for an application to load and save the state of an object of this class:

import java.awt.*;
import java.awt.event.*;
import java.io.*;
 
class SerializationExample {
 
    ExampleClass bing;
 
    public void readFile() {
        bing = null;
        try {
 
            FileInputStream myFile = new FileInputStream("bing.dat");
            ObjectInputStream in = new ObjectInputStream(myFile);
            bing = (ExampleClass) in.readObject();
 
        } catch (Exception ex) {
            System.out.println("Error!");
        }
    }
 
    public void writeFile() {
        try {
 
            FileOutputStream myFile = new FileOutputStream("bing.dat");
            ObjectOutputStream out = new ObjectOutputStream(myFile);
            out.writeObject(bing);
 
        } catch (Exception ex) {
            System.out.println("Error!");
        }
    }
 
    public SerializationExample() {
        bing = new ExampleClass("hello", "world");
        writeFile();
        readFile();
        System.out.println("" + bing);
    }
 
    public static void main(String args[]) {
        System.out.println("Starting SerializationExample...");
        SerializationExample mainFrame = new SerializationExample();
    }
}

Conclusion

Most of the material around JavaBeans is largely theoretical - it's all about naming conventions, design patterns and object models. By this point in your development as programmers, there will be very little in the way of 'new' code that comes your way. Sure, there will be new concepts, and new techniques to learn - but the syntax doesn't get much harder from here-on-in.

JavaBeans are a useful first foray into software components, because they are syntactically trivial, coherent in their design, and immediately relevant... there's nothing to stop you now writing your own powerful JavaBeans and incorporating them into your own projects and into the projects of others.

We'll have cause to return to the subject of JavaBeans in later chapters - we're not quite done yet with this particular concept. Be afraid, be very afraid!


Reader Exercises

Exercise One

The CountdownClockBean looks pretty boring - add in a few properties to spice it up. Let people change the font, the colour, and the size.

Exercise Two

The AlarmBean can only have a single alarm set, and that alarm can only be triggered on the hour - this is because of the way the bound properties are set up. Change the bound properties so that each tick of the clock, be it hour, minute or second, will trigger a 'time change' PropertyChange event.

  • Create a new class that will act as a container for the three pieces of data: the hours, the minutes, the seconds.
  • Change the AlarmBean so that it is possible to set up any number of alarms to trigger at any point in the countdown.

Exercise Three

Add in a few more properties for the AlarmBean - make use of index properties to allow developers to make use of any number of possible alarm sound files.