Author: kylem Date: Thu Dec 2 18:54:09 2004 New Revision: 109621 URL: http://svn.apache.org/viewcvs?view=rev&rev=109621 Log: Added support for bound and constrained properties in controls. You can now annotate any propert declaration method within an @PropertySet with @PropertyInfo(bound=true,constrained=true) to signal that PropertyChangeListener and/or VetoableChangeListener registration methods should be exposed. PropertyChangeEvents will be delivered for these bound and/or constrained properties as described in section 7.4 of the JavaBeans spec when property setters are called. Still remaining to be done is a way to expose bound/constrained property change events to an associated ControlImplementation class, but this checkin fully enables external listeners for PropertyChangeEvents. Included is a new checkin test case that validates the new functionality.
Added: incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEvents.java incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEventsImpl.jcs incubator/beehive/trunk/controls/test/src/units/org/apache/beehive/controls/test/java/property/PropEvents.java Modified: incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm Modified: incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java&r1=109620&p2=incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java (original) +++ incubator/beehive/trunk/controls/src/api/org/apache/beehive/controls/api/bean/ControlBean.java Thu Dec 2 18:54:09 2004 @@ -29,6 +29,8 @@ * interface to provide a way to get the <code>BeanContext</code> directly associated * with the Java Control. The <code>getBeanContext()</code> API on the interface will * return the parent (containing) context. + * + * @see java.beans.beancontext.BeanContextProxy */ public interface ControlBean extends BeanContextProxy, java.io.Serializable { Modified: incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java&r1=109620&p2=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java (original) +++ incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/bean/ControlBean.java Thu Dec 2 18:54:09 2004 @@ -19,6 +19,10 @@ import java.beans.beancontext.BeanContext; import java.beans.beancontext.BeanContextServices; +import java.beans.PropertyChangeSupport; +import java.beans.PropertyChangeListener; +import java.beans.VetoableChangeSupport; +import java.beans.VetoableChangeListener; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.AnnotatedElement; @@ -527,9 +531,6 @@ */ protected void setControlProperty(PropertyKey key, Object o) { - // - // TODO: Property change notification and veto handling go here - // _properties.setProperty(key, o); } @@ -561,32 +562,15 @@ return method.invoke(eventNotifier, args); } - // - // These can all be removed once auto-boxing can be assumed - // - protected void setControlProperty(PropertyKey key, int i) - { setControlProperty(key, new Integer(i)); } - - protected void setControlProperty(PropertyKey key, short s) - { setControlProperty(key, new Short(s)); } - - protected void setControlProperty(PropertyKey key, long l) - { setControlProperty(key, new Long(l)); } - - protected void setControlProperty(PropertyKey key, byte b) - { setControlProperty(key, new Byte(b)); } - - protected void setControlProperty(PropertyKey key, char c) - { setControlProperty(key, new Character(c)); } - - protected void setControlProperty(PropertyKey key, float f) - { setControlProperty(key, new Float(f)); } - - protected void setControlProperty(PropertyKey key, double d) - { setControlProperty(key, new Double(d)); } - - protected void setControlProperty(PropertyKey key, boolean b) - { setControlProperty(key, new Boolean(b)); } + /** + * Returns a property on the ControlBean instance. This version does not coerce + * an annotation type property from a PropertyMap to a proxy instance of the + * type. + */ + protected Object getRawControlProperty(PropertyKey key) + { + return _properties.getProperty(key); + } /** * Returns a property on the ControlBean instance. All generated property getter methods @@ -594,7 +578,7 @@ */ protected Object getControlProperty(PropertyKey key) { - Object value = _properties.getProperty(key); + Object value = getRawControlProperty(key); // If the held value is a PropertyMap, then wrap it in an annotation proxy of // the expected type. @@ -643,6 +627,67 @@ } /** + * This protected version is only available to concrete subclasses that expose bound + * property support. This method is synchronized to enable lazy instantiation, in + * the belief that is a bigger win to avoid allocating when there are no listeners + * than it is to introduce synchronization overhead on access. + */ + synchronized protected PropertyChangeSupport getPropertyChangeSupport() + { + if (_changeSupport == null) + _changeSupport = new PropertyChangeSupport(this); + + return _changeSupport; + } + + /** + * Delivers a PropertyChangeEvent to any registered PropertyChangeListeners associated + * with the property referenced by the specified key. + * + * This method *should not* be synchronized, as the PropertyChangeSupport has its own + * built in synchronization mechanisms. + */ + protected void firePropertyChange(PropertyKey propertyKey, Object oldValue, Object newValue) + { + // No change support instance means no listeners + if (_changeSupport == null) + return; + + _changeSupport.firePropertyChange(propertyKey.getPropertyName(), oldValue, newValue); + } + + /** + * This protected version is only available to concrete subclasses that expose bound + * property support. This method is synchronized to enable lazy instantiation, in + * the belief that is a bigger win to avoid allocating when there are no listeners + * than it is to introduce synchronization overhead on access. + */ + synchronized protected VetoableChangeSupport getVetoableChangeSupport() + { + if (_vetoSupport == null) + _vetoSupport = new VetoableChangeSupport(this); + + return _vetoSupport; + } + + /** + * Delivers a PropertyChangeEvent to any registered VetoableChangeListeners associated + * with the property referenced by the specified key. + * + * This method *should not* be synchronized, as the VetoableChangeSupport has its own + * built in synchronization mechanisms. + */ + protected void fireVetoableChange(PropertyKey propertyKey, Object oldValue, Object newValue) + throws java.beans.PropertyVetoException + { + // No veto support instance means no listeners + if (_vetoSupport == null) + return; + + _vetoSupport.fireVetoableChange(propertyKey.getPropertyName(), oldValue, newValue); + } + + /** * Returns the parameter names for a method on the ControlBean. Actual mapping is done * by generated subclasses, so if we reach the base ControlBean implementation, then * no parameter names are available for the target method. @@ -719,6 +764,16 @@ * is threadsafe, then the value will be null. */ transient private Semaphore _invokeLock; + + /** + * This field manages PropertyChangeListeners (if supporting bound properties). + */ + private PropertyChangeSupport _changeSupport; + + /** + * This field manages VetoabbleChangeListeners (if supporting constrained properties) + */ + private VetoableChangeSupport _vetoSupport; /** END synchronized fields */ Modified: incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java&r1=109620&p2=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java (original) +++ incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptControlInterface.java Thu Dec 2 18:54:09 2004 @@ -246,13 +246,29 @@ new AptPropertySet(this, (AnnotationTypeDeclaration)innerDecl, _env)); } } + + // + // Detect the presence of locally declared bound or constrained properties + // + for (AptPropertySet propSet : propSets) + { + for (AptProperty prop : propSet.getProperties()) + { + if (prop.isBound()) + _hasBoundProperties = true; + + if (prop.isConstrained()) + _hasConstrainedProperties = true; + } + } + return propSets; } /** * Returns the list of PropertySets declared directly by this AptControlInterface */ - public ArrayList<AptPropertySet> getPropertySets() { return _propertySets; } + public Collection<AptPropertySet> getPropertySets() { return _propertySets; } /** * Returns the total number of properties for this control interface @@ -267,25 +283,59 @@ } /** - * Returns true if the control BeanInfo needs a customized set of PropertyDescriptors - * code generated or false if standard introspection via reflection is ok. + * Returns true if the interface has any bound properties associated with it. */ - public boolean needsCustomPropertyDescriptors() + public boolean hasBoundProperties() + { + if (_superClass != null && _superClass.hasBoundProperties()) + return true; + + return _hasBoundProperties; + } + + /** + * Returns true if this interface is the first interface in the inheritance hierarchy + * to declare support for bound properties. This is used to declared PropertyChangeListener + * registration methods for the bean once (and only once). + */ + public boolean addsBoundPropertySupport() { // - // The algorithm here is pretty simple.. if any individual property needs a customized - // descriptor, then you have to generate them all. Reflection-driven introspection is - // an all-or-nothing deal as implemented by java.beans.Introspector. + // If a super interface has already added support, then not added here // - for (AptPropertySet propertySet : _propertySets) - { - for (AptProperty property : propertySet.getProperties()) - { - if (property.needsCustomPropertyDescriptor()) - return true; - } - } - return false; + if (_superClass != null && _superClass.addsBoundPropertySupport()) + return false; + + return hasBoundProperties(); + } + + /** + * Returns true if any properties declared directly by this control interface are constrained + * properties. This <b>will not</b> reflect the attributes of properties declared on + * an interface from which this interface derives. + */ + public boolean hasConstrainedProperties() + { + if (_superClass != null && _superClass.hasConstrainedProperties()) + return true; + + return _hasConstrainedProperties; + } + + /** + * Returns true if this interface is the first interface in the inheritance hierarchy + * to declare support for constrained properties. This is used to declared + * VetoableChangeListener registration methods for the bean once (and only once). + */ + public boolean addsConstrainedPropertySupport() + { + // + // If a super interface has already added support, then not added here + // + if (_superClass != null && _superClass.addsConstrainedPropertySupport()) + return false; + + return hasConstrainedProperties(); } /** @@ -609,6 +659,8 @@ private AptControlInterface _superClass; AptMethodSet<AptOperation> _operations; ArrayList<AptPropertySet> _propertySets; + boolean _hasBoundProperties; + boolean _hasConstrainedProperties;; ArrayList<AptEventSet> _eventSets; ControlBean _bean; FeatureInfo _featureInfo; Modified: incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java&r1=109620&p2=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java (original) +++ incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/AptProperty.java Thu Dec 2 18:54:09 2004 @@ -71,26 +71,29 @@ * Returns the base property name. The associated accessor methods will have the * form set{name} and get{name} */ - public String getName() + public String getAccessorName() { StringBuffer sb = new StringBuffer(); sb.append(_propertySet.getPrefix()); - String memberName = getMemberName(); - sb.append(Character.toUpperCase(memberName.charAt(0))); - if (memberName.length() > 0) - sb.append(memberName.substring(1)); + String name = getName(); + sb.append(Character.toUpperCase(name.charAt(0))); + if (name.length() > 0) + sb.append(name.substring(1)); return sb.toString(); } /** - * Returns the member name associated with this Property in the PropertySet + * Returns the name associated with this Property in the PropertySet */ - public String getMemberName() + public String getName() { if ( _propDecl == null ) return ""; + // + // Use the member name of the property method in the property set + // return _propDecl.getSimpleName(); } @@ -99,15 +102,7 @@ */ public String getKeyName() { - return getName() + "Key"; - } - - /** - * Returns true if the property needs a custom-generated PropertyDescriptor, false otherwise - */ - public boolean needsCustomPropertyDescriptor() - { - return getPropertyInfo() != null || getFeatureInfo() != null; + return getAccessorName() + "Key"; } /** @@ -144,6 +139,41 @@ } /** + * Returns any FeatureInfo associated with the property (or null if none) + */ + public FeatureInfo getFeatureInfo() + { + if ( _propDecl == null ) + return null; + + return _propDecl.getAnnotation(FeatureInfo.class); + } + + /** + * Returns 'true' is the property is a bound property that will support registration of + * a PropertyChangeListener for change notifications. + */ + public boolean isBound() + { + // + // Constrained properties are implicitly bound. Refer to section 7.4.3 of the JavaBeans + // spec for the rationale. + // + PropertyInfo propInfo = getPropertyInfo(); + return propInfo != null && (propInfo.bound() || propInfo.constrained()); + } + + /** + * Returns 'true' is the property is a constrained property that will support registration of + * a VetoableChangeListener for vetoable change notifications. + */ + public boolean isConstrained() + { + PropertyInfo propInfo = getPropertyInfo(); + return propInfo != null && propInfo.constrained(); + } + + /** * Returns the class name of the property editor class, or null */ public String getEditorClass() @@ -183,17 +213,6 @@ } } return null; - } - - /** - * Returns any FeatureInfo associated with the property (or null if none) - */ - public FeatureInfo getFeatureInfo() - { - if ( _propDecl == null ) - return null; - - return _propDecl.getAnnotation(FeatureInfo.class); } AnnotationTypeElementDeclaration _propDecl; Modified: incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm&r1=109620&p2=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm (original) +++ incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBean.vm Thu Dec 2 18:54:09 2004 @@ -167,24 +167,35 @@ ## This macro defines the property accessor methods for a bean property ## #macro (declarePropertyAccessors $property) - public static PropertyKey $property.keyName = new PropertyKey(${property.propertySet.className}.class, "$property.memberName"); + public static PropertyKey $property.keyName = new PropertyKey(${property.propertySet.className}.class, "$property.name"); #if ($property.isAnnotation()) - public void set${property.name}(PropertyMap map) - { - setControlProperty($property.keyName, map); - } + public void set${property.accessorName}(PropertyMap value) #else - public void set${property.name}($property.type prop) + public void set${property.accessorName}($property.type value) + #end + #if ($property.constrained) + throws java.beans.PropertyVetoException + #end { - setControlProperty($property.keyName, prop); + + #if ($property.bound || $property.constrained) + Object oldValue = getRawControlProperty($property.keyName); + #end + + #if ($property.constrained) + fireVetoableChange($property.keyName, oldValue, value); + #end + setControlProperty($property.keyName, value); + #if ($property.bound) + firePropertyChange($property.keyName, oldValue, value); + #end } - #end #if ($property.getType().equals("boolean")) - public $property.type is${property.name}() + public $property.type is${property.accessorName}() #else - public $property.type get${property.name}() + public $property.type get${property.accessorName}() #end { return (#toObject($property.type))getControlProperty($property.keyName); @@ -192,6 +203,86 @@ #end ## +## This macro declares the registration methods supporting a PropertyChangeListener +## +#macro (declareBoundPropertySupport) + /** + * Adds a new PropertyChangeListener for listening to changes on bound properties of this + * control. + */ + public void addPropertyChangeListener(PropertyChangeListener pcl) + { + getPropertyChangeSupport().addPropertyChangeListener(pcl); + } + + /** + * Adds a new PropertyChangeListener for listening to changes to a specific bound + * property of this control. + */ + public void addPropertyChangeListener(String propertyName, PropertyChangeListener pcl) + { + getPropertyChangeSupport().addPropertyChangeListener(propertyName, pcl); + } + + /** + * Removes a registered PropertyChangeListener listening to changes on bound properties of + * this control. + */ + public void removePropertyChangeListener(PropertyChangeListener pcl) + { + getPropertyChangeSupport().removePropertyChangeListener(pcl); + } + + /** + * Removes a registered PropertyChangeListener listening to changes on a specific bound + * property of this control. + */ + public void removePropertyChangeListener(String propertyName, PropertyChangeListener pcl) + { + getPropertyChangeSupport().removePropertyChangeListener(propertyName, pcl); + } +#end +## +## This macro declares the registration methods supporting a VetoableChangeListener +## +#macro (declareConstrainedPropertySupport) + /** + * Adds a new PropertyChangeListener for listening to changes on bound properties of this + * control. + */ + public void addVetoableChangeListener(VetoableChangeListener vcl) + { + getVetoableChangeSupport().addVetoableChangeListener(vcl); + } + + /** + * Adds a new PropertyChangeListener for listening to changes to a specific constrained + * property of this control. + */ + public void addVetoableChangeListener(String propertyName, VetoableChangeListener vcl) + { + getVetoableChangeSupport().addVetoableChangeListener(propertyName, vcl); + } + + /** + * Removes a registered PropertyChangeListener listening to changes on constrained properties + * of this control. + */ + public void removeVetoableChangeListener(VetoableChangeListener vcl) + { + getVetoableChangeSupport().removeVetoableChangeListener(vcl); + } + + /** + * Removes a registered PropertyChangeListener listening to changes to a specific constrained + * property of this control. + */ + public void removeVetoableChangeListener(String propertyName, VetoableChangeListener vcl) + { + getVetoableChangeSupport().removeVetoableChangeListener(propertyName, vcl); + } +#end +## ## This macro defines the implementation of an event routing method ## #macro (declareEventImpl $event) @@ -276,10 +367,13 @@ ## package $bean.package; +import java.beans.*; + import java.lang.reflect.Method; import java.lang.reflect.UndeclaredThrowableException; import java.util.HashMap; import java.util.Map; + import org.apache.beehive.controls.api.bean.Extensible; import org.apache.beehive.controls.api.context.ControlBeanContext; import org.apache.beehive.controls.api.properties.PropertyKey; @@ -310,6 +404,14 @@ #foreach ($property in $propertySet.properties) #declarePropertyAccessors ($property) #end + #end + + #if ($intf.addsBoundPropertySupport()) + #declareBoundPropertySupport() + #end + + #if ($intf.addsConstrainedPropertySupport()) + #declareConstrainedPropertySupport() #end #foreach ($eventSet in $intf.eventSets) Modified: incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm?view=diff&rev=109621&p1=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm&r1=109620&p2=incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm&r2=109621 ============================================================================== --- incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm (original) +++ incubator/beehive/trunk/controls/src/runtime/org/apache/beehive/controls/runtime/generator/ControlBeanInfo.vm Thu Dec 2 18:54:09 2004 @@ -88,7 +88,7 @@ return bd; } - #if ($intf.needsCustomPropertyDescriptors()) + #if ($intf.propertyCount != 0) public PropertyDescriptor [] getPropertyDescriptors() { PropertyDescriptor [] propDescriptors = new PropertyDescriptor[$intf.propertyCount]; @@ -98,7 +98,7 @@ PropertyDescriptor pd; #foreach ($propertySet in $intf.propertySets) #foreach ($property in $propertySet.properties) - pd = new PropertyDescriptor("$property.memberName", ${bean.className}.class); + pd = new PropertyDescriptor("$property.name", ${bean.className}.class); #if ($property.propertyInfo) pd.setBound($property.propertyInfo.bound()); pd.setConstrained($property.propertyInfo.constrained()); @@ -107,7 +107,7 @@ #end #end #if ($property.featureInfo) - #initFeatureDescriptor("pd" $property.featureInfo $property.memberName) + #initFeatureDescriptor("pd" $property.featureInfo $property.name) #end propDescriptors[i++] = pd; #end Added: incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEvents.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEvents.java?view=auto&rev=109621 ============================================================================== --- (empty file) +++ incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEvents.java Thu Dec 2 18:54:09 2004 @@ -0,0 +1,71 @@ +package org.apache.beehive.controls.test.controls.property; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.beans.PropertyChangeListener; +import java.beans.VetoableChangeListener; + +import org.apache.beehive.controls.api.bean.ControlInterface; +import org.apache.beehive.controls.api.packaging.PropertyInfo; +import org.apache.beehive.controls.api.properties.PropertySet; +import org.apache.beehive.controls.api.events.EventSet; + +/** + * A simple control that can be used for property testing of bound and constrained + * property event behavior. + */ [EMAIL PROTECTED] +public interface PropEvents +{ + // + // Declare a set of bound properties. These should deliver PropertyChange events + // if modified. + // + @PropertySet + @Retention(RetentionPolicy.RUNTIME) + @Target( {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD} ) + public @interface BoundProps + { + @PropertyInfo(bound=true) + public int boundInt() default 0; + } + + // + // Declare a set of bound properties. These should deliver PropertyChange and + // VetoableChange events if modified. + // + @PropertySet + @Retention(RetentionPolicy.RUNTIME) + @Target( {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD} ) + public @interface ConstrainedProps + { + @PropertyInfo(constrained=true) + public int constrainedInt() default 0; + + } + + // + // Declared unbound and unconstrained events. These should deliver no events if + // modified. + // + @PropertySet + @Retention(RetentionPolicy.RUNTIME) + @Target( {ElementType.TYPE, ElementType.FIELD, ElementType.METHOD} ) + public @interface BasicProps + { + public int basicInt() default 0; + } + + // + // These EventSets are used as an external test point for events received by the + // implementation class + // + //@EventSet + //public interface PropertyChangeOnImpl extends PropertyChangeListener {} + + //@EventSet + //public interface VetoableChangeOnImpl extends VetoableChangeListener {} +} Added: incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEventsImpl.jcs Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEventsImpl.jcs?view=auto&rev=109621 ============================================================================== --- (empty file) +++ incubator/beehive/trunk/controls/test/src/controls/org/apache/beehive/controls/test/controls/property/PropEventsImpl.jcs Thu Dec 2 18:54:09 2004 @@ -0,0 +1,16 @@ +package org.apache.beehive.controls.test.controls.property; + +import org.apache.beehive.controls.api.bean.ControlImplementation; +import org.apache.beehive.controls.api.context.Context; +import org.apache.beehive.controls.api.context.ControlBeanContext; + [EMAIL PROTECTED] +public class PropEventsImpl implements PropEvents +{ + static final long serialVersionUID = 1L; + + @Context + ControlBeanContext context; + + // Does nothing (for now) +} Added: incubator/beehive/trunk/controls/test/src/units/org/apache/beehive/controls/test/java/property/PropEvents.java Url: http://svn.apache.org/viewcvs/incubator/beehive/trunk/controls/test/src/units/org/apache/beehive/controls/test/java/property/PropEvents.java?view=auto&rev=109621 ============================================================================== --- (empty file) +++ incubator/beehive/trunk/controls/test/src/units/org/apache/beehive/controls/test/java/property/PropEvents.java Thu Dec 2 18:54:09 2004 @@ -0,0 +1,293 @@ +package org.apache.beehive.controls.test.java.property; + +import java.beans.Beans; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.beans.PropertyVetoException; +import java.beans.VetoableChangeListener; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import junit.framework.Assert; +import junit.framework.TestCase; + +import org.apache.beehive.test.tools.mantis.annotations.tch.Freq; + +import org.apache.beehive.controls.api.bean.ControlBean; +import org.apache.beehive.controls.test.controls.property.PropEventsBean; + +/** + * This test case validates bound and constrained property behaviors, w.r.t. to the delivery + * of PropertyChange events for bound and constrained events + */ [EMAIL PROTECTED]("checkin") +public class PropEvents extends TestCase +{ + PropEventsBean eventBean; + + public PropEvents( String s ) { super( s ); } + + /** + * This base class hold a queue of property change events and makes them accessible + * for event validation. + */ + abstract static class QueueListener + { + QueueListener() + { + initEvents(); + } + + /** + * Resets/initializes the internal event queue + */ + public void initEvents() + { + eventQueue = new ArrayList<PropertyChangeEvent>(); + } + + /** + * Returns the collection of events + */ + public Collection<PropertyChangeEvent> getEvents() { return eventQueue; } + + ArrayList<PropertyChangeEvent> eventQueue; + } + + static class ChangeTestListener extends QueueListener + implements java.beans.PropertyChangeListener + { + /** + * Implementation of PropertyChangeListener.propertyChange(). Will enqueue + * the received event. + */ + public void propertyChange(PropertyChangeEvent pce) + { + eventQueue.add(pce); + } + } + + static class VetoableTestListener extends QueueListener + implements java.beans.VetoableChangeListener + { + /** + * Implementation of PropertyChangeListener.propertyChange(). Will enqueue + * the received event. + */ + public void vetoableChange(PropertyChangeEvent pce) throws PropertyVetoException + { + eventQueue.add(pce); + + // Veto attempts to set even values + if ((((Integer)pce.getNewValue()).intValue() & 1) == 0) + throw new PropertyVetoException("Sorry", pce); + } + } + + + public void setUp() throws Exception + { + eventBean = (PropEventsBean) + Beans.instantiate(Thread.currentThread().getContextClassLoader(), + "org.apache.beehive.controls.test.controls.property.PropEventsBean"); + } + + /** + * Basic test: setting/reading property values by a control client + */ + public void testPropertyChange() throws Exception + { + // Set the test property to a well-defined initial value + eventBean.setBoundInt(0); + + // Create a new test listener and register it on the test bean + ChangeTestListener ctl = new ChangeTestListener(); + eventBean.addPropertyChangeListener(ctl); + + // Call the bound property setter on the bean 100 times + for (int i = 1; i < 100; i++) + eventBean.setBoundInt(i); + + // Now validate the received properties + int i = 0; + for (PropertyChangeEvent pce : ctl.getEvents()) + { + if (pce.getSource() != eventBean) + fail("Invalid source value in PropertyChangeEvent"); + + if (!pce.getPropertyName().equals("boundInt")) + fail("Invalid property name: " + pce.getPropertyName()); + + if (!pce.getOldValue().equals(new Integer(i))) + fail("Unexpected old value: " + pce.getOldValue() + ", expected: " + i); + + if (!pce.getNewValue().equals(new Integer(i+1))) + fail("Unexpected new value: " + pce.getNewValue() + ", expected: " + (i+1)); + + i++; + } + if (i != 99) + fail("Received less events than expected: " + i); + + // Reset the event queue + ctl.initEvents(); + + // Change an unbound property and verify that no property change event was delivered + eventBean.setBasicInt(0); + if (ctl.getEvents().size() != 0) + fail("Unexpected event delivered on unbound property change"); + + // Remove the event listener, change a bound property, and verify no event is delivered + eventBean.removePropertyChangeListener(ctl); + eventBean.setBoundInt(0); + if (ctl.getEvents().size() != 0) + fail("Unexpected event delivered after listener removed"); + } + + /** + * Basic test: setting/reading property values by a control client + */ + public void testVetoChange() throws Exception + { + // Set the test property to a well-defined initial value + eventBean.setConstrainedInt(0); + + // Create a new test listener and register it on the test bean + VetoableTestListener vtl = new VetoableTestListener(); + eventBean.addVetoableChangeListener(vtl); + + // Create a change listener and register it... this will be used to validate the + // property changes that were not vetoed + ChangeTestListener ctl = new ChangeTestListener(); + eventBean.addPropertyChangeListener(ctl); + + // Call the bound property setter on the bean 100 times + int expected = 0; + for (int i = 1; i < 100; i++) + { + boolean vetoed = false; + boolean expectVeto = (i & 1) == 0; + try + { + eventBean.setConstrainedInt(i); + } + catch (PropertyVetoException pve) + { + vetoed = true; + } + + if (vetoed) + { + if (!expectVeto) + fail("Unexpected PropertyVetoException: " + i); + } + else + { + if (expectVeto) + fail("Did not receive expected PropertVetoException: " + i); + + expected = i; + } + + // + // Read back the property and see if it was successfully changed or vetoed + if (eventBean.getConstrainedInt() != expected) + fail("Did not get expected value: " + expected + " for " + i); + } + + // Now validate the received properties, there should be one per vetoed property, + // two per allowed change + int i = 1; + expected = 0; + boolean expectVeto = false; + Iterator<PropertyChangeEvent> changeIter = ctl.getEvents().iterator(); + Iterator<PropertyChangeEvent> vetoIter = vtl.getEvents().iterator(); + while(vetoIter.hasNext()) + { + PropertyChangeEvent vce = vetoIter.next(); + + expectVeto = (i & 1) == 0; + + if (vce.getSource() != eventBean) + fail("Invalid source value in PropertyChangeEvent"); + + if (!vce.getPropertyName().equals("constrainedInt")) + fail("Invalid property name: " + vce.getPropertyName()); + + if (!vce.getOldValue().equals(new Integer(expected))) + fail("Unexpected old value: " + vce.getOldValue() + ", expected: " + expected); + + if (!vce.getNewValue().equals(new Integer(i))) + fail("Unexpected new value: " + vce.getNewValue() + ", expected: " + i); + + if (expectVeto) + { + // If a veto occurred, then there should be a 2nd vetoable change event that + // goes from the vetoed value back to the last valid value + if (!vetoIter.hasNext()) + fail("Did not find expected veto revert event"); + + // + // Pull the next event, which should revert from the attempted change back + // to the last accepted value + // + vce = vetoIter.next(); + if (vce.getSource() != eventBean) + fail("Invalid source value in PropertyChangeEvent"); + + if (!vce.getPropertyName().equals("constrainedInt")) + fail("Invalid property name: " + vce.getPropertyName()); + + if (!vce.getOldValue().equals(new Integer(i))) + fail("Unexpected old value: " + vce.getOldValue() + ", expected: " + i); + + if (!vce.getNewValue().equals(new Integer(expected))) + fail("Unexpected new value: " + vce.getNewValue() + ", expected: " + expected); + } + else + { + // Expected to succeed so look for the corresponding PropertyChange + if (!changeIter.hasNext()) + fail("Missing PropertyChange event"); + + PropertyChangeEvent pce = changeIter.next(); + if (pce.getSource() != eventBean) + fail("Invalid source value in PropertyChangeEvent"); + + if (!pce.getPropertyName().equals("constrainedInt")) + fail("Invalid property name: " + pce.getPropertyName()); + + if (!pce.getOldValue().equals(new Integer(expected))) + fail("Unexpected old value: " + pce.getOldValue() + ", expected: " + expected); + + if (!pce.getNewValue().equals(new Integer(i))) + fail("Unexpected new value: " + pce.getNewValue() + ", expected: " + i); + + expected = i; + } + + i++; + } + if (expected != 99) + fail("Received less events than expected: " + expected); + + // Reset the event queue + vtl.initEvents(); + ctl.initEvents(); + + // Change an unbound property and verify that no property change event was delivered + eventBean.setBasicInt(0); + if (vtl.getEvents().size() != 0 || ctl.getEvents().size() != 0) + fail("Unexpected event delivered on unbound property change"); + + // Remove the veto event listener but not the change listener, change a constrained + // property, and verify no veto event is delivered but a change event is delivered + eventBean.removeVetoableChangeListener(vtl); + eventBean.setConstrainedInt(1); + if (vtl.getEvents().size() != 0) + fail("Unexpected event delivered after listener removed"); + if (ctl.getEvents().size() != 1) + fail("Change event not delivered after listener removed"); + } +}
