001    /* PropertyChangeSupport.java -- support to manage property change listeners
002       Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006
003       Free Software Foundation, Inc.
004    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011    
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    
040    package java.beans;
041    
042    import java.io.IOException;
043    import java.io.ObjectInputStream;
044    import java.io.ObjectOutputStream;
045    import java.io.Serializable;
046    import java.util.ArrayList;
047    import java.util.Arrays;
048    import java.util.Hashtable;
049    import java.util.Iterator;
050    import java.util.Map.Entry;
051    import java.util.Vector;
052    
053    /**
054     * PropertyChangeSupport makes it easy to fire property change events and
055     * handle listeners. It allows chaining of listeners, as well as filtering
056     * by property name. In addition, it will serialize only those listeners
057     * which are serializable, ignoring the others without problem. This class
058     * is thread-safe.
059     *
060     * @author John Keiser
061     * @author Eric Blake (ebb9@email.byu.edu)
062     * @since 1.1
063     * @status updated to 1.4
064     */
065    public class PropertyChangeSupport implements Serializable
066    {
067      /**
068       * Compatible with JDK 1.1+.
069       */
070      private static final long serialVersionUID = 6401253773779951803L;
071    
072      /**
073       * Maps property names (String) to named listeners (PropertyChangeSupport).
074       * If this is a child instance, this field will be null.
075       *
076       * @serial the map of property names to named listener managers
077       * @since 1.2
078       */
079      private Hashtable children;
080    
081      /**
082       * The non-null source object for any generated events.
083       *
084       * @serial the event source
085       */
086      private final Object source;
087    
088      /**
089       * A field to compare serialization versions - this class uses version 2.
090       *
091       * @serial the serialization format
092       */
093      private static final int propertyChangeSupportSerializedDataVersion = 2;
094    
095      /**
096       * The list of all registered property listeners. If this instance was
097       * created by user code, this only holds the global listeners (ie. not tied
098       * to a name), and may be null. If it was created by this class, as a
099       * helper for named properties, then this vector will be non-null, and this
100       * instance appears as a value in the <code>children</code> hashtable of
101       * another instance, so that the listeners are tied to the key of that
102       * hashtable entry.
103       */
104      private transient Vector listeners;
105    
106      /**
107       * Create a PropertyChangeSupport to work with a specific source bean.
108       *
109       * @param source the source bean to use
110       * @throws NullPointerException if source is null
111       */
112      public PropertyChangeSupport(Object source)
113      {
114        this.source = source;
115        if (source == null)
116          throw new NullPointerException();
117      }
118    
119      /**
120       * Adds a PropertyChangeListener to the list of global listeners. All
121       * property change events will be sent to this listener. The listener add
122       * is not unique: that is, <em>n</em> adds with the same listener will
123       * result in <em>n</em> events being sent to that listener for every
124       * property change. Adding a null listener is silently ignored.
125       * This method will unwrap a PropertyChangeListenerProxy,
126       * registering the underlying delegate to the named property list.
127       *
128       * @param l the listener to add
129       */
130      public synchronized void addPropertyChangeListener(PropertyChangeListener l)
131      {
132        if (l == null)
133          return;
134    
135        if (l instanceof PropertyChangeListenerProxy)
136          {
137            PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
138            addPropertyChangeListener(p.propertyName,
139                                      (PropertyChangeListener) p.getListener());
140          }
141        else
142          {
143            if (listeners == null)
144              listeners = new Vector();
145            listeners.add(l);
146          }
147      }
148    
149      /**
150       * Removes a PropertyChangeListener from the list of global listeners. If
151       * any specific properties are being listened on, they must be deregistered
152       * by themselves; this will only remove the general listener to all
153       * properties. If <code>add()</code> has been called multiple times for a
154       * particular listener, <code>remove()</code> will have to be called the
155       * same number of times to deregister it. This method will unwrap a
156       * PropertyChangeListenerProxy, removing the underlying delegate from the
157       * named property list.
158       *
159       * @param l the listener to remove
160       */
161      public synchronized void
162        removePropertyChangeListener(PropertyChangeListener l)
163      {
164        if (l instanceof PropertyChangeListenerProxy)
165          {
166            PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
167            removePropertyChangeListener(p.propertyName,
168                                         (PropertyChangeListener) p.getListener());
169          }
170        else if (listeners != null)
171          {
172            listeners.remove(l);
173            if (listeners.isEmpty())
174              listeners = null;
175          }
176      }
177    
178      /**
179       * Returns an array of all registered property change listeners. Those that
180       * were registered under a name will be wrapped in a
181       * <code>PropertyChangeListenerProxy</code>, so you must check whether the
182       * listener is an instance of the proxy class in order to see what name the
183       * real listener is registered under. If there are no registered listeners,
184       * this returns an empty array.
185       *
186       * @return the array of registered listeners
187       * @see PropertyChangeListenerProxy
188       * @since 1.4
189       */
190      public synchronized PropertyChangeListener[] getPropertyChangeListeners()
191      {
192        ArrayList list = new ArrayList();
193        if (listeners != null)
194          list.addAll(listeners);
195        if (children != null)
196          {
197            int i = children.size();
198            Iterator iter = children.entrySet().iterator();
199            while (--i >= 0)
200              {
201                Entry e = (Entry) iter.next();
202                String name = (String) e.getKey();
203                Vector v = ((PropertyChangeSupport) e.getValue()).listeners;
204                int j = v.size();
205                while (--j >= 0)
206                  list.add(new PropertyChangeListenerProxy
207                    (name, (PropertyChangeListener) v.get(j)));
208              }
209          }
210        return (PropertyChangeListener[])
211          list.toArray(new PropertyChangeListener[list.size()]);
212      }
213    
214      /**
215       * Adds a PropertyChangeListener listening on the specified property. Events
216       * will be sent to the listener only if the property name matches. The
217       * listener add is not unique; that is, <em>n</em> adds on a particular
218       * property for a particular listener will result in <em>n</em> events
219       * being sent to that listener when that property is changed. The effect is
220       * cumulative, too; if you are registered to listen to receive events on
221       * all property changes, and then you register on a particular property,
222       * you will receive change events for that property twice. Adding a null
223       * listener is silently ignored. This method will unwrap a
224       * PropertyChangeListenerProxy, registering the underlying
225       * delegate to the named property list if the names match, and discarding
226       * it otherwise.
227       *
228       * @param propertyName the name of the property to listen on
229       * @param l the listener to add
230       * @throws NullPointerException if propertyName is null
231       */
232      public synchronized void addPropertyChangeListener(String propertyName,
233                                                         PropertyChangeListener l)
234      {
235        if (l == null)
236          return;
237    
238        while (l instanceof PropertyChangeListenerProxy)
239          {
240            PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
241            if (propertyName == null ? p.propertyName != null
242                : ! propertyName.equals(p.propertyName))
243              return;
244            l = (PropertyChangeListener) p.getListener();
245          }
246        PropertyChangeSupport s = null;
247        if (children == null)
248          children = new Hashtable();
249        else
250          s = (PropertyChangeSupport) children.get(propertyName);
251        if (s == null)
252          {
253            s = new PropertyChangeSupport(source);
254            s.listeners = new Vector();
255            children.put(propertyName, s);
256          }
257        s.listeners.add(l);
258      }
259    
260      /**
261       * Removes a PropertyChangeListener from listening to a specific property.
262       * If <code>add()</code> has been called multiple times for a particular
263       * listener on a property, <code>remove()</code> will have to be called the
264       * same number of times to deregister it. This method will unwrap a
265       * PropertyChangeListenerProxy, removing the underlying delegate from the
266       * named property list if the names match.
267       *
268       * @param propertyName the property to stop listening on
269       * @param l the listener to remove
270       * @throws NullPointerException if propertyName is null
271       */
272      public synchronized void
273        removePropertyChangeListener(String propertyName, PropertyChangeListener l)
274      {
275        if (children == null)
276          return;
277        PropertyChangeSupport s
278          = (PropertyChangeSupport) children.get(propertyName);
279        if (s == null)
280          return;
281        while (l instanceof PropertyChangeListenerProxy)
282          {
283            PropertyChangeListenerProxy p = (PropertyChangeListenerProxy) l;
284            if (propertyName == null ? p.propertyName != null
285                : ! propertyName.equals(p.propertyName))
286              return;
287            l = (PropertyChangeListener) p.getListener();
288          }
289        s.listeners.remove(l);
290        if (s.listeners.isEmpty())
291          {
292            children.remove(propertyName);
293            if (children.isEmpty())
294              children = null;
295          }
296      }
297    
298      /**
299       * Returns an array of all property change listeners registered under the
300       * given property name. If there are no registered listeners, or
301       * propertyName is null, this returns an empty array.
302       *
303       * @return the array of registered listeners
304       * @since 1.4
305       */
306      public synchronized PropertyChangeListener[]
307        getPropertyChangeListeners(String propertyName)
308      {
309        if (children == null || propertyName == null)
310          return new PropertyChangeListener[0];
311        PropertyChangeSupport s
312          = (PropertyChangeSupport) children.get(propertyName);
313        if (s == null)
314          return new PropertyChangeListener[0];
315        return (PropertyChangeListener[])
316          s.listeners.toArray(new PropertyChangeListener[s.listeners.size()]);
317      }
318    
319      /**
320       * Fire a PropertyChangeEvent containing the old and new values of the
321       * property to all the global listeners, and to all the listeners for the
322       * specified property name. This does nothing if old and new are non-null
323       * and equal.
324       *
325       * @param propertyName the name of the property that changed
326       * @param oldVal the old value
327       * @param newVal the new value
328       */
329      public void firePropertyChange(String propertyName,
330                                     Object oldVal, Object newVal)
331      {
332        firePropertyChange(new PropertyChangeEvent(source, propertyName,
333                                                   oldVal, newVal));
334      }
335    
336      /**
337       * Fire a PropertyChangeEvent containing the old and new values of the
338       * property to all the global listeners, and to all the listeners for the
339       * specified property name. This does nothing if old and new are equal.
340       *
341       * @param propertyName the name of the property that changed
342       * @param oldVal the old value
343       * @param newVal the new value
344       */
345      public void firePropertyChange(String propertyName, int oldVal, int newVal)
346      {
347        if (oldVal != newVal)
348          firePropertyChange(new PropertyChangeEvent(source, propertyName,
349                                                     Integer.valueOf(oldVal),
350                                                     Integer.valueOf(newVal)));
351      }
352    
353      /**
354       * Fire a PropertyChangeEvent containing the old and new values of the
355       * property to all the global listeners, and to all the listeners for the
356       * specified property name. This does nothing if old and new are equal.
357       *
358       * @param propertyName the name of the property that changed
359       * @param oldVal the old value
360       * @param newVal the new value
361       */
362      public void firePropertyChange(String propertyName,
363                                     boolean oldVal, boolean newVal)
364      {
365        if (oldVal != newVal)
366          firePropertyChange(new PropertyChangeEvent(source, propertyName,
367                                                     Boolean.valueOf(oldVal),
368                                                     Boolean.valueOf(newVal)));
369      }
370    
371      /**
372       * Fire a PropertyChangeEvent to all the global listeners, and to all the
373       * listeners for the specified property name. This does nothing if old and
374       * new values of the event are equal.
375       *
376       * @param event the event to fire
377       * @throws NullPointerException if event is null
378       */
379      public void firePropertyChange(PropertyChangeEvent event)
380      {
381        if (event.oldValue != null && event.oldValue.equals(event.newValue))
382          return;
383        Vector v = listeners; // Be thread-safe.
384        if (v != null)
385          {
386            int i = v.size();
387            while (--i >= 0)
388              ((PropertyChangeListener) v.get(i)).propertyChange(event);
389          }
390        Hashtable h = children; // Be thread-safe.
391        if (h != null && event.propertyName != null)
392          {
393            PropertyChangeSupport s
394              = (PropertyChangeSupport) h.get(event.propertyName);
395            if (s != null)
396              {
397                v = s.listeners; // Be thread-safe.
398                int i = v == null ? 0 : v.size();
399                while (--i >= 0)
400                  ((PropertyChangeListener) v.get(i)).propertyChange(event);
401              }
402          }
403      }
404    
405      /**
406       * Fire an indexed property change event.  This will only fire
407       * an event if the old and new values are not equal and not null.
408       * @param name the name of the property which changed
409       * @param index the index of the property which changed
410       * @param oldValue the old value of the property
411       * @param newValue the new value of the property
412       * @since 1.5
413       */
414      public void fireIndexedPropertyChange(String name, int index,
415                                            Object oldValue, Object newValue)
416      {
417        // Argument checking is done in firePropertyChange(PropertyChangeEvent) .
418        firePropertyChange(new IndexedPropertyChangeEvent(source, name,
419                                                          oldValue, newValue,
420                                                          index));
421      }
422    
423      /**
424       * Fire an indexed property change event.  This will only fire
425       * an event if the old and new values are not equal.
426       * @param name the name of the property which changed
427       * @param index the index of the property which changed
428       * @param oldValue the old value of the property
429       * @param newValue the new value of the property
430       * @since 1.5
431       */
432      public void fireIndexedPropertyChange(String name, int index,
433                                            int oldValue, int newValue)
434      {
435        if (oldValue != newValue)
436          fireIndexedPropertyChange(name, index, Integer.valueOf(oldValue),
437                                    Integer.valueOf(newValue));
438      }
439    
440      /**
441       * Fire an indexed property change event.  This will only fire
442       * an event if the old and new values are not equal.
443       * @param name the name of the property which changed
444       * @param index the index of the property which changed
445       * @param oldValue the old value of the property
446       * @param newValue the new value of the property
447       * @since 1.5
448       */
449      public void fireIndexedPropertyChange(String name, int index,
450                                            boolean oldValue, boolean newValue)
451      {
452        if (oldValue != newValue)
453          fireIndexedPropertyChange(name, index, Boolean.valueOf(oldValue),
454                                    Boolean.valueOf(newValue));
455      }
456    
457      /**
458       * Tell whether the specified property is being listened on or not. This
459       * will only return <code>true</code> if there are listeners on all
460       * properties or if there is a listener specifically on this property.
461       *
462       * @param propertyName the property that may be listened on
463       * @return whether the property is being listened on
464       */
465      public synchronized boolean hasListeners(String propertyName)
466      {
467        return listeners != null || (children != null
468                                     && children.get(propertyName) != null);
469      }
470    
471      /**
472       * Saves the state of the object to the stream.
473       *
474       * @param s the stream to write to
475       * @throws IOException if anything goes wrong
476       * @serialData this writes out a null-terminated list of serializable
477       *             global property change listeners (the listeners for a named
478       *             property are written out as the global listeners of the
479       *             children, when the children hashtable is saved)
480       */
481      private synchronized void writeObject(ObjectOutputStream s)
482        throws IOException
483      {
484        s.defaultWriteObject();
485        if (listeners != null)
486          {
487            int i = listeners.size();
488            while (--i >= 0)
489              if (listeners.get(i) instanceof Serializable)
490                s.writeObject(listeners.get(i));
491          }
492        s.writeObject(null);
493      }
494    
495      /**
496       * Reads the object back from stream (deserialization).
497       *
498       * XXX Since serialization for 1.1 streams was not documented, this may
499       * not work if propertyChangeSupportSerializedDataVersion is 1.
500       *
501       * @param s the stream to read from
502       * @throws IOException if reading the stream fails
503       * @throws ClassNotFoundException if deserialization fails
504       * @serialData this reads in a null-terminated list of serializable
505       *             global property change listeners (the listeners for a named
506       *             property are written out as the global listeners of the
507       *             children, when the children hashtable is saved)
508       */
509      private void readObject(ObjectInputStream s)
510        throws IOException, ClassNotFoundException
511      {
512        s.defaultReadObject();
513        PropertyChangeListener l = (PropertyChangeListener) s.readObject();
514        while (l != null)
515          {
516            addPropertyChangeListener(l);
517            l = (PropertyChangeListener) s.readObject();
518          }
519        // Sun is not as careful with children as we are, and lets some proxys
520        // in that can never receive events. So, we clean up anything that got
521        // serialized, to make sure our invariants hold.
522        if (children != null)
523          {
524            int i = children.size();
525            Iterator iter = children.entrySet().iterator();
526            while (--i >= 0)
527              {
528                Entry e = (Entry) iter.next();
529                String name = (String) e.getKey();
530                PropertyChangeSupport pcs = (PropertyChangeSupport) e.getValue();
531                if (pcs.listeners == null)
532                  pcs.listeners = new Vector();
533                if (pcs.children != null)
534                  pcs.listeners.addAll
535                    (Arrays.asList(pcs.getPropertyChangeListeners(name)));
536                if (pcs.listeners.size() == 0)
537                  iter.remove();
538                else
539                  pcs.children = null;
540              }
541            if (children.size() == 0)
542              children = null;
543          }
544      }
545    } // class PropertyChangeSupport