diff options
author | Eric Blake <ebb9@byu.net> | 2002-04-01 03:00:06 +0000 |
---|---|---|
committer | Eric Blake <ebb9@byu.net> | 2002-04-01 03:00:06 +0000 |
commit | d5d80641eeb6c205f9a957e4345615d81323dda9 (patch) | |
tree | ba2aac61185b0c3fd7fa864baec3c08f5c2f9b38 | |
parent | fa2337f0086ea3a19b7da08d2a5066a690430ab9 (diff) | |
download | classpath-d5d80641eeb6c205f9a957e4345615d81323dda9.tar.gz |
2002-03-31 Eric Blake <ebb9@email.byu.edu>
* java/beans/ExceptionListener.java: New file.
* java/beans/Makefile.am (EXTRA_DIST): Add 3 new files.
* java/beans/PropertyChangeEvent.java: Update to 1.4.
* java/beans/PropertyChangeListener.java: Update to 1.4.
* java/beans/PropertyChangeListenerProxy.java: New file.
* java/beans/PropertyChangeSupport.java: Update to 1.4.
* java/beans/VetoableChangeListener.java: Update to 1.4.
* java/beans/VetoableChangeListenerProxy.java: New file.
* java/beans/VetoableChangeSupport.java: Update to 1.4.
-rw-r--r-- | ChangeLog | 12 | ||||
-rw-r--r-- | java/beans/ExceptionListener.java | 57 | ||||
-rw-r--r-- | java/beans/Makefile.am | 7 | ||||
-rw-r--r-- | java/beans/PropertyChangeEvent.java | 223 | ||||
-rw-r--r-- | java/beans/PropertyChangeListener.java | 39 | ||||
-rw-r--r-- | java/beans/PropertyChangeListenerProxy.java | 102 | ||||
-rw-r--r-- | java/beans/PropertyChangeSupport.java | 656 | ||||
-rw-r--r-- | java/beans/VetoableChangeListener.java | 65 | ||||
-rw-r--r-- | java/beans/VetoableChangeListenerProxy.java | 102 | ||||
-rw-r--r-- | java/beans/VetoableChangeSupport.java | 679 |
10 files changed, 1348 insertions, 594 deletions
@@ -1,3 +1,15 @@ +2002-03-31 Eric Blake <ebb9@email.byu.edu> + + * java/beans/ExceptionListener.java: New file. + * java/beans/Makefile.am (EXTRA_DIST): Add 3 new files. + * java/beans/PropertyChangeEvent.java: Update to 1.4. + * java/beans/PropertyChangeListener.java: Update to 1.4. + * java/beans/PropertyChangeListenerProxy.java: New file. + * java/beans/PropertyChangeSupport.java: Update to 1.4. + * java/beans/VetoableChangeListener.java: Update to 1.4. + * java/beans/VetoableChangeListenerProxy.java: New file. + * java/beans/VetoableChangeSupport.java: Update to 1.4. + 2002-03-30 Eric Blake <ebb9@email.byu.edu> * java/awt/Makefile.am (EXTRA_DIST): Add AWTKeyStroke.java. diff --git a/java/beans/ExceptionListener.java b/java/beans/ExceptionListener.java new file mode 100644 index 000000000..09f7d1a8a --- /dev/null +++ b/java/beans/ExceptionListener.java @@ -0,0 +1,57 @@ +/* ExceptionListener.java -- listen for recoverable internal exceptions + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.beans; + +/** + * This interface allows a class to monitor internal exceptions, to try to + * recover from them. + * + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.4 + * @status updated to 1.4 + */ +public interface ExceptionListener +{ + /** + * Fired after an exception occurs. + * + * @param e the trapped exception + */ + void exceptionThrown(Exception e); +} // interface ExceptionListener diff --git a/java/beans/Makefile.am b/java/beans/Makefile.am index 0b52827d9..68e4cffc6 100644 --- a/java/beans/Makefile.am +++ b/java/beans/Makefile.am @@ -10,6 +10,7 @@ Beans.java \ Customizer.java \ DesignMode.java \ EventSetDescriptor.java \ +ExceptionListener.java \ FeatureDescriptor.java \ IndexedPropertyDescriptor.java \ IntrospectionException.java \ @@ -18,16 +19,16 @@ MethodDescriptor.java \ ParameterDescriptor.java \ PropertyChangeEvent.java \ PropertyChangeListener.java \ +PropertyChangeListenerProxy.java \ PropertyChangeSupport.java \ PropertyDescriptor.java \ PropertyEditor.java \ PropertyEditorManager.java \ PropertyEditorSupport.java \ -Customizer.java \ PropertyVetoException.java \ SimpleBeanInfo.java \ TODO \ VetoableChangeListener.java \ +VetoableChangeListenerProxy.java \ VetoableChangeSupport.java \ -Visibility.java - +Visibility.java diff --git a/java/beans/PropertyChangeEvent.java b/java/beans/PropertyChangeEvent.java index d65ca6857..36eee4592 100644 --- a/java/beans/PropertyChangeEvent.java +++ b/java/beans/PropertyChangeEvent.java @@ -1,5 +1,5 @@ -/* java.beans.PropertyChangeEvent - Copyright (C) 1998, 2000 Free Software Foundation, Inc. +/* PropertyChangeEvent.java -- describes a change in a property + Copyright (C) 1998, 2000, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - + GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -38,87 +38,138 @@ exception statement from your version. */ package java.beans; +import java.util.EventObject; + /** - ** PropertyChangeEvents are fired in the PropertyChange - ** and VetoableChange event classes. They represent the - ** old and new values as well as the source Bean.<P> - ** - ** If the old or new value is a primitive type, it must - ** be wrapped in the appropriate wrapper type - ** (java.lang.Integer for int, etc., etc.).<P> - ** - ** If the old or new values are unknown (although why - ** that would be I do not know), they may be null.<P> - ** - ** Right now Sun put in a propagationId, reserved for - ** future use. Read the comments on the constructor - ** and on setPropagationId for more information. - ** - ** @author John Keiser - ** @since JDK1.1 - ** @version 1.1.0, 29 Jul 1998 - **/ - -public class PropertyChangeEvent extends java.util.EventObject { - String propertyName; - Object oldValue; - Object newValue; - Object propagationId; - - private static final long serialVersionUID = 7042693688939648123L; - - /** Create a new PropertyChangeEvent. Remember that if - ** you received a PropertyChangeEvent and are sending - ** a new one, you should also set the propagation ID - ** from the old PropertyChangeEvent. - ** @param source the Bean containing the property. - ** @param propertyName the property's name. - ** @param oldValue the old value of the property. - ** @param newValue the new value of the property. - **/ - public PropertyChangeEvent(Object source, String propertyName, Object oldVal, Object newVal) { - super(source); - this.propertyName = propertyName; - oldValue = oldVal; - newValue = newVal; - } - - /** Get the property name. - ** @return the property name. - **/ - public String getPropertyName() { - return propertyName; - } - - /** Get the property's old value. - ** @return the property's old value. - **/ - public Object getOldValue() { - return oldValue; - } - - /** Get the property's new value. - ** @return the property's new value. - **/ - public Object getNewValue() { - return newValue; - } - - /** Set the propagation ID. This is a way for the event - ** to be passed from hand to hand and retain a little - ** extra state. Right now it is unused, but it should - ** be propagated anyway so that future versions of - ** JavaBeans can use it, for God knows what. - ** @param propagationId the propagation ID. - **/ - public void setPropagationId(Object propagationId) { - this.propagationId = propagationId; - } - - /** Get the propagation ID. - ** @return the propagation ID. - **/ - public Object getPropagationId() { - return propagationId; - } -} + * PropertyChangeEvents are fired in the PropertyChange and VetoableChange + * event classes. They represent the old and new values as well as the + * source Bean. If the old or new value is a primitive type, it must be + * wrapped in the appropriate wrapper type (java.lang.Integer for int, etc., + * etc.). + * + * <p>If the old or new values are unknown (although why that would be I do + * not know), they may be null. Also, if the set of properties itself has + * changed, the name should be null, and the old and new values may also be + * null. Right now Sun put in a propagationId, reserved for future use. Read + * the comments on the constructor and on setPropagationId for more + * information. + * + * @author John Keiser + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.1 + * @status udpated to 1.4 + */ +public class PropertyChangeEvent extends EventObject +{ + /** + * Compatible with JDK 1.1+. + */ + private static final long serialVersionUID = 7042693688939648123L; + + /** + * The name of the property that changed, may be null. Package visible for + * use by PropertyChangeSupport. + * + * @serial the changed property name + */ + final String propertyName; + + /** + * The new value of the property, may be null. Package visible for use by + * PropertyChangeSupport. + * + * @serial the new property value + */ + final Object newValue; + + /** + * The old value of the property, may be null. Package visible for use by + * PropertyChangeSupport. + * + * @serial the old property value + */ + final Object oldValue; + + /** + * The propagation ID, reserved for future use. May be null. + * + * @see #getPropagationId() + * @serial the Propagation ID + */ + private Object propagationId; + + /** + * Create a new PropertyChangeEvent. Remember that if you received a + * PropertyChangeEvent and are sending a new one, you should also set the + * propagation ID from the old PropertyChangeEvent. + * + * @param source the Bean containing the property + * @param propertyName the property's name + * @param oldValue the old value of the property + * @param newValue the new value of the property + * @throws IllegalArgumentException if source is null + */ + public PropertyChangeEvent(Object source, String propertyName, + Object oldVal, Object newVal) + { + super(source); + this.propertyName = propertyName; + oldValue = oldVal; + newValue = newVal; + } + + /** + * Get the property name. May be null if multiple properties changed. + * + * @return the property name + */ + public String getPropertyName() + { + return propertyName; + } + + /** + * Get the property's new value. May be null if multiple properties changed. + * + * @return the property's new value + */ + public Object getNewValue() + { + return newValue; + } + + /** + * Get the property's old value. May be null if multiple properties changed. + * + * @return the property's old value + */ + public Object getOldValue() + { + return oldValue; + } + + /** + * Set the propagation ID. This is a way for the event to be passed from + * hand to hand and retain a little extra state. Right now it is unused, + * but it should be propagated anyway so that future versions of JavaBeans + * can use it, for God knows what. + * + * @param propagationId the propagation ID + * @see #getPropagationId() + */ + public void setPropagationId(Object propagationId) + { + this.propagationId = propagationId; + } + + /** + * Get the propagation ID. Right now, it is not used for anything. + * + * @return the propagation ID + * @see #setPropagationId(Object) + */ + public Object getPropagationId() + { + return propagationId; + } +} // class PropertyChangeEvent diff --git a/java/beans/PropertyChangeListener.java b/java/beans/PropertyChangeListener.java index c3ff93a4a..df5bd867d 100644 --- a/java/beans/PropertyChangeListener.java +++ b/java/beans/PropertyChangeListener.java @@ -1,5 +1,5 @@ -/* java.beans.PropertyChangeListener - Copyright (C) 1998, 2000 Free Software Foundation, Inc. +/* PropertyChangeListener.java -- listen for changes in a bound property + Copyright (C) 1998, 2000, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - + GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -41,22 +41,21 @@ package java.beans; import java.util.EventListener; /** - ** PropertyChangeListener allows a class to monitor - ** properties of a Bean for changes.<P> - ** - ** A propertyChange() event will only be fired - ** <EM>after</EM> the property has changed. - ** - ** @author John Keiser - ** @since JDK1.1 - ** @version 1.1.0, 29 Jul 1998 - ** @see java.beans.PropertyChangeSupport - **/ - + * PropertyChangeListener allows a class to monitor properties of a Bean for + * changes. A propertyChange() event will only be fired <em>after</em> the + * property has changed. + * + * @author John Keiser + * @see PropertyChangeSupport + * @since 1.1 + * @status updated to 1.4 + */ public interface PropertyChangeListener extends EventListener { - /** Fired after a Bean's property has changed. - ** @param e the change (containing the old and new values) - **/ - public abstract void propertyChange(PropertyChangeEvent e); -} + /** + * Fired after a Bean's property has changed. + * + * @param e the change (containing the old and new values) + */ + void propertyChange(PropertyChangeEvent e); +} // interface PropertyChangeListener diff --git a/java/beans/PropertyChangeListenerProxy.java b/java/beans/PropertyChangeListenerProxy.java new file mode 100644 index 000000000..6e707038d --- /dev/null +++ b/java/beans/PropertyChangeListenerProxy.java @@ -0,0 +1,102 @@ +/* PropertyChangeListenerProxy.java -- adds a name to a property listener + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.beans; + +import java.util.EventListenerProxy; + +/** + * This class provides an extension to <code>PropertyChangeListener</code> - + * associating a name with the listener. This can be used to filter the + * changes that one is interested in. + * + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.4 + * @status udpated to 1.4 + */ +public class PropertyChangeListenerProxy extends EventListenerProxy + implements PropertyChangeListener +{ + /** + * The name of the property to listen for. Package visible for use by + * PropertyChangeSupport. + */ + final String propertyName; + + /** + * Create a new proxy which filters property change events and only passes + * changes to the named property on to the delegate. A null propertyName + * or listener does not fail now, but may cause a NullPointerException down + * the road. + * + * @param propertyName the property's name to filter on + * @param listener the delegate listener + */ + public PropertyChangeListenerProxy(String propertyName, + PropertyChangeListener listener) + { + super(listener); + this.propertyName = propertyName; + } + + /** + * Forwards the event on to the delegate if the property name matches. + * + * @param event the event to pass on, if it meets the filter + * @throws NullPointerException if the delegate this was created with is null + */ + public void propertyChange(PropertyChangeEvent event) + { + // Note: Sun does not filter, under the assumption that since + // PropertyChangeSupport unwraps proxys, this method should never be + // called by normal use of listeners. + String name = event == null ? null : event.getPropertyName(); + if (name == null ? propertyName == null : name.equals(propertyName)) + ((PropertyChangeListener) getListener()).propertyChange(event); + } + + /** + * Gets the name of the property this proxy is filtering on. + * + * @return the property name + */ + public String getPropertyName() + { + return propertyName; + } +} // class PropertyChangeListenerProxy diff --git a/java/beans/PropertyChangeSupport.java b/java/beans/PropertyChangeSupport.java index 6fbc85f0a..862302735 100644 --- a/java/beans/PropertyChangeSupport.java +++ b/java/beans/PropertyChangeSupport.java @@ -1,5 +1,5 @@ -/* java.beans.PropertyChangeSupport - Copyright (C) 1998, 1999, 2000 Free Software Foundation, Inc. +/* PropertyChangeSupport.java -- support to manage property change listeners + Copyright (C) 1998, 1999, 2000, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - + GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -37,214 +37,452 @@ exception statement from your version. */ package java.beans; -import java.util.Hashtable; -import java.util.Vector; -import java.util.Enumeration; + +import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Vector; /** - ** PropertyChangeSupport makes it easy to fire property - ** change events and handle listeners. - ** - ** @author John Keiser - ** @since JDK1.1 - ** @version 1.2.0, 15 Mar 1999 - **/ - -public class PropertyChangeSupport implements java.io.Serializable { - transient Hashtable propertyListeners = new Hashtable(); - transient Vector listeners = new Vector(); - Hashtable children; - Object source; - int propertyChangeSupportSerializedDataVersion = 2; - private static final long serialVersionUID = 6401253773779951803L; - - /** - * Saves the state of the object to the stream. */ - private void writeObject(ObjectOutputStream stream) throws IOException { - children = propertyListeners.isEmpty() ? null : propertyListeners; - stream.defaultWriteObject(); - for (Enumeration e = listeners.elements(); e.hasMoreElements(); ) { - PropertyChangeListener l = (PropertyChangeListener)e.nextElement(); - if (l instanceof Serializable) - stream.writeObject(l); - } - stream.writeObject(null); - } - - /** - * Reads the object back from stream (deserialization). - */ - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - propertyListeners = (children == null) ? new Hashtable() : children; - PropertyChangeListener l; - while ((l = (PropertyChangeListener)stream.readObject()) != null) { - addPropertyChangeListener(l); - } - // FIXME: XXX: There is no spec for JDK 1.1 serialization - // so it is unclear what to do if the value of - // propertyChangeSupportSerializedDataVersion is 1. - } - - /** Create PropertyChangeSupport to work with a specific - ** source bean. - ** @param source the source bean to use. - **/ - public PropertyChangeSupport(Object source) { - this.source = source; - } - - /** Adds a PropertyChangeListener to the list of listeners. - ** All property change events will be sent to this listener. - ** <P> - ** - ** The listener add is not unique: that is, <em>n</em> adds with - ** the same listener will result in <em>n</em> events being sent - ** to that listener for every property change. - ** <P> - ** - ** Adding a null listener will cause undefined behavior. - ** - ** @param l the listener to add. - **/ - public void addPropertyChangeListener(PropertyChangeListener l) { - listeners.addElement(l); - } - - /** Adds a PropertyChangeListener listening on the specified property. - ** Events will be sent to the listener for that particular property. - ** <P> - ** - ** The listener add is not unique; that is, <em>n</em> adds on a - ** particular property for a particular listener will result in - ** <em>n</em> events being sent to that listener when that - ** property is changed. - ** <P> - ** - ** The effect is cumulative, too; if you are registered to listen - ** to receive events on all property changes, and then you - ** register on a particular property, you will receive change - ** events for that property twice. - ** <P> - ** - ** Adding a null listener will cause undefined behavior. - ** - ** @param propertyName the name of the property to listen on. - ** @param l the listener to add. - **/ - public void addPropertyChangeListener(String propertyName, PropertyChangeListener l) { - synchronized(propertyListeners) { - Vector v = (Vector)propertyListeners.get(propertyName); - try { - v.addElement(l); - } catch(NullPointerException e) { - /* If v is not found, create a new vector. */ - v = new Vector(); - v.addElement(l); - propertyListeners.put(propertyName, v); - } - } - } - - /** Removes a PropertyChangeListener from the list of listeners. - ** If any specific properties are being listened on, they must - ** be deregistered by themselves; this will only remove the - ** general listener to all properties. - ** <P> - ** - ** If <code>add()</code> has been called multiple times for a - ** particular listener, <code>remove()</code> will have to be - ** called the same number of times to deregister it. - ** - ** @param l the listener to remove. - **/ - public void removePropertyChangeListener(PropertyChangeListener l) { - listeners.removeElement(l); - } - - /** Removes a PropertyChangeListener from listening to a specific property. - ** <P> - ** - ** If <code>add()</code> has been called multiple times for a - ** particular listener on a property, <code>remove()</code> will - ** have to be called the same number of times to deregister it. - ** - ** @param propertyName the property to stop listening on. - ** @param l the listener to remove. - **/ - public void removePropertyChangeListener(String propertyName, PropertyChangeListener l) { - synchronized(propertyListeners) { - Vector v = (Vector)propertyListeners.get(propertyName); - try { - v.removeElement(l); - if(v.size() == 0) { - propertyListeners.remove(propertyName); - } - } catch(NullPointerException e) { - /* if v is not found, do nothing. */ - } - } - } - - /** Fire a PropertyChangeEvent to all the listeners. - ** - ** @param event the event to fire. - **/ - public void firePropertyChange(PropertyChangeEvent event) { - for(int i=0;i<listeners.size();i++) { - ((PropertyChangeListener)listeners.elementAt(i)).propertyChange(event); - } - Vector moreListeners = (Vector)propertyListeners.get(event.getPropertyName()); - if(moreListeners != null) { - for(int i=0;i<moreListeners.size();i++) { - ((PropertyChangeListener)moreListeners.elementAt(i)).propertyChange(event); - } - } - } - - /** Fire a PropertyChangeEvent containing the old and new values of the property to all the listeners. - ** - ** @param propertyName the name of the property that changed. - ** @param oldVal the old value. - ** @param newVal the new value. - **/ - public void firePropertyChange(String propertyName, Object oldVal, Object newVal) { - firePropertyChange(new PropertyChangeEvent(source,propertyName,oldVal,newVal)); - } - - /** Fire a PropertyChangeEvent containing the old and new values of the property to all the listeners. - ** - ** @param propertyName the name of the property that changed. - ** @param oldVal the old value. - ** @param newVal the new value. - **/ - public void firePropertyChange(String propertyName, boolean oldVal, boolean newVal) { - firePropertyChange(new PropertyChangeEvent(source, propertyName, new Boolean(oldVal), new Boolean(newVal))); - } - - /** Fire a PropertyChangeEvent containing the old and new values of the property to all the listeners. - ** - ** @param propertyName the name of the property that changed. - ** @param oldVal the old value. - ** @param newVal the new value. - **/ - public void firePropertyChange(String propertyName, int oldVal, int newVal) { - firePropertyChange(new PropertyChangeEvent(source, propertyName, new Integer(oldVal), new Integer(newVal))); - } - - /** Tell whether the specified property is being listened on or not. - ** This will only return <code>true</code> if there are listeners - ** on all properties or if there is a listener specifically on this - ** property. - ** - ** @param propertyName the property that may be listened on - ** @return whether the property is being listened on - **/ - public boolean hasListeners(String propertyName) { - return listeners.size() > 0 || propertyListeners.get(propertyName) != null; - } -} + * PropertyChangeSupport makes it easy to fire property change events and + * handle listeners. It allows chaining of listeners, as well as filtering + * by property name. In addition, it will serialize only those listeners + * which are serializable, ignoring the others without problem. This class + * is thread-safe. + * + * @author John Keiser + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.1 + * @status updated to 1.4 + */ +public class PropertyChangeSupport implements Serializable +{ + /** + * Compatible with JDK 1.1+. + */ + private static final long serialVersionUID = 6401253773779951803L; + + /** + * Maps property names (String) to named listeners (PropertyChangeSupport). + * If this is a child instance, this field will be null. + * + * @serial the map of property names to named listener managers + * @since 1.2 + */ + private Hashtable children; + + /** + * The non-null source object for any generated events. + * + * @serial the event source + */ + private final Object source; + + /** + * A field to compare serialization versions - this class uses version 2. + * + * @serial the serialization format + */ + private final int propertyChangeSupportSerializedDataVersion = 2; + + /** + * The list of all registered property listeners. If this instance was + * created by user code, this only holds the global listeners (ie. not tied + * to a name), and may be null. If it was created by this class, as a + * helper for named properties, then this vector will be non-null, and this + * instance appears as a value in the <code>children</code> hashtable of + * another instance, so that the listeners are tied to the key of that + * hashtable entry. + */ + private transient Vector listeners; + + /** + * Create a PropertyChangeSupport to work with a specific source bean. + * + * @param source the source bean to use + * @throws NullPointerException if source is null + */ + public PropertyChangeSupport(Object source) + { + this.source = source; + if (source == null) + throw new NullPointerException(); + } + + /** + * Adds a PropertyChangeListener to the list of global listeners. All + * property change events will be sent to this listener. The listener add + * is not unique: that is, <em>n</em> adds with the same listener will + * result in <em>n</em> events being sent to that listener for every + * property change. Adding a null listener may cause a NullPointerException + * down the road. This method will unwrap a PropertyChangeListenerProxy, + * registering the underlying delegate to the named property list. + * + * @param l the listener to add + */ + public synchronized void addPropertyChangeListener(PropertyChangeListener l) + { + if (l instanceof PropertyChangeListenerProxy) + { + PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l; + addPropertyChangeListener(p.propertyName, + (PropertyChangeListener) p.getListener()); + } + else + { + if (listeners == null) + listeners = new Vector(); + listeners.add(l); + } + } + + /** + * Removes a PropertyChangeListener from the list of global listeners. If + * any specific properties are being listened on, they must be deregistered + * by themselves; this will only remove the general listener to all + * properties. If <code>add()</code> has been called multiple times for a + * particular listener, <code>remove()</code> will have to be called the + * same number of times to deregister it. This method will unwrap a + * PropertyChangeListenerProxy, removing the underlying delegate from the + * named property list. + * + * @param l the listener to remove + */ + public synchronized void + removePropertyChangeListener(PropertyChangeListener l) + { + if (l instanceof PropertyChangeListenerProxy) + { + PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l; + removePropertyChangeListener(p.propertyName, + (PropertyChangeListener) p.getListener()); + } + else if (listeners != null) + { + listeners.remove(l); + if (listeners.isEmpty()) + listeners = null; + } + } + + /** + * Returns an array of all registered property change listeners. Those that + * were registered under a name will be wrapped in a + * <code>PropertyChangeListenerProxy</code>, so you must check whether the + * listener is an instance of the proxy class in order to see what name the + * real listener is registered under. If there are no registered listeners, + * this returns an empty array. + * + * @return the array of registered listeners + * @see PropertyChangeListenerProxy + * @since 1.4 + */ + public synchronized PropertyChangeListener[] getPropertyChangeListeners() + { + ArrayList list = new ArrayList(); + if (listeners != null) + list.addAll(listeners); + if (children != null) + { + int i = children.size(); + Iterator iter = children.entrySet().iterator(); + while (--i >= 0) + { + Entry e = (Entry) iter.next(); + String name = (String) e.getKey(); + Vector v = ((PropertyChangeSupport) e.getValue()).listeners; + int j = v.size(); + while (--j >= 0) + list.add(new PropertyChangeListenerProxy + (name, (PropertyChangeListener) v.get(j))); + } + } + return (PropertyChangeListener[]) + list.toArray(new PropertyChangeListener[list.size()]); + } + + /** + * Adds a PropertyChangeListener listening on the specified property. Events + * will be sent to the listener only if the property name matches. The + * listener add is not unique; that is, <em>n</em> adds on a particular + * property for a particular listener will result in <em>n</em> events + * being sent to that listener when that property is changed. The effect is + * cumulative, too; if you are registered to listen to receive events on + * all property changes, and then you register on a particular property, + * you will receive change events for that property twice. Adding a null + * listener may cause a NullPointerException down the road. This method + * will unwrap a PropertyChangeListenerProxy, registering the underlying + * delegate to the named property list if the names match, and discarding + * it otherwise. + * + * @param propertyName the name of the property to listen on + * @param l the listener to add + * @throws NullPointerException if propertyName is null + */ + public synchronized void addPropertyChangeListener(String propertyName, + PropertyChangeListener l) + { + while (l instanceof PropertyChangeListenerProxy) + { + PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l; + if (propertyName == null ? p.propertyName != null + : ! propertyName.equals(p.propertyName)) + return; + l = (PropertyChangeListener) p.getListener(); + } + PropertyChangeSupport s = null; + if (children == null) + children = new Hashtable(); + else + s = (PropertyChangeSupport) children.get(propertyName); + if (s == null) + { + s = new PropertyChangeSupport(source); + s.listeners = new Vector(); + children.put(propertyName, s); + } + s.listeners.add(l); + } + + /** + * Removes a PropertyChangeListener from listening to a specific property. + * If <code>add()</code> has been called multiple times for a particular + * listener on a property, <code>remove()</code> will have to be called the + * same number of times to deregister it. This method will unwrap a + * PropertyChangeListenerProxy, removing the underlying delegate from the + * named property list if the names match. + * + * @param propertyName the property to stop listening on + * @param l the listener to remove + * @throws NullPointerException if propertyName is null + */ + public synchronized void + removePropertyChangeListener(String propertyName, PropertyChangeListener l) + { + if (children == null) + return; + PropertyChangeSupport s + = (PropertyChangeSupport) children.get(propertyName); + if (s == null) + return; + while (l instanceof PropertyChangeListenerProxy) + { + PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l; + if (propertyName == null ? p.propertyName != null + : ! propertyName.equals(p.propertyName)) + return; + l = (PropertyChangeListener) p.getListener(); + } + s.listeners.remove(l); + if (s.listeners.isEmpty()) + { + children.remove(propertyName); + if (children.isEmpty()) + children = null; + } + } + + /** + * Returns an array of all property change listeners registered under the + * given property name. If there are no registered listeners, this returns + * an empty array. + * + * @return the array of registered listeners + * @throws NullPointerException if propertyName is null + * @since 1.4 + */ + public synchronized PropertyChangeListener[] + getPropertyChangeListeners(String propertyName) + { + if (children == null) + return new PropertyChangeListener[0]; + PropertyChangeSupport s + = (PropertyChangeSupport) children.get(propertyName); + if (s == null) + return new PropertyChangeListener[0]; + return (PropertyChangeListener[]) + s.listeners.toArray(new PropertyChangeListener[s.listeners.size()]); + } + + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are non-null + * and equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + */ + public void firePropertyChange(String propertyName, + Object oldVal, Object newVal) + { + firePropertyChange(new PropertyChangeEvent(source, propertyName, + oldVal, newVal)); + } + + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + */ + public void firePropertyChange(String propertyName, int oldVal, int newVal) + { + if (oldVal != newVal) + firePropertyChange(new PropertyChangeEvent(source, propertyName, + new Integer(oldVal), + new Integer(newVal))); + } + + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + */ + public void firePropertyChange(String propertyName, + boolean oldVal, boolean newVal) + { + if (oldVal != newVal) + firePropertyChange(new PropertyChangeEvent(source, propertyName, + Boolean.valueOf(oldVal), + Boolean.valueOf(newVal))); + } + + /** + * Fire a PropertyChangeEvent to all the global listeners, and to all the + * listeners for the specified property name. This does nothing if old and + * new values of the event are equal. + * + * @param event the event to fire + * @throws NullPointerException if event is null + */ + public void firePropertyChange(PropertyChangeEvent event) + { + if (event.oldValue != null && event.oldValue.equals(event.newValue)) + return; + Vector v = listeners; // Be thread-safe. + if (v != null) + { + int i = v.size(); + while (--i >= 0) + ((PropertyChangeListener) v.get(i)).propertyChange(event); + } + Hashtable h = children; // Be thread-safe. + if (h != null && event.propertyName != null) + { + PropertyChangeSupport s + = (PropertyChangeSupport) h.get(event.propertyName); + if (s != null) + { + v = s.listeners; // Be thread-safe. + int i = v == null ? 0 : v.size(); + while (--i >= 0) + ((PropertyChangeListener) v.get(i)).propertyChange(event); + } + } + } + + /** + * Tell whether the specified property is being listened on or not. This + * will only return <code>true</code> if there are listeners on all + * properties or if there is a listener specifically on this property. + * + * @param propertyName the property that may be listened on + * @return whether the property is being listened on + * @throws NullPointerException if propertyName is null + */ + public synchronized boolean hasListeners(String propertyName) + { + return listeners != null || (children != null + && children.get(propertyName) != null); + } + + /** + * Saves the state of the object to the stream. + * + * @param s the stream to write to + * @throws IOException if anything goes wrong + * @serialData this writes out a null-terminated list of serializable + * global property change listeners (the listeners for a named + * property are written out as the global listeners of the + * children, when the children hashtable is saved) + */ + private synchronized void writeObject(ObjectOutputStream s) + throws IOException + { + s.defaultWriteObject(); + if (listeners != null) + { + int i = listeners.size(); + while (--i >= 0) + if (listeners.get(i) instanceof Serializable) + s.writeObject(listeners.get(i)); + } + s.writeObject(null); + } + + /** + * Reads the object back from stream (deserialization). + * + * XXX Since serialization for 1.1 streams was not documented, this may + * not work if propertyChangeSupportSerializedDataVersion is 1. + * + * @param s the stream to read from + * @throws IOException if reading the stream fails + * @throws ClassNotFoundException if deserialization fails + * @serialData this reads in a null-terminated list of serializable + * global property change listeners (the listeners for a named + * property are written out as the global listeners of the + * children, when the children hashtable is saved) + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + s.defaultReadObject(); + PropertyChangeListener l = (PropertyChangeListener) s.readObject(); + while (l != null) + { + addPropertyChangeListener(l); + l = (PropertyChangeListener) s.readObject(); + } + // Sun is not as careful with children as we are, and lets some proxys + // in that can never receive events. So, we clean up anything that got + // serialized, to make sure our invariants hold. + if (children != null) + { + int i = children.size(); + Iterator iter = children.entrySet().iterator(); + while (--i >= 0) + { + Entry e = (Entry) iter.next(); + String name = (String) e.getKey(); + PropertyChangeSupport pcs = (PropertyChangeSupport) e.getValue(); + if (pcs.listeners == null) + pcs.listeners = new Vector(); + if (pcs.children != null) + pcs.listeners.addAll + (Arrays.asList(pcs.getPropertyChangeListeners(name))); + if (pcs.listeners.size() == 0) + iter.remove(); + else + pcs.children = null; + } + if (children.size() == 0) + children = null; + } + } +} // class PropertyChangeSupport diff --git a/java/beans/VetoableChangeListener.java b/java/beans/VetoableChangeListener.java index bb91e7491..67d2a9b24 100644 --- a/java/beans/VetoableChangeListener.java +++ b/java/beans/VetoableChangeListener.java @@ -1,5 +1,5 @@ -/* java.beans.VetoableChangeListener - Copyright (C) 1998, 2000 Free Software Foundation, Inc. +/* VetoableChangeListener.java -- listen for a change which can be vetoed + Copyright (C) 1998, 2000, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - + GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -41,37 +41,32 @@ package java.beans; import java.util.EventListener; /** - ** VetoableChangeListener allows a class to monitor - ** proposed changes to properties of a Bean and, if - ** desired, prevent them from occurring.<P> - ** - ** A vetoableChange() event will be fired <EM>before</EM> - ** the property has changed. If any listener rejects the - ** change by throwing the PropertyChangeException, a new - ** vetoableChange() event will be fired to all listeners - ** who received a vetoableChange() event in the first - ** place informing them of a reversion to the old value. - ** The value, of course, never actually changed.<P> - ** - ** <STRONG>Note:</STRONG> This class may not be reliably - ** used to determine whether a property has actually - ** changed. Use the PropertyChangeListener interface - ** for that instead. - ** - ** @author John Keiser - ** @version 1.1.0, 29 Jul 1998 - ** @since JDK1.1 - ** @see java.beans.PropertyChangeListener - ** @see java.beans.VetoableChangeSupport - **/ - + * VetoableChangeListener allows a class to monitor proposed changes to + * properties of a Bean and, if desired, prevent them from occurring. A + * vetoableChange() event will be fired <em>after</em> the property change has + * been requested, but before it is permanent. If any listener rejects the + * change by throwing the PropertyChangeException, a new vetoableChange() + * event will be fired to all listeners who received a vetoableChange() event + * in the first place, informing them to revert back to the old value. + * The value, of course, never actually changed. + * + * <p><strong>Note:</strong> This class may not be reliably used to determine + * whether a property has actually changed. Use the PropertyChangeListener + * interface for that instead. + * + * @author John Keiser + * @see java.beans.PropertyChangeListener + * @see java.beans.VetoableChangeSupport + * @since 1.1 + * @status updated to 1.4 + */ public interface VetoableChangeListener extends EventListener { - /** Fired before a Bean's property changes. - ** @param e the change (containing the old and new values) - ** @exception PropertyChangeException if the listener - ** does not desire the change to be made. - **/ - public abstract void vetoableChange(PropertyChangeEvent e) - throws PropertyVetoException; -} + /** + * Fired before a Bean's property changes. + * + * @param e the change (containing the old and new values) + * @throws PropertyVetoException if the change is vetoed by the listener + */ + void vetoableChange(PropertyChangeEvent e) throws PropertyVetoException; +} // interface VetoableChangeListener diff --git a/java/beans/VetoableChangeListenerProxy.java b/java/beans/VetoableChangeListenerProxy.java new file mode 100644 index 000000000..f9d799587 --- /dev/null +++ b/java/beans/VetoableChangeListenerProxy.java @@ -0,0 +1,102 @@ +/* VetoableChangeListenerProxy.java -- adds a name to a vetoable listener + Copyright (C) 2002 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.beans; + +import java.util.EventListenerProxy; + +/** + * This class provides an extension to <code>VetoableChangeListener</code> - + * associating a name with the listener. This can be used to filter the + * changes that one is interested in. + * + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.4 + * @status udpated to 1.4 + */ +public class VetoableChangeListenerProxy extends EventListenerProxy + implements VetoableChangeListener +{ + /** + * The name of the property to listen for. Package visible for use by + * VetoableChangeSupport. + */ + final String propertyName; + + /** + * Create a new proxy which filters property change events and only passes + * changes to the named property on to the delegate. + * + * @param propertyName the property's name to filter on + * @param listener the delegate listener + */ + public VetoableChangeListenerProxy(String propertyName, + VetoableChangeListener listener) + { + super(listener); + this.propertyName = propertyName; + } + + /** + * Forwards the event on to the delegate if the property name matches. + * + * @param event the event to pass on, if it meets the filter + * @throws NullPointerException if the delegate this was created with is null + * @throws PropertyVetoException if the change is vetoed by the listener + */ + public void vetoableChange(PropertyChangeEvent event) + throws PropertyVetoException + { + // Note: Sun does not filter, under the assumption that since + // VetoableChangeSupport unwraps proxys, this method should never be + // called by normal use of listeners. + String name = event == null ? null : event.getPropertyName(); + if (name == null ? propertyName == null : name.equals(propertyName)) + ((VetoableChangeListener) getListener()).vetoableChange(event); + } + + /** + * Gets the name of the property this proxy is filtering on. + * + * @return the property name + */ + public String getPropertyName() + { + return propertyName; + } +} // class VetoableChangeListenerProxy diff --git a/java/beans/VetoableChangeSupport.java b/java/beans/VetoableChangeSupport.java index 831384830..335c86dda 100644 --- a/java/beans/VetoableChangeSupport.java +++ b/java/beans/VetoableChangeSupport.java @@ -1,5 +1,5 @@ -/* java.beans.VetoableChangeSupport - Copyright (C) 1998 Free Software Foundation, Inc. +/* VetoableChangeSupport.java -- support to manage vetoable change listeners + Copyright (C) 1998, 1999, 2000, 2002 Free Software Foundation, Inc. This file is part of GNU Classpath. @@ -7,7 +7,7 @@ GNU Classpath is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. - + GNU Classpath is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU @@ -35,265 +35,462 @@ this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ + package java.beans; -import java.util.Hashtable; -import java.util.Vector; -import java.util.Enumeration; + +import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -import java.io.IOException; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Vector; /** - ** VetoableChangeSupport makes it easy to fire vetoable - ** change events and handle listeners as well as reversion - ** of old values when things go wrong. - ** - ** @author John Keiser - ** @since JDK1.1 - ** @version 1.2.0, 15 Mar 1998 - **/ + * VetoableChangeSupport makes it easy to fire vetoable change events and + * handle listeners. It allows chaining of listeners, as well as filtering + * by property name. In addition, it will serialize only those listeners + * which are serializable, ignoring the others without problem. This class + * is thread-safe. + * + * @author John Keiser + * @author Eric Blake <ebb9@email.byu.edu> + * @since 1.1 + * @status updated to 1.4 + */ +public class VetoableChangeSupport implements Serializable +{ + /** + * Compatible with JDK 1.1+. + */ + private static final long serialVersionUID = -5090210921595982017L; -public class VetoableChangeSupport implements java.io.Serializable { - transient Hashtable propertyListeners = new Hashtable(); - transient Vector listeners = new Vector(); - Hashtable children; - Object source; - int vetoableChangeSupportSerializedDataVersion = 2; - private static final long serialVersionUID = -5090210921595982017L; + /** + * Maps property names (String) to named listeners (VetoableChangeSupport). + * If this is a child instance, this field will be null. + * + * @serial the map of property names to named listener managers + * @since 1.2 + */ + private Hashtable children; - /** - * Saves the state of the object to the stream. */ - private void writeObject(ObjectOutputStream stream) throws IOException { - children = propertyListeners.isEmpty() ? null : propertyListeners; - stream.defaultWriteObject(); - for (Enumeration e = listeners.elements(); e.hasMoreElements(); ) { - VetoableChangeListener l = (VetoableChangeListener)e.nextElement(); - if (l instanceof Serializable) - stream.writeObject(l); - } - stream.writeObject(null); - } + /** + * The non-null source object for any generated events. + * + * @serial the event source + */ + private final Object source; - /** - * Reads the object back from stream (deserialization). - */ - private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { - stream.defaultReadObject(); - propertyListeners = (children == null) ? new Hashtable() : children; - VetoableChangeListener l; - while ((l = (VetoableChangeListener)stream.readObject()) != null) { - addVetoableChangeListener(l); - } - // FIXME: XXX: There is no spec for JDK 1.1 serialization - // so it is unclear what to do if the value of - // vetoableChangeSupportSerializedDataVersion is 1. - } + /** + * A field to compare serialization versions - this class uses version 2. + * + * @serial the serialization format + */ + private final int vetoableChangeSupportSerializedDataVersion = 2; + /** + * The list of all registered vetoable listeners. If this instance was + * created by user code, this only holds the global listeners (ie. not tied + * to a name), and may be null. If it was created by this class, as a + * helper for named properties, then this vector will be non-null, and this + * instance appears as a value in the <code>children</code> hashtable of + * another instance, so that the listeners are tied to the key of that + * hashtable entry. + */ + private transient Vector listeners; - /** Create VetoableChangeSupport to work with a specific - ** source bean. - ** @param source the source bean to use. - **/ - public VetoableChangeSupport(Object source) { - this.source = source; - } + /** + * Create a VetoableChangeSupport to work with a specific source bean. + * + * @param source the source bean to use + * @throws NullPointerException if source is null + */ + public VetoableChangeSupport(Object source) + { + this.source = source; + if (source == null) + throw new NullPointerException(); + } - /** Adds a VetoableChangeListener to the list of listeners. - ** All property change events will be sent to this listener. - ** <P> - ** - ** The listener add is not unique: that is, <em>n</em> adds with - ** the same listener will result in <em>n</em> events being sent - ** to that listener for every property change. - ** <P> - ** - ** Adding a null listener will cause undefined behavior. - ** - ** @param l the listener to add. - **/ - public void addVetoableChangeListener(VetoableChangeListener l) { - listeners.addElement(l); - } + /** + * Adds a VetoableChangeListener to the list of global listeners. All + * vetoable change events will be sent to this listener. The listener add + * is not unique: that is, <em>n</em> adds with the same listener will + * result in <em>n</em> events being sent to that listener for every + * vetoable change. Adding a null listener may cause a NullPointerException + * down the road. This method will unwrap a VetoableChangeListenerProxy, + * registering the underlying delegate to the named property list. + * + * @param l the listener to add + */ + public synchronized void addVetoableChangeListener(VetoableChangeListener l) + { + if (l instanceof VetoableChangeListenerProxy) + { + VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l; + addVetoableChangeListener(p.propertyName, + (VetoableChangeListener) p.getListener()); + } + else + { + if (listeners == null) + listeners = new Vector(); + listeners.add(l); + } + } - /** Adds a VetoableChangeListener listening on the specified property. - ** Events will be sent to the listener for that particular property. - ** <P> - ** - ** The listener add is not unique; that is, <em>n</em> adds on a - ** particular property for a particular listener will result in - ** <em>n</em> events being sent to that listener when that - ** property is changed. - ** <P> - ** - ** The effect is cumulative, too; if you are registered to listen - ** to receive events on all property changes, and then you - ** register on a particular property, you will receive change - ** events for that property twice. - ** <P> - ** - ** Adding a null listener will cause undefined behavior. - ** - ** @param propertyName the name of the property to listen on. - ** @param l the listener to add. - **/ - public void addVetoableChangeListener(String propertyName, VetoableChangeListener l) { - synchronized(propertyListeners) { - Vector v = (Vector)propertyListeners.get(propertyName); - try { - v.addElement(l); - } catch(NullPointerException e) { - /* If v is not found, create a new vector. */ - v = new Vector(); - v.addElement(l); - propertyListeners.put(propertyName, v); - } - } - } + /** + * Removes a VetoableChangeListener from the list of global listeners. If + * any specific properties are being listened on, they must be deregistered + * by themselves; this will only remove the general listener to all + * properties. If <code>add()</code> has been called multiple times for a + * particular listener, <code>remove()</code> will have to be called the + * same number of times to deregister it. This method will unwrap a + * VetoableChangeListenerProxy, removing the underlying delegate from the + * named property list. + * + * @param l the listener to remove + */ + public synchronized void + removeVetoableChangeListener(VetoableChangeListener l) + { + if (l instanceof VetoableChangeListenerProxy) + { + VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l; + removeVetoableChangeListener(p.propertyName, + (VetoableChangeListener) p.getListener()); + } + else if (listeners != null) + { + listeners.remove(l); + if (listeners.isEmpty()) + listeners = null; + } + } - /** Removes a VetoableChangeListener from the list of listeners. - ** If any specific properties are being listened on, they must - ** be deregistered by themselves; this will only remove the - ** general listener to all properties. - ** <P> - ** - ** If <code>add()</code> has been called multiple times for a - ** particular listener, <code>remove()</code> will have to be - ** called the same number of times to deregister it. - ** - ** @param l the listener to remove. - **/ - public void removeVetoableChangeListener(VetoableChangeListener l) { - listeners.removeElement(l); - } + /** + * Returns an array of all registered vetoable change listeners. Those that + * were registered under a name will be wrapped in a + * <code>VetoableChangeListenerProxy</code>, so you must check whether the + * listener is an instance of the proxy class in order to see what name the + * real listener is registered under. If there are no registered listeners, + * this returns an empty array. + * + * @return the array of registered listeners + * @see VetoableChangeListenerProxy + * @since 1.4 + */ + public synchronized VetoableChangeListener[] getVetoableChangeListeners() + { + ArrayList list = new ArrayList(); + if (listeners != null) + list.addAll(listeners); + if (children != null) + { + int i = children.size(); + Iterator iter = children.entrySet().iterator(); + while (--i >= 0) + { + Entry e = (Entry) iter.next(); + String name = (String) e.getKey(); + Vector v = ((VetoableChangeSupport) e.getValue()).listeners; + int j = v.size(); + while (--j >= 0) + list.add(new VetoableChangeListenerProxy + (name, (VetoableChangeListener) v.get(j))); + } + } + return (VetoableChangeListener[]) + list.toArray(new VetoableChangeListener[list.size()]); + } - /** Removes a VetoableChangeListener from listening to a specific property. - ** <P> - ** - ** If <code>add()</code> has been called multiple times for a - ** particular listener on a property, <code>remove()</code> will - ** have to be called the same number of times to deregister it. - ** - ** @param propertyName the property to stop listening on. - ** @param l the listener to remove. - **/ - public void removeVetoableChangeListener(String propertyName, VetoableChangeListener l) { - synchronized(propertyListeners) { - Vector v = (Vector)propertyListeners.get(propertyName); - try { - v.removeElement(l); - if(v.size() == 0) { - propertyListeners.remove(propertyName); - } - } catch(NullPointerException e) { - /* if v is not found, do nothing. */ - } - } - } + /** + * Adds a VetoableChangeListener listening on the specified property. Events + * will be sent to the listener only if the property name matches. The + * listener add is not unique; that is, <em>n</em> adds on a particular + * property for a particular listener will result in <em>n</em> events + * being sent to that listener when that property is changed. The effect is + * cumulative, too; if you are registered to listen to receive events on + * all vetoable changes, and then you register on a particular property, + * you will receive change events for that property twice. Adding a null + * listener may cause a NullPointerException down the road. This method + * will unwrap a VetoableChangeListenerProxy, registering the underlying + * delegate to the named property list if the names match, and discarding + * it otherwise. + * + * @param propertyName the name of the property to listen on + * @param l the listener to add + * @throws NullPointerException if propertyName is null + */ + public synchronized void addVetoableChangeListener(String propertyName, + VetoableChangeListener l) + { + while (l instanceof VetoableChangeListenerProxy) + { + VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l; + if (propertyName == null ? p.propertyName != null + : ! propertyName.equals(p.propertyName)) + return; + l = (VetoableChangeListener) p.getListener(); + } + VetoableChangeSupport s = null; + if (children == null) + children = new Hashtable(); + else + s = (VetoableChangeSupport) children.get(propertyName); + if (s == null) + { + s = new VetoableChangeSupport(source); + s.listeners = new Vector(); + children.put(propertyName, s); + } + s.listeners.add(l); + } + /** + * Removes a VetoableChangeListener from listening to a specific property. + * If <code>add()</code> has been called multiple times for a particular + * listener on a property, <code>remove()</code> will have to be called the + * same number of times to deregister it. This method will unwrap a + * VetoableChangeListenerProxy, removing the underlying delegate from the + * named property list if the names match. + * + * @param propertyName the property to stop listening on + * @param l the listener to remove + * @throws NullPointerException if propertyName is null + */ + public synchronized void + removeVetoableChangeListener(String propertyName, VetoableChangeListener l) + { + if (children == null) + return; + VetoableChangeSupport s + = (VetoableChangeSupport) children.get(propertyName); + if (s == null) + return; + while (l instanceof VetoableChangeListenerProxy) + { + VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l; + if (propertyName == null ? p.propertyName != null + : ! propertyName.equals(p.propertyName)) + return; + l = (VetoableChangeListener) p.getListener(); + } + s.listeners.remove(l); + if (s.listeners.isEmpty()) + { + children.remove(propertyName); + if (children.isEmpty()) + children = null; + } + } - /** Fire a VetoableChangeEvent to all the listeners. - ** If any listener objects, a reversion event will be sent to - ** those listeners who received the initial event. - ** - ** @param proposedChange the event to send. - ** @exception PropertyVetoException if the change is vetoed. - **/ - public void fireVetoableChange(PropertyChangeEvent proposedChange) throws PropertyVetoException { - int currentListener=0; - try { - for(;currentListener<listeners.size();currentListener++) { - ((VetoableChangeListener)listeners.elementAt(currentListener)).vetoableChange(proposedChange); - } - } catch(PropertyVetoException e) { - PropertyChangeEvent reversion = new PropertyChangeEvent(proposedChange.getSource(),proposedChange.getPropertyName(),proposedChange.getNewValue(),proposedChange.getOldValue()); - for(int sendAgain=0;sendAgain<currentListener;sendAgain++) { - try { - ((VetoableChangeListener)listeners.elementAt(sendAgain)).vetoableChange(reversion); - } catch(PropertyVetoException e2) { - } - } - throw e; - } + /** + * Returns an array of all vetoable change listeners registered under the + * given property name. If there are no registered listeners, this returns + * an empty array. + * + * @return the array of registered listeners + * @throws NullPointerException if propertyName is null + * @since 1.4 + */ + public synchronized VetoableChangeListener[] + getVetoableChangeListeners(String propertyName) + { + if (children == null) + return new VetoableChangeListener[0]; + VetoableChangeSupport s + = (VetoableChangeSupport) children.get(propertyName); + if (s == null) + return new VetoableChangeListener[0]; + return (VetoableChangeListener[]) + s.listeners.toArray(new VetoableChangeListener[s.listeners.size()]); + } - Vector moreListeners = (Vector)propertyListeners.get(proposedChange.getPropertyName()); - if(moreListeners != null) { - try { - for(currentListener = 0; currentListener < moreListeners.size(); currentListener++) { - ((VetoableChangeListener)moreListeners.elementAt(currentListener)).vetoableChange(proposedChange); - } - } catch(PropertyVetoException e) { - PropertyChangeEvent reversion = new PropertyChangeEvent(proposedChange.getSource(),proposedChange.getPropertyName(),proposedChange.getNewValue(),proposedChange.getOldValue()); - for(int sendAgain=0;sendAgain<listeners.size();sendAgain++) { - try { - ((VetoableChangeListener)listeners.elementAt(currentListener)).vetoableChange(proposedChange); - } catch(PropertyVetoException e2) { - } - } + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are non-null + * and equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + * @throws PropertyVetoException if the change is vetoed by a listener + */ + public void fireVetoableChange(String propertyName, + Object oldVal, Object newVal) + throws PropertyVetoException + { + fireVetoableChange(new PropertyChangeEvent(source, propertyName, + oldVal, newVal)); + } - for(int sendAgain=0;sendAgain<currentListener;sendAgain++) { - try { - ((VetoableChangeListener)moreListeners.elementAt(sendAgain)).vetoableChange(reversion); - } catch(PropertyVetoException e2) { - } - } - throw e; - } - } - } + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + * @throws PropertyVetoException if the change is vetoed by a listener + */ + public void fireVetoableChange(String propertyName, int oldVal, int newVal) + throws PropertyVetoException + { + if (oldVal != newVal) + fireVetoableChange(new PropertyChangeEvent(source, propertyName, + new Integer(oldVal), + new Integer(newVal))); + } - /** Fire a VetoableChangeEvent containing the old and new values of the property to all the listeners. - ** If any listener objects, a reversion event will be sent to - ** those listeners who received the initial event. - ** - ** @param propertyName the name of the property that - ** changed. - ** @param oldVal the old value. - ** @param newVal the new value. - ** @exception PropertyVetoException if the change is vetoed. - **/ - public void fireVetoableChange(String propertyName, Object oldVal, Object newVal) throws PropertyVetoException { - fireVetoableChange(new PropertyChangeEvent(source,propertyName,oldVal,newVal)); - } + /** + * Fire a PropertyChangeEvent containing the old and new values of the + * property to all the global listeners, and to all the listeners for the + * specified property name. This does nothing if old and new are equal. + * + * @param propertyName the name of the property that changed + * @param oldVal the old value + * @param newVal the new value + * @throws PropertyVetoException if the change is vetoed by a listener + */ + public void fireVetoableChange(String propertyName, + boolean oldVal, boolean newVal) + throws PropertyVetoException + { + if (oldVal != newVal) + fireVetoableChange(new PropertyChangeEvent(source, propertyName, + Boolean.valueOf(oldVal), + Boolean.valueOf(newVal))); + } - /** Fire a VetoableChangeEvent containing the old and new values of the property to all the listeners. - ** If any listener objects, a reversion event will be sent to - ** those listeners who received the initial event. - ** - ** @param propertyName the name of the property that - ** changed. - ** @param oldVal the old value. - ** @param newVal the new value. - ** @exception PropertyVetoException if the change is vetoed. - **/ - public void fireVetoableChange(String propertyName, boolean oldVal, boolean newVal) throws PropertyVetoException { - fireVetoableChange(new PropertyChangeEvent(source,propertyName,new Boolean(oldVal),new Boolean(newVal))); - } + /** + * Fire a PropertyChangeEvent to all the global listeners, and to all the + * listeners for the specified property name. This does nothing if old and + * new values of the event are equal. + * + * @param event the event to fire + * @throws NullPointerException if event is null + * @throws PropertyVetoException if the change is vetoed by a listener + */ + public void fireVetoableChange(PropertyChangeEvent event) + throws PropertyVetoException + { + if (event.oldValue != null && event.oldValue.equals(event.newValue)) + return; + Vector v = listeners; // Be thread-safe. + if (v != null) + { + int i = v.size(); + while (--i >= 0) + ((VetoableChangeListener) v.get(i)).vetoableChange(event); + } + Hashtable h = children; // Be thread-safe. + if (h != null && event.propertyName != null) + { + VetoableChangeSupport s + = (VetoableChangeSupport) h.get(event.propertyName); + if (s != null) + { + v = s.listeners; // Be thread-safe. + int i = v == null ? 0 : v.size(); + while (--i >= 0) + ((VetoableChangeListener) v.get(i)).vetoableChange(event); + } + } + } - /** Fire a VetoableChangeEvent containing the old and new values of the property to all the listeners. - ** If any listener objects, a reversion event will be sent to - ** those listeners who received the initial event. - ** - ** @param propertyName the name of the property that - ** changed. - ** @param oldVal the old value. - ** @param newVal the new value. - ** @exception PropertyVetoException if the change is vetoed. - **/ - public void fireVetoableChange(String propertyName, int oldVal, int newVal) throws PropertyVetoException { - fireVetoableChange(new PropertyChangeEvent(source,propertyName,new Integer(oldVal),new Integer(newVal))); - } + /** + * Tell whether the specified property is being listened on or not. This + * will only return <code>true</code> if there are listeners on all + * properties or if there is a listener specifically on this property. + * + * @param propertyName the property that may be listened on + * @return whether the property is being listened on + * @throws NullPointerException if propertyName is null + */ + public synchronized boolean hasListeners(String propertyName) + { + return listeners != null || (children != null + && children.get(propertyName) != null); + } + /** + * Saves the state of the object to the stream. + * + * @param s the stream to write to + * @throws IOException if anything goes wrong + * @serialData this writes out a null-terminated list of serializable + * global vetoable change listeners (the listeners for a named + * property are written out as the global listeners of the + * children, when the children hashtable is saved) + */ + private synchronized void writeObject(ObjectOutputStream s) + throws IOException + { + s.defaultWriteObject(); + if (listeners != null) + { + int i = listeners.size(); + while (--i >= 0) + if (listeners.get(i) instanceof Serializable) + s.writeObject(listeners.get(i)); + } + s.writeObject(null); + } - /** Tell whether the specified property is being listened on or not. - ** This will only return <code>true</code> if there are listeners - ** on all properties or if there is a listener specifically on this - ** property. - ** - ** @param propertyName the property that may be listened on - ** @return whether the property is being listened on - **/ - public boolean hasListeners(String propertyName) { - return listeners.size() > 0 || propertyListeners.get(propertyName) != null; - } -} + /** + * Reads the object back from stream (deserialization). + * + * XXX Since serialization for 1.1 streams was not documented, this may + * not work if vetoableChangeSupportSerializedDataVersion is 1. + * + * @param s the stream to read from + * @throws IOException if reading the stream fails + * @throws ClassNotFoundException if deserialization fails + * @serialData this reads in a null-terminated list of serializable + * global vetoable change listeners (the listeners for a named + * property are written out as the global listeners of the + * children, when the children hashtable is saved) + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException + { + s.defaultReadObject(); + VetoableChangeListener l = (VetoableChangeListener) s.readObject(); + while (l != null) + { + addVetoableChangeListener(l); + l = (VetoableChangeListener) s.readObject(); + } + // Sun is not as careful with children as we are, and lets some proxys + // in that can never receive events. So, we clean up anything that got + // serialized, to make sure our invariants hold. + if (children != null) + { + int i = children.size(); + Iterator iter = children.entrySet().iterator(); + while (--i >= 0) + { + Entry e = (Entry) iter.next(); + String name = (String) e.getKey(); + VetoableChangeSupport vcs = (VetoableChangeSupport) e.getValue(); + if (vcs.listeners == null) + vcs.listeners = new Vector(); + if (vcs.children != null) + vcs.listeners.addAll + (Arrays.asList(vcs.getVetoableChangeListeners(name))); + if (vcs.listeners.size() == 0) + iter.remove(); + else + vcs.children = null; + } + if (children.size() == 0) + children = null; + } + } +} // class VetoableChangeSupport |