Add indexed properties and complex property editors to your JavaBeans If you’re a JavaBeans developer, you already know that a JavaBeans property is a named access to part of the internal state of a JavaBean. (If you don’t know about properties, read all about them in my previous JavaBeans column, “Double Shot, Half Decaf, Skinny Latte — Customize your Java,” listed in this column’s Resources.) You also know that a property editor can be used to configure and/or customize JavaBeans at design time. (To get up to speed on property editors and customization, check out “The trick to controlling bean customization” and “Soup up your Java classes with customization,” also listed in the Resources section.)What you may not know is that JavaBeans properties are not limited to scalar (that is, single-valued) values. A JavaBeans property can also be an array of values, accessed by an integer index, called an indexed property.This month, we’re going to tackle creating a shiny new JavaBean with an indexed property, and a custom property editor that allows the developer to manipulate the indexed property at design time. We’ll start by creating a basic URLFileLoader bean that downloads a file from an arbitrary URL on the Web, and stores it in a disk file on demand (taking a quick side excursion into boolean properties, a simple property type). Then we’ll improve the URLFileLoader class loader, enabling it to fetch files using HTTP GET with parameters maintained by a new indexed property. (We’ll go over GET later on in the text.) Next, we’ll create a custom editor (so we can access the property graphically), and see how to specify this custom editor with the builder tool (in our case, Sun’s BeanBox.) We’ll wrap up with a discussion of possible extensions to this project. Index property backgrounderBefore we dive into creating a property editor for an indexed property, let’s have a look at why indexed properties are useful.As noted above, an indexed property is nothing more than a JavaBeans property whose value is an integer-indexed array, instead of being a single scalar value like a String or an int. For example, a RelationalTable bean might have a property called Columns that returns an array of Column objects that describe the table’s columns. A ScatterPlot bean could have a property called DataPoints, itself being an array of DataPoint objects that the bean displays. A SpreadSheet bean could have two properties: Rows, which manipulates a list of Column objects, and Columns, which manipulates a list of Row objects. Each Row (or Column) object could itself be a bean with an indexed property called Cells, an array of Cell objects.Indexed properties provide enormous flexibility to JavaBeans. Implementing them can be confusing, though, as some of my e-mail from readers attests. I own five books on JavaBeans, and most explain what indexed properties are, but none provides sample code for an indexed property editor. This was mostly because the BeanBox, the standard JavaBeans test container, didn’t support indexed property editors until quite recently. (For more on the BeanBox, see the links in Resources.) An HTTP file loader beanFor a tutorial example of creating a bean with indexed properties, let’s start with a simple bean: the URLFileLoader. We’ll first go over the requirements of this new class, and then extend it to include an indexed property and its associated editor.Requirements Let’s start by describing our new component’s requirements in terms of functionality and properties. In the following description, note that the properties we define are italicized. On request, the URLFileLoader JavaBean fetches the content of a particular URL on the Web using the HTTP protocol. It stores the content it receives in a file in a directory on a local disk. By the way, this bean is intended to be used in Java applications, not with applets, as it accesses both the filesystem and the network. Also, it optionally communicates through a firewall proxy, which listens at a specific port number. (If you don’t know what a firewall is exactly, see the introductory article link to the World Wide Web Security FAQ in the Resources section below.)All the terms italicized above are bean attributes a user (meaning an application developer) may want to control at design or runtime. Developers do this by accessing the bean via the properties we define for it, which appear in the following table:PropertyTypeDescriptionURLStringThe URL to load from the WebFileStringThe file in which to store the received contentDirStringThe directory in which to create the fileUseFirewallboolean (!)Whether or not to connect through a firewallFirewallStringFirewall namePortintThe port number of the firewall http proxy Figure 2. URLFileLoader properties The above properties customize the bean to get information from a certain place, storing it in a certain other place, optionally doing it a certain way. In short, customization is the use of properties to define what all of those “certain” things are.Now that we’ve defined what the bean is going to do, let’s define how it does it.Implementation I’ve chosen to implement URLFileLoader as a subclass of an abstract base class, because I know that eventually I’m going to extend that base class to handle other kinds of URL storing. Regardless of how the data is accessed, there are three basic steps to fetching and storing a file with HTTP:Open a connection to the remote serverDownload and save the contentClose the connectionSomewhere in there, I also have to specify what to do if there’s a firewall in the way. These are abstract operations; in other words, saying “connect to a server” does nothing about how that connection is made. Of course, if any particular operation (such as downloading and saving content from an open connection) has a reasonable default implementation, it makes sense to put it in the base class and let subclasses inherit and reuse it.But when subclasses differ in how they implement abstract operations, the base class simply defines the signature (name, return type, and argument number and types) of the method, and then tags it with the abstract keyword. Java won’t allow creation of an instance of a class that contains an abstract method declaration, so any subclass of an abstract base class must either define an implementation of every abstract method in all of its superclasses, or end up being abstract itself. There’s no way to say “don’t inherit” a method from a parent. I’m planning to create at least two classes: one that gets files from Web sites via HTTP (URLFileLoader) and one that fetches contents from Web sites that require HTTP GET parameters. You also might imagine another bean that downloads the result of HTTP POST, or another bean that transfers files via FTP. All these beans can be implementations of the abstract operations open connection, transfer data and store, and close connection, differing only in how they implement those operations.In the text box below, you’ll find the source for abstract class URLLoader.001 import java.net.*; 002 import java.io.*; 003 004 public abstract class URLLoader 005 implements Serializable { 006 007 protected final int BufSize_ = 512; 008 009 protected String sFile_; // Name of file in which to store results 010 protected String sDir_; // Directory for results 011 protected String sURL_; // The URL to load 012 protected URLConnection uc_; // Server connection to the URL 013 protected URL uURL_; // URL object used to fetch contents 014 protected InputStream is_; // Input stream used to read 015 protected BufferedInputStream bis_;// Uses is_ to read efficiently 016 protected FileOutputStream os_; // Save results to file 017 protected boolean bUseFirewall_; // Use a firewall 018 protected String sFirewall_;// Firewall name 019 protected int iPort_; // Fireall port number 020 021 // Methods 022 public URLLoader() { 023 sURL_ = new String(“http://www.javaworld.com”); 024 sFile_ = new String(“javaworld.html”); 025 sDir_ = new String(“”); 026 bUseFirewall_ = false; 027 sFirewall_ = “firewall”; 028 iPort_ = 80; 029 } 030 031 // Accessors 032 public String getURL() { 033 return sURL_; 034 } 035 public void setURL(String _sURL) { 036 sURL_ = _sURL; 037 } 038 public String getFile() { 039 return sFile_; 040 } 041 public void setFile(String _sFile) { 042 sFile_ = _sFile; 043 } 044 public String getDir() { 045 return sDir_; 046 } 047 public void setDir(String _sDir) { 048 sDir_ = _sDir; 049 } 050 public void setUseFirewall(boolean _bUseFirewall) { 051 bUseFirewall_ = _bUseFirewall; 052 } 053 public boolean isUseFirewall() { 054 return bUseFirewall_; 055 } 056 057 public String getFirewall() { 058 return sFirewall_; 059 } 060 public void setFirewall(String _sFirewall) { 061 sFirewall_ = _sFirewall; 062 } 063 064 public int getPort() { 065 return iPort_; 066 } 067 public void setPort(int _iPort) { 068 iPort_ = _iPort; 069 } 070 071 // OpenConnection should call this function to set 072 // up proxy connection if necessary 073 protected void setupFirewall() { 074 if (isUseFirewall()) { 075 System.getProperties().put(“proxySet”, “true”); 076 System.getProperties().put(“proxyHost”, sFirewall_); 077 System.getProperties().put(“proxyPort”, Integer.toString(iPort_)); 078 } else { 079 System.getProperties().put(“proxySet”, “false”); 080 } 081 } 082 083 // Copy from input stream to output stream 084 public void fetchContent() { 085 byte[] buf = new byte[BufSize_]; 086 System.out.println(“Reading…”); 087 try { 088 int n = bis_.read(buf, 0, BufSize_); 089 while (n > 0) { 090 System.out.println(“Read ” + n + ” bytes”); 091 os_.write(buf, 0, n); 092 n = bis_.read(buf, 0, BufSize_); 093 } 094 } 095 catch (IOException e) { 096 System.out.println(e.getMessage()); 097 System.exit(1); 098 } 099 } 100 101 // perform execution 102 public void execute() { 103 openConnection(); 104 fetchContent(); 105 closeConnection(); 106 } 107 108 // ABSTRACT action Methods 109 public abstract void openConnection(); 110 public abstract void closeConnection(); 111 112 }; Figure 3. URLLoader.java The URLLoader.java class has accessor methods for all the properties described in Figure 2, as well as the following operations:MethodTypeDescriptionsetupFirewall()protected voidIf using a firewall, set up Java environment to access HTTP connections through a proxy.fetchContent()public voidGet the content from the URL and store it in the file.execute()public voidOpen the connection to the data source, fetch and store the contents, and close the connection.openConnection()public abstract voidOpen a connection to the URL.closeConnection()public abstract voidClose the open connection. Figure 4. Operations of the abstract class URLLoaderThese operations provide a basis for implementing subclasses that get files from the Web. Notice that only two of the methods, openConnection() and closeConnection(), are abstract; all the others are provided as default implementations. This is pretty sensible, since the subclasses only differ in how they connect to their data source. A boolean property Notice that on line 50 above, there’s a method:public void setUseFirewall(boolean _bUseFirewall) { and on line 53, another method: public boolean isUseFirewall() { The method setUseFirewall looks like a standard property editor, but what is isUseFirewall()? This is a standard design pattern (Sun’s unfortunate term for naming conventions) for a boolean property. When java.beans.Introspector runs across a pair of methods with signatures that look like void setPropertyName(boolean) and boolean isPropertyName(), it treats PropertyName as a boolean property. The builder tool can display boolean properties any way it chooses: as a checkbox, a True or False pull-down list (the BeanBox’s choice), or any other two-state widget. You’ll notice that when we see an instance of URLFileLoader running in the BeanBox, the boolean property useFirewall appears as a True or False pull-down list.Let’s continue with a class that actually loads and copies files.Accessing file URLs To create the URLFileLoader class, we need only define the methods openConnection() and closeConnection(). If you’re a follower of JavaWorld‘s Java Tips column, you may recognize the technique used in these functions. (See “Java Tip 34: POSTing via Java”.) The source code for URLFileLoader appears here:001 import java.net.*; 002 import java.io.*; 003 import URLLoader; 004 005 public class URLFileLoader extends URLLoader 006 implements Serializable { 007 // Open connection to server, and open output file 008 public void openConnection() { 009 try { 010 setupFirewall(); 011 uURL_ = new URL(getURL()); 012 uc_ = uURL_.openConnection(); 013 is_ = uURL_.openStream(); 014 bis_ = new BufferedInputStream(is_, BufSize_); 015 os_ = new FileOutputStream(sDir_ + “/” + sFile_); 016 } 017 catch (IOException e) { 018 // Poor exception handling for simplicity’s sake 019 System.out.println(e.getMessage()); 020 System.exit(1); 021 } 022 System.out.println(“Opened ” + sURL_ + “, writing to ” + 023 sDir_ + “/” + sFile_); 024 } 025 public void closeConnection() { 026 try { 027 is_.close(); is_ = null; 028 bis_.close(); bis_ = null; 029 os_.close(); os_ = null; 030 } 031 catch (IOException e) { 032 // Poor exception handling for simplicity’s sake 033 System.out.println(e.getMessage()); 034 System.exit(1); 035 } 036 System.out.println(“Connection closed.”); 037 } 038 }; Figure 5. URLFileLoader.java copies a URL to local fileIn the code above, openConnection() creates an open connection to a URL by calling getURL() to get the URL from the parent. An aside: It’s always polite to ask your parent class for property values via the accessor, instead of rummaging through its fields directly. It’s sometimes said that inheritance breaks encapsulation, meaning that if you change the implementation of a superclass method, but your subclasses depend on that implementation, you may break subclass functionality. Using superclass accessors makes this less likely. The superclass fields I do access (uc_ and so on) suffer from this liability, and a purist would set and get these objects through calls on protected methods in the parent class, but I didn’t want to obfuscate the code.After opening the URLConnection, openConnection() creates the buffered I/O streams that fetchContent() uses to transfer data. It can also use the superclass’s setupFirewall() method to initialize the Java environment to communicate through a firewall, if the bean is customized to do so. The closeConnection() method shuts down the connection and closes streams.To use this bean in the BeanBox: Package URLFileLoader.class in a jar file (instructions can be found on Sun’s java.sun.com Web site — see Resources)Put the jar file in the BeanBox’s classpath (the easiest way to do this is to put it in bdk/jarfiles in the BDK distribution)Drop a URLFileLoader object into the BeanBoxCustomize the loader bean as you wish using the property sheetAdd a Button of some sort to the BeanBox, and select the buttonUsing the menu item Edit->Events->button push->actionPerformed, bind the button’s actionPerformed event to the URLFileLoader‘s execute event.When you click the button, the bean will go and fetch the contents of the URL and copy them to your local disk.Now that we have a functioning URL loader, let’s create a loader class that can handle parameters.Handling HTTP GET parametersThere are two common ways of passing parameters to a CGI script in HTML: GET and POST. In this section, we’re going to discuss GET and leave POST as an exercise for the reader. (If it’s an exercise you want to do, JavaWorld‘s Java Tips on “POSTing” via Java in Resources below may be of some help.) HTTP GET passes parameters to a CGI program by specifying the values of variables in the URL itself. For example, searching for “post applet” on JavaWorld‘s search engine (at http://www.javaworld.com/javaworld/search.html, itself being an HTML form that uses POST) results in a URL like this:http://search.javaworld.com/query.html?rq=0&col=jw&qp=&qt=post+applet&qs=&qc=&ws=0&qm=0&st=1&nh=10&lk=1&rf=0&oq=&rq=0 Everything following the question mark (?) in the URL is an encoded list of variables passed to the script. Each variable/value pair is separated by an ampersand (&), and spaces are encoded as plus marks (+). This is the standard text format for transferring variables in HTTP. The query program uses these variables to search the database, make decisions about formatting, and so on.Now, we could stuff this ungainly URL into our URLFileLoader‘s URL property, and it would operate correctly. But it would be much more pleasant (and flexible) to create a single property of the bean that allows us to specify an arbitrary number of parameters to a CGI program. But how to do that? (Bet you can guess.) Indexed property naming conventions Builder tools can automatically identify indexed properties that conform to the indexed property naming and signature conventions (design patterns) described in the JavaBeans Specification. Since each property is an array, we simply add an index value to both property accessors.Let’s say we have an indexed property of String called formData. Then, the property accessor signatures look like this: public String getFormData(int index) public void setFormData(String value, int index) The index indicates which element in the array is being accessed. If the index is negative or otherwise out of range, the accessor can throw an ArrayIndexOutOfBoundsException. (This exception is not checked, so it doesn’t need to appear in the throws clause of the accessor.) Anyone who uses this bean can now set and get form data by index, just as if the accessor were an array.Optionally, a bean developer can add another pair of accessors that set and get the entire array at once: public String[] getFormData() public void setFormData(String[] value) In these cases, the index is gone because we’re getting or setting the entire array at once.Implementing the FormData indexed property Now let’s create a class that implements the String[] property FormData. I’ve chosen to create an intermediate class between URLLoader and the new class, URLGetLoader. I’ve called this intermediate class URLDataLoader. The reason I’ve done this is because I also plan to implement a URLPostLoader bean, and I’ve placed all of the code that both classes have in common in their common base class. The inheritance tree for all of these classes appears in Figure 6, below.You can browse the source code for the URLDataLoader class in the text box in Figure 7. Note that URLDataLoader is abstract because its base class (URLLoader) is abstract, but URLDataLoader doesn’t implement its abstract methods. This is an example of using inheritance to extend the functionality of an existing class, even though URLDataLoader can’t be used to create instances.001 import java.net.*; 002 import java.io.*; 003 import java.beans.*; 004 import URLLoader; 005 import FormDataEditor; 006 007 public abstract class URLDataLoader extends URLLoader 008 implements Serializable { 009 010 protected String[] sFormData_; 011 012 public URLDataLoader() { 013 super(); 014 sFormData_ = new String[0]; 015 } 016 017 public String getFormData(int index) { 018 return sFormData_[index]; 019 } 020 021 public String[] getFormData() 022 { 023 return sFormData_; 024 } 025 026 public void setFormData(String[] _sFormData) { 027 sFormData_ = _sFormData; 028 } 029 030 public void setFormData(int index, String _sValue) 031 { 032 // Grow form data array if index requires it 033 if (index > sFormData_.length) { 034 String[] sNewData = new String[index]; 035 for (int i = 0; i = 0) { 056 sContent = sKey.substring(j+1); 057 sKey = sKey.substring(0, j); 058 } else { 059 sContent = “”; 060 } 061 062 if (sResult != “”) 063 sResult += “&”; 064 065 sResult += sKey; 066 067 if (sContent != “”) 068 sResult += “=” + URLEncoder.encode(sContent); 069 } 070 return sResult; 071 } 072 }; Figure 7. URLDataLoader.java with indexed propertiesYou’ll notice in URLDataLoader.java that the implementations of the two accessors String[]getFormData() and void setFormData(String[]_sFormData) look pretty much like any other simple accessor. String getFormData(int index) is also very simple. The only one with any complexity is void setFormData(String[]_sFormData), which simply grows the sFormData_ array if it gets an index that is too large. This “auto-grow” isn’t a requirement for an indexed property; in fact, in some applications it wouldn’t be appropriate. It’s up to the designer to make the call whether or not to use it. In this case, I want the number of form variables I can enter to be arbitrarily long.We’ll use the other method in URLDataLoader — getFormDataEncoded() — in the subclasses.The URLGetLoader class: ‘Getting’ from a CGI One of the nice things about creating a large number of classes is that each class has fewer methods and so is easier to understand. The concrete (as opposed to abstract) class URLGetLoader, seen in Figure 8, is almost identical to class URLFileLoader, except that it appends a question mark (?) (indicating that a GET is occurring) and a list of encoded form variables to the URL is being fetched. The method URLDataLoader.getFormDataEncoded() takes each element of the formData indexed property, splits it into variable (everything before the first equal-to sign [=]) and value (everything after the equal-to sign), and url-encodes the value. It then joins all variable/value pairs with an equal-to sign, and joins all of the elements of the formData to create a single string. This string conforms to the standard format for specifying parameters to an HTTP GET request. (The GET request itself is specified by the question mark (?) in the URL.) With the exception of this single string, URLGetLoader is identical to URLFileLoader.Figure 8 shows the source code for the URLGetLoader bean. Notice that it extends URLDataLoader, just as URLFileLoader does.001 import java.net.*; 002 import java.io.*; 003 import URLLoader; 004 005 public class URLGetLoader extends URLDataLoader 006 implements Serializable { 007 // Open connection to server, and open output file 008 public void openConnection() { 009 try { 010 setupFirewall(); 011 012 String sURL = getURL(); 013 String sGetString = getFormDataEncoded(); 014 if (sGetString != “”) 015 sURL += “?” + sGetString; 016 017 System.out.println(“URL=” + sURL); 018 019 uURL_ = new URL(sURL); 020 uc_ = uURL_.openConnection(); 021 is_ = uURL_.openStream(); 022 bis_ = new BufferedInputStream(is_, BufSize_); 023 os_ = new FileOutputStream(sDir_ + “/” + sFile_); 024 } 025 catch (IOException e) { 026 // Poor exception handling for simplicity’s sake 027 System.out.println(e.getMessage()); 028 System.exit(1); 029 } 030 System.out.println(“Opened ” + sURL_ + “, writing to ” + 031 sDir_ + “/” + sFile_); 032 } 033 034 035 public void closeConnection() { 036 try { 037 is_.close(); is_ = null; 038 bis_.close(); bis_ = null; 039 os_.close(); os_ = null; 040 } 041 catch (IOException e) { 042 // Poor exception handling for simplicity’s sake 043 System.out.println(e.getMessage()); 044 System.exit(1); 045 } 046 System.out.println(“Connection closed.”); 047 } 048 049 }; Figure 8. URLGetLoader.java gets files via HTTP GETCreating a complex property editorBecause URLGetLoader is a bean, you can put its class file in a jar and drop it into the BeanBox. Unfortunately, you’ll be in for a nasty surprise: the BeanBox doesn’t have a standard property editor for an indexed property. Fortunately, the JavaBeans Spec provides a way for us to create our own property editor by extending java.beans.PropertyEditor.In the past (see the “Customization” links in Resources), we’ve used the simple interfaces on java.beans.PropertyEditorSupport to handle simple property editors. This time, we’re going for the big enchilada: a complete property editor that allows us to edit this indexed property. We’re going to create a custom editor called FormDataEditor, and it will be capable of editing an indexed property of type String[]. After creating the custom editor, we’ll tell the builder tool how to access it, and we’ll see it running in the BeanBox. Let’s start by getting an in-depth understanding of the property editor interface.The interface java.beans.PropertyEditor The interface java.beans.PropertyEditor is pretty complex, and takes some figuring out. A property editor is essentially a custom component that a builder tool (like the BeanBox) “plugs into” a property sheet. Each instance of a property editor manipulates a single property of an individual bean by firing property change events at the source bean, thereby notifying it whenever a property has changed. (Find out more about property change events in “Keep listening for upcoming events”; you’ll find a link to this article in Resources below.) I’m going to assume for this discussion that you’re already up to speed on the PropertyChangeListener interface.Usually, a programmer can just subclass the convenience class java.beans.PropertyEditorSupport, override a method or two, and get a slightly better property editor. Unfortunately, there’s no way to do that for indexed properties. You pretty much have to implement the entire interface. But it’s really not too bad once you’ve been through it.Keep in mind that, while we’re writing a complex property editor in order to edit an indexed property, property editors can be used for any property of any level of complexity. Custom property editors are not only for indexed properties. Also, consider using a Customizer instead of a property editor when using indexed properties. (You can find a link to a previous JavaBeans article on Customizers in Resources below.)The property editor interface has a few groups of methods that work together. Let’s go through the methods one by one:Property change listener methods The following methods add and remove property change listeners:addPropertyChangeListener()removePropertyChangeListener()Typically, you’ll use a PropertyChangeSupport object to implement these. As a property editor developer, you’re responsible for firing property change events any time the user changes a property in your editor. (You get to decide when the event is fired, though.)Value accessors Just as a JavaBean has accessor methods that allow the user to set and get properties, the builder tool uses the following methods to get and set the value being edited in the property editor:getValue()setValue()When the property editor is first created, the builder calls getValue() to set the initial value of the object. The property editor should maintain and edit a copy of the object passed in, not the object itself. (Going into the reasons for this rule are beyond our scope here — just trust me.)The builder tool also registers itself as a PropertyChangeListener on the property editor. When the user changes a value in the editor, the property editor fires a PropertyChangeEvent. The builder tool responds by calling getValue() to get the new value of the property. This happens regardless of the content of the PropertyChangeEvent. This explains the firePropertyChange("", null, null) that you’ll see in the code that follows shortly.Painting methods These methods are for displaying a graphical representation of the property editor in the property sheet:isPaintable()paintValue()Usually, the space in the property sheet is inhabited by a text item or dropdown list. But if your PropertyEditor returns true for isPaintable, then repaints for the small area within the property sheet are delegated to the paintValue() method you supply. In our sample class below (FormDataEditor), this method simply paints the whole area bright red, to make it stand out like a sore thumb. To activate the property editor, you double-click anywhere inside the paint area (the red box). This is BeanBox functionality — other builder tools might handle this quite differently.Getting the component The following methods invokes the component:supportsCustomEditor()getCustomEditor()If supportsCustomEditor returns true, a builder tool may then call getCustomEditor(), which must return the entire custom Component that is the GUI editor for the property.In our case, we return a subclass of java.awt.Panel (the most common choice) that is used to edit a list of strings.Miscellaneous inapplicable methods These methods have to do with alternate ways of specifying property editors:getAsText()setAsText()getTags()These should return null, if they return anything at all.Oddball methodThis method is used in code generation.getJavaInitializationString()It’s used to set the initial value of the property when your builder tool generates code for new instances of this object. We’re not going to consider it any further here.The FormDataEditor property editor The source for the FormDataEditor class is pretty complex. First, it’s a GUI component that edits the list of strings passed to it via setValue. It accepts registration of PropertyChangeListeners, at which it fires PropertyChangeEvents whenever any value in the string list changes. The class also implements several event listener interfaces in order to handle user interaction. Finally, it completely implements java.beans.PropertyEditor, including returning itself as a custom editor. (See getCustomEditor() in the source code.) The source code appears in Figure 9.001 import java.awt.*; 002 import java.awt.event.*; 003 import java.beans.*; 004 import java.util.*; 005 006 // This is a property editor for the indexed FormData class 007 public class FormDataEditor extends Panel 008 implements ActionListener, ItemListener, 009 TextListener, java.beans.PropertyEditor { 010 011 static public void main(String[] args) { 012 Frame f = new Frame(); 013 FormDataEditor ed = new FormDataEditor(); 014 015 f.add(ed); 016 f.pack(); 017 f.show(); 018 } 019 020 // Members 021 private PropertyChangeSupport pcs_; 022 private List lKeys_; 023 private TextField tfContent_; 024 private TextField tfKey_; 025 private Button btDel_; 026 private Button btNew_; 027 private Vector vContents_; 028 029 public FormDataEditor() 030 { 031 setLayout(new BorderLayout(5, 5)); 032 033 // Put containers in for border layout 034 Panel pSouth = new Panel(); 035 Panel pEast = new Panel(); 036 Panel pWest = new Panel(); 037 Color ltblue = new Color(192, 192, 255); 038 039 setBackground(ltblue); 040 pSouth.setBackground(ltblue); 041 pEast.setBackground(ltblue); 042 pWest.setBackground(ltblue); 043 044 add(pSouth, “South”); 045 add(pEast, “East”); 046 add(pWest, “West”); 047 048 btDel_ = new Button(“Delete”); 049 btNew_ = new Button(“New”); 050 btDel_.addActionListener(this); 051 btNew_.addActionListener(this); 052 053 pSouth.setLayout(new GridLayout(1,2)); 054 pSouth.add(btNew_); 055 pSouth.add(btDel_); 056 057 pEast.setLayout(new GridLayout(4, 1)); 058 059 tfContent_ = new TextField(32); 060 tfContent_.addTextListener(this); 061 062 tfKey_ = new TextField(32); 063 tfKey_.addTextListener(this); 064 065 pEast.add(new Label(“Key”)); 066 pEast.add(tfKey_); 067 pEast.add(new Label(“Content of key”)); 068 pEast.add(tfContent_); 069 070 lKeys_ = new List(6, false); 071 072 // The panel processes selection changes from the list 073 lKeys_.addItemListener(this); 074 075 pWest.setLayout(new BorderLayout()); 076 pWest.add(new Label(“Select key”), “North”); 077 pWest.add(lKeys_, “South”); 078 079 pcs_ = new PropertyChangeSupport(this); 080 vContents_ = new Vector(); 081 } 082 083 public Insets getInsets() 084 { 085 return new Insets(5, 5, 5, 5); 086 } 087 088 public void Dump() { 089 String[] slValues = (String[])getValue(); 090 for (int i = 0; i = 0) { 121 lKeys_.remove(i); 122 vContents_.removeElementAt(i); 123 doItemStateChange(ItemEvent.DESELECTED); 124 } 125 firePropertyChange(); 126 } 127 } 128 129 // When selection changes, change what’s in text boxes. 130 protected void doItemStateChange(int stateChange) 131 { 132 if (stateChange == ItemEvent.SELECTED) { 133 134 String sItem = lKeys_.getSelectedItem(); 135 tfKey_.setText(sItem); 136 137 int iItem = lKeys_.getSelectedIndex(); 138 String sContent = (String)vContents_.elementAt(iItem); 139 140 if (sContent == null) 141 sContent = “”; 142 143 tfContent_.setText(sContent); 144 tfContent_.requestFocus(); 145 tfContent_.setCaretPosition(sContent.length()); 146 } else if (stateChange == ItemEvent.DESELECTED) { 147 tfKey_.setText(“”); 148 tfContent_.setText(“”); 149 } 150 } 151 152 // Handle list selections 153 public void itemStateChanged(ItemEvent e) 154 { 155 doItemStateChange(e.getStateChange()); 156 } 157 158 // As a new value is typed, update what’s being edited. 159 public void textValueChanged(TextEvent e) { 160 int i = lKeys_.getSelectedIndex(); 161 if (i = 0) { 207 sContent = sKey.substring(i+1); 208 sKey = sKey.substring(0, i); 209 } else { 210 sContent = “”; 211 } 212 213 lKeys_.add(sKey); 214 vContents_.addElement(sContent); 215 iNonNull++; 216 } 217 } 218 } 219 220 // Return the current value of the object (the string list) 221 public Object getValue() { 222 int count = lKeys_.getItemCount(); 223 String[] slResult = new String[count]; 224 for (int i = 0; i Figure 9. The FormDataEditor custom property editorThere are a couple of “gotchas” you’ll need to watch out for. When working with the BeanBox, if getValue() returns null when the BeanBox is initializing, the BeanBox will ignore the property, and that property will never make it into the property sheet. Also, never put null strings into a java.awt.List object, unless you’re planning to never display the list. That’s a general Java programming tip.Interfacing with builder toolsNow that we have a custom property editor, there’s one final step: telling the builder tool (in this case, the BeanBox) about the custom editor. According to the JavaBeans Spec, a developer can provide detailed information to a builder tool in a java.beans.BeanInfo object. A builder tool locates the BeanInfo for a bean by appending BeanInfo to the bean’s class name and trying to load a class with that name. For example, if a builder loads a JavaBean called Pinto, it will search for and load (if found) a class called PintoBeanInfo.Separating the BeanInfo from the bean sounds confusing, but it’s actually a great idea. BeanInfo is usually used at design time only. By separating design-time code from runtime code, a developer can customize and distribute JavaBeans with customizers and property editors, but avoid the overhead of distributing design-time-only code to end users.The contents of BeanInfo merit a column or two on their own. If you want to brush up on BeanInfo, see my article “Controlling Customization,” in the Resources section.The source code for the URLDataLoaderBeanInfo appears in Figure 10.001 import URLDataLoader; 002 import FormDataEditor; 003 import java.beans.*; 004 005 // Define BeanInfo for this class 006 public class URLDataLoaderBeanInfo extends SimpleBeanInfo { 007 008 protected PropertyDescriptor makeProp(String sName) { 009 try { 010 return new PropertyDescriptor(sName, 011 URLDataLoader.class); 012 } 013 catch (IntrospectionException e) {} 014 return null; 015 }; 016 017 public PropertyDescriptor[] getPropertyDescriptors() { 018 try { 019 System.out.println(“Creating indexed property descriptor”); 020 // Create descriptor for indexed property 021 IndexedPropertyDescriptor FormDataPD = 022 new IndexedPropertyDescriptor(“formData”, 023 URLDataLoader.class); 024 025 // Set editor for this property 026 FormDataPD.setPropertyEditorClass(FormDataEditor.class); 027 028 // Return array of property editors, including indexed one. 029 PropertyDescriptor[] props = { 030 makeProp(“URL”), 031 makeProp(“Dir”), 032 makeProp(“File”), 033 makeProp(“UseFirewall”), 034 makeProp(“Firewall”), 035 makeProp(“Port”), 036 FormDataPD 037 }; 038 return props; 039 } 040 catch (IntrospectionException e) { 041 System.out.println(e.getMessage()); 042 } 043 return null; 044 }; 045 }; 046 Figure 10. URLDataLoaderBeanInfo informs the builder about properties and property editorsThe URLDataLoaderBeanInfo code that appears in Figure 9 simply defines all of the properties of the bean in the standard way. One new twist is the creation of an IndexedPropertyDescriptor, which is the special PropertyDescriptor class for indexed properties. It is in this descriptor that we set the editor class, by calling FormDataPD.setPropertyEditorClass(FormDataEditor.class).Pulling it all togetherAfter all this coding, we finally have a usable indexed property editor. In Figure 10 below, you can see the finished product. And, if you’ve got JDK 1.1 or later, and the BDK, you can download the jar file from the Resources link and try it for yourself!When you double-click the red box in the property sheet, the BeanBox creates a dialog on the fly, called PropertyEditor.getCustomEditor(), and embeds the component it gets back in the dialog. That’s the dialog you see floating above the property sheet: the custom editor. The bean URLGetLoader receives FormDataEditor as its custom property editor because the Introspector (we’ll cover that some other time) searches superclasses for property editors if subclasses don’t define them, and then uses a default class if nothing else is found.Future possibilitiesIf you like, you might try building an application based around these URL-copying beans. Parse the HTML as it comes in and recursively load the image and class files referenced in the page. Create timers that fire and interact with Web sites at preset times. Build your own Web spider!I wanted to create a bean that stores the result of a POST, but I’m going to leave that as an exercise for the reader. Notice that while the JavaBeans we’re using this month have a visible interface at design-time, they’re invisible at runtime. Many people think of JavaBeans as pluggable interface components, and they do excel in that role. JavaBeans is, however, a completely general component technology in its own right. JavaBeans doesn’t only mean “user interface component” any more than Java only means “browser applet.”So, learn to use indexed properties in your designs! With indexed properties and custom property editors, your JavaBeans can have more elegant interfaces, both technically and visually.Mark Johnson has a BS in computer and electrical engineering from Purdue University (1986). He is a fanatical devotee of the design pattern approach in object-oriented architecture, of software components in theory, and of JavaBeans in practice. Over the past several years, he worked for Kodak, Booz-Allen and Hamilton, and EDS in Mexico City, developing Oracle and Informix database applications for the Mexican Federal Electoral Institute and for Mexican Customs. He currently works as a designer and developer for Object Products, Inc. in Fort Collins, CO. Java