Source for java.beans.VetoableChangeSupport

   1: /* VetoableChangeSupport.java -- support to manage vetoable 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:  * VetoableChangeSupport makes it easy to fire vetoable 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 VetoableChangeSupport implements Serializable
  65: {
  66:   /**
  67:    * Compatible with JDK 1.1+.
  68:    */
  69:   private static final long serialVersionUID = -5090210921595982017L;
  70: 
  71:   /**
  72:    * Maps property names (String) to named listeners (VetoableChangeSupport).
  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 vetoableChangeSupportSerializedDataVersion = 2;
  93: 
  94:   /**
  95:    * The list of all registered vetoable 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 VetoableChangeSupport 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 VetoableChangeSupport(Object source)
 112:   {
 113:     this.source = source;
 114:     if (source == null)
 115:       throw new NullPointerException();
 116:   }
 117: 
 118:   /**
 119:    * Adds a VetoableChangeListener to the list of global listeners. All
 120:    * vetoable 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:    * vetoable change. Adding a null listener may cause a NullPointerException
 124:    * down the road. This method will unwrap a VetoableChangeListenerProxy,
 125:    * registering the underlying delegate to the named property list.
 126:    *
 127:    * @param l the listener to add
 128:    */
 129:   public synchronized void addVetoableChangeListener(VetoableChangeListener l)
 130:   {
 131:     if (l instanceof VetoableChangeListenerProxy)
 132:       {
 133:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 134:         addVetoableChangeListener(p.propertyName,
 135:                                   (VetoableChangeListener) 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 VetoableChangeListener 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:    * VetoableChangeListenerProxy, removing the underlying delegate from the
 153:    * named property list.
 154:    *
 155:    * @param l the listener to remove
 156:    */
 157:   public synchronized void
 158:     removeVetoableChangeListener(VetoableChangeListener l)
 159:   {
 160:     if (l instanceof VetoableChangeListenerProxy)
 161:       {
 162:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 163:         removeVetoableChangeListener(p.propertyName,
 164:                                      (VetoableChangeListener) 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 vetoable change listeners. Those that
 176:    * were registered under a name will be wrapped in a
 177:    * <code>VetoableChangeListenerProxy</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 VetoableChangeListenerProxy
 184:    * @since 1.4
 185:    */
 186:   public synchronized VetoableChangeListener[] getVetoableChangeListeners()
 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 = ((VetoableChangeSupport) e.getValue()).listeners;
 200:             int j = v.size();
 201:             while (--j >= 0)
 202:               list.add(new VetoableChangeListenerProxy
 203:                 (name, (VetoableChangeListener) v.get(j)));
 204:           }
 205:       }
 206:     return (VetoableChangeListener[])
 207:       list.toArray(new VetoableChangeListener[list.size()]);
 208:   }
 209: 
 210:   /**
 211:    * Adds a VetoableChangeListener 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 vetoable 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 VetoableChangeListenerProxy, 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 addVetoableChangeListener(String propertyName,
 229:                                                      VetoableChangeListener l)
 230:   {
 231:     while (l instanceof VetoableChangeListenerProxy)
 232:       {
 233:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 234:         if (propertyName == null ? p.propertyName != null
 235:             : ! propertyName.equals(p.propertyName))
 236:           return;
 237:         l = (VetoableChangeListener) p.getListener();
 238:       }
 239:     VetoableChangeSupport s = null;
 240:     if (children == null)
 241:       children = new Hashtable();
 242:     else
 243:       s = (VetoableChangeSupport) children.get(propertyName);
 244:     if (s == null)
 245:       {
 246:         s = new VetoableChangeSupport(source);
 247:         s.listeners = new Vector();
 248:         children.put(propertyName, s);
 249:       }
 250:     s.listeners.add(l);
 251:   }
 252: 
 253:   /**
 254:    * Removes a VetoableChangeListener 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:    * VetoableChangeListenerProxy, 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:     removeVetoableChangeListener(String propertyName, VetoableChangeListener l)
 267:   {
 268:     if (children == null)
 269:       return;
 270:     VetoableChangeSupport s
 271:       = (VetoableChangeSupport) children.get(propertyName);
 272:     if (s == null)
 273:       return;
 274:     while (l instanceof VetoableChangeListenerProxy)
 275:       {
 276:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 277:         if (propertyName == null ? p.propertyName != null
 278:             : ! propertyName.equals(p.propertyName))
 279:           return;
 280:         l = (VetoableChangeListener) 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 vetoable 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 VetoableChangeListener[]
 301:     getVetoableChangeListeners(String propertyName)
 302:   {
 303:     if (children == null)
 304:       return new VetoableChangeListener[0];
 305:     VetoableChangeSupport s
 306:       = (VetoableChangeSupport) children.get(propertyName);
 307:     if (s == null)
 308:       return new VetoableChangeListener[0];
 309:     return (VetoableChangeListener[])
 310:       s.listeners.toArray(new VetoableChangeListener[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. If the change is vetoed, a new event is fired to notify
 318:    * listeners about the rollback before the exception is thrown.
 319:    *
 320:    * @param propertyName the name of the property that changed
 321:    * @param oldVal the old value
 322:    * @param newVal the new value
 323:    * @throws PropertyVetoException if the change is vetoed by a listener
 324:    */
 325:   public void fireVetoableChange(String propertyName,
 326:                                  Object oldVal, Object newVal)
 327:     throws PropertyVetoException
 328:   {
 329:     fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 330:                                                oldVal, newVal));
 331:   }
 332: 
 333:   /**
 334:    * Fire a PropertyChangeEvent containing the old and new values of the
 335:    * property to all the global listeners, and to all the listeners for the
 336:    * specified property name. This does nothing if old and new are equal.
 337:    * If the change is vetoed, a new event is fired to notify listeners about
 338:    * the rollback before the exception is thrown.
 339:    *
 340:    * @param propertyName the name of the property that changed
 341:    * @param oldVal the old value
 342:    * @param newVal the new value
 343:    * @throws PropertyVetoException if the change is vetoed by a listener
 344:    */
 345:   public void fireVetoableChange(String propertyName, int oldVal, int newVal)
 346:     throws PropertyVetoException
 347:   {
 348:     if (oldVal != newVal)
 349:       fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 350:                                                  new Integer(oldVal),
 351:                                                  new Integer(newVal)));
 352:   }
 353: 
 354:   /**
 355:    * Fire a PropertyChangeEvent containing the old and new values of the
 356:    * property to all the global listeners, and to all the listeners for the
 357:    * specified property name. This does nothing if old and new are equal.
 358:    * If the change is vetoed, a new event is fired to notify listeners about
 359:    * the rollback before the exception is thrown.
 360:    *
 361:    * @param propertyName the name of the property that changed
 362:    * @param oldVal the old value
 363:    * @param newVal the new value
 364:    * @throws PropertyVetoException if the change is vetoed by a listener
 365:    */
 366:   public void fireVetoableChange(String propertyName,
 367:                                  boolean oldVal, boolean newVal)
 368:     throws PropertyVetoException
 369:   {
 370:     if (oldVal != newVal)
 371:       fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 372:                                                  Boolean.valueOf(oldVal),
 373:                                                  Boolean.valueOf(newVal)));
 374:   }
 375: 
 376:   /**
 377:    * Fire a PropertyChangeEvent to all the global listeners, and to all the
 378:    * listeners for the specified property name. This does nothing if old and
 379:    * new values of the event are equal. If the change is vetoed, a new event
 380:    * is fired to notify listeners about the rollback before the exception is
 381:    * thrown.
 382:    *
 383:    * @param event the event to fire
 384:    * @throws NullPointerException if event is null
 385:    * @throws PropertyVetoException if the change is vetoed by a listener
 386:    */
 387:   public void fireVetoableChange(PropertyChangeEvent event)
 388:     throws PropertyVetoException
 389:   {
 390:     if (event.oldValue != null && event.oldValue.equals(event.newValue))
 391:       return;
 392:     Vector v = listeners; // Be thread-safe.
 393:     if (v != null)
 394:       {
 395:         int i = v.size();
 396:         try
 397:           {
 398:             while (--i >= 0)
 399:               ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 400:           }
 401:         catch (PropertyVetoException e)
 402:           {
 403:             event = event.rollback();
 404:             int limit = i;
 405:             i = v.size();
 406:             while (--i >= limit)
 407:               ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 408:             throw e;
 409:           }
 410:       }
 411:     Hashtable h = children; // Be thread-safe.
 412:     if (h != null && event.propertyName != null)
 413:       {
 414:         VetoableChangeSupport s
 415:           = (VetoableChangeSupport) h.get(event.propertyName);
 416:         if (s != null)
 417:           {
 418:             Vector v1 = s.listeners; // Be thread-safe.
 419:             int i = v1 == null ? 0 : v1.size();
 420:             try
 421:               {
 422:                 while (--i >= 0)
 423:                   ((VetoableChangeListener) v1.get(i)).vetoableChange(event);
 424:               }
 425:             catch (PropertyVetoException e)
 426:               {
 427:                 event = event.rollback();
 428:                 int limit = i;
 429:                 i = v.size();
 430:                 while (--i >= 0)
 431:                   ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 432:                 i = v1.size();
 433:                 while (--i >= limit)
 434:                   ((VetoableChangeListener) v1.get(i)).vetoableChange(event);
 435:                 throw e;
 436:               }
 437:           }
 438:       }
 439:   }
 440: 
 441:   /**
 442:    * Tell whether the specified property is being listened on or not. This
 443:    * will only return <code>true</code> if there are listeners on all
 444:    * properties or if there is a listener specifically on this property.
 445:    *
 446:    * @param propertyName the property that may be listened on
 447:    * @return whether the property is being listened on
 448:    * @throws NullPointerException if propertyName is null
 449:    */
 450:   public synchronized boolean hasListeners(String propertyName)
 451:   {
 452:     return listeners != null || (children != null
 453:                                  && children.get(propertyName) != null);
 454:   }
 455: 
 456:   /**
 457:    * Saves the state of the object to the stream.
 458:    *
 459:    * @param s the stream to write to
 460:    * @throws IOException if anything goes wrong
 461:    * @serialData this writes out a null-terminated list of serializable
 462:    *             global vetoable change listeners (the listeners for a named
 463:    *             property are written out as the global listeners of the
 464:    *             children, when the children hashtable is saved)
 465:    */
 466:   private synchronized void writeObject(ObjectOutputStream s)
 467:     throws IOException
 468:   {
 469:     s.defaultWriteObject();
 470:     if (listeners != null)
 471:       {
 472:         int i = listeners.size();
 473:         while (--i >= 0)
 474:           if (listeners.get(i) instanceof Serializable)
 475:             s.writeObject(listeners.get(i));
 476:       }
 477:     s.writeObject(null);
 478:   }
 479: 
 480:   /**
 481:    * Reads the object back from stream (deserialization).
 482:    *
 483:    * XXX Since serialization for 1.1 streams was not documented, this may
 484:    * not work if vetoableChangeSupportSerializedDataVersion is 1.
 485:    *
 486:    * @param s the stream to read from
 487:    * @throws IOException if reading the stream fails
 488:    * @throws ClassNotFoundException if deserialization fails
 489:    * @serialData this reads in a null-terminated list of serializable
 490:    *             global vetoable change listeners (the listeners for a named
 491:    *             property are written out as the global listeners of the
 492:    *             children, when the children hashtable is saved)
 493:    */
 494:   private void readObject(ObjectInputStream s)
 495:     throws IOException, ClassNotFoundException
 496:   {
 497:     s.defaultReadObject();
 498:     VetoableChangeListener l = (VetoableChangeListener) s.readObject();
 499:     while (l != null)
 500:       {
 501:         addVetoableChangeListener(l);
 502:         l = (VetoableChangeListener) s.readObject();
 503:       }
 504:     // Sun is not as careful with children as we are, and lets some proxys
 505:     // in that can never receive events. So, we clean up anything that got
 506:     // serialized, to make sure our invariants hold.
 507:     if (children != null)
 508:       {
 509:         int i = children.size();
 510:         Iterator iter = children.entrySet().iterator();
 511:         while (--i >= 0)
 512:           {
 513:             Entry e = (Entry) iter.next();
 514:             String name = (String) e.getKey();
 515:             VetoableChangeSupport vcs = (VetoableChangeSupport) e.getValue();
 516:             if (vcs.listeners == null)
 517:               vcs.listeners = new Vector();
 518:             if (vcs.children != null)
 519:               vcs.listeners.addAll
 520:                 (Arrays.asList(vcs.getVetoableChangeListeners(name)));
 521:             if (vcs.listeners.size() == 0)
 522:               iter.remove();
 523:             else
 524:               vcs.children = null;
 525:           }
 526:         if (children.size() == 0)
 527:           children = null;
 528:       }
 529:   }
 530: } // class VetoableChangeSupport