- Manipulating Bean Properties
- Property Basics
- Accessor Methods
- Indexed Properties
- Bound Properties
- Constrained Properties
- Using Properties
- API Support
Properties are one of the most important aspects of JavaBeans because they represent the internal state of a bean. If you recall from Chapter 3, "The JavaBeans API at a Glance," properties are discrete, named attributes of a bean that determine its appearance and behavior. Fortunately, the JavaBeans API provides a wide range of support for managing bean properties. In this chapter you learn a great deal about how the JavaBeans API goes about managing properties, including the different types of properties supported and the different ways in which properties are used.
Properties are also important in JavaBeans because they represent one of the primary
means in which users interact with beans at design time. By altering property values,
an application developer can customize both the appearance and the behavior of beans.
This customization can be carried out
either programmatically or visually, depending on the development tools being used.
In this chapter, you learn about the following topics:
- The basics of properties
- Accessor methods
- Indexed properties
- Bound properties
- Constrained properties
- Practical use of properties
- API support for properties
You probably already get the idea that properties form the internal state of a bean and in doing so serve as the data portion of a bean's structure. It is through properties that a bean is allowed to take on different values, which in turn impact its appearance and behavior. Properties appear as bean attributes that can be modified to enable full customization of a bean. When you are developing your own beans, properties take on an even bigger role, because you define the data part of your beans entirely through them. In other words, constructing a bean of your own consists largely of defining the different properties supported by the bean and assigning them default values. You learn a great deal more about defining properties in your own beans in Part III, "Creating Your Own Beans."
Even though properties often represent built-in Java data types such as int and long, they also can represent class and interface types. In this way, a property can really be any data type you choose, including your own custom types. If that still doesn't offer enough freedom for you, you'll be glad to know that properties also can be computed values based on other properties or pieces of information. This might sound strange at first, because you typically think of a property as being a piece of discrete information, but there is no stipulation in the JavaBeans API that a property remain independent of other pieces of information.
Along with having the capability of being dependent on other pieces of information, properties also can cause changes in the appearance or behavior of a bean any time the bean is modified. As an example, consider a user interface bean that has its background color represented by a property. If this color property is changed, it is necessary for the bean to repaint itself in order for the change to affect the bean's appearance. This is necessary because the bean must reflect its current state at all times. If the bean didn't repaint itself, it would be displaying the previous background color while its background color property would have the new value. This inconsistency is not a good thing, which is why property modification isn't always just a process of changing a simple value.
Just so you understand how important properties are in the world of beans, look at an example of the different properties that constitute a simple bean. Here is a list of properties for a hypothetical meter bar bean:
Just in case you aren't familiar with a meter bar, it's a user interface element
representing a meter that graphically shows some value as a fraction of a total.
Meter bars are commonly used to display the status of time-consuming operations such
as loading a large file or performing a complex mathematical computation. Figure
4.1 shows a meter bar in action at 25 percent full.
Figure 4.1. A horizontal meter bar that is 25 percent full.
Getting back to the original reason for bringing up the issue of meter bars, look at the meter bar and see if the property list makes sense to you. The four properties listed are in fact sufficient for representing the state of the meter bar. The only one you might not see the importance of right off is the orientation property, which determines whether the meter bar is horizontal or vertical. Just so you understand the difference, Figure 4.2 shows a vertical meter bar at 75 percent full. Moving along, the fillColor property simply determines the color of the bar itself. The totalParts and partsFilled properties form the fractional pair that determine how much of the meter is filled.
4.2. A vertical meter bar that is 75 percent
The only remaining question at this point is how to model these properties with Java data types. Table 4.1 shows these properties with some reasonable data types.
The orientation property is a boolean data type because it has only two possible states: horizontal or vertical. In this case, the documentation for the bean would clearly indicate which state (horizontal and vertical) went with each boolean state (true and false). The fillColor property is a Color data type because it represents a Java AWT color. This is an example of a property representing a complex data type--in this case, a standard Java AWT class type. The totalParts and partsFilled properties are both float data types because that provides the most flexibility; you want to allow the user as much freedom as possible when using a bean. In other words, int types can easily be cast to float types if necessary, but the reverse isn't always true.
This set of properties is sufficient to represent the internal state of the meter bar bean. All you need now is a way to access them and put the bean to use. Read on!
Because properties serve as the data portion of a bean, it stands to reason that managing access to them should be of great importance. In fact, property access is one of the biggest responsibilities of the JavaBeans API. Fortunately, property access is managed through a very simple and straightforward technique: accessor methods. An accessor method is a public method defined in a bean that enables access to the value of a particular property. Accessor methods typically come in pairs, with one half of the pair capable of reading a value and the other half capable of writing a value. A pair of accessor methods is all that is required to have full access to a bean property.
Not all properties have a pair of accessor methods, however. There are certainly cases in which there is a need to be able to read a property but not write it. In other words, there is a need for read-only properties that change state internally rather than being writable externally. Likewise, there might also be situations in which a property is writable and not readable. Granted, this is much less likely, but the JavaBeans API nevertheless supports this type of arrangement. The point is that accessor methods can be used individually as well as in pairs, depending on the particular property requirements.
Accessor methods responsible for reading a bean's properties are known as getter methods, while accessor methods responsible for writing a beans properties are known as setter methods. These names are based on the fact that reading is a process of getting something, while writing is a process of setting something. Getter and setter methods aren't just important as cute names for accessor methods, however, because they play a vital role in determining how a bean's properties are accessed externally. In fact, getter and setter methods form the interface between a bean's properties and the outside world. This brings up an important point: Under no circumstances are you ever allowed direct access to a property from the outside of a bean. You must always work with properties through accessor methods, which should give you a clue as to why they are so important. Figure 4.3 illustrates how accessor methods enable access to a bean's internal properties.
Figure 4.3. The role accessor methods play in allowing external access to a bean's properties.
Accessor methods use a simple naming convention to specify the action they perform and on which property they perform it. The two actions available to accessor methods are getting and setting a property. The names of accessor methods are based on these actions as follows: An accessor method that gets a property must begin with get, and an accessor method that sets a property must begin with set. For example, if you have a bean that has a property called depth, a getter method for the bean would be called getDepth(), and a setter method would be called setDepth(). As I said earlier in this chapter, accessor methods are a very simple and straightforward means of accessing properties. But don't be mistaken, they are also very powerful in their simplicity.
Now take a quick look at some more accessor methods just to make sure you understand how this naming stuff works. Here are the definitions of a pair of accessor methods for a bean with a color property:
public Color getColor(); public void setColor(Color c);
The property in this case is of type Color, and the accessor methods are responsible for providing a means of getting and setting the color property value. Notice that both methods are defined as being public, which is important because the whole point of accessor methods is that they can be accessed from outside of a bean. Also notice that the getColor() method returns a Color object, whereas the setColor() method takes a Color object as its only parameter. Matching data types among getter and setter methods is a fundamental design guideline for accessor methods, which only makes sense considering that the methods are acting on the same property.
The most obvious reason for having accessor methods is to provide an interface for applications and other beans to query or modify the internal state of a bean. Although this reason is certainly very important, there is another reason for having accessor methods--one that makes the naming conventions of getter and setter methods much more significant. I'm referring to introspection, which is the mechanism that enables an outside party to query for the internal structure of a bean. It turns out that a major part of the JavaBeans default approach to determining a bean's structure is examining its accessor methods. By examining a bean's accessor methods, it's possible to ascertain a bean's properties. This is due to the fact that accessor methods follow a consistent naming convention that fully specifies both the type and name of the property they access. You learn a great deal more about introspection and how it relates to accessor methods in Chapter 5, "Introspection: Getting to Know a Bean."
The discussion of properties thus far has been limited to single-valued properties. Based on what you've learned, you might think that the JavaBeans API only supports properties with a single value, because that's by and large what you think of in terms of properties. Not so! The JavaBeans API also supports indexed properties, which are properties that are very similar to arrays in traditional Java programming. Indexed properties contain several elements of the same type that can be accessed via an integer index; hence the name indexed property. Indexed properties aren't typically used as much as single-valued properties, but they serve a vital purpose nonetheless.
NOTE: JavaSoft has mentioned that indexed properties might eventually enable element access via an index type other than an integer. However, in the 1.0 release of JavaBeans, integers are the only allowed index type. Keep in mind that this in no way limits the type of indexed properties themselves, just the way in which you access individual elements.
Because indexed properties ultimately refer to multiple individual elements stored in an array, the standard getter and setter methods aren't sufficient for property access. It is actually necessary to have two pairs of accessor methods; one pair gets and sets individual properties in the array via an index, while the other pair gets and sets the entire array of properties as a single unit. Here is an example of two pairs of accessor methods for a bean with an indexed property containing float values:
public float getMass(int index); public void setMass(int index, float mass); public float getMass(); public void setMass(float masses);
In this case, the mass property is an indexed property. The first two accessor methods get and set an individual mass element from the property array, which is evident by the fact that each method requires an integer index. The last two accessor methods get and set the mass property array as a whole, which is evident by the array brackets () used when defining the property type. These last two methods are pretty powerful in that they enable you to get and set the entire array of elements as a single unit.
Just as with single-value properties, remember that bean developers are free to implement any of these accessor methods they choose. Therefore, a bean with an indexed property might or might not provide a setter method enabling you to set the entire array as a whole. For beans that do enable you to set the property array to a new one, keep in mind that this enables you to change the size of the array. In fact, this is the only way to change the size of an indexed property array.
Just as in working with standard Java arrays, it is possible to index outside
the bounds of an indexed property array. When this occurs, the accessor method used
to perform the indexing typically throws an ArrayIndex-
OutOfBoundsException runtime exception, which is consistent with how
normal Java arrays behave when indexed out of bounds.
As if indexed properties aren't enough, the JavaBeans API goes a few steps further by providing some other, more advanced property types. One of these types is bound properties, which are properties that provide a notification service upon being changed. Bound properties are registered with an outside party (an applet, application, or bean) that is interested in knowing about changes in the property. Whenever the value of the bound property changes, the interested party is notified. These properties are called bound properties because they are effectively bound to outside parties via changes in their value. Outside parties can be either applications or other beans, and are also commonly referred to as listeners.
The mechanism by which a bound property is connected with an interested party is actually very simple. A bean with a bound property is required to support a pair of event listener registration methods: addPropertyChangeListener() and removePropertyChangeListener(). These methods are used to register outside parties (listeners) with the bean that contains properties to which they want to bind themselves. Both of these methods take an object implementing the PropertyChangeListener interface as their only argument, as the following method definitions show:
public void addPropertyChangeListener(PropertyChangeListener l); public void removePropertyChangeListener(PropertyChangeListener l);
As you might have guessed, these two methods are responsible for adding and removing event listeners. When an interested party wants to bind itself to a property, it must call addPropertyChangeListener() and provide a suitable object implementing the PropertyChangeListener interface. The PropertyChangeListener interface is used to report changes in the bound property. More specifically, when a bound property's value changes, the propertyChange() method is called on all registered PropertyChangeListener interfaces. This method is passed a PropertyChangeEvent object, which contains information about the specific property that has changed, along with its old and new values.
If this explanation of bound property notifications was a little too much to handle in one paragraph, look at Figure 4.4, which graphically shows how a bound property works.
4.4. The inner workings of a bound property.
Notice in the figure that the main thing taking place is the sending of a property change notification from a bean to a listener. This notification is sent whenever a change occurs in one of the bean's bound properties. The notification itself comes in the form of a PropertyChangeEvent object, which encapsulates all the information related to the property change. The listener is free to use this information however it chooses.
You might have noticed from the discussion thus far that I haven't mentioned a way for listeners to register themselves with a specific property. Instead, they must register themselves with the bean and then determine the particular property that has changed by examining the PropertyChangeEvent object sent with the notification. Although this approach technically works fine, the JavaBeans API supports another approach involving the registration of listeners with specific bound properties.
If a bean developer wants to provide listener registration on a per-property basis, she must support a pair of methods for each property of the form: add<PropertyName>Listener() and remove<PropertyName>Listener(). This method pair works exactly like the global pair of event listener registration methods about which you learned earlier, except they apply only to a specific property. Here is an example of definitions for these methods given a bound property named numberOfLives:
public void addNumberOfLivesListener(PropertyChangeListener l); public void removeNumberOfLivesListener(PropertyChangeListener l);
When an interested party wants to bind itself to the numberOfLives property, it simply registers itself by calling the addNumberOfLivesListener() method. When the numberOfLives property changes, a notification is sent to the listener via a call to its propertyChange() method. This situation is no different than the one described earlier, except that the listener in this case only receives notifications for the numberOfLives property.
Keep in mind that per-property listener registration methods are always provided
in addition to the global registration methods (addProperty-
ChangeListener() and removePropertyChangeListener()). In other words, per-property listener registration methods act as optimizations to the global listener registration approach, as opposed to replacing the global approach. Even so, per-property methods can still be a nice convenience.
Another type of advanced property type offered by the JavaBeans API is constrained properties, which are properties that enable an outside party to validate a change in them before accepting the change. In other words, when you attempt to change the value of a constrained property, the value is first checked with an outside validation source that accepts or rejects the change. Like bound properties, constrained properties are registered with an outside party, but in this case the party is given the opportunity to accept or reject changes in the property. Property change rejections come in the form of PropertyVetoException exceptions, which are handled by the bean that contains the property. When a rejection exception occurs, the bean is responsible for reverting the property back to its original value and issuing a new property change notification for the reversion.
Because property changes take place through setter methods, it is necessary for constrained property setter methods to support the PropertyVetoException exception. This makes it clear to users that the property is constrained and attempts to set it might be vetoed. Following is an example of the getter and setter methods for a simple constrained property:
public int getQuantity(); public void setQuantity(int i) throws PropertyVetoException;
The mechanism by which a constrained property is connected with an interested
party is very similar to the one used for bound properties. A bean with a constrained
property must support a pair of event listener registration methods:
addVetoableChangeListener() and removeVetoableChangeListener().
These methods are used to register listeners with the bean containing properties they want to validate. Both of these methods take a VetoableChangeListener object as their only argument, as the following method definitions show:
public void addVetoableChangeListener(VetoableChangeListener l); public void removeVetoableChangeListener(VetoableChangeListener l);
Just as in registering bound property listeners, when an interested party wants to register itself as validating a property, it must call addVetoableChangeListener() and provide a suitable object implementing the VetoableChangeListener interface. The VetoableChangeListener interface is used to report changes in the bound property. More specifically, when a bound property's value changes, the vetoableChange() method is called on all registered VetoableChangeListener interfaces. This method is passed a PropertyChangeEvent object, which is the same property change information object used in bound property notifications.
Just so you can see how similar the mechanism behind constrained properties is to that behind bound properties, Figure 4.5 graphically shows how a constrained property works.
4.5. The inner workings of a constrained
As with bound properties, the main thing taking place here is the sending of a property change notification from a bean to a listener. However, in this case the listener has the option of rejecting the change by throwing a PropertyVetoException exception. If an exception is thrown, the bean is responsible for handling it and appropriately restoring the original property value. Although the method that gets called is different (vetoableChange()), the property change notification sent for a constrained property is the same as that sent for a bound property, which is a PropertyChangeEvent object. This notification is the same because the same information is required to be sent: the property name, the old property value, and the new property value.
Like bound properties, constrained properties also support listener registration methods on a per-property basis. The naming of these methods is the same as for bound properties, with the distinguishing factor being the listener type passed as the sole argument to each method. Instead of taking a PropertyChangeListener object, the constrained listener registration methods take a VetoableChangeListener object. Following is a set of listener registration methods for a constrained version of the numberOfLives property mentioned in the previous section:
public void addNumberOfLivesListener(VetoableChangeListener l); public void removeNumberOfLivesListener(VetoableChangeListener l);
The only difference between these methods and their bound equivalents from earlier is the type of argument they take. This is logical because the argument specifies the type of listener, which in turn dictates the type of method called for a notification. In the case of bound properties the propertyChange() method is called, and in the case of constrained properties the vetoableChange() method is called.
Now that you understand the ins and outs of how properties are managed under the hood in the JavaBeans API, take a step back and see how they are used in practical situations. As you learned in the previous chapter, properties reveal themselves to the user in a variety of different scenarios, a few of which follow:
- Properties are accessed programmatically via public accessor methods.
- Properties are accessed visually via property sheets in application builder tools.
- Properties are used to persistently store and retrieve the state of a bean.
Now look at each of these scenarios and how the property management concepts you've learned in this chapter come into play in supporting each particular type of property usage.
One of the key ways beans expose themselves to scripting environments is through properties. Properties are accessible through scripting as member variables of bean objects. In other words, beans appear to scripting environments as extension objects, and their properties are accessible as member variables. Now, even though it appears to the script writer that he or she is actually given free reign to directly modify the properties of a bean object, in actuality the bean's accessor methods are being used behind the scenes to handle the access. As I mentioned earlier in the chapter, no outside party is ever allowed direct access to a bean's properties. The point is that scripting environments themselves make heavy use of bean accessor methods in enabling script writers to interact with beans. So, don't be misled when you see scripting code manipulating a bean property like this (thing is a bean object):
thing.numberOfLives = 12;
Although it looks like this code is directly modifying a bean property, you know better! Behind the scenes the scripting environment is executing a piece of code like this:
This might seem like a minor point, but trust me, it's not. Forcing all outside parties to use a standardized set of interfaces (accessor methods) to interact with the data parts of a bean is one of the key benefits of the JavaBeans technology. It guarantees that no property ever changes without somebody knowing about it, even if it is just a little accessor method.
Another common use of properties is in full-blown programming languages such as Java. Unlike scripting environments, Java uses no illusions to make you feel like you are closer to a bean than you really are. What I mean by this statement is that Java forces you to always interact with bean properties through accessor methods. So, going back to the example from the previous section, the following code wouldn't even work in Java:
thing.numberOfLives = 12;
In Java terms, this code amounts to you trying to access a private member variable of a class externally, which simply isn't allowed. You don't have any choice but to use getter and setter methods to work with a bean's properties in Java. This is just as well, because it helps you remember the fact that beans shield their internal properties by relying on interfaces.
One of the most touted features of JavaBeans as a technology is its support for the visual editing of bean properties. In this case, the glitter of editing a property through a purely visual means might have you thinking you somehow have some secret control over a bean. Not to worry, visual bean editing ultimately boils down to the same old accessor methods. Just when you thought JavaBeans was looking high-tech, I had to spoil it by telling you that the same boring mechanism is at the heart of it all!
Don't despair, because if you haven't gathered from the discussion thus far, it is this consistency of relying on the same property access mechanism throughout that makes JavaBeans so powerful. What it comes down to is that no matter how you interact with a bean property, the interaction ultimately ends up being a simple call to an accessor method. This insight should give you more cause to appreciate the simplicity inherent in the design of access methods. A more complex solution would probably run into difficulty in attempting to be scaleable to so many different scenarios.
The last area of property usage I want to touch on is that of persistence, which is the storage and retrieval of a bean's state. Because the state of a bean is ultimately defined by its properties, it stands to reason that persistence primarily involves the storage and retrieval of properties. And how do we store and retrieve bean properties? You got it, using accessor methods! Persistence is primarily a process of calling getter and setter methods on all the properties of a bean, depending on whether the bean is being stored or retrieved. What have you learned from this discussion? When in doubt, use an accessor method!
Having said about all there is to say about properties, I want to finish the chapter by taking a look at the classes and interfaces that make the property management facilities in the JavaBeans API a reality. Here are the classes and interfaces that comprise the property management portion of the JavaBeans API:
NOTE: All of these classes are covered in much greater detail in Appendix B, "JavaBeans API Quick Reference."
The PropertyChangeEvent class is used to store information relating to a change in a bound or constrained property. This information consists of the name of the property, the old value of the property, and the new value of the property. PropertyChangeEvent objects are sent as notifications from a bean to registered listeners through the PropertyChangeListener and VetoableChangeListener interfaces, depending on whether the property in question is bound or constrained. More specifically, a PropertyChangeEvent object is sent as the only argument to the propertyChange() and vetoableChange() methods, which are called on the respective listener interfaces.
The PropertyChangeSupport class is a helper class for managing listeners of bound and constrained properties. It primarily handles the chore of maintaining a list of listeners and firing property change notifications to each, which is often a nice convenience to beans with bound properties.
The PropertyVetoException class is used to notify a bean that an attempt has been made to set a constrained property to an unacceptable value. This exception is thrown by the vetoableChange() method of the VetoableChangeListener interface.
Similar to PropertyChangeSupport, the VetoableChangeSupport class is a helper class for managing listeners of bound and constrained properties. It primarily handles the chore of maintaining a list of listeners and firing property change notifications to each, which is often a nice convenience to beans with constrained properties.
The PropertyChangeListener interface serves as the listener interface implemented by classes wanting to receive notifications regarding a bound property change. This interface consists of the propertyChange() method, which is called whenever the value of a bound property has changed on a bean for which the interface is registered.
Similar to PropertyChangeListener, the VetoableChangeListener interface serves as the listener interface implemented by classes wanting to receive notifications regarding a constrained property change. This interface consists of the vetoableChange() method, which is called whenever the value of a constrained property has changed on a bean for which the interface is registered. The vetoableChange() method will throw a PropertyVetoException exception if it rejects the new value of the constrained property.
This chapter took you through a complete tour of the property management facilities that form an integral part of the JavaBeans API. As you learned, the JavaBeans technology relies heavily on properties as the data portion of beans. Realizing the importance of properties and how they are managed, the JavaBeans architects took care in making sure the property interfaces for beans are both consistent and simple. This chapter explored the primary mechanism behind property interfaces: accessor methods. You learned about the different types of accessor methods and how they are used to get and set the values of bean properties.
With accessor methods under your belt, you moved on to more advanced property topics such as indexed properties, bound properties, and constrained properties. In learning about each of these property types, you saw a different piece of the JavaBeans property management services fall into place. You then moved on to looking into some practical scenarios in which accessor methods are used to access bean properties. Finally, you finished off the chapter by taking a peek at the specific JavaBeans API classes and interfaces that make all the property management facilities a reality.
If you've had enough of properties, don't give up just yet. The next chapter focuses on introspection, which is highly dependent on properties. Fortunately, you won't have to delve quite so deeply into the mechanics behind properties, so you can relax a little.