/* * $Id: IntrospectionBasedNodeUpdater.java,v 1.3 2006/09/25 08:52:36 acaproni Exp $ * * $Date: 2006/09/25 08:52:36 $ * $Revision: 1.3 $ * $Author: acaproni $ * * Copyright CERN, All Rights Reserved. */ package cern.gp.beans.impl; import java.beans.BeanInfo; import java.beans.EventSetDescriptor; import java.beans.IntrospectionException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.openide.util.Utilities; import cern.gp.beans.BeanTagger; import cern.gp.beans.BeanUtils; import cern.gp.beans.GPBean; import cern.gp.beans.PropertyInfo; import cern.gp.util.GPManager; import java.beans.PropertyDescriptor; /** * <i><font size="-1" color="#FF0000">**For internal use only** </font></i> * Provides support for implementing a <code>NodeUpdater</code> based on introspection. * <p> * For beans that do not implement any interface we use the JavaBeans specification for doing the introspection of the * properties we are interested in. Those properties should have at least an associated getter. The list of properties * we look for is defined by the <code>GPBean</code> interface. * </p> * <p> This NodeUpdater also supports the <code>BeanInfo</code> tagging done through the <code>BeanTagger</code> class. * Tagging a <code>BeanInfo</code> allows to add information that is not directly supported by the * <code>BeanInfo</code>. Three tags are recognized by <code>BeanNode</code> : * <ul> * <li><code>BeanTagger.getActions()</code> to get the actions for this node. By default * no action is added.</li> * <li><code>BeanTagger.getDefaultAction()</code> to get the default action for this node. * By default the Property action is used.</li> * <li><code>BeanTagger.isCacheable()</code> to get whether the value of the properties of * the bean can be cached or not.</li> * </ul> * </p> * * @version $Revision: 1.3 $ $Date: 2006/09/25 08:52:36 $ * @author Lionel Mestre * */ public abstract class IntrospectionBasedNodeUpdater implements NodeUpdater { // // -- STATIC VARIABLES ----------------------------------------------- // private static final InternalPropertyDescriptor NAME_DESC = new InternalPropertyDescriptor(GPBean.NAME_PROPERTY_NAME, "getName", String.class); private static final InternalPropertyDescriptor DISPLAYNAME_DESC = new InternalPropertyDescriptor(GPBean.DISPLAYNAME_PROPERTY_NAME, "getDisplayName", String.class); private static final InternalPropertyDescriptor SHORTDESCRIPTION_DESC = new InternalPropertyDescriptor(GPBean.SHORTDESCRIPTION_PROPERTY_NAME, "getShortDescription", String.class); private static final InternalPropertyDescriptor NODE_ICON_DESC = new InternalPropertyDescriptor(GPBean.NODE_ICON_PROPERTY_NAME, "getNodeIcon", java.awt.Image.class); private static final InternalPropertyDescriptor NODE_DEFAULT_ACTION_DESC = new InternalPropertyDescriptor(GPBean.NODE_DEFAULT_ACTION_PROPERTY_NAME, "getNodeDefaultAction", String.class); private static final InternalPropertyDescriptor NODE_ACTIONS_DESC = new InternalPropertyDescriptor(GPBean.NODE_ACTIONS_PROPERTY_NAME, "getNodeActions", String[].class); private static final InternalPropertyDescriptor NODE_PROPERTIES_CACHEABLE_DESC = new InternalPropertyDescriptor( GPBean.NODE_PROPERTIES_CACHEABLE_PROPERTY_NAME, "getNodePropertiesCacheable", Boolean.class); private static final InternalPropertyDescriptor PROPERTY_INFO_DESC = new InternalPropertyDescriptor(GPBean.PROPERTY_INFO_PROPERTY_NAME, "getPropertyInfo", PropertyInfo[].class); private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; // // -- MEMBER VARIABLES ----------------------------------------------- // private Method _nameGetter; private Method _displayNameGetter; private Method _shortDescriptionGetter; private Method _nodeIconGetter; private Method _nodeDefaultActionGetter; private Method _nodeActionsGetter; private Method _nodePropertiesCacheableGetter; private Method _propertyInfoGetter; private boolean _shouldHideName; private boolean _shouldHideDisplayName; private boolean _shouldHideShortDescription; /** remove PropertyChangeListener method */ private Method _removePropertyChangeListenerMethod; /** listener for properties */ private PropertyChangeListener _propertyChangeListener; /** bean */ private Object _bean; /** bean info */ private BeanInfo _beanInfo; private boolean _hasRegisteredListener; // // -- CONSTRUCTORS ----------------------------------------------- // /** * Creates a new <code>GuiUpdaterIntrospector</code> */ protected IntrospectionBasedNodeUpdater(Object bean) throws IntrospectionException { Class targetClass = BeanUtils.findTargetClass(bean); initialize(bean, Utilities.getBeanInfo(targetClass), targetClass); } /** * Creates a new <code>GuiUpdaterIntrospector</code> */ protected IntrospectionBasedNodeUpdater(Object bean, BeanInfo beanInfo) throws IntrospectionException { this(bean, beanInfo, beanInfo.getClass()); } /** * Creates a new <code>GuiUpdaterIntrospector</code> */ protected IntrospectionBasedNodeUpdater(Object bean, BeanInfo beanInfo, Class targetClass) throws IntrospectionException { initialize(bean, beanInfo, targetClass); } private final void initialize(Object bean, BeanInfo beanInfo, Class targetClass) { this._bean = bean; this._beanInfo = beanInfo; _nameGetter = findGetterMethod(targetClass, NAME_DESC); _shouldHideName = _nameGetter == null; _displayNameGetter = findGetterMethod(targetClass, DISPLAYNAME_DESC); if (_displayNameGetter == null) { // if displayNameGetter does not exist we want to use the nameGetter // if it exist to prevent the display of a static string comming from BeanInfo _displayNameGetter = _nameGetter; _shouldHideDisplayName = true; } else { // if it exists we check that is does not return a null value. If it does return a null value we use // the getName instead if (getStringFromGetterMethod(_displayNameGetter) == null) { _displayNameGetter = _nameGetter; _shouldHideDisplayName = true; } } _shortDescriptionGetter = findGetterMethod(targetClass, SHORTDESCRIPTION_DESC); _shouldHideShortDescription = _shortDescriptionGetter == null || getStringFromGetterMethod(_shortDescriptionGetter) == null; _nodeIconGetter = findGetterMethod(targetClass, NODE_ICON_DESC); _nodeDefaultActionGetter = findGetterMethod(targetClass, NODE_DEFAULT_ACTION_DESC); _nodeActionsGetter = findGetterMethod(targetClass, NODE_ACTIONS_DESC); _nodePropertiesCacheableGetter = findGetterMethod(targetClass, NODE_PROPERTIES_CACHEABLE_DESC); _propertyInfoGetter = findGetterMethod(targetClass, PROPERTY_INFO_DESC); findPropertyChangeListenerMethods(); } // // -- PUBLIC STATIC METHODS ----------------------------------------------- // /** * Returns whether or not the property having the given <code>PropertyDescriptor</code> is a property * supported by this introspector that should automatically be hidden. * In the case the property is supported and should not be hidden, the <code>PropertyDescriptor</code> will * be updated with the display name of the property. * @param propertyName the property name to check * @return true if the property is automatically hidden false else */ public boolean isPropertyHidden(PropertyDescriptor propertyDescriptor) { String propertyName = propertyDescriptor.getName(); if (GPBean.NAME_PROPERTY_NAME.equals(propertyName)) { propertyDescriptor.setDisplayName(GPBean.NAME_PROPERTY_DISPLAY_NAME); return _shouldHideName; } if (GPBean.DISPLAYNAME_PROPERTY_NAME.equals(propertyName)) { propertyDescriptor.setDisplayName(GPBean.DISPLAYNAME_PROPERTY_DISPLAY_NAME); return _shouldHideDisplayName; } if (GPBean.SHORTDESCRIPTION_PROPERTY_NAME.equals(propertyName)) { propertyDescriptor.setDisplayName(GPBean.SHORTDESCRIPTION_PROPERTY_DISPLAY_NAME); return _shouldHideShortDescription; } if (GPBean.CLASS_PROPERTY_NAME.equals(propertyName)) return true; if (GPBean.NODE_ICON_PROPERTY_NAME.equals(propertyName)) return true; if (GPBean.NODE_DEFAULT_ACTION_PROPERTY_NAME.equals(propertyName)) return true; if (GPBean.NODE_ACTIONS_PROPERTY_NAME.equals(propertyName)) return true; if (GPBean.NODE_PROPERTIES_CACHEABLE_PROPERTY_NAME.equals(propertyName)) return true; if (GPBean.PROPERTY_INFO_PROPERTY_NAME.equals(propertyName)) return true; return false; } // // -- PUBLIC METHODS ----------------------------------------------- // // // -- implements NodeUpdater ----------------------------------------------- // public String getName() { if (_nameGetter != null) { String value = getStringFromGetterMethod(_nameGetter); if (value != null) return value; } return BeanUtils.generateUniqueBeanName(_bean); } public String getDisplayName() { if (_displayNameGetter != null) { String value = getStringFromGetterMethod(_displayNameGetter); if (value != null) return value; } return _beanInfo.getBeanDescriptor().getDisplayName(); } public String getShortDescription() { if (_shortDescriptionGetter != null) { String value = getStringFromGetterMethod(_shortDescriptionGetter); if (value != null) return value; } return _beanInfo.getBeanDescriptor().getShortDescription(); } public java.awt.Image getNodeIcon() { if (_nodeIconGetter != null) { java.awt.Image value = (java.awt.Image) getObjectFromGetterMethod(_nodeIconGetter); if (value != null) return value; } return _beanInfo.getIcon(BeanInfo.ICON_COLOR_16x16); } public String getNodeDefaultAction() { if (_nodeDefaultActionGetter != null) { String value = getStringFromGetterMethod(_nodeDefaultActionGetter); if (value != null) return value; } return BeanTagger.getDefaultAction(_beanInfo); } public String[] getNodeActions() { if (_nodeActionsGetter != null) { String[] value = (String[]) getObjectFromGetterMethod(_nodeActionsGetter); if (value != null) return value; } return BeanTagger.getActions(_beanInfo); } public Boolean getNodePropertiesCacheable() { if (_nodePropertiesCacheableGetter != null) { Boolean value = (Boolean) getObjectFromGetterMethod(_nodePropertiesCacheableGetter); if (value != null) return value; } return BeanTagger.isCacheable(_beanInfo.getBeanDescriptor()); } /* (non-Javadoc) * @see cern.gp.beans.GPBean#getPropertyInfo() */ public PropertyInfo[] getPropertyInfo() { if (_nodePropertiesCacheableGetter != null) { PropertyInfo[] value = (PropertyInfo[]) getObjectFromGetterMethod(_propertyInfoGetter); if (value != null) return value; } return null; } // // -- PROTECTED METHODS ----------------------------------------------- // protected abstract void firePropertyChange(String propertyName, Object oldValue, Object newValue); protected abstract void fireNameChange(String newName); protected abstract void fireDisplayNameChange(String newDisplayName); protected abstract void fireShortDescriptionChange(String newShortDescription); protected abstract void fireNodeDefaultActionChange(String newDefaultAction); protected abstract void fireNodeIconChange(java.awt.Image newIcon); protected final void removePropertyChangeListener() { if (_removePropertyChangeListenerMethod != null) { try { _removePropertyChangeListenerMethod.invoke(_bean, new Object[] { _propertyChangeListener }); } catch (Exception e) { // [PENDING] deal with the error to notify } } } protected final boolean hasRegisteredListener() { return _hasRegisteredListener; } // // -- PRIVATE METHODS ----------------------------------------------- // private void findPropertyChangeListenerMethods() { // add propertyChangeListener EventSetDescriptor[] eventSetDescriptors = _beanInfo.getEventSetDescriptors(); for (int i = 0; i < eventSetDescriptors.length; i++) { Method method = eventSetDescriptors[i].getAddListenerMethod(); if (method != null && method.getName().equals("addPropertyChangeListener") && Modifier.isPublic(method.getModifiers())) { // Possible for a public class to extend a package-private class, // where the private class defines addPropertyChangeListener, in which // case the introspector lists an inaccessible method in the event // set descriptor. In such a case, do not try to add a listener. try { _propertyChangeListener = new PropertyChangeListenerImpl(); method.invoke(_bean, new Object[] { _propertyChangeListener }); _hasRegisteredListener = true; } catch (Exception e) { // Warning, not info: likely to call e.g. getters or other things used // during startup of the bean, so it is not good to swallow errors here // (e.g. SharedClassObject.initialize throws RuntimeException -> it is // caught here and probably someone wants to know). GPManager.notify(GPManager.ERROR, e); // [PENDING] deal with the error to notify } _removePropertyChangeListenerMethod = eventSetDescriptors[i].getRemoveListenerMethod(); break; } } } private final String getStringFromGetterMethod(Method getterMethod) { try { return (String) getterMethod.invoke(_bean); } catch (Exception ex) { // [PENDING] notify of the problem return null; } } private final Object getObjectFromGetterMethod(Method getterMethod) { try { return getterMethod.invoke(_bean); } catch (Exception ex) { // [PENDING] notify of the problem return null; } } /** * Finds getter methods for the given getter name. The return type * of the getter must be of type String */ private static Method findGetterMethod(Class beanClass, InternalPropertyDescriptor propertyDesc) { try { Method getter = beanClass.getMethod(propertyDesc.getterName, EMPTY_CLASS_ARRAY); if (getter.getReturnType() != propertyDesc.type) return null; return getter; } catch (Exception e) { return null; } } // // -- INNER CLASSES ----------------------------------------------- // /** * Property change listener to update the properties of the node and * also the name of the node (sometimes) */ private final class PropertyChangeListenerImpl extends Object implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent e) { Object newValue = e.getNewValue(); String propertyName = e.getPropertyName(); firePropertyChange(propertyName, e.getOldValue(), newValue); if (propertyName == null) { fireNameChange(getName()); fireDisplayNameChange(getDisplayName()); fireShortDescriptionChange(getShortDescription()); fireNodeDefaultActionChange(getNodeDefaultAction()); fireNodeIconChange(getNodeIcon()); } else { if (NAME_PROPERTY_NAME.equals(propertyName)) { if (newValue == null || (!(newValue instanceof String))) { fireNameChange(getName()); } else { fireNameChange((String) newValue); } } else if (DISPLAYNAME_PROPERTY_NAME.equals(propertyName)) { if (newValue == null || (!(newValue instanceof String))) { fireDisplayNameChange(getDisplayName()); } else { fireDisplayNameChange((String) newValue); } } else if (SHORTDESCRIPTION_PROPERTY_NAME.equals(propertyName)) { if (newValue == null || (!(newValue instanceof String))) { fireShortDescriptionChange(getShortDescription()); } else { fireShortDescriptionChange((String) newValue); } } else if (NODE_DEFAULT_ACTION_PROPERTY_NAME.equals(propertyName)) { if (newValue == null || (!(newValue instanceof String))) { fireNodeDefaultActionChange(getNodeDefaultAction()); } else { fireNodeDefaultActionChange((String) newValue); } } else if (NODE_ICON_PROPERTY_NAME.equals(propertyName)) { if (newValue == null || (!(newValue instanceof String[]))) { fireNodeIconChange(getNodeIcon()); } else { fireNodeIconChange((java.awt.Image) newValue); } } } } } private final static class InternalPropertyDescriptor { String name; String getterName; Class type; InternalPropertyDescriptor(String name, String getterName, Class type) { this.name = name; this.getterName = getterName; this.type = type; } } }