15.9 Manipulating Beans

The ShowBean class of Chapter 11 is a simple beanbox for displaying and experimenting with individual beans. The ShowBean code listed in Example 11-30 is concerned primarily with the creation of a GUI, and the key bean-manipulation methods are handled by a separate Bean class. Now that we've seen how to write a bean and its auxiliary classes, we're ready to tackle this Bean class itself; it is listed in Example 15-10.

An instance of the Bean class represents a single bean and its associated BeanInfo. Bean defines methods for querying and setting bean properties and for querying and invoking bean commands. (It defines a command as a method with no arguments and no return values.) In some ways, Bean can be considered a simplified interface to the BeanInfo class. Note that the java.beans package does not define any class named Bean: JavaBeans are not required to implement any Bean interface or extend any Bean superclass, so we've appropriated this class name for our own use here.

The Bean class has a public constructor that uses the java.beans.Introspector class to obtain BeanInfo for the bean object you pass to it. Bean also defines three static factory methods that you can use to instantiate the bean object instead of creating it yourself: forClassName( ) instantiates a named class to create the bean; fromSerializedStream( ) reads a serialized bean object from a java.io.ObjectInputStream (see Chapter 10); and fromPersistentStream( ) uses the JavaBeans persistence mechanism to read a bean instance from a stream using java.beans.XMLDecoder. XMLDecoder and the corresponding XMLEncoder class (demonstrated in Example 11-30) are new in Java 1.4 and are usually a better choice for saving the persistent state of beans: although the storage format is XML-based, it is usually more compact than the binary serialization format, and it is based on the public API of the bean rather than the private implementation, which is subject to versioning problems.

Pay attention to the ways Bean sets named properties. The setPropertyValue( ) method is passed a property name and value as strings. It checks whether the type of the named property is one that it knows how to convert a string to, and, if so, it converts the string and sets the property. If it does not know the type of the property, it attempts to find and use a PropertyEditor for that type, but this does not work for editors that implement getCustomEditor( ) instead of setAsText( ).

ShowBean uses setPropertyValue( ) to set property values specified on the command line. It does not use this method to set properties from its Properties menu, however; in this case, ShowBean calls getPropertyEditor( ). getPropertyEditor( ) does not return a PropertyEditor object directly as we noted when implementing property editors, the PropertyEditor interface is confusing and hard to work with. Instead, getPropertyEditor( ) looks for a PropertyEditor for the named property and, if it finds one, returns a Component that is hooked up to the PropertyEditor. The Component is suitable for display in a dialog box or panel: when the user interacts with the returned component, the component interacts with the PropertyEditor to set the property.

