Code - Class EDU.oswego.cs.dl.util.concurrent.VetoableChangeMulticaster


1 /*
2   File: ProperyChangeMulticaster.java
3
4   Originally written by Doug Lea and released into the public domain.
5   This may be used for any purposes whatsoever without acknowledgment.
6   Thanks for the assistance and support of Sun Microsystems Labs,
7   and everyone contributing, testing, and using this code.
8
9   This class is based on Sun JDK java.beans.VetoableChangeSupport,
10   which is copyrighted by Sun. (It shares practically no code, but for
11   consistency, the documentation was lifted and adapted here.)
12
13   History:
14   Date Who What
15   14Mar1999 dl first release
16 */

17
18 package EDU.oswego.cs.dl.util.concurrent;
19
20 import java.beans.VetoableChangeListener;
21 import java.beans.PropertyChangeEvent;
22 import java.beans.PropertyVetoException;
23 import java.util.HashMap;
24 import java.io.Serializable;
25 import java.io.ObjectOutputStream;
26 import java.io.ObjectInputStream;
27 import java.io.IOException;
28
29 /**
30  * This class is interoperable with java.beans.VetoableChangeSupport,
31  * but relies on a streamlined copy-on-write scheme similar to
32  * that used in CopyOnWriteArrayList. It also adheres to clarified
33  * semantics of add, remove, and fireVetoableChange operations.
34  * <p>
35  * <b>Sample usage.</b>
36  *
37  * <pre>
38  * class Thing {
39  * protected Color myColor = Color.red; // an example property
40  * protected boolean changePending; // track whether in midst of change
41  *
42  * // vetoable listeners:
43  * protected VetoableChangeMulticaster vetoers =
44  * new VetoableChangeMulticaster(this);
45  *
46  * // Possibly also some ordinary listeners:
47  * protected PropertyChangeMulticaster listeners =
48  * new PropertyChangeMulticaster(this);
49  *
50  * // registration methods, including:
51  * void addVetoer(VetoableChangeListener l) {
52  * // Use the `ifAbsent' version to avoid duplicate notifications
53  * vetoers.addVetoableChangeListenerIfAbsent(l);
54  * }
55  *
56  * public synchronized Color getColor() { // accessor
57  * return myColor;
58  * }
59  *
60  * // Simple transactional control for vetos
61  *
62  * public void setColor(int newColor) throws PropertyVetoException {
63  * Color oldColor = prepareSetColor(newColor);
64  *
65  * try {
66  * vetoers.fireVetoableChange("color", oldColor, newColor);
67  * commitColor(newColor);
68  * listeners.firePropertyChange("color", oldColor, newColor);
69  * }
70  * catch(PropertyVetoException ex) {
71  * abortSetColor();
72  * throw ex;
73  * }
74  * }
75  *
76  * // Called on entry to proposed vetoable change from setColor.
77  * // Throws exception if there is already another change in progress.
78  * // Returns current color
79  * synchronized int prepareSetColor(Color c) throws PropertyVetoException {
80  * // only support one transaction at a time
81  * if (changePending)
82  * throw new PropertyVetoException("Concurrent modification");
83  * // (Could alternatively wait out other transactions via
84  * // a wait/notify construction based on changePending.)
85  *
86  * // perhaps some other screenings, like:
87  * else if (c == null)
88  * throw new PropertyVetoException("Cannot change color to Null");
89  * else {
90  * changePending = true;
91  * return myColor;
92  * }
93  * }
94  *
95  * synchronized void commitColor(Color newColor) {
96  * myColor = newColor;
97  * changePending = false;
98  * }
99  *
100  * synchronized void abortSetColor() {
101  * changePending = false;
102  * }
103  *
104  * }
105  * </pre>
106  * <p>[<a HREF="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
107  **/

