66 * this.value = newValue; 67 * } 68 * 69 * [...] 70 * } 71 * }</pre> 72 * <p> 73 * A {@code VetoableChangeSupport} 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 PropertyChangeSupport 80 * @since 1.1 81 */ 82 public class VetoableChangeSupport implements Serializable { 83 private VetoableChangeListenerMap map = new VetoableChangeListenerMap(); 84 85 /** 86 * Constructs a <code>VetoableChangeSupport</code> object. 87 * 88 * @param sourceBean The bean to be given as the source for any events. 89 */ 90 public VetoableChangeSupport(Object sourceBean) { 91 if (sourceBean == null) { 92 throw new NullPointerException(); 93 } 94 source = sourceBean; 95 } 96 97 /** 98 * Add a VetoableChangeListener 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 VetoableChangeListener to be added 106 */ 107 public void addVetoableChangeListener(VetoableChangeListener listener) { 108 if (listener == null) { 109 return; 110 } 111 if (listener instanceof VetoableChangeListenerProxy) { 112 VetoableChangeListenerProxy proxy = 113 (VetoableChangeListenerProxy)listener; 114 // Call two argument add method. 115 addVetoableChangeListener(proxy.getPropertyName(), 116 proxy.getListener()); 117 } else { 118 this.map.add(null, listener); 119 } 120 } 121 122 /** 123 * Remove a VetoableChangeListener from the listener list. 124 * This removes a VetoableChangeListener 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 VetoableChangeListener to be removed 132 */ 133 public void removeVetoableChangeListener(VetoableChangeListener listener) { 134 if (listener == null) { 135 return; 136 } 137 if (listener instanceof VetoableChangeListenerProxy) { 138 VetoableChangeListenerProxy proxy = 139 (VetoableChangeListenerProxy)listener; 140 // Call two argument remove method. 141 removeVetoableChangeListener(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 * VetoableChangeSupport object with addVetoableChangeListener(). 151 * <p> 152 * If some listeners have been added with a named property, then 153 * the returned array will be a mixture of VetoableChangeListeners 154 * and <code>VetoableChangeListenerProxy</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>VetoableChangeListenerProxy</code>, perform the cast, and examine 158 * the parameter. 159 * 160 * <pre>{@code 161 * VetoableChangeListener[] listeners = bean.getVetoableChangeListeners(); 162 * for (int i = 0; i < listeners.length; i++) { 163 * if (listeners[i] instanceof VetoableChangeListenerProxy) { 164 * VetoableChangeListenerProxy proxy = 165 * (VetoableChangeListenerProxy)listeners[i]; 166 * if (proxy.getPropertyName().equals("foo")) { 167 * // proxy is a VetoableChangeListener which was associated 168 * // with the property named "foo" 169 * } 170 * } 171 * } 172 * }</pre> 173 * 174 * @see VetoableChangeListenerProxy 175 * @return all of the <code>VetoableChangeListeners</code> added or an 176 * empty array if no listeners have been added 177 * @since 1.4 178 */ 179 public VetoableChangeListener[] getVetoableChangeListeners(){ 180 return this.map.getListeners(); 181 } 182 183 /** 184 * Add a VetoableChangeListener for a specific property. The listener 185 * will be invoked only when a call on fireVetoableChange 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 VetoableChangeListener to be added 195 * @since 1.2 196 */ 197 public void addVetoableChangeListener( 198 String propertyName, 199 VetoableChangeListener 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 VetoableChangeListener 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 VetoableChangeListener to be removed 221 * @since 1.2 222 */ 223 public void removeVetoableChangeListener( 224 String propertyName, 225 VetoableChangeListener 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 the <code>VetoableChangeListeners</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 VetoableChangeListener[] getVetoableChangeListeners(String propertyName) { 247 return this.map.getListeners(propertyName); 248 } 249 250 /** 251 * Reports a constrained 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 * Any listener can throw a {@code PropertyVetoException} to veto the update. 256 * If one of the listeners vetoes the update, this method passes 257 * a new "undo" {@code PropertyChangeEvent} that reverts to the old value 258 * to all listeners that already confirmed this update 259 * and throws the {@code PropertyVetoException} again. 260 * <p> 261 * No event is fired if old and new values are equal and non-null. 262 * <p> 383 } 384 } 385 catch (PropertyVetoException veto) { 386 event = new PropertyChangeEvent(this.source, name, newValue, oldValue); 387 for (int i = 0; i < current; i++) { 388 try { 389 listeners[i].vetoableChange(event); 390 } 391 catch (PropertyVetoException exception) { 392 // ignore exceptions that occur during rolling back 393 } 394 } 395 throw veto; // rethrow the veto exception 396 } 397 } 398 } 399 } 400 401 /** 402 * Check if there are any listeners for a specific property, including 403 * those registered on all properties. If <code>propertyName</code> 404 * is null, only check for listeners registered on all properties. 405 * 406 * @param propertyName the property name. 407 * @return true if there are one or more listeners for the given property 408 * @since 1.2 409 */ 410 public boolean hasListeners(String propertyName) { 411 return this.map.hasListeners(propertyName); 412 } 413 414 /** 415 * @serialData Null terminated list of <code>VetoableChangeListeners</code>. 416 * <p> 417 * At serialization time we skip non-serializable listeners and 418 * only serialize the serializable listeners. 419 */ 420 private void writeObject(ObjectOutputStream s) throws IOException { 421 Hashtable<String, VetoableChangeSupport> children = null; 422 VetoableChangeListener[] listeners = null; 423 synchronized (this.map) { 424 for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) { 425 String property = entry.getKey(); 426 if (property == null) { 427 listeners = entry.getValue(); 428 } else { 429 if (children == null) { 430 children = new Hashtable<>(); 431 } 432 VetoableChangeSupport vcs = new VetoableChangeSupport(this.source); 433 vcs.map.set(null, entry.getValue()); 434 children.put(property, vcs); 435 } | 66 * this.value = newValue; 67 * } 68 * 69 * [...] 70 * } 71 * }</pre> 72 * <p> 73 * A {@code VetoableChangeSupport} 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 PropertyChangeSupport 80 * @since 1.1 81 */ 82 public class VetoableChangeSupport implements Serializable { 83 private VetoableChangeListenerMap map = new VetoableChangeListenerMap(); 84 85 /** 86 * Constructs a {@code VetoableChangeSupport} object. 87 * 88 * @param sourceBean The bean to be given as the source for any events. 89 */ 90 public VetoableChangeSupport(Object sourceBean) { 91 if (sourceBean == null) { 92 throw new NullPointerException(); 93 } 94 source = sourceBean; 95 } 96 97 /** 98 * Add a VetoableChangeListener 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 VetoableChangeListener to be added 106 */ 107 public void addVetoableChangeListener(VetoableChangeListener listener) { 108 if (listener == null) { 109 return; 110 } 111 if (listener instanceof VetoableChangeListenerProxy) { 112 VetoableChangeListenerProxy proxy = 113 (VetoableChangeListenerProxy)listener; 114 // Call two argument add method. 115 addVetoableChangeListener(proxy.getPropertyName(), 116 proxy.getListener()); 117 } else { 118 this.map.add(null, listener); 119 } 120 } 121 122 /** 123 * Remove a VetoableChangeListener from the listener list. 124 * This removes a VetoableChangeListener 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 VetoableChangeListener to be removed 132 */ 133 public void removeVetoableChangeListener(VetoableChangeListener listener) { 134 if (listener == null) { 135 return; 136 } 137 if (listener instanceof VetoableChangeListenerProxy) { 138 VetoableChangeListenerProxy proxy = 139 (VetoableChangeListenerProxy)listener; 140 // Call two argument remove method. 141 removeVetoableChangeListener(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 * VetoableChangeSupport object with addVetoableChangeListener(). 151 * <p> 152 * If some listeners have been added with a named property, then 153 * the returned array will be a mixture of VetoableChangeListeners 154 * and {@code VetoableChangeListenerProxy}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 VetoableChangeListenerProxy}, perform the cast, and examine 158 * the parameter. 159 * 160 * <pre>{@code 161 * VetoableChangeListener[] listeners = bean.getVetoableChangeListeners(); 162 * for (int i = 0; i < listeners.length; i++) { 163 * if (listeners[i] instanceof VetoableChangeListenerProxy) { 164 * VetoableChangeListenerProxy proxy = 165 * (VetoableChangeListenerProxy)listeners[i]; 166 * if (proxy.getPropertyName().equals("foo")) { 167 * // proxy is a VetoableChangeListener which was associated 168 * // with the property named "foo" 169 * } 170 * } 171 * } 172 * }</pre> 173 * 174 * @see VetoableChangeListenerProxy 175 * @return all of the {@code VetoableChangeListeners} added or an 176 * empty array if no listeners have been added 177 * @since 1.4 178 */ 179 public VetoableChangeListener[] getVetoableChangeListeners(){ 180 return this.map.getListeners(); 181 } 182 183 /** 184 * Add a VetoableChangeListener for a specific property. The listener 185 * will be invoked only when a call on fireVetoableChange 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 VetoableChangeListener to be added 195 * @since 1.2 196 */ 197 public void addVetoableChangeListener( 198 String propertyName, 199 VetoableChangeListener 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 VetoableChangeListener 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 VetoableChangeListener to be removed 221 * @since 1.2 222 */ 223 public void removeVetoableChangeListener( 224 String propertyName, 225 VetoableChangeListener 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 the {@code VetoableChangeListeners} 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 VetoableChangeListener[] getVetoableChangeListeners(String propertyName) { 247 return this.map.getListeners(propertyName); 248 } 249 250 /** 251 * Reports a constrained 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 * Any listener can throw a {@code PropertyVetoException} to veto the update. 256 * If one of the listeners vetoes the update, this method passes 257 * a new "undo" {@code PropertyChangeEvent} that reverts to the old value 258 * to all listeners that already confirmed this update 259 * and throws the {@code PropertyVetoException} again. 260 * <p> 261 * No event is fired if old and new values are equal and non-null. 262 * <p> 383 } 384 } 385 catch (PropertyVetoException veto) { 386 event = new PropertyChangeEvent(this.source, name, newValue, oldValue); 387 for (int i = 0; i < current; i++) { 388 try { 389 listeners[i].vetoableChange(event); 390 } 391 catch (PropertyVetoException exception) { 392 // ignore exceptions that occur during rolling back 393 } 394 } 395 throw veto; // rethrow the veto exception 396 } 397 } 398 } 399 } 400 401 /** 402 * Check if there are any listeners for a specific property, including 403 * those registered on all properties. If {@code propertyName} 404 * is null, only check for listeners registered on all properties. 405 * 406 * @param propertyName the property name. 407 * @return true if there are one or more listeners for the given property 408 * @since 1.2 409 */ 410 public boolean hasListeners(String propertyName) { 411 return this.map.hasListeners(propertyName); 412 } 413 414 /** 415 * @serialData Null terminated list of {@code VetoableChangeListeners}. 416 * <p> 417 * At serialization time we skip non-serializable listeners and 418 * only serialize the serializable listeners. 419 */ 420 private void writeObject(ObjectOutputStream s) throws IOException { 421 Hashtable<String, VetoableChangeSupport> children = null; 422 VetoableChangeListener[] listeners = null; 423 synchronized (this.map) { 424 for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) { 425 String property = entry.getKey(); 426 if (property == null) { 427 listeners = entry.getValue(); 428 } else { 429 if (children == null) { 430 children = new Hashtable<>(); 431 } 432 VetoableChangeSupport vcs = new VetoableChangeSupport(this.source); 433 vcs.map.set(null, entry.getValue()); 434 children.put(property, vcs); 435 } |