Source for java.beans.PropertyChangeSupport

   1: /* PropertyChangeSupport.java -- support to manage property change listeners
   2:    Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006
   3:    Free Software Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: 
  40: package java.beans;
  41: 
  42: import java.io.IOException;
  43: import java.io.ObjectInputStream;
  44: import java.io.ObjectOutputStream;
  45: import java.io.Serializable;
  46: import java.util.ArrayList;
  47: import java.util.Arrays;
  48: import java.util.Hashtable;
  49: import java.util.Iterator;
  50: import java.util.Map.Entry;
  51: import java.util.Vector;
  52: 
  53: /**
  54:  * PropertyChangeSupport makes it easy to fire property change events and
  55:  * handle listeners. It allows chaining of listeners, as well as filtering
  56:  * by property name. In addition, it will serialize only those listeners
  57:  * which are serializable, ignoring the others without problem. This class
  58:  * is thread-safe.
  59:  *
  60:  * @author John Keiser
  61:  * @author Eric Blake (ebb9@email.byu.edu)
  62:  * @since 1.1
  63:  * @status updated to 1.4
  64:  */
  65: public class PropertyChangeSupport implements Serializable
  66: {
  67:   /**
  68:    * Compatible with JDK 1.1+.
  69:    */
  70:   private static final long serialVersionUID = 6401253773779951803L;
  71: 
  72:   /**
  73:    * Maps property names (String) to named listeners (PropertyChangeSupport).
  74:    * If this is a child instance, this field will be null.
  75:    *
  76:    * @serial the map of property names to named listener managers
  77:    * @since 1.2
  78:    */
  79:   private Hashtable children;
  80: 
  81:   /**
  82:    * The non-null source object for any generated events.
  83:    *
  84:    * @serial the event source
  85:    */
  86:   private final Object source;
  87: 
  88:   /**
  89:    * A field to compare serialization versions - this class uses version 2.
  90:    *
  91:    * @serial the serialization format
  92:    */
  93:   private static final int propertyChangeSupportSerializedDataVersion = 2;
  94: 
  95:   /**
  96:    * The list of all registered property listeners. If this instance was
  97:    * created by user code, this only holds the global listeners (ie. not tied
  98:    * to a name), and may be null. If it was created by this class, as a
  99:    * 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:                                                  new Integer(oldVal),
 350:                                                  new Integer(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