Sun Java Solaris Communities My SDN Account Join SDN
 
Tutorials & Code Camps

JavaBeans 101, Part III

 
Online Training Index

101, Part III:

Introduction | Page 1 | Page 2 | Page 3 | Page 4



Converting Existing Code to Beans

Got some old code you'd like to update? To see how it's done, look at the NervousText example program distributed with the JDK software.

All AWT components in JDK version 1.1 software, are JavaBeansTM technology. This means they are already set up to be used as reusable components in builder tools. However, older JDK software, version 1.0, programs and some JDK software, version 1.1 demo programs are not built as Beans. This lesson describes how to convert existing programs and applets into JavaBeans.

Converting existing programs has advantages when learning about JavaBeans tecnology. First, you can concentrate on the parts of the program or applet that are unique to Beans. Second, working with a familiar program helps you to focus on the JavaBean features that you need to learn first.

The concepts introduced in this lesson include:

  • Reflection
  • Event handling
  • Bean customization

Start by taking a look at the JDK 1.1 code in the NervousText.java file in the JDK distribution tree under the directory demo/NervousText. This applet displays jittering text on the screen, giving the impression that the text may have had a little too much coffee. The JDK 1.1 version of NervousText is the starting point of reference for this lesson.

The next section contains the steps to convert the NervousText applet into a JavaBean.

Making an Applet into a Bean

The following steps outline the process of converting a Java program or applet into a JavaBean.

Step 1. Select program to convert

First, select a demo applet or class to convert to a Bean. This particular example uses NervousText.

Step 2. Change applets to standalone Window objects

While it is possible to make applets into Beans, it's a little easier to convert standalone Window objects or components to Beans. To see an example of an applet that is a Bean, look at the source code for the JugglerBean distributed with the Beans Development Toolkit (BDK).

To change NervousText into a standalone Window object, edit the line declaring the NervousText class. In the JDK 1.1 distribution, it reads:

