![]() ![]() ![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Writing Bean ComponentsTo create a Bean, you need to determine what it should do and then define the events, properties, and methods to get it there. Actually, most of the method definitions fall out of the definition of events and properties for the Bean. EventsAn event allows your Beans to communicate when something interesting happens. There are three parts to this communication:
EventObject
The public class java.util.EventObject extends Object implements java.io.Serializable { public java.util.EventObject (Object source); public Object getSource(); public String toString(); }Although you can create EventObject instances directly for your Bean events, design pattern
guidelines require you to subclass EventObject so you have a specific event type. For example,
to define an event for an employee's hire date, you could create a HireEvent class.
public class HireEvent extends EventObject { private long hireDate; public HireEvent (Object source) { super (source); hireDate = System.currentTimeMillis(); } public HireEvent (Object source, long hired) { super (source); hireDate = hired; } public long getHireDate () { return hireDate; } } EventListener
The public interface HireListener extends java.util.EventListener { public abstract void hired (HireEvent e); } Event Source
Without an event source, the public synchronized void addListenerType(ListenerType l); public synchronized void removeListenerType( ListenerType l);The event source needs to maintain the list itself, so the entire code to do this is: private Vector hireListeners = new Vector(); public synchronized void addHireListener ( HireListener l) { hireListeners.addElement (l); } public synchronized void removeHireListener (HireListener l) { hireListeners.removeElement (l); }
If you are using AWT components, AWT events already have this behavior. Maintaining listeners is only
necessary for new event types, or adding listeners where they previously were not (
If you want to permit only one listener (unicast), you have the addListenerType method throw the
Once the event happens, it is necessary for the event source to notify all the listeners. For the hiring example, this would translate into a method like the following: protected void notifyHired () { Vector l; // Create Event HireEvent h = new HireEvent (this); // Copy listener vector so it won't // change while firing synchronized (this) { l = (Vector)hireListeners.clone(); } for (int i=0;i<l.size();i++) { HireListener hl = (HireListener)l.elementAt (i); hl.hired(h); } }You have to call the method directly when the triggering event happens. PropertiesA property is a public attribute of the Bean, usually represented by a non-public instance variable. It can be read-write, read-only, or write-only. There are four different types of properties:
Simple PropertiesAs the name implies, simple properties represent the simplest of the four. To create a property, define a pair of set/get routines. Whatever name used in the pair of routines, becomes the property name (no matter what instance variable name you use). Normally, the instance variable name matches the property name. However, there is nothing that requires this. For instance, to define a property salary for an Employee Bean: float salary; public void setSalary (float newSalary) { salary = newSalary; } public float getSalary () { return salary; }If you need a read-only property, only define a getPropertyName routine.
If you want a write-only property, only define a setPropertyName routine.
Boolean properties can change their get routine to an isPropertyName routine:
boolean trained; public void setTrained (boolean trained) { this.trained = trained; } public boolean isTrained () { return trained; } Indexed PropertiesAn indexed property is for when a single property can hold an array of values. The design pattern for these properties is: public void setPropertyName (PropertyType[] list) public void setPropertyName ( PropertyType element, int position) public PropertyType[] getPropertyName () public PropertyType getPropertyName (int position)For instance, if you were to complete the item indexed property for the AWT List component, it might look something like this:
public class ListBean extends List { public String[] getItem () { return getItems (); } public synchronized void setItem (String item[]) { removeAll(); for (int i=0;i<item.length;i++) addItem (item[i]); } public void setItem (String item, int position) { replaceItem (item, position) } }
The
The AWT Bound Properties
Revisit the Employee Bean, and make salary a bound property.
That way, if two people were using an employee Bean (say a manager and a spouse),
and one (the manager) changes an employee's salary, the other (the spouse) would like to know about
said change. In order for the notification to happen, you need to maintain a watch list for
private PropertyChangeSupport changes = new PropertyChangeSupport (this);And then, you have to maintain the list: public void addPropertyChangeListener ( PropertyChangeListener p) { changes.addPropertyChangeListener (p); } public void removePropertyChangeListener ( PropertyChangeListener p) { changes.removePropertyChangeListener (p); }Finally, when the change happens ( setSalary in this case), you check to see if the property value
changed, and if so, notify all the listeners.
public void setSalary (float salary) { Float oldSalary = new Float (this.salary); this.salary = salary; changes.firePropertyChange ( "salary", oldSalary, new Float (this.salary)); }On the receiving end, you then need a propertyChange method.
public void propertyChange(PropertyChangeEvent e);Since Java reports PropertyChangeEvent s at the class (Bean) level (versus the property level), you
need to check the property name via getPropertyName to see if you received a
PropertyChangeEvent you were expecting, or something else.
Also, if the property datatype is primitive, you need to wrap it into the appropriate object when firing.
Constrained Properties
Constrained properties are similar to bound properties. In addition to maintaining a list of
Changing private VetoableChangeSupport vetoes = new VetoableChangeSupport (this); public void addVetoableChangeListener ( VetoableChangeListener v) { vetoes.addVetoableChangeListener (v); } public void removeVetoableChangeListener ( VetoableChangeListener v) { vetoes.removeVetoableChangeListener (v); }And changes: public void setSalary (float salary) throws PropertyVetoException { Float oldSalary = new Float (this.salary); vetoes.fireVetoableChange ( "salary", oldSalary, new Float (salary)); this.salary = salary; changes.firePropertyChange ( "salary", oldSalary, new Float (this.salary)); }On the receiving end, your VetoableChangeListener needs a vetoableChange method.
public void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException;Instead of providing one listener for all property change events, and another for all vetoable change events, you can elect to maintain separate lists of listeners for each property. The design pattern for this is: public void addPropertyNameListener ( PropertyChangeListener p); public void removePropertyNameListener ( PropertyChangeListener p);and public void addPropertyNameListener ( VetoableChangeListener v); public void removePropertyNameListener ( VetoableChangeListener v); MethodsMethods are operations for others to interact with a Bean. Basically, anything can call any public method of a Bean. Beans receive notification of events by having the appropriate method called on them by the event source. Besides all the methods required for each property and event of a Bean, you usually create support methods which accept no arguments or an argument of an event you listen for. That way, a builder application can inspect a class quickly for a list of the appropriate methods it can connect to. Non-public support methods may also be available. However, public methods with other parameter patterns are usually the result of a bad design pattern, and not easily connectable from a builder application. For instance, if you always pass along a time-stamp with an event, you should probably subclass the event, add a time-stamp attribute, and pass the subclass around. This results in a simplified design and easier maintainability. Intro to Customization
Customization of Beans allows you as the Beans developer to control what a Bean-integrator will see
when they use a builder tool. By default, a builder tool uses reflection to determine what to display
when designing programs with Beans. In most cases, this is sufficient. However, there are times when you
want to provide different functionality. For instance, if you want to provide your own customization
interface, instead of using the default property sheet, you can implement the
On the other hand, if you want to provide an alternative input mechanism for one property (like
an enumerated list of valid choices), you can extend the
Using either of these mechanisms requires your Bean to provide a supporting class, along with the Bean
itself. This second class implements the BeanInfo
The Exercises
Design-time vs. Run-time Beans "mode"
Beans must be able to operate in a running application as well as inside a builder. At design-time, Beans
must provide the design information necessary to edit properties and customize behavior. Also it has to
expose methods and events so a builder tool can create code that interacts with the Bean at run-time.
The IntrospectionIntrospection is the process of determining the supported properties, methods, and events of a Bean. It can be done with the help of theIntrospector class,
or directly through the use of the Reflection API. The Introspector provides access to the
BeanInfo for the Bean component via its getBeanInfo method, which requires an instance
of Class as its parameter:
TextField tf = new TextField (); BeanInfo bi = Introspector.getBeanInfo (tf.getClass());If you don't provide BeanInfo for a Bean, the Reflection API is used to determine the different
pieces for you.
EventsThegetEventSetDescriptors method reports all the events that this Bean fires.
For every pair of add/removeListenerTypeListener methods (that return void), an event set
is defined for the Bean.
EventSetDescriptor[] esd = bi.getEventSetDescriptors(); for (int i=0;i<esd.length;i++) System.out.print (esd[i].getName() + " "); System.out.println ();For a TextField , this would print:
text mouse key component action focus mouseMotion Properties
The public void setPropertyName(PropertyType value); public PropertyType getPropertyName(); public boolean isPropertyName(); PropertyDescriptor pd[] = bi.getPropertyDescriptors(); for (int i=0;i<pd.length;i++) System.out.print (pd[i].getName() + " "); System.out.println ();For a TextField , this would print:
selectionStart enabled text preferredSize foreground visible background selectedText echoCharacter font columns echoChar name caretPosition selectionEnd minimumSize editable Methods
The MethodDescriptor md[] = bi.getMethodDescriptors(); for (int i=0;i<md.length;i++) System.out.print (md[i].getName() + " "); System.out.println ();For a TextField , this would print the names of all 155 methods available, most of which
are inherited from Component . This includes the add/remove event listener methods, as they are
methods like the others.
BeanInfo
Although the examples have been using the import java.beans.*; public class SizedTextFieldBeanInfo extends SimpleBeanInfo { private final static Class beanClass = SizedTextField.class; public PropertyDescriptor[] getPropertyDescriptors() { try { PropertyDescriptor length = new PropertyDescriptor("length", beanClass); PropertyDescriptor rv[] = {length}; return rv; } catch (IntrospectionException e) { throw new Error(e.toString()); } } }Then, instead of the 17 properties of TextField, the only property displayed by the tool, would be the new length property. The SizedTextField class still needs to implement
the set/getLength methods. Also, creating a custom BeanInfo does not prevent someone from calling
the methods of the now hidden properties. The property accessor methods are still public. So, by using the
Reflection API (or just knowing the method names), you can still invoke all the public methods.
Also, if you wanted to have the Bean have a space delimited name, instead of being all crunched together,
you could add the following to the public BeanDescriptor getBeanDescriptor() { BeanDescriptor bd = new BeanDescriptor(beanClass); bd.setDisplayName("Sized Text Field"); return bd; }And, to wrap things up, you could even supply an icon for your Bean with the help of an image file and the following added to your BeanInfo definition:
public Image getIcon (int iconKind) { if (iconKind == BeanInfo.ICON_COLOR_16x16) { Image img = loadImage("sized.gif"); return img; } return null; } CustomizersUsing Customizers allow YOU, the Bean builder, to control how your Bean user is going to configure visually your Bean at design time. Instead of relying on the builder tool's property sheet to setup the properties of a Bean, you can provide either a full-screen customization option or an alternative to the datatype's default property sheet option. Writing Customizers
If the Employee Bean only had a salary property, you could create a customizer like the following.
It would only permit numeric characters in the input field. package employee; import java.awt.*; import java.awt.event.*; import java.beans.*; public class EmployeeCustomizer extends Panel implements Customizer, KeyListener { private Employee target; private TextField salaryField; private PropertyChangeSupport support = new PropertyChangeSupport(this); public void setObject(Object obj) { target = (Employee) obj; Label t1 = new Label("Salary :"); add(t1); salaryField = new TextField( String.valueOf(target.getSalary()), 20); add(salaryField); salaryField.addKeyListener(this); } public Dimension getPreferredSize() { return new Dimension(225,50); } public void keyPressed(KeyEvent e) {} public void keyTyped(KeyEvent e) {} public void keyReleased(KeyEvent e) { Object source = e.getSource(); if (source==salaryField) { String txt = salaryField.getText(); try { target.setSalary( (new Float(txt)).floatValue()); } catch (NumberFormatException ex) { salaryField.setText( String.valueOf(target.getSalary())); } support.firePropertyChange("", null, null); } } public void addPropertyChangeListener( PropertyChangeListener l) { support.addPropertyChangeListener(l); } public void removePropertyChangeListener( PropertyChangeListener l) { support.removePropertyChangeListener(l); } }All of this work results in the following screen being displayed when the developer chooses to customize your Bean.
This also assumes that the following package employee; import java.beans.*; public class EmployeeBeanInfo extends SimpleBeanInfo { public BeanDescriptor getBeanDescriptor() { return new BeanDescriptor( beanClass, customizerClass); } private final static Class beanClass = Employee.class; private final static Class customizerClass = EmployeeCustomizer.class; } Writing Property Customizers
If you think providing a custom screen for all the properties is not necessary, but want to
have some control over what is displayed in the property sheet for a specific property,
implementing
If you add a position property to the Employee, you can provide a list of positions by
creating a import java.beans.*; public class EmployeePositionEditor extends PropertyEditorSupport { public String[] getTags() { String values[] = { "President", "Technical Support", "Window Washer", "Janitor"}; return values; } }
When using a tag list with a property, the property must be initialized to one of the tags. If you
forget, when a Bean user goes to use the property editor, a mysterious
Then you add a few things to the public PropertyDescriptor[] getPropertyDescriptors() { try { PropertyDescriptor pd = new PropertyDescriptor("position", beanClass); pd.setPropertyEditorClass(positionEditorClass); PropertyDescriptor result[] = { pd }; return result; } catch (Exception e) { System.err.println("Unexpected exception: " + e); return null; } }And, the property sheet for the Employee Bean might look like this:
If there are multiple Using System Property Editors
The JDK comes with a handful of suitable property editors available for your use. You should
expect similar editors available with other tools:
All of these are part of the sun.beans.editors package. You do not have to do anything special to use them. The BDK's BeanBox tool is smart enough to know that if you have a float or Float property, to use the FloatEditor , similarly for the other datatypes. If you are not using the
BeanBox, you can try to use them in your own environment.
Persistence
Persistence is the ability of an object to store its state. Beans use Java's object Serialization API
to provide a great medium-weight solution for persistence. In the simplest case, the way to enable
the serialization of any object is to implement the
Bean Serialization
A
If you're
The first is easy, does your class implement the
For the second, check to see if all each instance variable is The third is easy. Is it okay to save everything?
Fourth, is it more efficient to save the data in a different format? For instance, it is more efficient
to serialize a Fifth, is it okay to use default values for non-persistent variables? If not, you have to add code to handle this. Finally, do you want to validate the deserialized object network? Like, is your graph consistent? If yes, you need to register a validation method.
For instance, if you wanted to have a public class TreeNode implements Serializable { Vector children; TreeNode parent; String name; transient Date date; public TreeNode(String s) { children = new Vector(5); name = s; initClass(); } private void initClass () { date = new Date(); } ... private void writeObject(ObjectOutputStream s) throws IOException { s.defaultWriteObject(); } private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException { s.defaultReadObject(); initClass(); } }Although the writeObject does the default functionality, whenever a readObject method
is provided, a writeObject method is necessary. [complete source]
Bean Reconstitution
Normally, you call Component c = (Component)Beans.instantiate( null, "java.awt.TextField");
By itself, this is identical to public class MyTextField extends java.awt.TextField { }Nothing fancy yet. But, create a little program to save a serialized MyTextField ,
with a couple of properties set:
import java.awt.*; import java.io.*; public class TfSaver { public static void main(String args[]) { MyTextField mtf = new MyTextField(); // set the properties Font ft = new Font("Serif", Font.ITALIC, 36); mtf.setFont(ft); mtf.setText("Hello World"); // serialize try { FileOutputStream f = new FileOutputStream( "MyTextField.ser"); ObjectOutputStream s = new ObjectOutputStream(f); s.writeObject(mtf); s.flush(); } catch (Exception e) { System.out.println(e); } System.exit(0); } }Now, if you create an application that uses a MyTextField instance, it can start with the
font and text properties already set (to 36-point italic Serif font and "Hello World" text).
Component c = (Component)Beans.instantiate(null, "MyTextField");If a filename of the form Classname.ser exists ( MyTextField.ser in this case), then the
class is deserialized. Otherwise, the Bean will be created by using the default no-arg constructor of the
class.
With Bean Versioning
Changes made to a Bean that change the persistence structure flag the new version as incompatible with the
old, and prevents reconstitution. While this sounds good in theory, in practice it has its shortcomings.
Adding any new method to a Bean makes the new class definition incompatible (for deserialization),
resulting in a private static final long serialVersionUID = -2966288784432217853L; Then, when the versions are truly incompatible, you change the SUID variable. When this does happen, you should keep track of all the old values so you can properly handle the differences (e.g., default setting for new instance variable). If you forget to save the old setting, it is necessary to revert back to an old version, regenerating the value, and moving forward again.
The values are generated from a hash algorithm, not randomly picked out of the air nor even a one-up
numbering scheme. Since static variables are not serialized, the value is regenerated during deserialization
and compared against the current class definition. The program to generate the values is
Exercises
ResourcesSome web-based resources:
Copyright © 1997 MageLang Institute. All Rights Reserved. |