Source for java.beans.PropertyChangeSupport

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