public class NervousText extends java.applet.Applet


    	implements Runnable {

Instead of making NervousText a subclass of Applet, make it a subclass of Panel, as follows:

public class NervousText extends Panel implements Runnable {

Step 3. Add AWT and Beans import and package statements

Add the following import statement after import java.awt.Font; statement:

import java.awt.*;

The most effective way to manage the different Beans you create is to define them to be part of a package, and then build a JAR file based on the directory structure implicitly defined by the package name.

To test the various versions of the NervousText Bean in the BeanBox, you need to add a package statement indicating the location of the Java class files that make up the Bean. The package reflects the subsystem where you plan to store your class files locally, and not necessarily where the BeanBox expects to find the actual class files. Once you have bundled the set of classes making up a Bean in a JAR file, you can place the JAR file in the default location where the BeanBox, on its initialization, looks for JAR files. For example, the demo Beans for the NervousText example in this lesson are part of the sun.beanbox.beans package.

You can define your own package and corresponding subdirectories. However, it is convenient to use the supplied demo package.

Add the following package statement before the first import statement, import java.awt.Graphics;:

package sun.beanbox.beans;

The beginning of the NervousText file now looks like this:

package sun.beanbox.beans;
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.*;
public class NervousText01 extends Panel implements
  Runnable, MouseListener {
  ...

Step 4. Add a constructor

When converting applets to Window objects, you no longer need the init method, but you need a constructor, if one is not already defined. For NervousText, you can change the line declaring the init method to a constructor by renaming the method and removing the return type from the method declaration.

Change:

public void init() {

to:

public NervousText() {

The constructor now looks like this.

public NervousText() {
    addMouseListener(this);
    s = getParameter("text");
    if (s == null) {
      s = "HotJava";
    }
 
    separated =  new char [s.length()];
    s.getChars(0,s.length(),separated,0);
    resize((s.length()+1)*15, 50);
    setFont(new Font("TimesRoman",Font.BOLD,36));
  }

Step 5. Adapt applet parameters

Since NervousText is no longer an applet, it can't call getParameter to get the text it is going to display. You may have to add a line or two to hard-code parameters that were formerly passed to applets. In the case of NervousText, you see a conditional test that hard-codes the displayed text in the event that a parameter argument is not supplied.

if (s == null) {
	s = "HotJava";
}

You can assure that the value of s is always set to HotJava by commenting out the call to getParameter, as follows.

public void init() {
  ...
  // s = getParameter("text");
  if (s == null) {
    s = "HotJava";
  }
  ... 
}

Step 6. Start the component's thread

A browser or appletviewer automatically starts an applet such as NervousText by calling its start method whenever a page referencing the applet is loaded. The start method invokes the primary thread for the applet or, in this case, the Window Object. With our changes, you must call start manually to set its thread running.

Add the following line at the end of the constructor NervousText, just after the setFont statement.

start();

The constructor should now look like this:

public NervousText01() {
    addMouseListener(this);
    // s = getParameter("text");
    if (s == null) {
      //s = "HotJava";
      s = "Nervous bean";
    }
 
    separated =  new char [s.length()];
    s.getChars(0,s.length(),separated,0);
    resize((s.length()+1)*15, 50);
    setFont(new Font("TimesRoman",Font.BOLD,36));
    start();
}

Step 7. Add properties and support for introspection

A nice feature is to allow users of the NervousText Bean to customize the text it displays when they are building new applications. You can enable builder tools to give users the option to change the text displayed by NervousText by adding a text property. Adding the methods getText and setText allows you to change the String value of the jittering text.

Add these methods after the NervousText constructor:

public void setText(String newstring){
    s=new String(newstring);
    separated= new char[s.length()];
    s.getChars(0,s.length(),separated,0);
}
 
public String getText(){
    return(s);
}

Step 8. Define the preferred and minimum sizes for the Bean

Strictly speaking, you do not need to define preferredSize and minimumSize call back methods for the Bean. If these methods are defined, builder tools (such as the BeanBox) call them to determine how the Bean should be drawn. Add a preferredSize method to indicate the initial size of the Bean.

What is the difference between preferredSize and minimumSize ? If minimumSize is defined and preferredSize is not, the size of the Bean can be changed later inside the builder tool. If preferredSize is set, the Bean remains a fixed size. You'll see an example version of NervousText using minimumSize later in the lesson.

The BeanBox calls the Bean's preferredSize method at the time a user selects the Bean from the menu of available Beans to be placed in the top-level container.

Add the following preferredSize code after the getText method that you just added:

public Dimension preferredSize() {
    return (new Dimension(150,150));
}

Step 9. Remove extraneous event handlers

Some event-handler routines used for applets no longer apply once the applet is turned into a Bean. Within NervousText, comment out the mousePressed action event handler so that it does not interfere with the operation of the BeanBox.

  • Add this:
public void mousePressed(MouseEvent e) {
  /*
  e.consume();
  if (threadSuspended) {
    killme.resume();
  }
  else {
    killme.suspend();
  }
  threadSuspended = !threadSuspended;
  */
}

Now the mouse down handler is defined, but it no longer performs any action. Specifically, a mouse down event no longer kills the thread and stops the nervous text from jittering.

Step 10. Test the Bean in the BeanBox

You can now add the NervousText component from the Toolbox into the BeanBox panel. You can also change the text parameter in the Property Sheet that is automatically associated with the Bean by the builder tool.

When using the BeanBox for testing, it is easiest to package the Bean as a JAR (Java Archive File) and then load the JAR into the BeanBox. To do this, you use a manifest file to define the contents of the JAR so the BeanBox or builder tool knows how to use each of the files contained in the JAR.

Your manifest file for NervousText looks like this:

Manifest-Version: 1.0
Name: sun/beanbox/beans/NervousText01.class
Java-bean: True

This manifest indicates the JAR contains a single Java class file, NervousText01.class , which is a Bean. The manifest file also indicates that this class is a JavaBean.

Assuming you have installed the Beans Development Kit (BDK) in a directory defined as BDK_HOME, the BeanBox looks in a directory called BDK_HOME/beans/jars for the JAR files defining the initial Beans it displays in its Bean menu.

Compile the source with the -d flag to make sure classes are placed in the proper subdirectories for the package:

javac -d . NervousText01.java

The dot following the -d flag indicates the package root is the current directory, and the resulting class file is placed in the ./sun/beanbox/beans directory.

Once you've compiled the class file and created the manifest file, build the JAR as follows:

jar cfm NervousText01.jar manifest.tmp 
	sun/beanbox/beans/NervousText01.class

You can now remove the temporary manifest file and copy the JAR file to the JAR directory for the BeanBox.

rm manifest.tmp
cp -p NervousText01.jar BDK_HOME/beans/jars

After copying the JAR file to the BeanBox jars directory, you can start the BeanBox and NervousText01 appears in the menu of available Beans.

Program Source Code

The makefile for this lesson automates source code compilation, JAR file construction, and copying of JAR files to the appropriate BeanBox directory. You'll have to edit several of the variables in the makefile to indicate the location of your JDK 1.1 and BDK installation directories.

You may want to look at the final source file for NervousText Bean, Version 01, to verify the changes you have made to the original NervousText.java file.

JDK 1.1 Event Handlers for Beans

In this lesson, you add JDK 1.1 event handler methods to NervousText. These methods let you program NervousText Beans to change their run time (as opposed to design time) behavior. Event handlers allow you to define methods which can be fired in a running application. This differs from properties, which allow you to change the way a Bean looks or behaves while building an application.

We'll use event handlers to change the direction in which NervousText writes text. Start by adding an event receiver so that you can change the text direction from the OurButton object. (OurButton is already available in the BeanBox.)

Step 1. Add import Statement for AWT events

First add the JDK 1.1 event handler import statement after the import java.awt.*; statement:

import java.awt.event.*;

You now have the following package and import statments at the top of NervousText02.java:

package sun.beanbox.beans;
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.*;

Step 2. Add a Variable to Control Direction of Text

Add a boolean toggle variable called lefttoright in the NervousText class after the line:

booleanthreadSuspended = false

boolean lefttoright=true;

The data field declartions for NervousText02 now looks like this:

public class NervousText02 extends Panel 
  implements Runnable, MouseListener {
	char separated[];
	String s = null;
 	Thread killme = null;
	int i;
	int x_coord = 0, y_coord = 0;
	String num;
	int speed=35;
	int counter =0;
	boolean threadSuspended = false;
	boolean lefttoright = true;
	...

Step 3. Create an ActionEvent Handler Method

Create an action event receiver target called changeDirection that toggles the state of lefttoright. The BeanBox has a button called OurButton that fires off ActionEvents, and it finds changeDirection through introspection.

  • Add this method immediately after the getText property setter:
public void changeDirection(ActionEvent x)
{
  if(lefttoright)
    lefttoright=false;
  else
    lefttoright=true;
}

Step 4. Modify NervousText's paint Method

Finally, change the paint method to draw the nervous text in a different direction depending on the value of lefttoright.

Change the call to drawChars in the paint method from:

g.drawChars(separated, i,1,
               x_coord,y_coord);

to:

if(lefttoright)
    g.drawChars(separated, i,
      1,x_coord,y_coord);
else
    g.drawChars
	(separated, (s.length())-i-1,1,
                     x_coord,y_coord);

The paint method now looks like this:

public void paint(Graphics g) {
  for(i=0;i < s.length();I++)
  {
    x_coord = (int) (math.random()*10+15*I);
    y_coord = (int) (math.random()*10+36);
    if (lefttoright)
      g.drawChars(separated,
          i,1,x_coord,y_coord);
    else
	g.drawChars(
	   separated, (Ss.length))-I-i,
                    1,x_coord,y_coord);
  }
}

Step 5. Build the JAR and Install in the BeanBox

Compile NervousText02.java :

javac -d . NervousText02.java

Create the manifest in a text editor:

Name: sun/beanbox/beans/NervousText02.class
Java-Bean: True

Create the JAR file:

jar cfm NervousText02.jar manifest.tmp 
  sun/beanbox/beans/NervousText02.class

Install the JAR in the BeanBox JAR file directory (substituting your BDK installation directory for BDK_HOME ):

cp -p NervousText02.jar BDK_HOME/beans/jars

Step 6. Test NervousText02 in the BeanBox

This test adds two Beans to the BeanBox: OurButton and NervousText02.

To run the BeanBox:

  • Switch to the BDK_HOME/beans/beanbox directory.
  • Invoke run.sh (UNIX) or run.bat (Window/DOS).
  • Select an OurButton Bean from the menu and place it in the BeanBox.
  • Select a NervousText02 Bean from the menu, placing it in the BeanBox below NervousText.

Now you want a mouse press on the OurButton Bean to fire an actionPerformed button event. Use the BeanBox Edit menu to direct this action event to be handled by the changeDirection method in NervousText02.

  • Make sure the OurButton Bean is selected; it is surrounded by a dotted-line box when you click on it, indicating it is the currently selected bean. A property sheet for OurButton also displays.
  • Use the Edit menu to choose Edit->Events->action->actionPerformed.
  • Join the event line to NervousText02 by clicking over the NervousText02 component.

You then see a popup dialog named EventTargetDialog.

  • Select the changeDirection method and click the OK button.

Pressing OurButton now causes the text displayed by the NervousText02 button to be reversed from right to left.

Program Source Code

The makefile for this lesson automates source code compilation, JAR file construction, and copying of JAR files to the appropriate BeanBox directory. You have to edit several of the variables in the makefile to indicate the location of your JDK 1.1 and BDK installation directories.

You may want to look at the final source file for NervousText Bean, Version 02, to verify the changes you have made to the original NervousText.java file.

Making Beans Serializable

Adding serialization to a bean allows it to save itself—to persist—even after it has been customized in a builder tool.

This lesson adds methods to NervousText03.java so that it can write its state to disk and read the state back into memory from its serialized representation on disk. It is important to know which fields of the Bean are made transient, in which case their state is not saved or restored. For example, the state of a thread—running, stopped, waiting—cannot be saved and restored through serialization. Thus, you must mark fields referring to threads as transient.

Step 1. Add an import statement for serialization

Add the following lines after the existing import statements:

import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;

It's also a good idea to add support for those exceptions that can occur while reading or writing a serialized bean to a file.

To do this, add the following import statement after the two you just added:

import java.io.IOException;

Now you can handle any trouble your Bean may have when reading the saved object's state from disk. The beginning of the file looks like this:

package sun.beanbox.beans;
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Font;
import java.awt.*;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
 
public class NervousText03 extends Panel 
  implements Runnable, MouseListener {
...

Step 2. Make the thread fields transient

You should mark as transient any thread fields declared inside a class whose instances are to be serialized. You must do this because when a serialized bean is reinstantiated, the original thread context no longer exists. There are other types of objects which can not be serialized, and they, too, must be marked as transient. Refer to the documentation on object serialization for details.

Thus, the killme field in NervousText03 must be marked as transient.

Change the line

Thread killme = null;

to

transient Thread killme = null;

You can't save the context of a thread and reload it in the same state because the thread's state depends on the runtime environment.

The instance fields declared in NervousText03 now look like this.

public class NervousText03 extends Panel 
  implements Runnable, MouseListener {
    
  char separated[];
  String s = null;
  transient Thread killme = null;
  int i;
  int x_coord = 0, y_coord = 0;
  String num;
  int speed=35;
  int counter =0;
  boolean threadSuspended = false; 
  boolean lefttoright = true;
  ...

Step 3. Add serialization methods for persistence

Now you're ready to add the writeObject and readObject serialization methods to NervousText03. When you write the object to storage, you can determine whether the NervousText bean is currently running or not. When reading the serialized representation of the Bean back into memory, you must initialize the thread again.

Add the following definitions for writeObject and readObject at the end of the class.

private void writeObject(ObjectOutputStream s) 
	throws IOException
  {
    s.defaultWriteObject();
    if(killme == null)
      s.writeBoolean(false);
    else
      s.writeBoolean(true);
  }
 
  private void readObject(ObjectInputStream s)
    throws ClassNotFoundException, IOException
  {
    s.defaultReadObject();
    if(s.readBoolean()==false)
      killme=null;
    else
      start();
  }

Step 4. Build the JAR and install it in the BeanBox

Compile NervousText03.java:

javac -d . NervousText03.java

Create the manifest, which looks like this:

Name: sun/beanbox/beans/NervousText03.class
Java-Bean: True

Create the JAR file:

jar cfm NervousText03.jar manifest.tmp 
    sun/beanbox/beans/NervousText03.class

Remove the temporary manifest and install the JAR in the BeanBox JAR file directory (substituting your BDK installation directory for BDK_HOME ):

rm manifest.tmp
cp -p NervousText03.jar BDK_HOME/beans/jars

Step 5. Test NervousText03 in the BeanBox

  • To test that serialization works, first build a simple application by performing the same steps in the BeanBox that you did in the previous lesson.
  • Run the BeanBox from your BDK_HOME/beans/beanbox directory.
  • Invoke run.sh (UNIX) or run.bat (Window/DOS).
  • Select an OurButton Bean from the menu and place it in the BeanBox.
  • Select a NervousText03 Bean from the menu, placing it in the BeanBox below the button.
  • Use the Edit menu to choose Edit->Events->action->actionPerformed.
  • Join the event line to NervousText03 by clicking over the NervousText03 component.
  • When the EventTargetDialog appears, select the changeDirection method and click the OK button.

Pressing OurButton now causes the text displayed by the NervousText03 button to be reversed from right to left.

  • Change the text property of NervousText03 to read Anxious Text.
  • Change Press to Anxious Text in the Property sheet.
  • You're now ready to save the serialized state of all components displayed in the BeanBox.
  • Serialize the BeanBox components by choosing the menu item File->Save.

A file dialog box appears to let you specify the file name for the application.

  • Enter NervousText03.beanbox for the filename and press OK.

The BeanBox application has now been serialized and saved to disk.

  • Clear the BeanBox by choosing File->Clear.

All components disappear. You're now ready to reload the application.

  • Choose the menu item File->Load.

A Load File dialog appears.

  • Select NervousText03.beanbox from the Files pane and press OK.

The application appears exactly as it did when you saved it.

What's happening behind the covers

Notice when the saved file is restored, the text is set to the same string you specified in the property sheet editor, plus its direction is reversed. Also notice, that the application comes up in a running state. That's because NervousText03 simulates the saving of the bean's thread state by appending the value of the boolean variable, killme at the end of the serialized output stream when the bean is serialized to disk. It does this by calling writeBoolean, on the ObjectOutputStream used to serialize the bean with an argument of either true or false, depending on the value of killme:

private void writeObject(ObjectOutputStream s)
    throws IOException
  {
    s.defaultWriteObject();
    if(killme == null)
      s.writeBoolean(false);
    else
      s.writeBoolean(true);
   }

Because the thread was running when you saved the application, the code to reinstantiate the bean in readObject gets a value of true when it calls readBoolean immediately after reinstantiating the bean using the defaultReadObject method. Here's the full definition:

private void readObject(ObjectInputStream s)
    throws ClassNotFoundException, IOException
  {
    s.defaultReadObject();
    if(s.readBoolean()==false)
      killme=null;
    else
      start();
  }

If killme was true when the Bean was saved, the Bean's start method is called to reactivate the Bean when it is reinstantiated. Had you saved out the application with the thread suspended, the thread would not run when you reinstantiated the Bean. For an additional exercise, try adding two more OurButtons. Label them stop and start. First label the existing button change direction.

You will have to figure out how to hook up the action events generated by the start and stop buttons to the appropriate handlers to start and stop the thread in the NervousText03 Bean. If you need a hint, go back and review how this was done with the original OurButton to change the text direction. Now stop the application by pressing the stop button, serialize it, clear the BeanBox and reload it. Then press the start button and try the exercise again.

Program Source Code

The makefile for this lesson automates source code compilation, JAR file construction, and copying of JAR files to the appropriate BeanBox directory. You have to edit several of the variables in the makefile to indicate the location of your JDK 1.1 and BDK installation directories.

You may want to look at the final source file for NervousText Bean, Version 03, to verify the changes you have made to the original NervousText.java file.

Introduction | Page 1 | Page 2 | Page 3 | Page 4