66 * this.pcs.firePropertyChange("value", oldValue, newValue); 67 * } 68 * 69 * [...] 70 * } 71 * </pre> 72 * <p> 73 * A {@code PropertyChangeSupport} instance is thread-safe. 74 * <p> 75 * This class is serializable. When it is serialized it will save 76 * (and restore) any listeners that are themselves serializable. Any 77 * non-serializable listeners will be skipped during serialization. 78 * 79 * @see VetoableChangeSupport 80 * @since 1.1 81 */ 82 public class PropertyChangeSupport implements Serializable { 83 private PropertyChangeListenerMap map = new PropertyChangeListenerMap(); 84 85 /** 86 * Constructs a <code>PropertyChangeSupport</code> object. 87 * 88 * @param sourceBean The bean to be given as the source for any events. 89 */ 90 public PropertyChangeSupport(Object sourceBean) { 91 if (sourceBean == null) { 92 throw new NullPointerException(); 93 } 94 source = sourceBean; 95 } 96 97 /** 98 * Add a PropertyChangeListener to the listener list. 99 * The listener is registered for all properties. 100 * The same listener object may be added more than once, and will be called 101 * as many times as it is added. 102 * If <code>listener</code> is null, no exception is thrown and no action 103 * is taken. 104 * 105 * @param listener The PropertyChangeListener to be added 106 */ 107 public void addPropertyChangeListener(PropertyChangeListener listener) { 108 if (listener == null) { 109 return; 110 } 111 if (listener instanceof PropertyChangeListenerProxy) { 112 PropertyChangeListenerProxy proxy = 113 (PropertyChangeListenerProxy)listener; 114 // Call two argument add method. 115 addPropertyChangeListener(proxy.getPropertyName(), 116 proxy.getListener()); 117 } else { 118 this.map.add(null, listener); 119 } 120 } 121 122 /** 123 * Remove a PropertyChangeListener from the listener list. 124 * This removes a PropertyChangeListener that was registered 125 * for all properties. 126 * If <code>listener</code> was added more than once to the same event 127 * source, it will be notified one less time after being removed. 128 * If <code>listener</code> is null, or was never added, no exception is 129 * thrown and no action is taken. 130 * 131 * @param listener The PropertyChangeListener to be removed 132 */ 133 public void removePropertyChangeListener(PropertyChangeListener listener) { 134 if (listener == null) { 135 return; 136 } 137 if (listener instanceof PropertyChangeListenerProxy) { 138 PropertyChangeListenerProxy proxy = 139 (PropertyChangeListenerProxy)listener; 140 // Call two argument remove method. 141 removePropertyChangeListener(proxy.getPropertyName(), 142 proxy.getListener()); 143 } else { 144 this.map.remove(null, listener); 145 } 146 } 147 148 /** 149 * Returns an array of all the listeners that were added to the 150 * PropertyChangeSupport object with addPropertyChangeListener(). 151 * <p> 152 * If some listeners have been added with a named property, then 153 * the returned array will be a mixture of PropertyChangeListeners 154 * and <code>PropertyChangeListenerProxy</code>s. If the calling 155 * method is interested in distinguishing the listeners then it must 156 * test each element to see if it's a 157 * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine 158 * the parameter. 159 * 160 * <pre>{@code 161 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners(); 162 * for (int i = 0; i < listeners.length; i++) { 163 * if (listeners[i] instanceof PropertyChangeListenerProxy) { 164 * PropertyChangeListenerProxy proxy = 165 * (PropertyChangeListenerProxy)listeners[i]; 166 * if (proxy.getPropertyName().equals("foo")) { 167 * // proxy is a PropertyChangeListener which was associated 168 * // with the property named "foo" 169 * } 170 * } 171 * } 172 * }</pre> 173 * 174 * @see PropertyChangeListenerProxy 175 * @return all of the <code>PropertyChangeListeners</code> added or an 176 * empty array if no listeners have been added 177 * @since 1.4 178 */ 179 public PropertyChangeListener[] getPropertyChangeListeners() { 180 return this.map.getListeners(); 181 } 182 183 /** 184 * Add a PropertyChangeListener for a specific property. The listener 185 * will be invoked only when a call on firePropertyChange names that 186 * specific property. 187 * The same listener object may be added more than once. For each 188 * property, the listener will be invoked the number of times it was added 189 * for that property. 190 * If <code>propertyName</code> or <code>listener</code> is null, no 191 * exception is thrown and no action is taken. 192 * 193 * @param propertyName The name of the property to listen on. 194 * @param listener The PropertyChangeListener to be added 195 * @since 1.2 196 */ 197 public void addPropertyChangeListener( 198 String propertyName, 199 PropertyChangeListener listener) { 200 if (listener == null || propertyName == null) { 201 return; 202 } 203 listener = this.map.extract(listener); 204 if (listener != null) { 205 this.map.add(propertyName, listener); 206 } 207 } 208 209 /** 210 * Remove a PropertyChangeListener for a specific property. 211 * If <code>listener</code> was added more than once to the same event 212 * source for the specified property, it will be notified one less time 213 * after being removed. 214 * If <code>propertyName</code> is null, no exception is thrown and no 215 * action is taken. 216 * If <code>listener</code> is null, or was never added for the specified 217 * property, no exception is thrown and no action is taken. 218 * 219 * @param propertyName The name of the property that was listened on. 220 * @param listener The PropertyChangeListener to be removed 221 * @since 1.2 222 */ 223 public void removePropertyChangeListener( 224 String propertyName, 225 PropertyChangeListener listener) { 226 if (listener == null || propertyName == null) { 227 return; 228 } 229 listener = this.map.extract(listener); 230 if (listener != null) { 231 this.map.remove(propertyName, listener); 232 } 233 } 234 235 /** 236 * Returns an array of all the listeners which have been associated 237 * with the named property. 238 * 239 * @param propertyName The name of the property being listened to 240 * @return all of the <code>PropertyChangeListeners</code> associated with 241 * the named property. If no such listeners have been added, 242 * or if <code>propertyName</code> is null, an empty array is 243 * returned. 244 * @since 1.4 245 */ 246 public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 247 return this.map.getListeners(propertyName); 248 } 249 250 /** 251 * Reports a bound property update to listeners 252 * that have been registered to track updates of 253 * all properties or a property with the specified name. 254 * <p> 255 * No event is fired if old and new values are equal and non-null. 256 * <p> 257 * This is merely a convenience wrapper around the more general 258 * {@link #firePropertyChange(PropertyChangeEvent)} method. 259 * 260 * @param propertyName the programmatic name of the property that was changed 261 * @param oldValue the old value of the property 262 * @param newValue the new value of the property 394 * <p> 395 * No event is fired if old and new values are equal. 396 * <p> 397 * This is merely a convenience wrapper around the more general 398 * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method. 399 * 400 * @param propertyName the programmatic name of the property that was changed 401 * @param index the index of the property element that was changed 402 * @param oldValue the old value of the property 403 * @param newValue the new value of the property 404 * @since 1.5 405 */ 406 public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) { 407 if (oldValue != newValue) { 408 fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); 409 } 410 } 411 412 /** 413 * Check if there are any listeners for a specific property, including 414 * those registered on all properties. If <code>propertyName</code> 415 * is null, only check for listeners registered on all properties. 416 * 417 * @param propertyName the property name. 418 * @return true if there are one or more listeners for the given property 419 * @since 1.2 420 */ 421 public boolean hasListeners(String propertyName) { 422 return this.map.hasListeners(propertyName); 423 } 424 425 /** 426 * @serialData Null terminated list of <code>PropertyChangeListeners</code>. 427 * <p> 428 * At serialization time we skip non-serializable listeners and 429 * only serialize the serializable listeners. 430 */ 431 private void writeObject(ObjectOutputStream s) throws IOException { 432 Hashtable<String, PropertyChangeSupport> children = null; 433 PropertyChangeListener[] listeners = null; 434 synchronized (this.map) { 435 for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) { 436 String property = entry.getKey(); 437 if (property == null) { 438 listeners = entry.getValue(); 439 } else { 440 if (children == null) { 441 children = new Hashtable<>(); 442 } 443 PropertyChangeSupport pcs = new PropertyChangeSupport(this.source); 444 pcs.map.set(null, entry.getValue()); 445 children.put(property, pcs); 446 } | 66 * this.pcs.firePropertyChange("value", oldValue, newValue); 67 * } 68 * 69 * [...] 70 * } 71 * </pre> 72 * <p> 73 * A {@code PropertyChangeSupport} instance is thread-safe. 74 * <p> 75 * This class is serializable. When it is serialized it will save 76 * (and restore) any listeners that are themselves serializable. Any 77 * non-serializable listeners will be skipped during serialization. 78 * 79 * @see VetoableChangeSupport 80 * @since 1.1 81 */ 82 public class PropertyChangeSupport implements Serializable { 83 private PropertyChangeListenerMap map = new PropertyChangeListenerMap(); 84 85 /** 86 * Constructs a {@code PropertyChangeSupport} object. 87 * 88 * @param sourceBean The bean to be given as the source for any events. 89 */ 90 public PropertyChangeSupport(Object sourceBean) { 91 if (sourceBean == null) { 92 throw new NullPointerException(); 93 } 94 source = sourceBean; 95 } 96 97 /** 98 * Add a PropertyChangeListener to the listener list. 99 * The listener is registered for all properties. 100 * The same listener object may be added more than once, and will be called 101 * as many times as it is added. 102 * If {@code listener} is null, no exception is thrown and no action 103 * is taken. 104 * 105 * @param listener The PropertyChangeListener to be added 106 */ 107 public void addPropertyChangeListener(PropertyChangeListener listener) { 108 if (listener == null) { 109 return; 110 } 111 if (listener instanceof PropertyChangeListenerProxy) { 112 PropertyChangeListenerProxy proxy = 113 (PropertyChangeListenerProxy)listener; 114 // Call two argument add method. 115 addPropertyChangeListener(proxy.getPropertyName(), 116 proxy.getListener()); 117 } else { 118 this.map.add(null, listener); 119 } 120 } 121 122 /** 123 * Remove a PropertyChangeListener from the listener list. 124 * This removes a PropertyChangeListener that was registered 125 * for all properties. 126 * If {@code listener} was added more than once to the same event 127 * source, it will be notified one less time after being removed. 128 * If {@code listener} is null, or was never added, no exception is 129 * thrown and no action is taken. 130 * 131 * @param listener The PropertyChangeListener to be removed 132 */ 133 public void removePropertyChangeListener(PropertyChangeListener listener) { 134 if (listener == null) { 135 return; 136 } 137 if (listener instanceof PropertyChangeListenerProxy) { 138 PropertyChangeListenerProxy proxy = 139 (PropertyChangeListenerProxy)listener; 140 // Call two argument remove method. 141 removePropertyChangeListener(proxy.getPropertyName(), 142 proxy.getListener()); 143 } else { 144 this.map.remove(null, listener); 145 } 146 } 147 148 /** 149 * Returns an array of all the listeners that were added to the 150 * PropertyChangeSupport object with addPropertyChangeListener(). 151 * <p> 152 * If some listeners have been added with a named property, then 153 * the returned array will be a mixture of PropertyChangeListeners 154 * and {@code PropertyChangeListenerProxy}s. If the calling 155 * method is interested in distinguishing the listeners then it must 156 * test each element to see if it's a 157 * {@code PropertyChangeListenerProxy}, perform the cast, and examine 158 * the parameter. 159 * 160 * <pre>{@code 161 * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners(); 162 * for (int i = 0; i < listeners.length; i++) { 163 * if (listeners[i] instanceof PropertyChangeListenerProxy) { 164 * PropertyChangeListenerProxy proxy = 165 * (PropertyChangeListenerProxy)listeners[i]; 166 * if (proxy.getPropertyName().equals("foo")) { 167 * // proxy is a PropertyChangeListener which was associated 168 * // with the property named "foo" 169 * } 170 * } 171 * } 172 * }</pre> 173 * 174 * @see PropertyChangeListenerProxy 175 * @return all of the {@code PropertyChangeListeners} added or an 176 * empty array if no listeners have been added 177 * @since 1.4 178 */ 179 public PropertyChangeListener[] getPropertyChangeListeners() { 180 return this.map.getListeners(); 181 } 182 183 /** 184 * Add a PropertyChangeListener for a specific property. The listener 185 * will be invoked only when a call on firePropertyChange names that 186 * specific property. 187 * The same listener object may be added more than once. For each 188 * property, the listener will be invoked the number of times it was added 189 * for that property. 190 * If {@code propertyName} or {@code listener} is null, no 191 * exception is thrown and no action is taken. 192 * 193 * @param propertyName The name of the property to listen on. 194 * @param listener The PropertyChangeListener to be added 195 * @since 1.2 196 */ 197 public void addPropertyChangeListener( 198 String propertyName, 199 PropertyChangeListener listener) { 200 if (listener == null || propertyName == null) { 201 return; 202 } 203 listener = this.map.extract(listener); 204 if (listener != null) { 205 this.map.add(propertyName, listener); 206 } 207 } 208 209 /** 210 * Remove a PropertyChangeListener for a specific property. 211 * If {@code listener} was added more than once to the same event 212 * source for the specified property, it will be notified one less time 213 * after being removed. 214 * If {@code propertyName} is null, no exception is thrown and no 215 * action is taken. 216 * If {@code listener} is null, or was never added for the specified 217 * property, no exception is thrown and no action is taken. 218 * 219 * @param propertyName The name of the property that was listened on. 220 * @param listener The PropertyChangeListener to be removed 221 * @since 1.2 222 */ 223 public void removePropertyChangeListener( 224 String propertyName, 225 PropertyChangeListener listener) { 226 if (listener == null || propertyName == null) { 227 return; 228 } 229 listener = this.map.extract(listener); 230 if (listener != null) { 231 this.map.remove(propertyName, listener); 232 } 233 } 234 235 /** 236 * Returns an array of all the listeners which have been associated 237 * with the named property. 238 * 239 * @param propertyName The name of the property being listened to 240 * @return all of the {@code PropertyChangeListeners} associated with 241 * the named property. If no such listeners have been added, 242 * or if {@code propertyName} is null, an empty array is 243 * returned. 244 * @since 1.4 245 */ 246 public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) { 247 return this.map.getListeners(propertyName); 248 } 249 250 /** 251 * Reports a bound property update to listeners 252 * that have been registered to track updates of 253 * all properties or a property with the specified name. 254 * <p> 255 * No event is fired if old and new values are equal and non-null. 256 * <p> 257 * This is merely a convenience wrapper around the more general 258 * {@link #firePropertyChange(PropertyChangeEvent)} method. 259 * 260 * @param propertyName the programmatic name of the property that was changed 261 * @param oldValue the old value of the property 262 * @param newValue the new value of the property 394 * <p> 395 * No event is fired if old and new values are equal. 396 * <p> 397 * This is merely a convenience wrapper around the more general 398 * {@link #fireIndexedPropertyChange(String, int, Object, Object)} method. 399 * 400 * @param propertyName the programmatic name of the property that was changed 401 * @param index the index of the property element that was changed 402 * @param oldValue the old value of the property 403 * @param newValue the new value of the property 404 * @since 1.5 405 */ 406 public void fireIndexedPropertyChange(String propertyName, int index, boolean oldValue, boolean newValue) { 407 if (oldValue != newValue) { 408 fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); 409 } 410 } 411 412 /** 413 * Check if there are any listeners for a specific property, including 414 * those registered on all properties. If {@code propertyName} 415 * is null, only check for listeners registered on all properties. 416 * 417 * @param propertyName the property name. 418 * @return true if there are one or more listeners for the given property 419 * @since 1.2 420 */ 421 public boolean hasListeners(String propertyName) { 422 return this.map.hasListeners(propertyName); 423 } 424 425 /** 426 * @serialData Null terminated list of {@code PropertyChangeListeners}. 427 * <p> 428 * At serialization time we skip non-serializable listeners and 429 * only serialize the serializable listeners. 430 */ 431 private void writeObject(ObjectOutputStream s) throws IOException { 432 Hashtable<String, PropertyChangeSupport> children = null; 433 PropertyChangeListener[] listeners = null; 434 synchronized (this.map) { 435 for (Entry<String, PropertyChangeListener[]> entry : this.map.getEntries()) { 436 String property = entry.getKey(); 437 if (property == null) { 438 listeners = entry.getValue(); 439 } else { 440 if (children == null) { 441 children = new Hashtable<>(); 442 } 443 PropertyChangeSupport pcs = new PropertyChangeSupport(this.source); 444 pcs.map.set(null, entry.getValue()); 445 children.put(property, pcs); 446 } |