001/* PropertyChangeSupport.java -- support to manage property change listeners
002   Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006
003   Free Software Foundation, Inc.
004
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039
040package java.beans;
041
042import java.io.IOException;
043import java.io.ObjectInputStream;
044import java.io.ObjectOutputStream;
045import java.io.Serializable;
046import java.util.ArrayList;
047import java.util.Arrays;
048import java.util.Hashtable;
049import java.util.Iterator;
050import java.util.Map.Entry;
051import 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 */
065public 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