GNU Classpath (0.20) | |
Frames | No Frames |
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
GNU Classpath (0.20) |