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 */ VetoableChangeSupport(Object source)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 */ addVetoableChangeListener(VetoableChangeListener l)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 removeVetoableChangeListener(VetoableChangeListener l)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 */ getVetoableChangeListeners()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 */ addVetoableChangeListener(String propertyName, VetoableChangeListener l)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 removeVetoableChangeListener(String propertyName, VetoableChangeListener l)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[] getVetoableChangeListeners(String propertyName)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 */ fireVetoableChange(String propertyName, Object oldVal, Object newVal)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 */ fireVetoableChange(String propertyName, int oldVal, int newVal)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 */ fireVetoableChange(String propertyName, boolean oldVal, boolean newVal)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 */ fireVetoableChange(PropertyChangeEvent event)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 */ hasListeners(String propertyName)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 */ writeObject(ObjectOutputStream s)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 */ readObject(ObjectInputStream s)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 533