108
109 public class VetoableChangeMulticaster implements Serializable {
110
111   // This code is 90% identical with PropertyChangeMulticaster,
112
// but there is no good way to unify the code while maintaining
113
// interoperability with beans versions.
114

115   /**
116    * The array of listeners. Copied on each update
117    **/

118
119   protected transient VetoableChangeListener[] listeners = new VetoableChangeListener[0];
120
121
122   /**
123    * The object to be provided as the "source" for any generated events.
124    * @serial
125    */

126   protected final Object source;
127
128   /**
129    * HashMap for managing listeners for specific properties.
130    * Maps property names to VetoableChangeMulticaster objects.
131    * @serial
132    */

133   protected HashMap children;
134
135   /**
136    * Return the child associated with property, or null if no such
137    **/

138
139   protected synchronized VetoableChangeMulticaster getChild(String propertyName) {
140     return (children == null)? null :
141       ((VetoableChangeMulticaster)children.get(propertyName));
142   }
143
144
145   /**
146    * Constructs a <code>VetoableChangeMulticaster</code> object.
147    *
148    * @param sourceBean The bean to be given as the source for any events.
149    * @exception NullPointerException if sourceBean is null
150    */

151   
152   public VetoableChangeMulticaster(Object sourceBean) {
153     if (sourceBean == null) {
154       throw new NullPointerException();
155     }
156
157     source = sourceBean;
158   }
159
160   /**
161    * Add a VetoableChangeListener to the listener list.
162    * The listener is registered for all properties.
163    * If the listener is added multiple times, it will
164    * receive multiple change notifications upon any fireVetoableChange.
165    *
166    * @param listener The VetoableChangeListener to be added
167    */

168   
169   public synchronized void addVetoableChangeListener(VetoableChangeListener listener) {
170
171     if (listener == null) throw new NullPointerException();
172
173     int len = listeners.length;
174     VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1];
175     if (len > 0)
176       System.arraycopy(listeners, 0, newArray, 0, len);
177     newArray[len] = listener;
178     listeners = newArray;
179   }
180
181   /**
182    * Add a PropertyChangeListener to the listener list if it is
183    * not already present.
184    * The listener is registered for all properties.
185    * The operation maintains Set semantics: If the listener is already
186    * registered, the operation has no effect.
187    *
188    * @param listener The PropertyChangeListener to be added
189    * @exception NullPointerException If listener is null
190    */

191   
192   public synchronized void addVetoableChangeListenerIfAbsent(VetoableChangeListener listener) {
193
194     if (listener == null) throw new NullPointerException();
195
196     // Copy while checking if already present.
197
int len = listeners.length;
198     VetoableChangeListener[] newArray = new VetoableChangeListener[len + 1];
199     for (int i = 0; i < len; ++i) {
200       newArray[i] = listeners[i];
201       if (listener.equals(listeners[i]))
202     return; // already present -- throw away copy
203
}
204     newArray[len] = listener;
205     listeners = newArray;
206   }
207
208
209   /**
210    * Remove an occurrence of a VetoableChangeListener from the listener list.
211    * It removes at most one occurrence of the given listener.
212    * If the listener was added multiple times it must be removed
213    * mulitple times.
214    * This removes a VetoableChangeListener that was registered
215    * for all properties, and has no effect if registered for only
216    * one or more specified properties.
217    *
218    * @param listener The VetoableChangeListener to be removed
219    */

220   
221   public synchronized void removeVetoableChangeListener(VetoableChangeListener listener) {
222
223     int newlen = listeners.length-1;
224     if (newlen < 0 || listener == null) return;
225
226     // Copy while searching for element to remove
227

228     VetoableChangeListener[] newArray = new VetoableChangeListener[newlen];
229
230     for (int i = 0; i < newlen; ++i) {
231       if (listener.equals(listeners[i])) {
232         // copy remaining and exit
233
for (int k = i + 1; k <= newlen; ++k) newArray[k-1] = listeners[k];
234         listeners = newArray;
235         return;
236       }
237       else
238         newArray[i] = listeners[i];
239     }
240     
241     // special-case last cell
242
if (listener.equals(listeners[newlen]))
243       listeners = newArray;
244
245   }
246
247   /**
248    * Add a VetoableChangeListener for a specific property. The listener
249    * will be invoked only when a call on fireVetoableChange names that
250    * specific property. However, if a listener is registered both for all
251    * properties and a specific property, it will receive multiple
252    * notifications upon changes to that property.
253    *
254    * @param propertyName The name of the property to listen on.
255    * @param listener The VetoableChangeListener to be added
256    * @exception NullPointerException If listener is null
257    */

258   
259   public void addVetoableChangeListener(String propertyName,
260                                         VetoableChangeListener listener) {
261
262     if (listener == null) throw new NullPointerException();
263
264     VetoableChangeMulticaster child = null;
265
266     synchronized(this) {
267       if (children == null)
268         children = new HashMap();
269       else
270         child = (VetoableChangeMulticaster)children.get(propertyName);
271       
272       if (child == null) {
273         child = new VetoableChangeMulticaster(source);
274         children.put(propertyName, child);
275       }
276     }
277
278     child.addVetoableChangeListener(listener);
279   }
280
281   /**
282    * Add a VetoableChangeListener for a specific property, if it is not
283    * already registered. The listener
284    * will be invoked only when a call on fireVetoableChange names that
285    * specific property.
286    *
287    * @param propertyName The name of the property to listen on.
288    * @param listener The VetoableChangeListener to be added
289    * @exception NullPointerException If listener is null
290    */

