001 /*
002 * Copyright 1996-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package java.beans;
027
028 import java.io.Serializable;
029 import java.io.ObjectOutputStream;
030 import java.io.ObjectInputStream;
031 import java.io.IOException;
032 import java.util.Arrays;
033 import java.util.ArrayList;
034 import java.util.Iterator;
035 import java.util.List;
036
037 /**
038 * This is a utility class that can be used by beans that support constrained
039 * properties. You can use an instance of this class as a member field
040 * of your bean and delegate various work to it.
041 *
042 * This class is serializable. When it is serialized it will save
043 * (and restore) any listeners that are themselves serializable. Any
044 * non-serializable listeners will be skipped during serialization.
045 */
046
047 public class VetoableChangeSupport implements java.io.Serializable {
048
049 /**
050 * Constructs a <code>VetoableChangeSupport</code> object.
051 *
052 * @param sourceBean The bean to be given as the source for any events.
053 */
054
055 public VetoableChangeSupport(Object sourceBean) {
056 if (sourceBean == null) {
057 throw new NullPointerException();
058 }
059 source = sourceBean;
060 }
061
062 /**
063 * Add a VetoableListener to the listener list.
064 * The listener is registered for all properties.
065 * The same listener object may be added more than once, and will be called
066 * as many times as it is added.
067 * If <code>listener</code> is null, no exception is thrown and no action
068 * is taken.
069 *
070 * @param listener The VetoableChangeListener to be added
071 */
072
073 public synchronized void addVetoableChangeListener(
074 VetoableChangeListener listener) {
075 if (listener == null) {
076 return;
077 }
078 if (listener instanceof VetoableChangeListenerProxy) {
079 VetoableChangeListenerProxy proxy = (VetoableChangeListenerProxy) listener;
080 // Call two argument add method.
081 addVetoableChangeListener(proxy.getPropertyName(),
082 (VetoableChangeListener) proxy.getListener());
083 } else {
084 if (listeners == null) {
085 listeners = new java.util.Vector();
086 }
087 listeners.addElement(listener);
088 }
089 }
090
091 /**
092 * Remove a VetoableChangeListener from the listener list.
093 * This removes a VetoableChangeListener that was registered
094 * for all properties.
095 * If <code>listener</code> was added more than once to the same event
096 * source, it will be notified one less time after being removed.
097 * If <code>listener</code> is null, or was never added, no exception is
098 * thrown and no action is taken.
099 *
100 * @param listener The VetoableChangeListener to be removed
101 */
102 public synchronized void removeVetoableChangeListener(
103 VetoableChangeListener listener) {
104 if (listener == null) {
105 return;
106 }
107 if (listener instanceof VetoableChangeListenerProxy) {
108 VetoableChangeListenerProxy proxy = (VetoableChangeListenerProxy) listener;
109 // Call two argument remove method.
110 removeVetoableChangeListener(proxy.getPropertyName(),
111 (VetoableChangeListener) proxy.getListener());
112 } else {
113 if (listeners == null) {
114 return;
115 }
116 listeners.removeElement(listener);
117 }
118 }
119
120 /**
121 * Returns the list of VetoableChangeListeners. If named vetoable change listeners
122 * were added, then VetoableChangeListenerProxy wrappers will returned
123 * <p>
124 * @return List of VetoableChangeListeners and VetoableChangeListenerProxys
125 * if named property change listeners were added.
126 * @since 1.4
127 */
128 public synchronized VetoableChangeListener[] getVetoableChangeListeners() {
129 List returnList = new ArrayList();
130
131 // Add all the VetoableChangeListeners
132 if (listeners != null) {
133 returnList.addAll(listeners);
134 }
135
136 // Add all the VetoableChangeListenerProxys
137 if (children != null) {
138 Iterator iterator = children.keySet().iterator();
139 while (iterator.hasNext()) {
140 String key = (String) iterator.next();
141 VetoableChangeSupport child = (VetoableChangeSupport) children
142 .get(key);
143 VetoableChangeListener[] childListeners = child
144 .getVetoableChangeListeners();
145 for (int index = childListeners.length - 1; index >= 0; index--) {
146 returnList.add(new VetoableChangeListenerProxy(key,
147 childListeners[index]));
148 }
149 }
150 }
151 return (VetoableChangeListener[]) (returnList
152 .toArray(new VetoableChangeListener[0]));
153 }
154
155 /**
156 * Add a VetoableChangeListener for a specific property. The listener
157 * will be invoked only when a call on fireVetoableChange names that
158 * specific property.
159 * The same listener object may be added more than once. For each
160 * property, the listener will be invoked the number of times it was added
161 * for that property.
162 * If <code>propertyName</code> or <code>listener</code> is null, no
163 * exception is thrown and no action is taken.
164 *
165 * @param propertyName The name of the property to listen on.
166 * @param listener The VetoableChangeListener to be added
167 */
168
169 public synchronized void addVetoableChangeListener(
170 String propertyName, VetoableChangeListener listener) {
171 if (listener == null || propertyName == null) {
172 return;
173 }
174 if (children == null) {
175 children = new java.util.Hashtable();
176 }
177 VetoableChangeSupport child = (VetoableChangeSupport) children
178 .get(propertyName);
179 if (child == null) {
180 child = new VetoableChangeSupport(source);
181 children.put(propertyName, child);
182 }
183 child.addVetoableChangeListener(listener);
184 }
185
186 /**
187 * Remove a VetoableChangeListener for a specific property.
188 * If <code>listener</code> was added more than once to the same event
189 * source for the specified property, it will be notified one less time
190 * after being removed.
191 * If <code>propertyName</code> is null, no exception is thrown and no
192 * action is taken.
193 * If <code>listener</code> is null, or was never added for the specified
194 * property, no exception is thrown and no action is taken.
195 *
196 * @param propertyName The name of the property that was listened on.
197 * @param listener The VetoableChangeListener to be removed
198 */
199
200 public synchronized void removeVetoableChangeListener(
201 String propertyName, VetoableChangeListener listener) {
202 if (listener == null || propertyName == null) {
203 return;
204 }
205 if (children == null) {
206 return;
207 }
208 VetoableChangeSupport child = (VetoableChangeSupport) children
209 .get(propertyName);
210 if (child == null) {
211 return;
212 }
213 child.removeVetoableChangeListener(listener);
214 }
215
216 /**
217 * Returns an array of all the listeners which have been associated
218 * with the named property.
219 *
220 * @param propertyName The name of the property being listened to
221 * @return all the <code>VetoableChangeListeners</code> associated with
222 * the named property. If no such listeners have been added,
223 * or if <code>propertyName</code> is null, an empty array is
224 * returned.
225 * @since 1.4
226 */
227 public synchronized VetoableChangeListener[] getVetoableChangeListeners(
228 String propertyName) {
229 List returnList = new ArrayList();
230
231 if (children != null && propertyName != null) {
232 VetoableChangeSupport support = (VetoableChangeSupport) children
233 .get(propertyName);
234 if (support != null) {
235 returnList.addAll(Arrays.asList(support
236 .getVetoableChangeListeners()));
237 }
238 }
239 return (VetoableChangeListener[]) (returnList
240 .toArray(new VetoableChangeListener[0]));
241 }
242
243 /**
244 * Report a vetoable property update to any registered listeners. If
245 * anyone vetos the change, then fire a new event reverting everyone to
246 * the old value and then rethrow the PropertyVetoException.
247 * <p>
248 * No event is fired if old and new are equal and non-null.
249 *
250 * @param propertyName The programmatic name of the property
251 * that is about to change..
252 * @param oldValue The old value of the property.
253 * @param newValue The new value of the property.
254 * @exception PropertyVetoException if the recipient wishes the property
255 * change to be rolled back.
256 */
257 public void fireVetoableChange(String propertyName,
258 Object oldValue, Object newValue)
259 throws PropertyVetoException {
260 if (listeners == null && children == null) {
261 return;
262 }
263
264 PropertyChangeEvent evt = new PropertyChangeEvent(source,
265 propertyName, oldValue, newValue);
266 fireVetoableChange(evt);
267 }
268
269 /**
270 * Report a int vetoable property update to any registered listeners.
271 * No event is fired if old and new are equal.
272 * <p>
273 * This is merely a convenience wrapper around the more general
274 * fireVetoableChange method that takes Object values.
275 *
276 * @param propertyName The programmatic name of the property
277 * that is about to change.
278 * @param oldValue The old value of the property.
279 * @param newValue The new value of the property.
280 */
281 public void fireVetoableChange(String propertyName, int oldValue,
282 int newValue) throws PropertyVetoException {
283 if (oldValue == newValue) {
284 return;
285 }
286 fireVetoableChange(propertyName, new Integer(oldValue),
287 new Integer(newValue));
288 }
289
290 /**
291 * Report a boolean vetoable property update to any registered listeners.
292 * No event is fired if old and new are equal.
293 * <p>
294 * This is merely a convenience wrapper around the more general
295 * fireVetoableChange method that takes Object values.
296 *
297 * @param propertyName The programmatic name of the property
298 * that is about to change.
299 * @param oldValue The old value of the property.
300 * @param newValue The new value of the property.
301 */
302 public void fireVetoableChange(String propertyName,
303 boolean oldValue, boolean newValue)
304 throws PropertyVetoException {
305 if (oldValue == newValue) {
306 return;
307 }
308 fireVetoableChange(propertyName, Boolean.valueOf(oldValue),
309 Boolean.valueOf(newValue));
310 }
311
312 /**
313 * Fire a vetoable property update to any registered listeners. If
314 * anyone vetos the change, then fire a new event reverting everyone to
315 * the old value and then rethrow the PropertyVetoException.
316 * <p>
317 * No event is fired if old and new are equal and non-null.
318 *
319 * @param evt The PropertyChangeEvent to be fired.
320 * @exception PropertyVetoException if the recipient wishes the property
321 * change to be rolled back.
322 */
323 public void fireVetoableChange(PropertyChangeEvent evt)
324 throws PropertyVetoException {
325
326 Object oldValue = evt.getOldValue();
327 Object newValue = evt.getNewValue();
328 String propertyName = evt.getPropertyName();
329 if (oldValue != null && newValue != null
330 && oldValue.equals(newValue)) {
331 return;
332 }
333
334 java.util.Vector targets = null;
335 VetoableChangeSupport child = null;
336 synchronized (this ) {
337 if (listeners != null) {
338 targets = (java.util.Vector) listeners.clone();
339 }
340 if (children != null && propertyName != null) {
341 child = (VetoableChangeSupport) children
342 .get(propertyName);
343 }
344 }
345
346 if (listeners != null) {
347 try {
348 for (int i = 0; i < targets.size(); i++) {
349 VetoableChangeListener target = (VetoableChangeListener) targets
350 .elementAt(i);
351 target.vetoableChange(evt);
352 }
353 } catch (PropertyVetoException veto) {
354 // Create an event to revert everyone to the old value.
355 evt = new PropertyChangeEvent(source, propertyName,
356 newValue, oldValue);
357 for (int i = 0; i < targets.size(); i++) {
358 try {
359 VetoableChangeListener target = (VetoableChangeListener) targets
360 .elementAt(i);
361 target.vetoableChange(evt);
362 } catch (PropertyVetoException ex) {
363 // We just ignore exceptions that occur during reversions.
364 }
365 }
366 // And now rethrow the PropertyVetoException.
367 throw veto;
368 }
369 }
370
371 if (child != null) {
372 child.fireVetoableChange(evt);
373 }
374 }
375
376 /**
377 * Check if there are any listeners for a specific property, including
378 * those registered on all properties. If <code>propertyName</code>
379 * is null, only check for listeners registered on all properties.
380 *
381 * @param propertyName the property name.
382 * @return true if there are one or more listeners for the given property
383 */
384 public synchronized boolean hasListeners(String propertyName) {
385 if (listeners != null && !listeners.isEmpty()) {
386 // there is a generic listener
387 return true;
388 }
389 if (children != null && propertyName != null) {
390 VetoableChangeSupport child = (VetoableChangeSupport) children
391 .get(propertyName);
392 if (child != null && child.listeners != null) {
393 return !child.listeners.isEmpty();
394 }
395 }
396 return false;
397 }
398
399 /**
400 * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
401 * <p>
402 * At serialization time we skip non-serializable listeners and
403 * only serialize the serializable listeners.
404 *
405 */
406
407 private void writeObject(ObjectOutputStream s) throws IOException {
408 s.defaultWriteObject();
409
410 java.util.Vector v = null;
411 synchronized (this ) {
412 if (listeners != null) {
413 v = (java.util.Vector) listeners.clone();
414 }
415 }
416
417 if (v != null) {
418 for (int i = 0; i < v.size(); i++) {
419 VetoableChangeListener l = (VetoableChangeListener) v
420 .elementAt(i);
421 if (l instanceof Serializable) {
422 s.writeObject(l);
423 }
424 }
425 }
426 s.writeObject(null);
427 }
428
429 private void readObject(ObjectInputStream s)
430 throws ClassNotFoundException, IOException {
431 s.defaultReadObject();
432
433 Object listenerOrNull;
434 while (null != (listenerOrNull = s.readObject())) {
435 addVetoableChangeListener((VetoableChangeListener) listenerOrNull);
436 }
437 }
438
439 /**
440 * "listeners" lists all the generic listeners.
441 *
442 * This is transient - its state is written in the writeObject method.
443 */
444 transient private java.util.Vector listeners;
445
446 /**
447 * Hashtable for managing listeners for specific properties.
448 * Maps property names to VetoableChangeSupport objects.
449 * @serial
450 * @since 1.2
451 */
452 private java.util.Hashtable children;
453
454 /**
455 * The object to be provided as the "source" for any generated events.
456 * @serial
457 */
458 private Object source;
459
460 /**
461 * Internal version number
462 * @serial
463 */
464 private int vetoableChangeSupportSerializedDataVersion = 2;
465
466 /**
467 * Serialization version ID, so we're compatible with JDK 1.1
468 */
469 static final long serialVersionUID = -5090210921595982017L;
470 }
|