1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.beans; 19 20import java.io.IOException; 21import java.io.ObjectInputStream; 22import java.io.ObjectOutputStream; 23import java.io.Serializable; 24import java.util.ArrayList; 25import java.util.HashMap; 26import java.util.Hashtable; 27import java.util.Iterator; 28import java.util.List; 29import java.util.Map; 30 31/** 32 * This utility class 33 * 34 */ 35public class PropertyChangeSupport implements Serializable { 36 37 private static final long serialVersionUID = 6401253773779951803l; 38 39 private transient Object sourceBean; 40 41 private transient List<PropertyChangeListener> allPropertiesChangeListeners = 42 new ArrayList<PropertyChangeListener>(); 43 44 private transient Map<String, List<PropertyChangeListener>> 45 selectedPropertiesChangeListeners = 46 new HashMap<String, List<PropertyChangeListener>>(); 47 48 // fields for serialization compatibility 49 private Hashtable<String, List<PropertyChangeListener>> children; 50 51 private Object source; 52 53 private int propertyChangeSupportSerializedDataVersion = 1; 54 55 /** 56 * Creates a new instance that uses the source bean as source for any event. 57 * 58 * @param sourceBean 59 * the bean used as source for all events. 60 */ 61 public PropertyChangeSupport(Object sourceBean) { 62 if (sourceBean == null) { 63 throw new NullPointerException(); 64 } 65 this.sourceBean = sourceBean; 66 } 67 68 /** 69 * Fires a {@link PropertyChangeEvent} with the given name, old value and 70 * new value. As source the bean used to initialize this instance is used. 71 * If the old value and the new value are not null and equal the event will 72 * not be fired. 73 * 74 * @param propertyName 75 * the name of the property 76 * @param oldValue 77 * the old value of the property 78 * @param newValue 79 * the new value of the property 80 */ 81 public void firePropertyChange(String propertyName, Object oldValue, 82 Object newValue) { 83 PropertyChangeEvent event = createPropertyChangeEvent(propertyName, 84 oldValue, newValue); 85 doFirePropertyChange(event); 86 } 87 88 /** 89 * Fires an {@link IndexedPropertyChangeEvent} with the given name, old 90 * value, new value and index. As source the bean used to initialize this 91 * instance is used. If the old value and the new value are not null and 92 * equal the event will not be fired. 93 * 94 * @param propertyName 95 * the name of the property 96 * @param index 97 * the index 98 * @param oldValue 99 * the old value of the property 100 * @param newValue 101 * the new value of the property 102 */ 103 public void fireIndexedPropertyChange(String propertyName, int index, 104 Object oldValue, Object newValue) { 105 106 // nulls and equals check done in doFire... 107 doFirePropertyChange(new IndexedPropertyChangeEvent(sourceBean, 108 propertyName, oldValue, newValue, index)); 109 } 110 111 /** 112 * Removes the listener from the specific property. This only happens if it 113 * was registered to this property. Nothing happens if it was not 114 * registered with this property or if the property name or the listener is 115 * null. 116 * 117 * @param propertyName 118 * the property name the listener is listening to 119 * @param listener 120 * the listener to remove 121 */ 122 public synchronized void removePropertyChangeListener(String propertyName, 123 PropertyChangeListener listener) { 124 if ((propertyName != null) && (listener != null)) { 125 List<PropertyChangeListener> listeners = 126 selectedPropertiesChangeListeners.get(propertyName); 127 128 if (listeners != null) { 129 listeners.remove(listener); 130 } 131 } 132 } 133 134 /** 135 * Adds a listener to a specific property. Nothing happens if the property 136 * name or the listener is null. 137 * 138 * @param propertyName 139 * the name of the property 140 * @param listener 141 * the listener to register for the property with the given name 142 */ 143 public synchronized void addPropertyChangeListener(String propertyName, 144 PropertyChangeListener listener) { 145 if ((listener != null) && (propertyName != null)) { 146 List<PropertyChangeListener> listeners = 147 selectedPropertiesChangeListeners.get(propertyName); 148 149 if (listeners == null) { 150 listeners = new ArrayList<PropertyChangeListener>(); 151 selectedPropertiesChangeListeners.put(propertyName, listeners); 152 } 153 154 // RI compatibility 155 if (listener instanceof PropertyChangeListenerProxy) { 156 PropertyChangeListenerProxy proxy = 157 (PropertyChangeListenerProxy) listener; 158 159 listeners.add(new PropertyChangeListenerProxy( 160 proxy.getPropertyName(), 161 (PropertyChangeListener) proxy.getListener())); 162 } else { 163 listeners.add(listener); 164 } 165 } 166 } 167 168 /** 169 * Returns an array of listeners that registered to the property with the 170 * given name. If the property name is null an empty array is returned. 171 * 172 * @param propertyName 173 * the name of the property whose listeners should be returned 174 * @return the array of listeners to the property with the given name. 175 */ 176 public synchronized PropertyChangeListener[] getPropertyChangeListeners( 177 String propertyName) { 178 List<PropertyChangeListener> listeners = null; 179 180 if (propertyName != null) { 181 listeners = selectedPropertiesChangeListeners.get(propertyName); 182 } 183 184 return (listeners == null) ? new PropertyChangeListener[] {} 185 : listeners.toArray( 186 new PropertyChangeListener[listeners.size()]); 187 } 188 189 /** 190 * Fires a property change of a boolean property with the given name. If the 191 * old value and the new value are not null and equal the event will not be 192 * fired. 193 * 194 * @param propertyName 195 * the property name 196 * @param oldValue 197 * the old value 198 * @param newValue 199 * the new value 200 */ 201 public void firePropertyChange(String propertyName, boolean oldValue, 202 boolean newValue) { 203 PropertyChangeEvent event = createPropertyChangeEvent(propertyName, 204 oldValue, newValue); 205 doFirePropertyChange(event); 206 } 207 208 /** 209 * Fires a property change of a boolean property with the given name. If the 210 * old value and the new value are not null and equal the event will not be 211 * fired. 212 * 213 * @param propertyName 214 * the property name 215 * @param index 216 * the index of the changed property 217 * @param oldValue 218 * the old value 219 * @param newValue 220 * the new value 221 */ 222 public void fireIndexedPropertyChange(String propertyName, int index, 223 boolean oldValue, boolean newValue) { 224 225 if (oldValue != newValue) { 226 fireIndexedPropertyChange(propertyName, index, Boolean 227 .valueOf(oldValue), Boolean.valueOf(newValue)); 228 } 229 } 230 231 /** 232 * Fires a property change of an integer property with the given name. If 233 * the old value and the new value are not null and equal the event will not 234 * be fired. 235 * 236 * @param propertyName 237 * the property name 238 * @param oldValue 239 * the old value 240 * @param newValue 241 * the new value 242 */ 243 public void firePropertyChange(String propertyName, int oldValue, 244 int newValue) { 245 PropertyChangeEvent event = createPropertyChangeEvent(propertyName, 246 oldValue, newValue); 247 doFirePropertyChange(event); 248 } 249 250 /** 251 * Fires a property change of an integer property with the given name. If 252 * the old value and the new value are not null and equal the event will not 253 * be fired. 254 * 255 * @param propertyName 256 * the property name 257 * @param index 258 * the index of the changed property 259 * @param oldValue 260 * the old value 261 * @param newValue 262 * the new value 263 */ 264 public void fireIndexedPropertyChange(String propertyName, int index, 265 int oldValue, int newValue) { 266 267 if (oldValue != newValue) { 268 fireIndexedPropertyChange(propertyName, index, 269 new Integer(oldValue), new Integer(newValue)); 270 } 271 } 272 273 /** 274 * Returns true if there are listeners registered to the property with the 275 * given name. 276 * 277 * @param propertyName 278 * the name of the property 279 * @return true if there are listeners registered to that property, false 280 * otherwise. 281 */ 282 public synchronized boolean hasListeners(String propertyName) { 283 boolean result = allPropertiesChangeListeners.size() > 0; 284 if (!result && (propertyName != null)) { 285 List<PropertyChangeListener> listeners = 286 selectedPropertiesChangeListeners.get(propertyName); 287 if (listeners != null) { 288 result = listeners.size() > 0; 289 } 290 } 291 return result; 292 } 293 294 /** 295 * removes a property change listener that was registered to all properties. 296 * 297 * @param listener 298 * the listener to remove 299 */ 300 public synchronized void removePropertyChangeListener( 301 PropertyChangeListener listener) { 302 if (listener != null) { 303 if (listener instanceof PropertyChangeListenerProxy) { 304 String name = ((PropertyChangeListenerProxy) listener) 305 .getPropertyName(); 306 PropertyChangeListener lst = (PropertyChangeListener) 307 ((PropertyChangeListenerProxy) listener).getListener(); 308 309 removePropertyChangeListener(name, lst); 310 } else { 311 allPropertiesChangeListeners.remove(listener); 312 } 313 } 314 } 315 316 /** 317 * Registers a listener with all properties. 318 * 319 * @param listener 320 * the listener to register 321 */ 322 public synchronized void addPropertyChangeListener( 323 PropertyChangeListener listener) { 324 if (listener != null) { 325 if (listener instanceof PropertyChangeListenerProxy) { 326 String name = ((PropertyChangeListenerProxy) listener) 327 .getPropertyName(); 328 PropertyChangeListener lst = (PropertyChangeListener) 329 ((PropertyChangeListenerProxy) listener).getListener(); 330 addPropertyChangeListener(name, lst); 331 } else { 332 allPropertiesChangeListeners.add(listener); 333 } 334 } 335 } 336 337 /** 338 * Returns an array with the listeners that registered to all properties. 339 * 340 * @return the array of listeners 341 */ 342 public synchronized PropertyChangeListener[] getPropertyChangeListeners() { 343 ArrayList<PropertyChangeListener> result = 344 new ArrayList<PropertyChangeListener>( 345 allPropertiesChangeListeners); 346 347 for (String propertyName : selectedPropertiesChangeListeners.keySet()) { 348 List<PropertyChangeListener> selectedListeners = 349 selectedPropertiesChangeListeners.get(propertyName); 350 351 if (selectedListeners != null) { 352 353 for (PropertyChangeListener listener : selectedListeners) { 354 result.add(new PropertyChangeListenerProxy(propertyName, 355 listener)); 356 } 357 } 358 } 359 360 return result.toArray(new PropertyChangeListener[result.size()]); 361 } 362 363 private void writeObject(ObjectOutputStream oos) throws IOException { 364 List<PropertyChangeListener> allSerializedPropertiesChangeListeners = 365 new ArrayList<PropertyChangeListener>(); 366 367 for (PropertyChangeListener pcl : allPropertiesChangeListeners) { 368 if (pcl instanceof Serializable) { 369 allSerializedPropertiesChangeListeners.add(pcl); 370 } 371 } 372 373 Map<String, List<PropertyChangeListener>> 374 selectedSerializedPropertiesChangeListeners = 375 new HashMap<String, List<PropertyChangeListener>>(); 376 377 for (String propertyName : selectedPropertiesChangeListeners.keySet()) { 378 List<PropertyChangeListener> keyValues = 379 selectedPropertiesChangeListeners.get(propertyName); 380 381 if (keyValues != null) { 382 List<PropertyChangeListener> serializedPropertiesChangeListeners 383 = new ArrayList<PropertyChangeListener>(); 384 385 for (PropertyChangeListener pcl : keyValues) { 386 if (pcl instanceof Serializable) { 387 serializedPropertiesChangeListeners.add(pcl); 388 } 389 } 390 391 if (!serializedPropertiesChangeListeners.isEmpty()) { 392 selectedSerializedPropertiesChangeListeners.put( 393 propertyName, serializedPropertiesChangeListeners); 394 } 395 } 396 } 397 398 children = new Hashtable<String, List<PropertyChangeListener>>( 399 selectedSerializedPropertiesChangeListeners); 400 children.put("", allSerializedPropertiesChangeListeners); //$NON-NLS-1$ 401 oos.writeObject(children); 402 403 Object source = null; 404 if (sourceBean instanceof Serializable) { 405 source = sourceBean; 406 } 407 oos.writeObject(source); 408 409 oos.writeInt(propertyChangeSupportSerializedDataVersion); 410 } 411 412 @SuppressWarnings("unchecked") 413 private void readObject(ObjectInputStream ois) throws IOException, 414 ClassNotFoundException { 415 children = (Hashtable<String, List<PropertyChangeListener>>) ois 416 .readObject(); 417 418 selectedPropertiesChangeListeners = new HashMap<String, List<PropertyChangeListener>>( 419 children); 420 allPropertiesChangeListeners = selectedPropertiesChangeListeners 421 .remove(""); //$NON-NLS-1$ 422 if (allPropertiesChangeListeners == null) { 423 allPropertiesChangeListeners = new ArrayList<PropertyChangeListener>(); 424 } 425 426 sourceBean = ois.readObject(); 427 propertyChangeSupportSerializedDataVersion = ois.readInt(); 428 } 429 430 /** 431 * Fires a property change event to all listeners of that property. 432 * 433 * @param event 434 * the event to fire 435 */ 436 public void firePropertyChange(PropertyChangeEvent event) { 437 doFirePropertyChange(event); 438 } 439 440 private PropertyChangeEvent createPropertyChangeEvent(String propertyName, 441 Object oldValue, Object newValue) { 442 return new PropertyChangeEvent(sourceBean, propertyName, oldValue, 443 newValue); 444 } 445 446 private PropertyChangeEvent createPropertyChangeEvent(String propertyName, 447 boolean oldValue, boolean newValue) { 448 return new PropertyChangeEvent(sourceBean, propertyName, oldValue, 449 newValue); 450 } 451 452 private PropertyChangeEvent createPropertyChangeEvent(String propertyName, 453 int oldValue, int newValue) { 454 return new PropertyChangeEvent(sourceBean, propertyName, oldValue, 455 newValue); 456 } 457 458 private void doFirePropertyChange(PropertyChangeEvent event) { 459 String propertyName = event.getPropertyName(); 460 Object oldValue = event.getOldValue(); 461 Object newValue = event.getNewValue(); 462 463 if ((newValue != null) && (oldValue != null) 464 && newValue.equals(oldValue)) { 465 return; 466 } 467 468 /* 469 * Copy the listeners collections so they can be modified while we fire 470 * events. 471 */ 472 473 // Listeners to all property change events 474 PropertyChangeListener[] listensToAll; 475 // Listens to a given property change 476 PropertyChangeListener[] listensToOne = null; 477 synchronized (this) { 478 listensToAll = allPropertiesChangeListeners 479 .toArray(new PropertyChangeListener[allPropertiesChangeListeners 480 .size()]); 481 482 List<PropertyChangeListener> listeners = selectedPropertiesChangeListeners 483 .get(propertyName); 484 if (listeners != null) { 485 listensToOne = listeners 486 .toArray(new PropertyChangeListener[listeners.size()]); 487 } 488 } 489 490 // Fire the listeners 491 for (PropertyChangeListener listener : listensToAll) { 492 listener.propertyChange(event); 493 } 494 if (listensToOne != null) { 495 for (PropertyChangeListener listener : listensToOne) { 496 listener.propertyChange(event); 497 } 498 } 499 } 500 501} 502