Building a JWAVE Bean
This section describes how to construct a JavaBean and use it with the JWAVE Beans. Throughout this description, the source for the JWAVE Bean tester Bean (used in the previous section) is used in the discussion of the Bean development techniques. You can find this source code in:
(UNIX) RW_DIR/classes/com/visualnumerics/jwave/beans/JWaveBeanTest.java
(WIN) RW_DIR\classes\com\visualnumerics\jwave\beans\
JWaveBeanTest.java
where RW_DIR is the main Rogue Wave installation directory.
note | The intended audience for this section is Java developers and those familiar with object-oriented programming. This section is by no means a comprehensive source for constructing a Bean. There are many books and Web sites on the subject that take a much more thorough look at the capabilities of JavaBeans. |
Deciding What the Bean Will Do
Before a JavaBean can be developed, you must decide what the Bean needs to do. JavaBeans are just objects. It is a good idea to create an overall design document of your project to determine which specific objects are needed and exactly what each object will do.
Once the Bean’s requirements are defined, it should be fairly obvious what the Bean should look like. Most Beans are visible components (buttons, fields, or canvases–the JWAVE Beans Tools are canvases). However, it is not required that Beans have any visible attributes at all. The JWAVE Bean tester is one of these “invisible” Beans that has no GUI elements at all. It is just a class that has input and output, and that generates data and events.
If your Bean does need to have visible components, just code them as you would any other class. Beans are required to have a no argument constructor, but you may use this constructor like any other, building the elements your class needs in it.
Adding Properties to Bean
Properties are named attributes that define the state of an object. JavaBeans are not required to have any properties, but since they define the state of the Bean, they most likely will. A property can be something as simple as the string appearing on a button, to more complex things such as a multi-dimensional dataset.
In terms of Beans, properties are exposed to visual programming tools and are usually editable through some sort of property editing interface (see
"Telling a Bean Environment How to Use Your Bean", and
"Building a Customizer for the Bean").
The naming convention followed by Beans developers for naming methods that access Bean properties is:
public void setPropertyName(PropertyType value);
public PropertyType getPropertyName();
As shown in the following example, the set method writes the data to the Bean, while the get method reads the data from the Bean. By having both methods, the property is defined as read/write enabled. By omitting one of the methods, the property becomes either read-only or write-only.
In the case of the JWAVE Bean tester Bean, the only properties included are the data objects that contain canned datasets.
A get/set pair, taken from JWAVE Bean tester
DoubleTable dataTable_;
/**
* Set the dataTable Proxy
* @see JWaveBeanTest#getDataTable
* @param Proxy The dataTable Proxy this bean will use
*/
public void setDataTable(Proxy p)
{
proxy_=p;
Object pObject=proxy_.retrieve();
try
{
dataTable_=(DoubleTable)pObject;
}
catch(ClassCastException e)
{
System.out.println(”Data was not a DoubleTable”);
System.out.println(e);
}
}
/**
* Get the dataTable Proxy
* @see JWaveBeanTest#setDataTable
* @return Proxy The dataTable Proxy
*/
public Proxy getDataTable()
{
if(dataTable_!=null)
{
Proxy p=new com.visualnumerics.data.LocalProxyImpl(dataTable_);
return(p);
}
else
return(null);
}
note | Properties do not have to be data members of the class. There can be get/set methods that calculate values or return hardcoded constants. |
Handling Data
Using the Proxy Class to Exchange Data
All of the JWAVE Beans take and send data as com.visualnumerics.data.Proxy objects. The Proxy class is an interface that contains a reference to a data object. The intent of the Proxy class is to allow data to be passed that is not necessarily inside your virtual machine (such as a CORBA object).
The class com.visualnumerics.jwave.JWaveDataProxy implements the Proxy class, making it valid to pass to the Beans. The JWaveDataProxy class is a client side proxy for data that is resident on the JWAVE server.
When data is to be sent to a JWAVE Bean (either through an event or a bound property), it must be a Proxy object.
The three methods shown in the following example are the input (
set2D_Data) and output (
get2D_Data and
start2d) for a two-dimensional array of data (
data2d_). The use of these methods is discussed further in a later section (
"Telling a Bean Environment How to Use Your Bean"), but for now, notice how the data either comes in or goes out of the methods as a
Proxy object. The class
com.visualnumerics.data.LocalProxyImpl is an implementation of
Proxy for data that is already resident inside your virtual machine.
Data being sent as a Proxy object in JWAVE Bean tester
double[][] data2d_;
/**
* Set the 2D data Proxy
* @see JWaveBeanTest#get2D_Data
* @param Proxy The 2D data Proxy this bean will use
*/
public void set2D_Data(Proxy p)
{
proxy_=p;
Object pObject=proxy_.retrieve();
try
{
data2d_=(double[][])pObject;
Proxy p=new
com.visualnumerics.data.LocalProxyImpl(data2d_);
changes_.firePropertyChange(”data_2d_change”,null,p);
}
catch(ClassCastException e)
{
System.out.println(”Data was not a 2D double”);
System.out.println(e);
}
}
/**
* Get the 2D data Proxy. @see JWaveBeanTest#set2D_Data
* @return Proxy The 2D data Proxy
*/
public Proxy get2D_Data()
{
if(data2d_!=null)
{
Proxy p=new
com.visualnumerics.data.LocalProxyImpl(data2d_);
return(p);
}
else
return(null);
}
/**
* Fire an event that the 2D data has changed
*/
public void start2d()
{
Proxy p=new com.visualnumerics.data.LocalProxyImpl(data2d_);
changes_.firePropertyChange(”data_2d_change”,null,p);
}
Using Events to Exchange Data
All JavaBeans essentially communicate with events. When a Bean has something happen to it (the user pressed a button, new data arrived, a condition was met, and so on), the Bean may want to communicate this event to other Beans. The communication can be in the form of actions, mouse clicks, data, or any other event that Java is capable of producing. Any class that is registered as a listener can receive this event and handle it in its own way.
In the case of the JWAVE Beans, one way of passing data between them is via a PropertyChangeEvent. This class is part of the java.beans package and is a standard way for Beans to communicate.
Most Beans implement java.beans.PropertyChangeListener and are therefore capable of receiving and handling PropertyChangeEvents.
All JWAVE Beans contain an instance of java.beans.PropertyChangeSupport. This class maintains a list of classes that have registered as listeners and allows events to be sent to those listeners.
Instances of PropertyChangeEvents from JWAVE Bean tester
PropertyChangeSupport changes_=new PropertyChangeSupport(this);
/**
* Adds a property change listener
* @param PropertyChangeListener listener
*/
public synchronized void addPropertyChangeListener(PropertyChangeListener l)
{
changes_.addPropertyChangeListener(l);
}
/**
* Removes a property change listener
* @param PropertyChangeListener listener
*/
public synchronized void removePropertyChangeListener(PropertyChangeListener l)
{
changes_.removePropertyChangeListener(l);
}
/**
* Fire an event that the 2D data has changed
*/
public void start2d()
{
Proxy p=new com.visualnumerics.data.LocalProxyImpl(data2d_);
changes_.firePropertyChange(”data_2d_change”,null,p);
}
The PropertyChangeSupport class changes_ maintains a list of classes that wish to listen to this Bean’s events. Any class that calls
addPropertyChangeListener will be added to that list. Similarly, any class that calls removePropertyChangeListener will be removed from the list.
When the start2d method is called, any class that has registered as a listener will receive the data found in the data2d_ array (as a Proxy object of course).
The firePropertyChange method of the PropertyChangeSupport object takes as arguments the name of the property that has changed, the old value of that property, and the new value of the property. These are the exact fields of the java.beans.PropertyChangeEvent class that PropertyChangeListeners expect to see.
In a Bean development environment (such as the BeanBox), when the start2d method is invoked (by a button click, for instance), any Bean connected to the JWAVE Bean tester propertyChange event will receive the data2d_ proxy.
Including Bound Properties to Exchange New Data
Bound properties are properties that support change events. When a Bean’s property changes (new data, user change, and so on), other Beans may want to know about it. When a bound property changes, an event is fired to all listeners.
All of the data properties in the JWAVE Beans are bound properties. When the data in a Bean changes, it will fire an event to all registered listeners. This event will contain the new data.
In the following example, notice how when the Bean has received new 2D data it fires a PropertyChangeEvent. All Beans listening will receive a Proxy with the new data array inside.
An input method to the JWAVE Bean tester Bean
/**
* Set the 2D data Proxy
* @see JWaveBeanTest#get2D_Data
* @param Proxy The 2D data Proxy this bean will use
*/
public void set2D_Data(Proxy p)
{
proxy_=p;
Object pObject=proxy_.retrieve();
try
{
data2d_=(double[][])pObject;
Proxy p=new
com.visualnumerics.data.LocalProxyImpl(data2d_);
changes_.firePropertyChange(”data_2d_change”,null,p);
}
catch(ClassCastException e)
{
System.out.println(”Data was not a 2D double”);
System.out.println(e);
}
}
Bound properties have another advantage. In the BeanBox, if you select a Bean that has bound properties, a Bind Property … choice is available under the Edit menu for displaying a list of the Bean’s bound properties. Selecting one of those bound properties allows you to connect it to another Bean’s bound property. The source bean will have its get method called and the destination Bean will have its set method called with the data retrieved from the get call. In this way, two Beans can share property values without any events.
Telling a Bean Environment How to Use Your Bean
When your Bean is placed in a Bean environment (such as the BeanBox), the first thing the environment does is called reflection. Essentially, the environment is using naming conventions to figure out what your Bean is capable of doing. However, there are times when there is more you want known about a Bean than can be supplied through naming conventions. There are also times when conventions fall by the wayside. When this happens, it is time to use a BeanInfo class to provide explicit information about your Bean.
Using a BeanInfo Class
A BeanInfo class must implement the java.beans.BeanInfo interface. However, since implementing this class requires implementations for each method in the BeanInfo interface, Java has provided a java.beans.SimpleBeanInfo class. Subclassing or extending the java.beans.SimpleBeanInfo class allows you to use only the methods you need for your Bean (the remainder have dummy implementations already defined). These methods allow you to tell the Bean environment what your Bean can do.
BeanInfo class names follow this convention:
BeanNameBeanInfo.java
When the Bean environment looks for a BeanInfo about your Bean, it searches only for the above naming convention. If the BeanInfo class is not in the same package as your Bean, your CLASSPATH is searched. The down side of all of this is that the Bean and the BeanInfo are tightly coupled and it’s up to the developer to keep this relationship. The developer must be careful to not allow one class to evolve without the other.
Let’s take a look at for the BeanInfo for the JWAVE Bean tester Bean. The Java code, purpose, and details of these methods are presented on the following pages:
BeanDescriptors
private BeanDescriptor beanDescriptor_;
private final static Class beanClass_ = com.visualnumerics.jwave.beans.JWaveBeanTest.class;
/**
* Default Constructor. This just sets-up the BeanDescriptor
*/
public JWaveBeanTestBeanInfo()
{
beanDescriptor_ = new BeanDescriptor(beanClass_);
beanDescriptor_.setDisplayName(”JWAVE Bean tester”);
beanDescriptor_.setShortDescription(”A test bean for use with JWAVE Tools”);
}
/**
* This method returns the BeanDescriptor.
* @return BeanDescriptor
*/
public BeanDescriptor getBeanDescriptor()
{
return beanDescriptor_;
}
The
Constructor and the
getBeanDescriptor methods both deal with the
java.beans.BeanDescriptor class. This class is needed by the Bean environments to describe features of the Bean. The only fields in this class are the Bean’s
Customizer (see
"Building a Customizer for the Bean") and the Bean itself.
JWAVE Bean tester’s BeanDescriptor only defines:
the Bean itself
the display name to use when showing the Bean
a short description of the Bean’s purpose.
Bean Events
/**
* This method describes which events of the Bean will be exposed.
* The only event exposed is propertyChange.
* @return EventSetDescriptor[] an array of EventSetDescriptors
* detailing which events of this bean are exposed.
*/
public EventSetDescriptor[] getEventSetDescriptors()
{
try
{
EventSetDescriptor changed = new EventSetDescriptor
(beanClass, ”propertyChange”,
java.beans.PropertyChangeListener.class,
”propertyChange”);
changed.setDisplayName(”bound property change”);
EventSetDescriptor[] rv = {changed};
return rv;
}
catch (IntrospectionException e)
{
throw new Error(e.toString());
}
}
The getEventSetDescriptors method returns an array of java.beans.EventSetDescriptor objects. Each object in the array describes an event supported by the Bean. The getEventSetDescriptors method therefore allows you to limit which events your Bean can fire. This is especially useful when your Bean is a Java component. Most components have many events they can deal with (actions, mouse events, window events, and so on) and you may not want your Bean to have to deal with all of them. The getEventSetDescriptors method allows you to set which events your Bean can handle.
In the source code in
Bean Events:
only one event,
propertyChange, will be seen by the Bean environments
an
EventSetDescriptor class is instantiated
a display name to be used when showing events is set
an array of just the one
EventSetDescriptor is passed back to the caller
Bean Methods
/**
* This method describes which methods of the Bean will be exposed.
* @return MethodDescriptor[] an array of MethodDescriptors
* detailing which methods of this bean are exposed.
*/
public MethodDescriptor[] getMethodDescriptors()
{
// First find the ”method” objects.
Method proxy2d,proxy2dRand,proxy1dRand;
Method proxySin,proxyCos;
Method proxyDataTable,proxyPieTable;
Method start2d,startSin,startCos,startDataTable,startPieTable;
Method start2dRand,start1dRand;
Class proxyEventArgs[] = {com.visualnumerics.data.Proxy.class};
try
{
proxy2d = beanClass_.getMethod(”set2D_Data”,
proxyEventArgs);
proxy2dRand = beanClass_.getMethod(”set2D_RandomData”,
proxyEventArgs);
proxy1dRand = beanClass_.getMethod(”set1D_RandomData”,
proxyEventArgs);
proxySin = beanClass_.getMethod(”setSineData”,
proxyEventArgs);
proxyCos = beanClass_.getMethod(”setCosineData”,
proxyEventArgs);
proxyDataTable = beanClass_.getMethod(”setDataTable”,
proxyEventArgs);
proxyPieTable = beanClass_.getMethod(”setPieTable”,
proxyEventArgs);
startPieTable= beanClass_.getMethod(”startPieTable”,
null);
startDataTable= beanClass_.getMethod(”startDataTable”,
null);
startSin = beanClass_.getMethod(”startSine”, null);
startCos = beanClass_.getMethod(”startCosine”, null);
start2d = beanClass_.getMethod(”start2d”, null);
start2dRand = beanClass_.getMethod(”start2dRandom”, null);
start1dRand = beanClass_.getMethod(”start1dRandom”, null);
}
catch (Exception ex)
{
throw new Error( ”Missing method: ” + ex );
}
// Now create the MethodDescriptor array
// with visible event response methods:
MethodDescriptor result[] = {new MethodDescriptor(proxy2d),
new MethodDescriptor(proxy2dRand),
new MethodDescriptor(proxy1dRand),
new MethodDescriptor(proxySin),
new MethodDescriptor(proxyCos),
new MethodDescriptor(proxyDataTable),
new MethodDescriptor(startDataTable),
new MethodDescriptor(proxyPieTable),
new MethodDescriptor(startPieTable),
new MethodDescriptor(startSin),
new MethodDescriptor(startCos),
new MethodDescriptor(start2d),
new MethodDescriptor(start2dRand),
new MethodDescriptor(start1dRand)};
return result;
}
The getMethodDescriptors method returns an array of java.beans.MethodDescriptor objects. This object describes a method supported by a Bean. This array of descriptors details all of the methods your Bean will have exposed to the Bean environment. The use of the getMethodDescriptors methods allows you to limit which methods can be exposed.
In the source code in
Bean Methods, 14 methods are returned to the caller:
First, a
java.lang.reflect.Method class is created for each method to be exposed. The
Method class is created by supplying the name and the arguments of the Bean’s method. When a method has no arguments, a
null is supplied to the
Method constructor.
Second, a
MethodDescriptor class is created using each of the
Method classes created earlier. These
MethodDescriptor classes are returned to the caller.
Bean properties
/**
* This method details which bean properties are exposed and how
* they are to be edited.
* @return PropertyDescriptor[]
*/
public PropertyDescriptor[] getPropertyDescriptors()
{
try
{
PropertyDescriptor data2PD = new PropertyDescriptor(”data2d”,
beanClass_,
”get2D_Data”,
”set2D_Data”);
data2PD.setDisplayName(”2D Data”);
data2PD.setBound(true);
PropertyDescriptor sinPD = new PropertyDescriptor(”sine wave”,
beanClass_,
”getSineData”,
”setSineData”);
sinPD.setDisplayName(”Sine Data”);
sinPD.setBound(true);
PropertyDescriptor cosPD = new PropertyDescriptor(”cosine wave”,
beanClass_,
”getCosineData”,
”setCosineData”);
cosPD.setDisplayName(”Cosine Data”);
cosPD.setBound(true);
PropertyDescriptor tablePD = new PropertyDescriptor(”dataTable”,
beanClass_,
”getDataTable”,
”setDataTable”);
tablePD.setDisplayName(”2D Table Data”);
tablePD.setBound(true);
PropertyDescriptor piePD = new PropertyDescriptor(”pieTable”,
beanClass_,
”getPieTable”,
”setPieTable”);
piePD.setDisplayName(”Pie Data”);
piePD.setBound(true);
PropertyDescriptor data2RPD=new PropertyDescriptor(”randData2d”,
beanClass_,
”get2D_RandomData”,
”set2D_RandomData”);
data2RPD.setDisplayName(”2D Random Data”);
data2RPD.setBound(true);
PropertyDescriptor data1RPD=new PropertyDescriptor(”randData1d”,
beanClass_,
”get1D_RandomData”,
”set1D_RandomData”);
data1RPD.setDisplayName(”1D Random Data”);
data1RPD.setBound(true);
PropertyDescriptor rv[] = {data2PD,sinPD,tablePD,piePD,cosPD,
data2RPD,data1RPD};
return rv;
}
catch (IntrospectionException e)
{
throw new Error(e.toString());
}
}
The getPropertyDescriptors method returns an array of java.beans.PropertyDescriptor objects. These objects describe properties the Bean will expose. The use of getPropertyDescriptors not only allows you to limit which properties are available to the Bean environment, but specific property attributes (such as names, which methods get/set this property, bound status, and so on) and specific property editor classes can also be set.
Using Property Editor Classes
Property editors are classes that a Bean environment uses to allow the property to be changed. The BeanBox arranges these property editors on its Property Sheet window. For every property listed, the BeanBox attempts to assign an editor class. For instance, if the property is a Color object, the BeanBox assigns a class that allows the user to edit colors. If the property is a String, the BeanBox assigns a TextField so the user can change the string.
There are two instances when this automatic assignment of editors fails:
The first case is when the property accepts the assignment of only specific values (such as a list of linestyles). The developer can write a specific class that only allows the user to select one of the specific values applicable to the property. This class can then be assigned to the
PropertyDescriptor object using the
setPropertyEditorClass method.
The second case where automatic editor assignment fails is when the property is of a type for which a known editor does not exist. This case occurs in the source code in
Bean properties. Because the properties are arrays or specific classes, the BeanBox reports that it cannot find a property editor for these properties. This condition is only a problem if you want those properties to be editable. If you do, then you must write a class to edit the properties and assign it using the
setPropertyEditorClass method. If the property does not need and editor (such as is the case in this source), then simply don’t assign one.
When using the property editors on the Property Sheet window, you are limited in the presentation of the editors. You cannot arrange them or use complex GUI components. A better alternative is to use a Customizer class (see
"Building a Customizer for the Bean").
The source code shown in
Bean properties creates a
PropertyDescriptor object for each property to be exposed. This object is created by supplying:
the property name
the Bean
the method name to ‘
get’ this property
the method name to ‘
set’ this property
You can see this is a way to get around the get/set convention; you may supply any method name you wish. Each property is supplied with a display name. This is the name that appears on the Property Sheet. Finally, each property is set as a bound property. The default for this value is false, so this method call is not needed if the property is not to be bound. All of these PropertyDescriptor objects are then returned to the caller.
Building a Customizer for the Bean
Customizers are classes that edit Bean properties. There is one Customizer class per Bean; however, a Bean is not required to have a Customizer. Although there are no naming conventions for Customizer classes, they must implement the java.bean.Customizer class and they must be a subclass of java.awt.Component. Customizers are most often GUI interfaces that allow the user to change Bean properties. It is a way for a developer to design a professional looking editor class for the Bean.
Methods
The java.beans.Customizer interface has just three methods:
addPropertyChangeListener removePropertyChangeListener setObject(Object bean) The
add/removePropertyChangeListener methods are described in
"Using Events to Exchange Data". The Customizer should fire a
PropertyChangeEvent when it changes a property of the Bean.
The setObject(Object bean) method of java.beans.Customizer is automatically called by the Bean environment when a Customizer is started. This method should query the bean object for its current status and use that information to fill in the GUI Customizer components. The method of querying the Bean is the set/get methods which are used in almost all Bean transactions.
Finally, a Customizer must have a no argument constructor. As with all other Bean classes, there is no method instantiating the Customizer so there are no available arguments to pass.
Since there is no naming convention for Customizers, the Bean environment has no way of telling which class is the Customizer unless it is told. This is accomplished in the BeanInfo class.
The BeanDescriptor class has another constructor that will take the Customizer class in addition to the Bean class.
Example from a BeanInfo class
/**
* java.lang.Class object. This is just the name of the bean
* this BeanInfo is representing.
*/
private final static Class beanClass = com.visualnumerics.jwave.beans.JWaveBar3dTool.class;
/**
* java.lang.Class object. This is name of the Customizer class
* this bean uses.
*/
private final static Class beanCustomizerClass = com.visualnumerics.jwave.beans.JWaveBar3dToolCustomizer.class;
public JWaveBar3dToolBeanInfo()
{
beanDescriptor_ = new
BeanDescriptor(beanClass,beanCustomizerClass);
String displayName = resources.getString(”display_name”);
beanDescriptor_.setDisplayName(displayName);
String descName = resources.getString(”short_description”);
beanDescriptor_.setShortDescription(descName);
}
The JWAVE Bean tester Bean has no Customizer. This is because it has no properties that are editable by the user. However, all of the remaining JWAVE Beans do have Customizers. These Customizers are based on the Java Foundation Classes (JFC) graphical components and allow the user to edit every property of the Bean except for the data.
note | To open a Customizer in the BeanBox, select a Bean and choose Customize on the Edit menu. If the Bean does not have a Customizer, then this option is not available. |
Adding Serializability to the Bean
One of the advantages to using JavaBeans is the ability to generate applications from the Beans you put together. When the MakeApplet command on the File menu is chosen on the BeanBox, the BeanBox attempts to serialize the Beans and make them into an applet. Serialization is the process of making the state of each Bean persistent. When you make an applet, the BeanBox captures the properties and hookups of each Bean and uses them in the resulting applet. The values found in the Beans are written to a java.io.FileOutputStream object to end-up in the applet code.
Unfortunately, serializing is not something that happens automatically. Each Bean must implement the java.io.Serializable interface. In most cases, this makes the Bean serializable. There are, of course, cases when this is not enough.
When serialization takes place, Java does not deal with transient variables of your Beans. They’re transient; they are not part of the state of your Bean. However, any data member that is part of your class will be serialized. In general, most standard Java API classes are serializable and won’t pose any problem. There are a few however, that are not (such as java.awt.Image). For these classes, or those not part of the Java API, you need to implement two methods to do the serialization. These methods are the writeObject and readObject methods of the Serializable class. The implementation of the methods is beyond the scope of this document, but it entails the breaking down of classes that aren’t serializable into their serializable parts. For instance, java.awt.Image is not serializable. To serialize java.awt.Image you must get the data values out of the class and write those values to the stream. For a more thorough discussion on serializing, consult a book or Web site on JavaBeans development.
note | If you generate an applet from your JWAVE Beans, you must modify the HTML file generated by the BDK. The ARCHIVE tag in the HTML file must include JWave.jar, JWaveBeans.jar, JWaveConnectInfo.jar, and swing.jar. If you want to run your generated applet with appletviewer, you must put the JAR files listed in the previous Tip in your CLASSPATH. This is because appletviewer does not pick up the settings of the ARCHIVE. |