Monkeys at Keyboards: Java-Fu
© Michael James Heron
Topic: Java Programming
Level: 3
Version: beta

The hardest thing to understand in the world is the income tax.
Albert Einstein

3 - Case Study 1 - Tip O' The Day Bean

PreviousTable of ContentsNext
Forum


Chapter Objectives

By the end of this chapter, the reader will be able to:

  • develop a self-contained Javabean to display a tip of the day.


3.1

Introduction

So, we've spent a little time looking at the architecture of a Javabean, and some of the techniques we can use to have Javabeans communicate with the rest of an application. We're going to take a break from 'new' material in this chapter and look at integrating what we've already learned. We'll spend a little time building a 'tip of the day' Javabean - one of those annoying little prompts that appear when you open up an application for the first time.

We'll make the assumption that later on, we'll have to integrate this Javabean into any number of other applications, so it's in our best interests to make sure that we make it as general as possible right from the very start. We'll add a range of properties and functionality, and see how we can make use of the Javabean in an example container application.

We'll also look at how we can make our Javabean communicate with a container application, just in case, for whatever reason, people want to register an interest in when the tip displayed in our bean is changed. You never know, some people are weird like that.

3.2

Our Storyboard

Step one is to setup the storyboard for how we want the Javabean to appear. We're not going to have much in the way of an interface - we want a button to go onto the next tip, a button to go onto the previous tip, and a button to display a random tip. We also want a text area which displays the text of the tip. We'll also display a nice image in the corner, which the user (as in, the person making use of our Javabean in their code) can decide to change as they wish via a property:

Tip Of The Day Storyboard
Fig 3.1: Tip Of The Day Storyboard

We'll make use of the BorderLayout manager to achieve this, applying the buttons through the use of the GridLayoutManager on a separate panel. Our image will be displayed as an ImageIcon on a label - that way it will resize properly and won't need any computation from us to place.

We're not going to do a storyboard for the container application, because that doesn't concern us - it's just something we're going to use to make sure the Javabean works properly. Our Javabean is going to extend from JPanel, because we want it to be used just like a JButton or a JScrollbar... people should be able to put it anywhere they want. That's a problem for later though, so let's put it out of our minds!

3.3

The Javabean Specification

Step one is to build the framework for the Javabean - the code that we're going to build on to add all our functionality. Remember that there are three things that define a Javabean:

  1. It must have a default constructor
  2. It must have its state queried by accessor methods
  3. It must be serializable

So let's start off slow, with our framework interface. We'll worry about properties later, when we have any of any note:

import javax.swing.*; 
import java.awt.*;
import java.io.*;
import java.awt.event.*;


public class TipBean extends JPanel implements Serializable, ActionListener {
private JButton next, previous, random;
private JTextArea tipArea;
private JLabel imageLabel;

public TipBean() {
setLayout (new BorderLayout());
tipArea = new JTextArea (10, 10);
imageLabel = new JLabel ("");
JPanel myPanel = new JPanel();
myPanel.setLayout (new GridLayout (1, 3));
next = new JButton ("Next");
next.addActionListener (this);
previous = new JButton ("Previous");
previous.addActionListener (this);
random = new JButton ("Random");
random.addActionListener (this);
myPanel.add (previous);
myPanel.add (random);
myPanel.add (next);
add (myPanel, BorderLayout.SOUTH);
add (tipArea, BorderLayout.CENTER);
add (imageLabel, BorderLayout.EAST);
}


public void actionPerformed (ActionEvent ex) {
}

}

Here we meet an interesting (perhaps) design question - why aren't the buttons exposed as properties?

Properties are used primarily to query the state of a Javabean - they don't usually exist for purely internal attributes. We don't want people to be able to change our buttons in the Javabean with their own buttons - it's not set up to be skinnable. Sure, we could decide that was one of the functional requirements, but we're not going to. You take the Javabean and you take the layout - it's all or nothing.

With than in mind, the Swing components don't relate to the state of the Javabean. They relate to the interface. The state of the Javabean is contained in those attributes that should change from application to application. We don't have any of those yet, but one obvious example is the library of tips itself. That's going to change depending on whether our bean is incorporated into a word processor or an image analysis program.

