blob: 7d1418e17d2ab1a6713a28a4d037b410f8d293d3 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25package java.beans;
26
27import java.io.Serializable;
28import java.io.ObjectStreamField;
29import java.io.ObjectOutputStream;
30import java.io.ObjectInputStream;
31import java.io.IOException;
32import java.util.Hashtable;
33import java.util.Map.Entry;
34
35/**
36 * This is a utility class that can be used by beans that support constrained
37 * properties. You can use an instance of this class as a member field
38 * of your bean and delegate various work to it.
39 *
40 * This class is serializable. When it is serialized it will save
41 * (and restore) any listeners that are themselves serializable. Any
42 * non-serializable listeners will be skipped during serialization.
43 */
44public class VetoableChangeSupport implements Serializable {
45 private VetoableChangeListenerMap map = new VetoableChangeListenerMap();
46
47 /**
48 * Constructs a <code>VetoableChangeSupport</code> object.
49 *
50 * @param sourceBean The bean to be given as the source for any events.
51 */
52 public VetoableChangeSupport(Object sourceBean) {
53 if (sourceBean == null) {
54 throw new NullPointerException();
55 }
56 source = sourceBean;
57 }
58
59 /**
60 * Add a VetoableChangeListener to the listener list.
61 * The listener is registered for all properties.
62 * The same listener object may be added more than once, and will be called
63 * as many times as it is added.
64 * If <code>listener</code> is null, no exception is thrown and no action
65 * is taken.
66 *
67 * @param listener The VetoableChangeListener to be added
68 */
69 public void addVetoableChangeListener(VetoableChangeListener listener) {
70 if (listener == null) {
71 return;
72 }
73 if (listener instanceof VetoableChangeListenerProxy) {
74 VetoableChangeListenerProxy proxy =
75 (VetoableChangeListenerProxy)listener;
76 // Call two argument add method.
77 addVetoableChangeListener(proxy.getPropertyName(),
78 proxy.getListener());
79 } else {
80 this.map.add(null, listener);
81 }
82 }
83
84 /**
85 * Remove a VetoableChangeListener from the listener list.
86 * This removes a VetoableChangeListener that was registered
87 * for all properties.
88 * If <code>listener</code> was added more than once to the same event
89 * source, it will be notified one less time after being removed.
90 * If <code>listener</code> is null, or was never added, no exception is
91 * thrown and no action is taken.
92 *
93 * @param listener The VetoableChangeListener to be removed
94 */
95 public void removeVetoableChangeListener(VetoableChangeListener listener) {
96 if (listener == null) {
97 return;
98 }
99 if (listener instanceof VetoableChangeListenerProxy) {
100 VetoableChangeListenerProxy proxy =
101 (VetoableChangeListenerProxy)listener;
102 // Call two argument remove method.
103 removeVetoableChangeListener(proxy.getPropertyName(),
104 proxy.getListener());
105 } else {
106 this.map.remove(null, listener);
107 }
108 }
109
110 /**
111 * Returns an array of all the listeners that were added to the
112 * VetoableChangeSupport object with addVetoableChangeListener().
113 * <p>
114 * If some listeners have been added with a named property, then
115 * the returned array will be a mixture of VetoableChangeListeners
116 * and <code>VetoableChangeListenerProxy</code>s. If the calling
117 * method is interested in distinguishing the listeners then it must
118 * test each element to see if it's a
119 * <code>VetoableChangeListenerProxy</code>, perform the cast, and examine
120 * the parameter.
121 *
122 * <pre>
123 * VetoableChangeListener[] listeners = bean.getVetoableChangeListeners();
124 * for (int i = 0; i < listeners.length; i++) {
125 * if (listeners[i] instanceof VetoableChangeListenerProxy) {
126 * VetoableChangeListenerProxy proxy =
127 * (VetoableChangeListenerProxy)listeners[i];
128 * if (proxy.getPropertyName().equals("foo")) {
129 * // proxy is a VetoableChangeListener which was associated
130 * // with the property named "foo"
131 * }
132 * }
133 * }
134 *</pre>
135 *
136 * @see VetoableChangeListenerProxy
137 * @return all of the <code>VetoableChangeListeners</code> added or an
138 * empty array if no listeners have been added
139 * @since 1.4
140 */
141 public VetoableChangeListener[] getVetoableChangeListeners(){
142 return this.map.getListeners();
143 }
144
145 /**
146 * Add a VetoableChangeListener for a specific property. The listener
147 * will be invoked only when a call on fireVetoableChange names that
148 * specific property.
149 * The same listener object may be added more than once. For each
150 * property, the listener will be invoked the number of times it was added
151 * for that property.
152 * If <code>propertyName</code> or <code>listener</code> is null, no
153 * exception is thrown and no action is taken.
154 *
155 * @param propertyName The name of the property to listen on.
156 * @param listener The VetoableChangeListener to be added
157 */
158 public void addVetoableChangeListener(
159 String propertyName,
160 VetoableChangeListener listener) {
161 if (listener == null || propertyName == null) {
162 return;
163 }
164 listener = this.map.extract(listener);
165 if (listener != null) {
166 this.map.add(propertyName, listener);
167 }
168 }
169
170 /**
171 * Remove a VetoableChangeListener for a specific property.
172 * If <code>listener</code> was added more than once to the same event
173 * source for the specified property, it will be notified one less time
174 * after being removed.
175 * If <code>propertyName</code> is null, no exception is thrown and no
176 * action is taken.
177 * If <code>listener</code> is null, or was never added for the specified
178 * property, no exception is thrown and no action is taken.
179 *
180 * @param propertyName The name of the property that was listened on.
181 * @param listener The VetoableChangeListener to be removed
182 */
183 public void removeVetoableChangeListener(
184 String propertyName,
185 VetoableChangeListener listener) {
186 if (listener == null || propertyName == null) {
187 return;
188 }
189 listener = this.map.extract(listener);
190 if (listener != null) {
191 this.map.remove(propertyName, listener);
192 }
193 }
194
195 /**
196 * Returns an array of all the listeners which have been associated
197 * with the named property.
198 *
199 * @param propertyName The name of the property being listened to
200 * @return all the <code>VetoableChangeListeners</code> associated with
201 * the named property. If no such listeners have been added,
202 * or if <code>propertyName</code> is null, an empty array is
203 * returned.
204 * @since 1.4
205 */
206 public VetoableChangeListener[] getVetoableChangeListeners(String propertyName) {
207 return this.map.getListeners(propertyName);
208 }
209
210 /**
211 * Report a vetoable property update to any registered listeners. If
212 * anyone vetos the change, then fire a new event reverting everyone to
213 * the old value and then rethrow the PropertyVetoException.
214 * <p>
215 * No event is fired if old and new are equal and non-null.
216 *
217 * @param propertyName The programmatic name of the property
218 * that is about to change..
219 * @param oldValue The old value of the property.
220 * @param newValue The new value of the property.
221 * @exception PropertyVetoException if the recipient wishes the property
222 * change to be rolled back.
223 */
224 public void fireVetoableChange(String propertyName,
225 Object oldValue, Object newValue)
226 throws PropertyVetoException {
227 if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
228 return;
229 }
230 PropertyChangeEvent evt = new PropertyChangeEvent(source, propertyName,
231 oldValue, newValue);
232 fireVetoableChange(evt);
233 }
234
235 /**
236 * Report a int vetoable property update to any registered listeners.
237 * No event is fired if old and new are equal.
238 * <p>
239 * This is merely a convenience wrapper around the more general
240 * fireVetoableChange method that takes Object values.
241 *
242 * @param propertyName The programmatic name of the property
243 * that is about to change.
244 * @param oldValue The old value of the property.
245 * @param newValue The new value of the property.
246 */
247 public void fireVetoableChange(String propertyName,
248 int oldValue, int newValue)
249 throws PropertyVetoException {
250 if (oldValue == newValue) {
251 return;
252 }
253 fireVetoableChange(propertyName, Integer.valueOf(oldValue), Integer.valueOf(newValue));
254 }
255
256 /**
257 * Report a boolean vetoable property update to any registered listeners.
258 * No event is fired if old and new are equal.
259 * <p>
260 * This is merely a convenience wrapper around the more general
261 * fireVetoableChange method that takes Object values.
262 *
263 * @param propertyName The programmatic name of the property
264 * that is about to change.
265 * @param oldValue The old value of the property.
266 * @param newValue The new value of the property.
267 */
268 public void fireVetoableChange(String propertyName,
269 boolean oldValue, boolean newValue)
270 throws PropertyVetoException {
271 if (oldValue == newValue) {
272 return;
273 }
274 fireVetoableChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
275 }
276
277 /**
278 * Fire a vetoable property update to any registered listeners. If
279 * anyone vetos the change, then fire a new event reverting everyone to
280 * the old value and then rethrow the PropertyVetoException.
281 * <p>
282 * No event is fired if old and new are equal and non-null.
283 *
284 * @param evt The PropertyChangeEvent to be fired.
285 * @exception PropertyVetoException if the recipient wishes the property
286 * change to be rolled back.
287 */
288 public void fireVetoableChange(PropertyChangeEvent evt)
289 throws PropertyVetoException {
290
291 Object oldValue = evt.getOldValue();
292 Object newValue = evt.getNewValue();
293 String propertyName = evt.getPropertyName();
294 if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
295 return;
296 }
297 VetoableChangeListener[] common = this.map.get(null);
298 VetoableChangeListener[] named = (propertyName != null)
299 ? this.map.get(propertyName)
300 : null;
301 fire(common, evt);
302 fire(named, evt);
303 }
304
305 private void fire(VetoableChangeListener[] listeners, PropertyChangeEvent event) throws PropertyVetoException {
306 if (listeners != null) {
307 VetoableChangeListener current = null;
308 try {
309 for (VetoableChangeListener listener : listeners) {
310 current = listener;
311 listener.vetoableChange(event);
312 }
313 } catch (PropertyVetoException veto) {
314 // Create an event to revert everyone to the old value.
315 event = new PropertyChangeEvent( this.source,
316 event.getPropertyName(),
317 event.getNewValue(),
318 event.getOldValue() );
319 for (VetoableChangeListener listener : listeners) {
320 if (current == listener) {
321 break;
322 }
323 try {
324 listener.vetoableChange(event);
325 } catch (PropertyVetoException ex) {
326 // We just ignore exceptions that occur during reversions.
327 }
328 }
329 // And now rethrow the PropertyVetoException.
330 throw veto;
331 }
332 }
333 }
334
335 /**
336 * Check if there are any listeners for a specific property, including
337 * those registered on all properties. If <code>propertyName</code>
338 * is null, only check for listeners registered on all properties.
339 *
340 * @param propertyName the property name.
341 * @return true if there are one or more listeners for the given property
342 */
343 public boolean hasListeners(String propertyName) {
344 return this.map.hasListeners(propertyName);
345 }
346
347 /**
348 * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
349 * <p>
350 * At serialization time we skip non-serializable listeners and
351 * only serialize the serializable listeners.
352 */
353 private void writeObject(ObjectOutputStream s) throws IOException {
354 Hashtable<String, VetoableChangeSupport> children = null;
355 VetoableChangeListener[] listeners = null;
356 synchronized (this.map) {
357 for (Entry<String, VetoableChangeListener[]> entry : this.map.getEntries()) {
358 String property = entry.getKey();
359 if (property == null) {
360 listeners = entry.getValue();
361 } else {
362 if (children == null) {
363 children = new Hashtable<String, VetoableChangeSupport>();
364 }
365 VetoableChangeSupport vcs = new VetoableChangeSupport(this.source);
366 vcs.map.set(null, entry.getValue());
367 children.put(property, vcs);
368 }
369 }
370 }
371 ObjectOutputStream.PutField fields = s.putFields();
372 fields.put("children", children);
373 fields.put("source", this.source);
374 fields.put("vetoableChangeSupportSerializedDataVersion", 2);
375 s.writeFields();
376
377 if (listeners != null) {
378 for (VetoableChangeListener l : listeners) {
379 if (l instanceof Serializable) {
380 s.writeObject(l);
381 }
382 }
383 }
384 s.writeObject(null);
385 }
386
387 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
388 this.map = new VetoableChangeListenerMap();
389
390 ObjectInputStream.GetField fields = s.readFields();
391
392 Hashtable<String, VetoableChangeSupport> children = (Hashtable<String, VetoableChangeSupport>) fields.get("children", null);
393 this.source = fields.get("source", null);
394 fields.get("vetoableChangeSupportSerializedDataVersion", 2);
395
396 Object listenerOrNull;
397 while (null != (listenerOrNull = s.readObject())) {
398 this.map.add(null, (VetoableChangeListener)listenerOrNull);
399 }
400 if (children != null) {
401 for (Entry<String, VetoableChangeSupport> entry : children.entrySet()) {
402 for (VetoableChangeListener listener : entry.getValue().getVetoableChangeListeners()) {
403 this.map.add(entry.getKey(), listener);
404 }
405 }
406 }
407 }
408
409 /**
410 * The object to be provided as the "source" for any generated events.
411 */
412 private Object source;
413
414 /**
415 * @serialField children Hashtable
416 * @serialField source Object
417 * @serialField propertyChangeSupportSerializedDataVersion int
418 */
419 private static final ObjectStreamField[] serialPersistentFields = {
420 new ObjectStreamField("children", Hashtable.class),
421 new ObjectStreamField("source", Object.class),
422 new ObjectStreamField("vetoableChangeSupportSerializedDataVersion", Integer.TYPE)
423 };
424
425 /**
426 * Serialization version ID, so we're compatible with JDK 1.1
427 */
428 static final long serialVersionUID = -5090210921595982017L;
429
430 /**
431 * This is a {@link ChangeListenerMap ChangeListenerMap} implementation
432 * that works with {@link VetoableChangeListener VetoableChangeListener} objects.
433 */
434 private static final class VetoableChangeListenerMap extends ChangeListenerMap<VetoableChangeListener> {
435 private static final VetoableChangeListener[] EMPTY = {};
436
437 /**
438 * Creates an array of {@link VetoableChangeListener VetoableChangeListener} objects.
439 * This method uses the same instance of the empty array
440 * when {@code length} equals {@code 0}.
441 *
442 * @param length the array length
443 * @return an array with specified length
444 */
445 @Override
446 protected VetoableChangeListener[] newArray(int length) {
447 return (0 < length)
448 ? new VetoableChangeListener[length]
449 : EMPTY;
450 }
451
452 /**
453 * Creates a {@link VetoableChangeListenerProxy VetoableChangeListenerProxy}
454 * object for the specified property.
455 *
456 * @param name the name of the property to listen on
457 * @param listener the listener to process events
458 * @return a {@code VetoableChangeListenerProxy} object
459 */
460 @Override
461 protected VetoableChangeListener newProxy(String name, VetoableChangeListener listener) {
462 return new VetoableChangeListenerProxy(name, listener);
463 }
464 }
465}