291   
292   public void addVetoableChangeListenerIfAbsent(String propertyName,
293                                         VetoableChangeListener listener) {
294
295     if (listener == null) throw new NullPointerException();
296
297     VetoableChangeMulticaster child = null;
298
299     synchronized(this) {
300       if (children == null)
301         children = new HashMap();
302       else
303         child = (VetoableChangeMulticaster)children.get(propertyName);
304       
305       if (child == null) {
306         child = new VetoableChangeMulticaster(source);
307         children.put(propertyName, child);
308       }
309     }
310
311     child.addVetoableChangeListenerIfAbsent(listener);
312   }
313
314
315   /**
316    * Remove a VetoableChangeListener for a specific property.
317    * Affects only the given property.
318    * If the listener is also registered for all properties,
319    * then it will continue to be registered for them.
320    *
321    * @param propertyName The name of the property that was listened on.
322    * @param listener The VetoableChangeListener to be removed
323    */

324   
325   public void removeVetoableChangeListener(String propertyName,
326                                            VetoableChangeListener listener) {
327
328     VetoableChangeMulticaster child = getChild(propertyName);
329     if (child != null)
330       child.removeVetoableChangeListener(listener);
331   }
332
333
334   /**
335    * Helper method to relay evt to all listeners.
336    * Called by all public fireVetoableChange methods.
337    **/

338
339   protected void multicast(PropertyChangeEvent evt) throws PropertyVetoException {
340
341     VetoableChangeListener[] array; // bind in synch block below
342
VetoableChangeMulticaster child = null;
343
344     synchronized (this) {
345       array = listeners;
346
347       if (children != null && evt.getPropertyName() != null)
348         child = (VetoableChangeMulticaster)children.get(evt.getPropertyName());
349     }
350
351     // Loop through array, and then cascade to child.
352

353     int i = 0; // make visible to catch clause
354

355     try {
356       for (i = 0; i < array.length; ++i)
357         array[i].vetoableChange(evt);
358
359       if (child != null)
360         child.multicast(evt);
361     }
362
363     catch (PropertyVetoException veto) {
364       
365       // Revert all that have been notified
366

367       PropertyChangeEvent revert =
368         new PropertyChangeEvent(evt.getSource(),
369                                 evt.getPropertyName(),
370                                 evt.getNewValue(),
371                                 evt.getOldValue());
372
373       int lastNotified = (i < array.length)? i : (array.length-1);
374
375       for (int k = 0; k <= lastNotified; ++k) {
376         try {
377           array[k].vetoableChange(revert);
378         }
379         catch (PropertyVetoException ignore) {
380           // Cannot veto a reversion
381
}
382       }
383       
384       // Rethrow the PropertyVetoException.
385
throw veto;
386     }
387   }
388
389   
390   /**
391    * Report a vetoable property update to any registered listeners.
392    * Notifications are sent serially (although in no particular order)
393    * to the list of listeners,
394    * aborting if one throws PropertyVetoException. Upon this exception,
395    * fire a new event reverting this
396    * change to all listeners that have already been notified
397    * (ignoring any further vetos),
398    * suppress notifications to all other listeners, and
399    * then rethrow the PropertyVetoException.
400    * <p>
401    * No event is fired if old and new are equal non-null.
402    *
403    * @param propertyName The programmatic name of the property
404    * that was changed.
405    * @param oldValue The old value of the property.
406    * @param newValue The new value of the property.
407    * @exception PropertyVetoException if a recipient wishes the property
408    * change to be rolled back.
409    */

410   public void fireVetoableChange(String propertyName,
411                                  Object oldValue, Object newValue) throws PropertyVetoException {
412    
413     if (oldValue == null || newValue == null || !oldValue.equals(newValue)) {
414       multicast(new PropertyChangeEvent(source,
415                                         propertyName,
416                                         oldValue,
417                                         newValue));
418     }
419     
420   }
421
422   /**
423    * Report a vetoable property update to any registered listeners.
424    * Notifications are sent serially (although in no particular order)
425    * to the list of listeners,
426    * aborting if one throws PropertyVetoException. Upon this exception,
427    * fire a new event reverting this
428    * change to all listeners that have already been notified
429    * (ignoring any further vetos),
430    * suppress notifications to all other listeners, and
431    * then rethrow the PropertyVetoException.
432    * <p>
433    * No event is fired if old and new are equal.
434    * <p>
435    * This is merely a convenience wrapper around the more general
436    * fireVetoableChange method that takes Object values.
437    *
438    * @param propertyName The programmatic name of the property
439    * that was changed.
440    * @param oldValue The old value of the property.
441    * @param newValue The new value of the property.
442    * @exception PropertyVetoException if the recipient wishes the property
443    * change to be rolled back.
444    */

