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