001 /* PropertyChangeSupport.java -- support to manage property change listeners 002 Copyright (C) 1998, 1999, 2000, 2002, 2005, 2006 003 Free Software Foundation, Inc. 004 005 This file is part of GNU Classpath. 006 007 GNU Classpath is free software; you can redistribute it and/or modify 008 it under the terms of the GNU General Public License as published by 009 the Free Software Foundation; either version 2, or (at your option) 010 any later version. 011 012 GNU Classpath is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of 014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 General Public License for more details. 016 017 You should have received a copy of the GNU General Public License 018 along with GNU Classpath; see the file COPYING. If not, write to the 019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 020 02110-1301 USA. 021 022 Linking this library statically or dynamically with other modules is 023 making a combined work based on this library. Thus, the terms and 024 conditions of the GNU General Public License cover the whole 025 combination. 026 027 As a special exception, the copyright holders of this library give you 028 permission to link this library with independent modules to produce an 029 executable, regardless of the license terms of these independent 030 modules, and to copy and distribute the resulting executable under 031 terms of your choice, provided that you also meet, for each linked 032 independent module, the terms and conditions of the license of that 033 module. An independent module is a module which is not derived from 034 or based on this library. If you modify this library, you may extend 035 this exception to your version of the library, but you are not 036 obligated to do so. If you do not wish to do so, delete this 037 exception statement from your version. */ 038 039 040 package java.beans; 041 042 import java.io.IOException; 043 import java.io.ObjectInputStream; 044 import java.io.ObjectOutputStream; 045 import java.io.Serializable; 046 import java.util.ArrayList; 047 import java.util.Arrays; 048 import java.util.Hashtable; 049 import java.util.Iterator; 050 import java.util.Map.Entry; 051 import java.util.Vector; 052 053 /** 054 * PropertyChangeSupport makes it easy to fire property change events and 055 * handle listeners. It allows chaining of listeners, as well as filtering 056 * by property name. In addition, it will serialize only those listeners 057 * which are serializable, ignoring the others without problem. This class 058 * is thread-safe. 059 * 060 * @author John Keiser 061 * @author Eric Blake (ebb9@email.byu.edu) 062 * @since 1.1 063 * @status updated to 1.4 064 */ 065 public class PropertyChangeSupport implements Serializable 066 { 067 /** 068 * Compatible with JDK 1.1+. 069 */ 070 private static final long serialVersionUID = 6401253773779951803L; 071 072 /** 073 * Maps property names (String) to named listeners (PropertyChangeSupport). 074 * If this is a child instance, this field will be null. 075 * 076 * @serial the map of property names to named listener managers 077 * @since 1.2 078 */ 079 private Hashtable children; 080 081 /** 082 * The non-null source object for any generated events. 083 * 084 * @serial the event source 085 */ 086 private final Object source; 087 088 /** 089 * A field to compare serialization versions - this class uses version 2. 090 * 091 * @serial the serialization format 092 */ 093 private static final int propertyChangeSupportSerializedDataVersion = 2; 094 095 /** 096 * The list of all registered property listeners. If this instance was 097 * created by user code, this only holds the global listeners (ie. not tied 098 * to a name), and may be null. If it was created by this class, as a 099 * 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 Integer.valueOf(oldVal), 350 Integer.valueOf(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