445   public void fireVetoableChange(String propertyName,
446                                  int oldValue, int newValue) throws PropertyVetoException {
447     if (oldValue != newValue) {
448       multicast(new PropertyChangeEvent(source,
449                                         propertyName,
450                                         new Integer(oldValue),
451                                         new Integer(newValue)));
452     }
453   }
454
455
456   /**
457    * Report a vetoable property update to any registered listeners.
458    * Notifications are sent serially (although in no particular order)
459    * to the list of listeners,
460    * aborting if one throws PropertyVetoException. Upon this exception,
461    * fire a new event reverting this
462    * change to all listeners that have already been notified
463    * (ignoring any further vetos),
464    * suppress notifications to all other listeners, and
465    * then rethrow the PropertyVetoException.
466    * <p>
467    * No event is fired if old and new are equal.
468    * <p>
469    * This is merely a convenience wrapper around the more general
470    * fireVetoableChange method that takes Object values.
471    *
472    * @param propertyName The programmatic name of the property
473    * that was changed.
474    * @param oldValue The old value of the property.
475    * @param newValue The new value of the property.
476    * @exception PropertyVetoException if the recipient wishes the property
477    * change to be rolled back.
478    */

479   public void fireVetoableChange(String propertyName,
480                                  boolean oldValue, boolean newValue) throws PropertyVetoException {
481     if (oldValue != newValue) {
482       multicast(new PropertyChangeEvent(source,
483                                         propertyName,
484                                         new Boolean(oldValue),
485                                         new Boolean(newValue)));
486     }
487   }
488
489   /**
490    * Report a vetoable property update to any registered listeners.
491    * Notifications are sent serially (although in no particular order)
492    * to the list of listeners,
493    * aborting if one throws PropertyVetoException. Upon this exception,
494    * fire a new event reverting this
495    * change to all listeners that have already been notified
496    * (ignoring any further vetos),
497    * suppress notifications to all other listeners, and
498    * then rethrow the PropertyVetoException.
499    * <p>
500    * No event is fired if old and new are equal and non-null.
501    *
502    * equal and non-null.
503    * @param evt The PropertyChangeEvent object.
504    * @exception PropertyVetoException if the recipient wishes the property
505    * change to be rolled back.
506    */

507   public void fireVetoableChange(PropertyChangeEvent evt) throws PropertyVetoException {
508     Object oldValue = evt.getOldValue();
509     Object newValue = evt.getNewValue();
510     if (oldValue == null || newValue == null || !oldValue.equals(newValue))
511       multicast(evt);
512   }
513
514   /**
515    * Check if there are any listeners for a specific property.
516    * If propertyName is null, return whether there are any listeners at all.
517    *
518    * @param propertyName the property name.
519    * @return true if there are one or more listeners for the given property
520    *
521    */

522   public boolean hasListeners(String propertyName) {
523
524     VetoableChangeMulticaster child;
525
526     synchronized (this) {
527       if (listeners.length > 0)
528         return true;
529       else if (propertyName == null || children == null)
530         return false;
531       else {
532         child = (VetoableChangeMulticaster)children.get(propertyName);
533         if (child == null)
534           return false;
535       }
536     }
537     
538     return child.hasListeners(null);
539   }
540
541
542   /**
543    * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
544    * <p>
545    * At serialization time we skip non-serializable listeners and
546    * only serialize the serializable listeners.
547    *
548    */

549   private synchronized void writeObject(ObjectOutputStream s) throws IOException {
550     s.defaultWriteObject();
551     
552     for (int i = 0; i < listeners.length; i++) {
553       VetoableChangeListener l = listeners[i];
554       if (listeners[i] instanceof Serializable) {
555         s.writeObject(listeners[i]);
556       }
557     }
558     s.writeObject(null);
559   }
560   
561   
562   private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
563     listeners = new VetoableChangeListener[0]; // paranoically reset
564
s.defaultReadObject();
565     
566     Object listenerOrNull;
567     while (null != (listenerOrNull = s.readObject())) {
568       addVetoableChangeListener((VetoableChangeListener)listenerOrNull);
569     }
570   }
571
572 }
573

Java API By Example, From Geeks To Geeks. | Conditions of Use | About Us © 2002 - 2005, KickJava.com, or its affiliates