We also hit a snag here - we have no way of testing our Javabean! So we need to write a simple container application that will place it on a frame for viewing:

import javax.swing.*; 
import java.awt.*;
import java.io.*;
import java.awt.event.*;


public class ContainerApp extends JFrame {

public ContainerApp() {
TipBean myTips = new TipBean();
add (myTips, BorderLayout.CENTER);
}


public static void main (String args[]) {
ContainerApp mainWindow = new ContainerApp();
mainWindow.setSize (400, 400);
mainWindow.setVisible (true);
}

}

This sets up the two key parts of a component interaction - the application, or the context, and the bean (the component). The context should be changeable, and all of the setup that properly belongs to a context should be done in the application. All of the setup that should properly be done in the component (so that someone else doesn't have to worry about it), should be in the bean.

Let's consider a few examples:

We need a mechanism for navigating through a list of tips. For now we'll say our tips are going to be stored as Strings in an ArrayList. We could provide a pair of accessor methods for a property (getTips and setTips), and then return the tips as an ArrayList to the main application.

In such a case, the application will have to provide a mechanism for stepping through the tips... as indeed, will any application making use of our bean. Not Cool.

Instead, the mechanism for stepping through the tips should be located within the Javabean.

On the other hand, we could set up the list of tips in the Javabean. That means that anyone who wants to use our bean is stuck with whatever random tips we've decided to provide. Again - not cool. The functionality for populating the library of ArrayLists should be provided in the application, although the Javabean must provide suitable methods to allow for this.

3.4

The Tips Library

Let's start off by implementing the tips library. We're going to have this as an ArrayList of strings. We need to provide a suitable attribute in our Javabean, and the accessor methods to go with it:

...import java.util.*; 
public class TipBean extends JPanel implements Serializable, ActionListener
{
...
private ArrayList<String> myTips;

public TipBean() {
...
myTips = new ArrayList<String>();
}


public void actionPerformed (ActionEvent ex) {
}


public ArrayList getTips() {
return myTips;
}


public void setTips (ArrayList<String> value) {
myTips = value;
}

}

If we're going to be kind and make it as easy as possible to setup our bean, we could also provide an addTip method that lets them be increased incrementally. Note though that this is a utility method and not a property... it doesn't fit into the Java naming convention and so won't be picked up via introspection:


public void addTip (String str) {
myTips.add (str);
}

Now the user of our bean (the developer incorporating it in their code) can choose to set a whole ArrayList of tips, or incrementally add them using addTip. Let's go with the latter in our container application:

import javax.swing.*; 
import java.awt.*;
import java.io.*;
import java.awt.event.*;


public class ContainerApp extends JFrame {

public ContainerApp() {
TipBean myTips = new TipBean();
Container c = getContentPane();
myTips.addTip ("Don't eat yellow snow!");
myTips.addTip ("Don't run with scissors!");
myTips.addTip ("Kicking a man when he's down is the best time to do it!");
myTips.addTip ("Oh no, I'm MEEELTTTTTING!");
c.add (myTips, BorderLayout.CENTER);
}


public static void main (String args[]) {
ContainerApp mainWindow = new ContainerApp();
mainWindow.setSize (400, 400);
mainWindow.setVisible (true);
}

}

Now that we have a library of tips, we should add the functionality for the buttons. We should also add a method that lets us display a particular tip - after all, we don't want to start off with an empty text area. We'll add that method first:


private void displayTip (int index) {
String tip = myTips.get (index);
tipArea.setText (tip);
}

We need a class-wide integer variable to store where we currently are in the ArrayList (we'll call it currIndex). With this, we can implement the code for our buttons:


public void actionPerformed (ActionEvent e) {
int rand;
if (e.getSource() == next) {
currIndex += 1;
if (currIndex == myTips.size()) {
currIndex = 0;
}

}

if (e.getSource() == previous) {
currIndex -= 1;
if (currIndex < 0) {
currIndex = myTips.size() - 1;
}

}

if (e.getSource() == random) {
rand = (int) (Math.random() * myTips.size());
currIndex = rand;
}

displayTip (currIndex);
}

And change our addTip and setTips methods a little:


public void setTips (ArrayList<String> value) {
myTips = value;
displayTip (0);
}


public void addTip (String str) {
myTips.add (str);
displayTip (0);
}

This gives us the majority of our functionality - although we also need to implement our logo code. Here we have another design choice.

Our Javabean should work independently of context - but what about if our Javabean is deployed in an Applet? Ideally, we want people to just be able to 'plug and play' with our Javabean without having different people re-implement different pieces of functionality. The problem is - applets run in a sandbox, and so they have a special way of getting images to display. Applications use an entirely different way to get images... the context of this matters.

Although it's not ideal, we'll leave it up to the user to worry about how to get an Image object, and we'll just have a property called setImage and getImage that manipulates an internal Image object:

private Image logo; 

public Image getLogo() {
return logo;
}


public void setLogo (Image value) {
logo = value;
}

And change our addTip and setTips methods a little:

Our setLogo method is a little more complicated than this though, because we must convert the image into an ImageIcon and display it on our label... this shows that accessor methods are not always purely about attribute manipulation. They often serve as valuable gatekeepers to functionality, provided they are the only valid entry point to an application (which we ensured by making all the attributes private):


public void setLogo (Image value) {
ImageIcon myIcon = new ImageIcon (value);
logo = value;
imageLabel.setIcon (myIcon);
}

Now to test that new property - our container application (our context) must provide a suitable Image for the Javabean:


public Image getImage (String file) {
Toolkit myTools = Toolkit.getDefaultToolkit();
Image myImage = myTools.getImage (file);
return myImage;
}

And then we need to set the appropriate property:

myTips.setLogo (getImage ("discworld.jpg")); 


public void setLogo (Image value) {
ImageIcon myIcon;
logo = value;
myIcon = new ImageIcon (getLogo());
imageLabel.setIcon (myIcon);
}

And:


public Image getLogo() {
return logo.getScaledInstance (200, 200, 10);
}

This is a little crude, but it will suffice for now. If we wished to be a little neater, we could work out how large the logo should be in relation to the size of the Javabean itself - this is quite easy to do (in code), but the maths of scaling an image are not the focus of this particular case study.

And with that, we have all our base functionality. Pretty neat!

But there's more we can do - a good Javabean will provide properties for everything that conceivably need to be configured. Like, for example, the colour of the buttons. Or perhaps the font used throughout, or the maximum size of the image... pretty much everything you could think that could be changed, should be possible to change. This is often just a case of providing valid accessor methods... for ones that change the colour of buttons (for example) don't even need attributes to store them:


public void setNextButtonColour (Color value) {
next.setBackground (value);
}


public Color setNextButtonColour() {
return next.getBackground();
}

Both of these accessors just pass a method call onto the components themselves - they act as an interpreter between the Swing component and the context of the Javabean. We don't need to store the colour ourselves - in fact, it's good design not to duplicate the data if we don't need to.

Alas, a little testing shows a big problem - large images will dominate the Javabean. We need to make sure that they can't exceed any given size. Again, we do this in our property methods:

3.5

Javabean Communication

That's the functionality of our bean completed, but we should also have it communicate to interested observers. Really, there's very little reason that someone may want to register an interest in any property change in our bean, but that's not really our business. As developers of a component, we just have to make sure that it's as adaptable as possible. If people want to listen for tips being changed, then that's entirely up to them, weirdos that they are.

Actually, it's not as pointless as it seems, because although we have developed this as a 'tip of the day application', there's nothing to stop someone using it for another purpose - perhaps as an online help system. Conceivably, someone may want to register what tips people have seen, or perhaps query them on how useful they found them. Letting people register an interest in these events is good component design.

What we are going to do is implement a bound property that is triggered whenever a tip is displayed. We're going to have to keep track of listener objects for this, but we've already seen how to do this in the last chapter.

First, we need a new package imported... java.beans. This gives us access to the PropertyChangeSupport object we need for our bean to communicate. We implement this as a class-wide object:

private PropertyChangeSupport myListeners; 
myListeners = new PropertyChangeSupport (this);

We need to add two utility methods now - one for registering interest and one for deregistering interest. These are exactly the same as the ones we saw in the last chapter (except for the name of the variable being used):


public void addPropertyChangeListener (PropertyChangeListener ob) {
myListeners.addPropertyChangeListener (ob);
}


public void removePropertyChangeListener (PropertyChangeListener ob) {
myListeners.removePropertyChangeListener (ob);
}

We also need to have our bean trigger PropertyChange events at the appropriate time - we can put this into our displayTip method:


private void displayTip (int index) {
String currentTip = tipArea.getText();
String tip = (String) myTips.get (index);
myListeners.firePropertyChange ("tip", currentTip, tip);
tipArea.setText (tip);
}

And now our bean can communicate with any interested object. Let's do just that by setting up our context as a listener object.

Any object listening for PropertyChange events must implement the PropertyChangeListener interface - this requires a property called propertyChange to be included in the code. Our container object is going to keep a track of which hints people find helpful via the use of a HashMap (called myFeedback) and some dialog box:


public void propertyChange (PropertyChangeEvent e) {
String oldValue = (String) e.getOldValue();
String newValue = (String) e.getNewValue();
Integer tmpInt;
int response = JOptionPane.showConfirmDialog (null, "The new tip is " + newValue
+ ". Do you find this helpful?", "Helpful?", JOptionPane.YES_NO_OPTION);
tmpInt = (Integer) myFeedback.get (newValue);
if (response == JOptionPane.YES_OPTION) {
if (tmpInt == null) {
tmpInt = new Integer (1);
}

else {
tmpInt = new Integer (tmpInt.intValue() + 1);
}

}

else {
if (tmpInt == null) {
tmpInt = new Integer (0);
}

}

myFeedback.put (newValue, tmpInt);
JOptionPane.showMessageDialog (null, "You found this ““hint helpful a total of " +
"" +tmpInt.intValue() + " times.");
}

All that's left to do now is to add the appropriate listener object to our Javabean - we do this after we've set it up in the constructor:

myTips.addPropertyChangeListener (this); 

And that's it! We've just written a fully functioning Javabean, with support for context applications registering their interest in property changes. Pretty neat, I think!

3.6

Conclusion

The fact that we can easily incorporate a Javabean into a container application is just one of their benefits - we could easily take our Javabean here and make use of it in a builder application (like JBuilder), and draw it onto a Java application in the same way we can draw a JButton (or any other kind of GUI component). That's pretty powerful - by simply dragging and dropping, we can add a fully featured 'tip of the day' component into any suitable application.

There are other benefits to this kind of component architecture - we'll see these when we look at SOAP later in the book. It's easy to feel under-whelmed at the moment - we hear a lot about Javabeans in the press, and it turns out they're pretty dull as far as their code goes. As we go through the book, we'll have cause to further explore different kinds of component architectures, and talk about how programs are put together (as opposed to what programs do).

We make a distinction in the way we teach programming at Abertay. First year concentrates primarily on learning syntax - this is the very easy stuff (as far as the big picture programming goes), but many people find they cannot cope with it. That's perfectly understandable - programming is really difficult, and it does us no justice to pretend that it isn't.

But syntax is the easiest part of it - the next step is learning about tools. This is what second year does, primarily... we introduce some elements of new syntax, but not much - primarily what we talk about is what you can do with code. We look at the kind of techniques you will find in the toolbox of any Java developer, and then we put those tools to work on a range of problems.

In third year, we start looking at the really hard part of programming - design. Sure, we learn new tools along the way, but these are a means to an end - as a way to introduce solid examples of the benefit of good design, or as a way to illuminate some later concept (our journey into XML, for example, is useful primarily to explain how SOAP works).

In this case study, we've seen very little that's new in terms of code, but we've looked at how to put together a property Javabean, and discussed some of the design decisions we must make along the way. As a wise jedi once said, 'You've taken your first step into a larger world".

Further Reading

The following table details further reading on the topic in this chapter, and also any external resources that you may find useful.

ResourceDescription
NothingThere's no links as yet, so go find your own you shiftless wasters.

PreviousTable of ContentsNext

© 2004-2006 Michael James Heron