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