/* * $Id: BeanSupport.java,v 1.2 2006/09/25 08:52:36 acaproni Exp $ * * $Date: 2006/09/25 08:52:36 $ * $Revision: 1.2 $ * $Author: acaproni $ * * Copyright CERN, All Rights Reserved. */ package cern.gp.beans; import java.awt.Image; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; /** * This support class should be subclassed by bean classes. It facilitates the writing of JavaBean classes that are * friendly with a node representing them by providing the extra information needed. The information provided by the * bean can be static or dynamic using PropertyChangeEvents. * <p> * This support class provides the necessary methods to register a listener and provides methods to send * PropertyChangeEvents. The bean can use the protected methods to fire events each time a property changes. * </p><p> * In order to be represented by a node, the bean can provide extra information that is not directly related to the * business of the bean but that is nevertheless interesting to control the node. The bean can provide that information * through its <code>BeanInfo</code> or through specific getters (defined in the interface <code>GPNode</code>). The * information provided can be static or dynamic. It is static if the value is just given once when the node is created, * it is dynamic if the bean allow the node to register itself as <code>PropertyChangeListener</code> and fire events * when those properties changes. * </p><p> * The list of properties recognized by the node and associated getters is given by the <code>GPBean</code> interface. * This support class implements all the getter methods in <code>GPNode</code> by returning a null value. You should * override each method you want to provide information for. For each property that can be dynamically updated there is * a protected method in this class you can use to fire an event to update the value. Here is the list : * </p> * <table border="1"> * <tr> * <td><b>property name</b></td> * <td><b>method name</b></td> * <td><b>method to fire related event</b></td> * </tr> * <tr> * <td>name</td> * <td><code>getName()</code></td> * <td><code>fireNamePropertyChange()</code></td> * </tr> * <tr> * <td>displayName</td> * <td><code>getDisplayName()</code></td> * <td><code>fireDisplayNamePropertyChange()</code></td> * </tr> * <tr> * <td>shortDescription</td> * <td><code>getShortDescription()</code></td> * <td><code>fireShortDescriptionPropertyChange()</code></td> * </tr> * <tr> * <td>nodeDefaultAction</td> * <td><code>getNodeDefaultAction()</code></td> * <td><code>fireNodeDefaultActionPropertyChange()</code></td> * </tr> * <tr> * <td>nodeIcon</td> * <td><code>getNodeIcon</code></td> * <td><code>fireNodeIconPropertyChange()</code></td> * </tr> * </table> * <p> * If the bean fire a given event, it should provide the getter matching the <code>PropertyChangeEvent</code> that * it fires. For instance if the bean call the protected method <code>fireNamePropertyChange</code> when a * name change occurs, it should have the matching getter <code>getName()</code> implemented and returning a non null * value. * </p><p> * When a given getter is not available in this bean, the corresponding information will be looked-up in the * <code>BeanInfo</code>. It will only be static, because the BeanInfo provides static information common to all beans. * There is only one BeanInfo for a given class of beans and therefore the information founds inside the BeanInfo is * shared by all beans of that class. If the <code>BeanInfo</code> does not contain the information, a standard default * value will be applied when possible. * </p> * * @version $Revision: 1.2 $ $Date: 2006/09/25 08:52:36 $ * @author Lionel Mestre */ public class BeanSupport implements GPBean { private PropertyChangeSupport propertyChangeSupport; // // -- CONSTRUCTORS ----------------------------------------------- // protected BeanSupport() { } // // -- PUBLIC METHODS ----------------------------------------------- // /** * Add a PropertyChangeListener to the listener list. The listener is registered for all properties. * @param listener The PropertyChangeListener to be added */ public void addPropertyChangeListener(PropertyChangeListener listener) { checkPropertyChangeSupport(); propertyChangeSupport.addPropertyChangeListener(listener); } /** * Remove a PropertyChangeListener from the listener list. This removes a PropertyChangeListener * that was registered for all properties. * @param listener The PropertyChangeListener to be removed */ public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { checkPropertyChangeSupport(); propertyChangeSupport.addPropertyChangeListener(propertyName, listener); } /** * Add a PropertyChangeListener for a specific property. The listener will be invoked only * when a call on firePropertyChange names that specific property. * @param propertyName The name of the property to listen on. * @param listener The PropertyChangeListener to be added */ public void removePropertyChangeListener(PropertyChangeListener listener) { if (propertyChangeSupport == null) return; propertyChangeSupport.removePropertyChangeListener(listener); } /** * Remove a PropertyChangeListener for a specific property. * @param propertyName The name of the property that was listened on. * @param listener The PropertyChangeListener to be removed */ public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { if (propertyChangeSupport == null) return; propertyChangeSupport.removePropertyChangeListener(propertyName, listener); } // // -- implements GPBean ----------------------------------------------- // /* (non-Javadoc) * @see cern.gp.beans.GPBean#getDisplayName() */ public String getDisplayName() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getName() */ public String getName() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getNodeActions() */ public String[] getNodeActions() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getNodeDefaultAction() */ public String getNodeDefaultAction() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getNodeIcon() */ public Image getNodeIcon() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getNodePropertiesCacheable() */ public Boolean getNodePropertiesCacheable() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getShortDescription() */ public String getShortDescription() { return null; } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getPropertyInfo() */ public PropertyInfo[] getPropertyInfo() { return null; } // // -- PROTECTED METHODS ----------------------------------------------- // /** * Reports a bound property update to any registered listeners. * No event is fired if old and new are equal and non-null. * * You pass null for all 3 parameters, to tell the listeners to read <em>all</em> * properties again, using the accessor methods. This is useful if you want to * force all properties of this bean to be refreshed in the GUI (e.g. refresh the * whole property sheet or a row in a ListTable at once). * * @param propertyName The programmatic name of the property that was changed. * @param oldValue The old value of the property. * @param newValue The new value of the property. */ protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) { if (propertyChangeSupport == null) return; propertyChangeSupport.firePropertyChange(propertyName, oldValue, newValue); } /** * Reports a Name property update to any registered listeners. * @param newName The new value of the name. */ protected void fireNamePropertyChange(String newName) { firePropertyChange(NAME_PROPERTY_NAME, null, newName); } /** * Reports a DisplayName property update to any registered listeners. * @param newDisplayName The new value of the DisplayName. */ protected void fireDisplayNamePropertyChange(String newDisplayName) { firePropertyChange(DISPLAYNAME_PROPERTY_NAME, null, newDisplayName); } /** * Reports a ShortDescription property update to any registered listeners. * @param newShortDescription The new value of the ShortDescription. */ protected void fireShortDescriptionPropertyChange(String newShortDescription) { firePropertyChange(SHORTDESCRIPTION_PROPERTY_NAME, null, newShortDescription); } /** * Reports a icon property update to any registered listeners. * @param newIcon The new value of the icon. * @see #getNodeIconFromPathname(String) */ protected void fireNodeIconPropertyChange(java.awt.Image newIcon) { firePropertyChange(NODE_ICON_PROPERTY_NAME, null, newIcon); } /** * Reports a default action property update to any registered listeners. * @param newDefaultAction The new value of the default action. */ protected void fireNodeDefaultActionPropertyChange(String newDefaultAction) { firePropertyChange(NODE_DEFAULT_ACTION_PROPERTY_NAME, null, newDefaultAction); } /** * Returns the icon image from the pathname of the icon. * <p> * The pathname should be relative to the package of this bean and be contained in the classpath. * For instance if this bean is in <code>cern.gp.beans</code> and the icon is stored in * <code>cern/gp/beans/images/MyIcon.gif</code> the pathname to give would be * <code>images/MyIcon.gif</code>. * </p> * @param newIconPathname The new value of the Icon pathname. * @return the image defined by the icon pathname or null if it cannot be found or loaded. */ protected java.awt.Image getNodeIconFromPathname(String newIconPathname) { return BeanUtils.loadImage(newIconPathname, this.getClass()); } /** * Merges the two <code>String</code> arrays into one. This method is useful when a class inherits from another that * already has a method <code>getNodeActions</code> that returned an array of <code>String</code>. If the subclass * wants to return a <code>String</code> array while taking into account the one from the parent class it can use this * method. For instance : * <pre> * String[] parentArray= super.getNodeActions(); * String[] myArray = new String [] { .... }; * return mergePropertyInfo(parentPI, myPI); * </pre> * * @param actions1 the first array of String to merge * @param actions2 the second array of String to merge * @return String[] the array resulting of the merging of the two given array */ protected static String[] mergeNodeActions(String[] actions1, String[] actions2) { if (actions1 == null || actions1.length == 0) return actions2; if (actions2 == null || actions2.length == 0) return actions1; String[] result = new String[actions1.length + actions2.length]; System.arraycopy(actions1, 0, result, 0, actions1.length); System.arraycopy(actions2, 0, result, actions1.length, actions2.length); return result; } /** * Merges the two <code>PropertyInfo</code> arrays into one. This method is useful when a class inherits from another * that already has a method <code>getPropertyInfo</code> that returned an array of <code>PropertyInfo</code>. If the * subclass wants to return <code>PropertyInfo</code> while taking into account the one from the parent class it can * use this method. For instance : * <pre> * PropertyInfo[] parentPI = super.getPropertyInfo(); * PropertyInfo[] myPI = new PropertyInfo[] { .... }; * return mergePropertyInfo(parentPI, myPI); * </pre> * * @param info1 the first array of PropertyInfo to merge * @param info2 the second array of PropertyInfo to merge * @return PropertyInfo[] the array resulting of the merging of the two given array */ protected static PropertyInfo[] mergePropertyInfo(PropertyInfo[] info1, PropertyInfo[] info2) { if (info1 == null || info1.length == 0) return info2; if (info2 == null || info2.length == 0) return info1; PropertyInfo[] result = new PropertyInfo[info1.length + info2.length]; System.arraycopy(info1, 0, result, 0, info1.length); System.arraycopy(info2, 0, result, info1.length, info2.length); return result; } /** * Lazily creates the PropertyChangeSupport */ protected synchronized void checkPropertyChangeSupport() { if (propertyChangeSupport == null) { propertyChangeSupport = new PropertyChangeSupport(this); } } // // -- PRIVATE METHODS ----------------------------------------------- // }