Source for java.beans.VetoableChangeSupport

   1: /* VetoableChangeSupport.java -- support to manage vetoable 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:  * VetoableChangeSupport makes it easy to fire vetoable 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 VetoableChangeSupport implements Serializable
  66: {
  67:   /**
  68:    * Compatible with JDK 1.1+.
  69:    */
  70:   private static final long serialVersionUID = -5090210921595982017L;
  71: 
  72:   /**
  73:    * Maps property names (String) to named listeners (VetoableChangeSupport).
  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 vetoableChangeSupportSerializedDataVersion = 2;
  94: 
  95:   /**
  96:    * The list of all registered vetoable 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 VetoableChangeSupport 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 VetoableChangeSupport(Object source)
 113:   {
 114:     this.source = source;
 115:     if (source == null)
 116:       throw new NullPointerException();
 117:   }
 118: 
 119:   /**
 120:    * Adds a VetoableChangeListener to the list of global listeners. All
 121:    * vetoable 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:    * vetoable change. This method will unwrap a VetoableChangeListenerProxy,
 125:    * registering the underlying delegate to the named property list.
 126:    *
 127:    * @param l the listener to add (<code>null</code> ignored).
 128:    */
 129:   public synchronized void addVetoableChangeListener(VetoableChangeListener l)
 130:   {
 131:     if (l == null)
 132:       return;
 133:     if (l instanceof VetoableChangeListenerProxy)
 134:       {
 135:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 136:         addVetoableChangeListener(p.propertyName,
 137:                                   (VetoableChangeListener) p.getListener());
 138:       }
 139:     else
 140:       {
 141:         if (listeners == null)
 142:           listeners = new Vector();
 143:         listeners.add(l);
 144:       }
 145:   }
 146: 
 147:   /**
 148:    * Removes a VetoableChangeListener from the list of global listeners. If
 149:    * any specific properties are being listened on, they must be deregistered
 150:    * by themselves; this will only remove the general listener to all
 151:    * properties. If <code>add()</code> has been called multiple times for a
 152:    * particular listener, <code>remove()</code> will have to be called the
 153:    * same number of times to deregister it. This method will unwrap a
 154:    * VetoableChangeListenerProxy, removing the underlying delegate from the
 155:    * named property list.
 156:    *
 157:    * @param l the listener to remove
 158:    */
 159:   public synchronized void
 160:     removeVetoableChangeListener(VetoableChangeListener l)
 161:   {
 162:     if (l instanceof VetoableChangeListenerProxy)
 163:       {
 164:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 165:         removeVetoableChangeListener(p.propertyName,
 166:                                      (VetoableChangeListener) p.getListener());
 167:       }
 168:     else if (listeners != null)
 169:       {
 170:         listeners.remove(l);
 171:         if (listeners.isEmpty())
 172:           listeners = null;
 173:       }
 174:   }
 175: 
 176:   /**
 177:    * Returns an array of all registered vetoable change listeners. Those that
 178:    * were registered under a name will be wrapped in a
 179:    * <code>VetoableChangeListenerProxy</code>, so you must check whether the
 180:    * listener is an instance of the proxy class in order to see what name the
 181:    * real listener is registered under. If there are no registered listeners,
 182:    * this returns an empty array.
 183:    *
 184:    * @return the array of registered listeners
 185:    * @see VetoableChangeListenerProxy
 186:    * @since 1.4
 187:    */
 188:   public synchronized VetoableChangeListener[] getVetoableChangeListeners()
 189:   {
 190:     ArrayList list = new ArrayList();
 191:     if (listeners != null)
 192:       list.addAll(listeners);
 193:     if (children != null)
 194:       {
 195:         int i = children.size();
 196:         Iterator iter = children.entrySet().iterator();
 197:         while (--i >= 0)
 198:           {
 199:             Entry e = (Entry) iter.next();
 200:             String name = (String) e.getKey();
 201:             Vector v = ((VetoableChangeSupport) e.getValue()).listeners;
 202:             int j = v.size();
 203:             while (--j >= 0)
 204:               list.add(new VetoableChangeListenerProxy
 205:                 (name, (VetoableChangeListener) v.get(j)));
 206:           }
 207:       }
 208:     return (VetoableChangeListener[])
 209:       list.toArray(new VetoableChangeListener[list.size()]);
 210:   }
 211: 
 212:   /**
 213:    * Adds a VetoableChangeListener listening on the specified property. Events
 214:    * will be sent to the listener only if the property name matches. The
 215:    * listener add is not unique; that is, <em>n</em> adds on a particular
 216:    * property for a particular listener will result in <em>n</em> events
 217:    * being sent to that listener when that property is changed. The effect is
 218:    * cumulative, too; if you are registered to listen to receive events on
 219:    * all vetoable changes, and then you register on a particular property,
 220:    * you will receive change events for that property twice. This method
 221:    * will unwrap a VetoableChangeListenerProxy, registering the underlying
 222:    * delegate to the named property list if the names match, and discarding
 223:    * it otherwise.
 224:    *
 225:    * @param propertyName the name of the property to listen on
 226:    * @param l the listener to add
 227:    */
 228:   public synchronized void addVetoableChangeListener(String propertyName,
 229:                                                      VetoableChangeListener l)
 230:   {
 231:     if (propertyName == null || l == null)
 232:       return;
 233:     while (l instanceof VetoableChangeListenerProxy)
 234:       {
 235:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 236:         if (propertyName == null ? p.propertyName != null
 237:             : ! propertyName.equals(p.propertyName))
 238:           return;
 239:         l = (VetoableChangeListener) p.getListener();
 240:       }
 241:     VetoableChangeSupport s = null;
 242:     if (children == null)
 243:       children = new Hashtable();
 244:     else
 245:       s = (VetoableChangeSupport) children.get(propertyName);
 246:     if (s == null)
 247:       {
 248:         s = new VetoableChangeSupport(source);
 249:         s.listeners = new Vector();
 250:         children.put(propertyName, s);
 251:       }
 252:     s.listeners.add(l);
 253:   }
 254: 
 255:   /**
 256:    * Removes a VetoableChangeListener from listening to a specific property.
 257:    * If <code>add()</code> has been called multiple times for a particular
 258:    * listener on a property, <code>remove()</code> will have to be called the
 259:    * same number of times to deregister it. This method will unwrap a
 260:    * VetoableChangeListenerProxy, removing the underlying delegate from the
 261:    * named property list if the names match.
 262:    *
 263:    * @param propertyName the property to stop listening on
 264:    * @param l the listener to remove
 265:    * @throws NullPointerException if propertyName is null
 266:    */
 267:   public synchronized void
 268:     removeVetoableChangeListener(String propertyName, VetoableChangeListener l)
 269:   {
 270:     if (children == null)
 271:       return;
 272:     VetoableChangeSupport s
 273:       = (VetoableChangeSupport) children.get(propertyName);
 274:     if (s == null)
 275:       return;
 276:     while (l instanceof VetoableChangeListenerProxy)
 277:       {
 278:         VetoableChangeListenerProxy p = (VetoableChangeListenerProxy) l;
 279:         if (propertyName == null ? p.propertyName != null
 280:             : ! propertyName.equals(p.propertyName))
 281:           return;
 282:         l = (VetoableChangeListener) p.getListener();
 283:       }
 284:     s.listeners.remove(l);
 285:     if (s.listeners.isEmpty())
 286:       {
 287:         children.remove(propertyName);
 288:         if (children.isEmpty())
 289:           children = null;
 290:       }
 291:   }
 292: 
 293:   /**
 294:    * Returns an array of all vetoable change listeners registered under the
 295:    * given property name. If there are no registered listeners, this returns
 296:    * an empty array.
 297:    *
 298:    * @return the array of registered listeners
 299:    * @throws NullPointerException if propertyName is null
 300:    * @since 1.4
 301:    */
 302:   public synchronized VetoableChangeListener[]
 303:     getVetoableChangeListeners(String propertyName)
 304:   {
 305:     if (children == null)
 306:       return new VetoableChangeListener[0];
 307:     VetoableChangeSupport s
 308:       = (VetoableChangeSupport) children.get(propertyName);
 309:     if (s == null)
 310:       return new VetoableChangeListener[0];
 311:     return (VetoableChangeListener[])
 312:       s.listeners.toArray(new VetoableChangeListener[s.listeners.size()]);
 313:   }
 314: 
 315:   /**
 316:    * Fire a PropertyChangeEvent containing the old and new values of the
 317:    * property to all the global listeners, and to all the listeners for the
 318:    * specified property name. This does nothing if old and new are non-null
 319:    * and equal. If the change is vetoed, a new event is fired to notify
 320:    * listeners about the rollback before the exception is thrown.
 321:    *
 322:    * @param propertyName the name of the property that changed
 323:    * @param oldVal the old value
 324:    * @param newVal the new value
 325:    * @throws PropertyVetoException if the change is vetoed by a listener
 326:    */
 327:   public void fireVetoableChange(String propertyName,
 328:                                  Object oldVal, Object newVal)
 329:     throws PropertyVetoException
 330:   {
 331:     fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 332:                                                oldVal, newVal));
 333:   }
 334: 
 335:   /**
 336:    * Fire a PropertyChangeEvent containing the old and new values of the
 337:    * property to all the global listeners, and to all the listeners for the
 338:    * specified property name. This does nothing if old and new are equal.
 339:    * If the change is vetoed, a new event is fired to notify listeners about
 340:    * the rollback before the exception is thrown.
 341:    *
 342:    * @param propertyName the name of the property that changed
 343:    * @param oldVal the old value
 344:    * @param newVal the new value
 345:    * @throws PropertyVetoException if the change is vetoed by a listener
 346:    */
 347:   public void fireVetoableChange(String propertyName, int oldVal, int newVal)
 348:     throws PropertyVetoException
 349:   {
 350:     if (oldVal != newVal)
 351:       fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 352:                                                  new Integer(oldVal),
 353:                                                  new Integer(newVal)));
 354:   }
 355: 
 356:   /**
 357:    * Fire a PropertyChangeEvent containing the old and new values of the
 358:    * property to all the global listeners, and to all the listeners for the
 359:    * specified property name. This does nothing if old and new are equal.
 360:    * If the change is vetoed, a new event is fired to notify listeners about
 361:    * the rollback before the exception is thrown.
 362:    *
 363:    * @param propertyName the name of the property that changed
 364:    * @param oldVal the old value
 365:    * @param newVal the new value
 366:    * @throws PropertyVetoException if the change is vetoed by a listener
 367:    */
 368:   public void fireVetoableChange(String propertyName,
 369:                                  boolean oldVal, boolean newVal)
 370:     throws PropertyVetoException
 371:   {
 372:     if (oldVal != newVal)
 373:       fireVetoableChange(new PropertyChangeEvent(source, propertyName,
 374:                                                  Boolean.valueOf(oldVal),
 375:                                                  Boolean.valueOf(newVal)));
 376:   }
 377: 
 378:   /**
 379:    * Fire a PropertyChangeEvent to all the global listeners, and to all the
 380:    * listeners for the specified property name. This does nothing if old and
 381:    * new values of the event are equal. If the change is vetoed, a new event
 382:    * is fired to notify listeners about the rollback before the exception is
 383:    * thrown.
 384:    *
 385:    * @param event the event to fire
 386:    * @throws NullPointerException if event is null
 387:    * @throws PropertyVetoException if the change is vetoed by a listener
 388:    */
 389:   public void fireVetoableChange(PropertyChangeEvent event)
 390:     throws PropertyVetoException
 391:   {
 392:     if (event.oldValue != null && event.oldValue.equals(event.newValue))
 393:       return;
 394:     Vector v = listeners; // Be thread-safe.
 395:     if (v != null)
 396:       {
 397:         int i = v.size();
 398:         try
 399:           {
 400:             while (--i >= 0)
 401:               ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 402:           }
 403:         catch (PropertyVetoException e)
 404:           {
 405:             event = event.rollback();
 406:             int limit = i;
 407:             i = v.size();
 408:             while (--i >= limit)
 409:               ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 410:             throw e;
 411:           }
 412:       }
 413:     Hashtable h = children; // Be thread-safe.
 414:     if (h != null && event.propertyName != null)
 415:       {
 416:         VetoableChangeSupport s
 417:           = (VetoableChangeSupport) h.get(event.propertyName);
 418:         if (s != null)
 419:           {
 420:             Vector v1 = s.listeners; // Be thread-safe.
 421:             int i = v1 == null ? 0 : v1.size();
 422:             try
 423:               {
 424:                 while (--i >= 0)
 425:                   ((VetoableChangeListener) v1.get(i)).vetoableChange(event);
 426:               }
 427:             catch (PropertyVetoException e)
 428:               {
 429:                 event = event.rollback();
 430:                 int limit = i;
 431:                 i = v.size();
 432:                 while (--i >= 0)
 433:                   ((VetoableChangeListener) v.get(i)).vetoableChange(event);
 434:                 i = v1.size();
 435:                 while (--i >= limit)
 436:                   ((VetoableChangeListener) v1.get(i)).vetoableChange(event);
 437:                 throw e;
 438:               }
 439:           }
 440:       }
 441:   }
 442: 
 443:   /**
 444:    * Tell whether the specified property is being listened on or not. This
 445:    * will only return <code>true</code> if there are listeners on all
 446:    * properties or if there is a listener specifically on this property.
 447:    *
 448:    * @param propertyName the property that may be listened on
 449:    * @return whether the property is being listened on
 450:    * @throws NullPointerException if propertyName is null
 451:    */
 452:   public synchronized boolean hasListeners(String propertyName)
 453:   {
 454:     return listeners != null || (children != null
 455:                                  && children.get(propertyName) != null);
 456:   }
 457: 
 458:   /**
 459:    * Saves the state of the object to the stream.
 460:    *
 461:    * @param s the stream to write to
 462:    * @throws IOException if anything goes wrong
 463:    * @serialData this writes out a null-terminated list of serializable
 464:    *             global vetoable change listeners (the listeners for a named
 465:    *             property are written out as the global listeners of the
 466:    *             children, when the children hashtable is saved)
 467:    */
 468:   private synchronized void writeObject(ObjectOutputStream s)
 469:     throws IOException
 470:   {
 471:     s.defaultWriteObject();
 472:     if (listeners != null)
 473:       {
 474:         int i = listeners.size();
 475:         while (--i >= 0)
 476:           if (listeners.get(i) instanceof Serializable)
 477:             s.writeObject(listeners.get(i));
 478:       }
 479:     s.writeObject(null);
 480:   }
 481: 
 482:   /**
 483:    * Reads the object back from stream (deserialization).
 484:    *
 485:    * XXX Since serialization for 1.1 streams was not documented, this may
 486:    * not work if vetoableChangeSupportSerializedDataVersion is 1.
 487:    *
 488:    * @param s the stream to read from
 489:    * @throws IOException if reading the stream fails
 490:    * @throws ClassNotFoundException if deserialization fails
 491:    * @serialData this reads in a null-terminated list of serializable
 492:    *             global vetoable change listeners (the listeners for a named
 493:    *             property are written out as the global listeners of the
 494:    *             children, when the children hashtable is saved)
 495:    */
 496:   private void readObject(ObjectInputStream s)
 497:     throws IOException, ClassNotFoundException
 498:   {
 499:     s.defaultReadObject();
 500:     VetoableChangeListener l = (VetoableChangeListener) s.readObject();
 501:     while (l != null)
 502:       {
 503:         addVetoableChangeListener(l);
 504:         l = (VetoableChangeListener) s.readObject();
 505:       }
 506:     // Sun is not as careful with children as we are, and lets some proxys
 507:     // in that can never receive events. So, we clean up anything that got
 508:     // serialized, to make sure our invariants hold.
 509:     if (children != null)
 510:       {
 511:         int i = children.size();
 512:         Iterator iter = children.entrySet().iterator();
 513:         while (--i >= 0)
 514:           {
 515:             Entry e = (Entry) iter.next();
 516:             String name = (String) e.getKey();
 517:             VetoableChangeSupport vcs = (VetoableChangeSupport) e.getValue();
 518:             if (vcs.listeners == null)
 519:               vcs.listeners = new Vector();
 520:             if (vcs.children != null)
 521:               vcs.listeners.addAll
 522:                 (Arrays.asList(vcs.getVetoableChangeListeners(name)));
 523:             if (vcs.listeners.size() == 0)
 524:               iter.remove();
 525:             else
 526:               vcs.children = null;
 527:           }
 528:         if (children.size() == 0)
 529:           children = null;
 530:       }
 531:   }
 532: } // class VetoableChangeSupport