Frames | No Frames |
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: Integer.valueOf(oldVal), 353: Integer.valueOf(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