Example 15-10. Bean.java
package je3.beans; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.beans.*; import java.lang.reflect.*; import java.util.*; import java.util.List;  // explicit import to disambiguate from java.awt.List import java.io.*; /**  * This class encapsulates a bean object and its BeanInfo.  * It is a key part of the ShowBean "beanbox" program, and demonstrates  * how to instantiate and instrospect beans, and how to use reflection to   * set properties and invoke methods.  It also illustrates how to work with  * PropertyEditor classes.  */ public class Bean {     Object bean;     // The bean object we encapsulate     BeanInfo info;   // Information about beans of that type     Map properties;  // Map property names to PropertyDescriptor objects     Map commands;    // Map command names to MethodDescriptor objects     boolean expert;  // Whether to include "expert" properties and commands     // Utility object used when invoking no-arg methods      static final Object[  ] NOARGS = new Object[0];     // This constructor introspects the specified component.     // Typically you'll use one of the static factory methods instead.     public Bean(Object bean, boolean expert) throws IntrospectionException {         this.bean = bean;     // The object to instrospect         this.expert = expert; // Is the end-user an expert?         // Introspect to get BeanInfo for the bean         info = Introspector.getBeanInfo(bean.getClass( ));         // Now create a map of property names to PropertyDescriptor objects         properties = new HashMap( );         PropertyDescriptor[  ] props = info.getPropertyDescriptors( );         for(int i = 0; i < props.length; i++) {             // Skip hidden properties, indexed properties, and expert              // properties unless the end-user is an expert.             if (props[i].isHidden( )) continue;             if (props[i] instanceof IndexedPropertyDescriptor) continue;             if (!expert && props[i].isExpert( )) continue;             properties.put(props[i].getDisplayName( ), props[i]);         }         // Create a map of command names to MethodDescriptor objects         // Commands are methods with no arguments and no return value.         // We skip commands defined in Object, Component, Container, and         // JComponent because they contain methods that meet this definition         // but are not intended for end-users.         commands = new HashMap( );         MethodDescriptor[  ] methods = info.getMethodDescriptors( );         for(int i = 0; i < methods.length; i++) {             // Skip it if it is hidden or expert (unless user is expert)             if (methods[i].isHidden( )) continue;             if (!expert && methods[i].isExpert( )) continue;             Method m = methods[i].getMethod( );             // Skip it if it has arguments or a return value             if (m.getParameterTypes( ).length > 0) continue;             if (m.getReturnType( ) != Void.TYPE) continue;             // Check the declaring class and skip useless superclasses             Class c = m.getDeclaringClass( );             if (c==JComponent.class || c==Component.class ||                 c==Container.class || c==Object.class)  continue;             // Get the unqualifed classname to prefix method name with             String classname = c.getName( );             classname = classname.substring(classname.lastIndexOf('.')+1);             // Otherwise, this is a valid command, so add it to the list             commands.put(classname + "." + m.getName( ),  methods[i]);         }     }     // Factory method to instantiate a bean from a named class     public static Bean forClassName(String className, boolean expert)         throws ClassNotFoundException, InstantiationException,                IllegalAccessException, IntrospectionException     {         // Load the named bean class         Class c = Class.forName(className);         // Instantiate it to create the component instance         Object bean = c.newInstance( );         return new Bean(bean, expert);     }     // Factory method to read a serialized bean     public static Bean fromSerializedStream(ObjectInputStream in,                                             boolean expert)         throws IOException, ClassNotFoundException, IntrospectionException     {         return new Bean(in.readObject( ), expert);     }     // Factory method to read a persistent XMLEncoded bean from a stream.     public static Bean fromPersistentStream(InputStream in, boolean expert)         throws IntrospectionException     {         return new Bean(new XMLDecoder(in).readObject( ), expert);     }     // Return the bean object itself.     public Object getBean( ) { return bean; }     // Return the name of the bean     public String getDisplayName( ) {         return info.getBeanDescriptor( ).getDisplayName( );     }     // Return an icon for the bean     public Image getIcon( ) {         Image icon = info.getIcon(BeanInfo.ICON_COLOR_32x32);         if (icon != null) return icon;         else return info.getIcon(BeanInfo.ICON_COLOR_16x16);     }     // Return a short description for the bean     public String getShortDescription( ) {         return info.getBeanDescriptor( ).getShortDescription( );     }     // Return an alphabetized list of property names for the bean     // Note the elegant use of the Collections Framework     public List getPropertyNames( ) {         // Make a List from a Set (from a Map), and sort it before returning.         List names = new ArrayList(properties.keySet( ));          Collections.sort(names);         return names;     }     // Return an alphabetized list of command names for the bean.     public List getCommandNames( ) {         List names = new ArrayList(commands.keySet( ));         Collections.sort(names);         return names;     }     // Get a description of a property; useful for tooltips     public String getPropertyDescription(String name) {         PropertyDescriptor p = (PropertyDescriptor) properties.get(name);         if (p == null) throw new IllegalArgumentException(name);         return p.getShortDescription( );     }     // Get a description of a command; useful for tooltips     public String getCommandDescription(String name) {         MethodDescriptor m = (MethodDescriptor) commands.get(name);         if (m == null) throw new IllegalArgumentException(name);         return m.getShortDescription( );     }     // Return true if the named property is read-only     public boolean isReadOnly(String name) {         PropertyDescriptor p = (PropertyDescriptor) properties.get(name);         if (p == null) throw new IllegalArgumentException(name);         return p.getWriteMethod( ) == null;     }     // Invoke the named (no-arg) method of the bean     public void invokeCommand(String name)         throws IllegalAccessException, InvocationTargetException     {         MethodDescriptor method = (MethodDescriptor) commands.get(name);         if (method == null) throw new IllegalArgumentException(name);         Method m = method.getMethod( );         m.invoke(bean, NOARGS);     }     // Return the value of the named property as a string     // This method relies on the toString( ) method of the returned value.     // A more robust implementation might use a PropertyEditor.     public String getPropertyValue(String name)         throws IllegalAccessException, InvocationTargetException     {         PropertyDescriptor p = (PropertyDescriptor) properties.get(name);         if (p == null) throw new IllegalArgumentException(name);         Method m = p.getReadMethod( );           // property accessor method         Object value = m.invoke(bean, NOARGS);  // invoke it to get value         if (value == null) return "null";         return value.toString( );                // use the toString method( )     }     // Set the named property to the named value, if possible.     // This method knows how to convert a handful of well-known types.  It     // attempts to use a PropertyEditor for types it does not know about but     // this only works for editors that have working setAsText( ) methods.     public void setPropertyValue(String name, String value)         throws IllegalAccessException, InvocationTargetException     {         // Get the descriptor for the named property         PropertyDescriptor p = (PropertyDescriptor) properties.get(name);         if (p == null || isReadOnly(name))  // Make sure we can set it             throw new IllegalArgumentException(name);         Object v;  // Store the converted string value here.         Class type = p.getPropertyType( );                  // Convert common types in well-known ways         if (type == String.class) v = value;            else if (type == boolean.class) v = Boolean.valueOf(value);         else if (type == byte.class) v = Byte.valueOf(value);         else if (type == char.class) v = new Character(value.charAt(0));         else if (type == short.class) v = Short.valueOf(value);         else if (type == int.class) v = Integer.valueOf(value);         else if (type == long.class) v = Long.valueOf(value);         else if (type == float.class) v = Float.valueOf(value);         else if (type == double.class) v = Double.valueOf(value);         else if (type == Color.class) v = Color.decode(value);         else if (type == Font.class) v = Font.decode(value);         else {             // Try to find a property editor for unknown types             PropertyEditor editor = PropertyEditorManager.findEditor(type);             if (editor != null) {                 editor.setAsText(value);                 v = editor.getValue( );             }             // Otherwise, give up.             else throw new UnsupportedOperationException("Can't set " +                                                          "properties of type "+                                                          type.getName( ));         }         // Now get the Method object for the property setter method and         // invoke it on the bean object, passing the converted value.         Method setter = p.getWriteMethod( );           setter.invoke(bean, new Object[  ] { v });     }     // Return a component that allows the user to edit the property value.     // The component is live and changes the property value in real time;     // there is no need to call setPropertyValue( ).     public Component getPropertyEditor(final String name)         throws IllegalAccessException, InvocationTargetException,                InstantiationException     {         // Get the descriptor for the named property; final for inner classes.         final PropertyDescriptor p = (PropertyDescriptor) properties.get(name);         if (p == null || isReadOnly(name))  // Make sure we can edit it.             throw new IllegalArgumentException(name);         // Find a PropertyEditor for the property         final PropertyEditor editor;  // final for inner class use         if (p.getPropertyEditorClass( ) != null) {               // If there is a custom editor for this property, instantiate one.             editor = (PropertyEditor)p.getPropertyEditorClass( ).newInstance( );         }         else {             // Otherwise, look up an editor based on the property type             Class type = p.getPropertyType( );             editor = PropertyEditorManager.findEditor(type);             // If there is no editor, give up             if (editor == null)                  throw new UnsupportedOperationException("Can't set " +                                                         "properties of type " +                                                         type.getName( ));         }         // Get the property accessor methods for this property so we can         // query the initial value and set the edited value         final Method getter = p.getReadMethod( );         final Method setter = p.getWriteMethod( );         // Use Java reflection to find the current property value. Then tell         // the property editor about it.         Object currentValue = getter.invoke(bean, NOARGS);         editor.setValue(currentValue);         // If the PropertyEditor has a custom editor, then we'll just return         // that custom editor component from this method. User changes to the         // component change the value in the PropertyEditor, which generates         // a PropertyChangeEvent. We register a listener so that these changes         // set the property on the bean as well.         if (editor.supportsCustomEditor( )) {             final Component editComponent = editor.getCustomEditor( );             // Note that we register the listener on the PropertyEditor, not             // on its custom editor Component.             editor.addPropertyChangeListener(new PropertyChangeListener( ) {                     public void propertyChange(PropertyChangeEvent e) {                         try {                             // Pass edited value to property setter                             Object editedValue = editor.getValue( );                             setter.invoke(bean, new Object[  ] { editedValue});                         }                         catch(Exception ex) {                             JOptionPane.showMessageDialog(editComponent,                                                   ex, ex.getClass( ).getName( ),                                                   JOptionPane.ERROR_MESSAGE);                         }                     }                 });             return editComponent;         }         // Otherwise, if the PropertyEditor is for an enumerated type based         // on a fixed list of possible values, then return a JComboBox         // component that allows the user to select one of the values.         final String[  ] tags = editor.getTags( );         if (tags != null) {             // Create the component             final JComboBox combobox = new JComboBox(tags);             // Use the current value of the property as the currently selected             // item in the combo box.             combobox.setSelectedItem(editor.getAsText( ));             // Add a listener to hook the combo box up to the property. When             // the user selects an item, set the property value.             combobox.addItemListener(new ItemListener( ) {                     public void itemStateChanged(ItemEvent e) {                         // Ignore deselect events                         if (e.getStateChange( ) == ItemEvent.DESELECTED) return;                         try {                             // Get the user's selected string from combo box                             String selectedTag =                                 (String)combobox.getSelectedItem( );                             // Tell the editor about this string value                             editor.setAsText(selectedTag);                             // Ask the editor to convert to the property type                             Object editedValue = editor.getValue( );                             // Pass this value to the property setter method                             setter.invoke(bean, new Object[  ] { editedValue });                         }                         catch(Exception ex) {                             JOptionPane.showMessageDialog(combobox,                                                   ex, ex.getClass( ).getName( ),                                                   JOptionPane.ERROR_MESSAGE);                         }                     }                 });             return combobox;         }         // Otherwise, property type is not enumerated, and we use a JTextField         // to allow the user to enter arbitrary text for conversion by the         // setAsText( ) method of the PropertyEditor         final JTextField textfield = new JTextField( );         // Display the current value of the property in the field         textfield.setText(editor.getAsText( ));         // Hook the JTextField up to the PropertyEditor.         textfield.addActionListener(new ActionListener( ) {                 // This is called when the user strikes the Enter key                 public void actionPerformed(ActionEvent e) {                     try {                         // Get the user's input from the text field                         String newText = textfield.getText( );                         // Tell the editor about it                         editor.setAsText(newText);                         // Ask the editor to convert to the property type                         Object editedValue = editor.getValue( );                         // Pass this value to the property setter method                         setter.invoke(bean, new Object[  ] { editedValue });                     }                     catch(Exception ex) {                         JOptionPane.showMessageDialog(textfield,                                               ex, ex.getClass( ).getName( ),                                               JOptionPane.ERROR_MESSAGE);                     }                 }             });         return textfield;     } }


Java Examples in a Nutshell
Java Examples in a Nutshell, 3rd Edition
ISBN: 0596006209
EAN: 2147483647
Year: 2003
Pages: 285

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net