001: /*
002: File: PropertyChangeMulticaster.java
003:
004: Originally written by Doug Lea and released into the public domain.
005: This may be used for any purposes whatsoever without acknowledgment.
006: Thanks for the assistance and support of Sun Microsystems Labs,
007: and everyone contributing, testing, and using this code.
008:
009: This class is based on Sun JDK java.beans.VetoableChangeSupport,
010: which is copyrighted by Sun. (It shares practically no code, but for
011: consistency, the documentation was lifted and adapted here.)
012:
013: History:
014: Date Who What
015: 14Mar1999 dl first release
016: */
017:
018: package org.dbunit.util.concurrent;
019:
020: import org.slf4j.Logger;
021: import org.slf4j.LoggerFactory;
022:
023: import java.beans.PropertyChangeEvent;
024: import java.beans.PropertyChangeListener;
025: import java.io.IOException;
026: import java.io.ObjectInputStream;
027: import java.io.ObjectOutputStream;
028: import java.io.Serializable;
029: import java.util.HashMap;
030:
031: /**
032: * This class is interoperable with java.beans.PropertyChangeSupport,
033: * but relies on a streamlined copy-on-write scheme similar to
034: * that used in CopyOnWriteArrayList. This leads to much better
035: * performance in most event-intensive programs. It also adheres to clarified
036: * semantics of add and remove operations.
037: * <p>
038: * <b>Sample usage.</b>
039: *
040: * <pre>
041: * class Thing {
042: * protected Color myColor = Color.red; // an example property
043: *
044: * protected PropertyChangeMulticaster listeners =
045: * new PropertyChangeMulticaster(this);
046: *
047: * // registration methods, including:
048: * void addListener(PropertyChangeListener l) {
049: * // Use the `ifAbsent' version to avoid duplicate notifications
050: * listeners.addPropertyChangeListenerIfAbsent(l);
051: * }
052: *
053: * public synchronized Color getColor() { // accessor
054: * return myColor;
055: * }
056: *
057: * // internal synchronized state change method; returns old value
058: * protected synchronized Color assignColor(Color newColor) {
059: * Color oldColor = myColor;
060: * myColor = newColor;
061: * return oldColor;
062: * }
063: *
064: * public void setColor(Color newColor) {
065: * // atomically change state
066: * Color oldColor = assignColor(newColor);
067: * // broadcast change notification without holding synch lock
068: * listeners.firePropertyChange("color", oldColor, newColor);
069: * }
070: * }
071: * </pre>
072: * <p>[<a href="http://gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/intro.html"> Introduction to this package. </a>]
073: **/
074:
075: public class PropertyChangeMulticaster implements Serializable {
076:
077: /**
078: * Logger for this class
079: */
080: private static final Logger logger = LoggerFactory
081: .getLogger(PropertyChangeMulticaster.class);
082:
083: // In order to allow this class to be lifted out without using
084: // the whole package, the basic mechanics of CopyOnWriteArrayList
085: // are used here, but not the class itself.
086: // This also makes it barely faster.
087:
088: /**
089: * The array of listeners. Copied on each update
090: **/
091:
092: protected transient PropertyChangeListener[] listeners = new PropertyChangeListener[0];
093:
094: /**
095: * The object to be provided as the "source" for any generated events.
096: * @serial
097: */
098: protected final Object source;
099:
100: /**
101: * HashMap for managing listeners for specific properties.
102: * Maps property names to PropertyChangeMulticaster objects.
103: * @serial
104: */
105: protected HashMap children;
106:
107: /**
108: * Return the child associated with property, or null if no such
109: **/
110:
111: protected synchronized PropertyChangeMulticaster getChild(
112: String propertyName) {
113: logger.debug("getChild(propertyName=" + propertyName
114: + ") - start");
115:
116: return (children == null) ? null
117: : ((PropertyChangeMulticaster) children
118: .get(propertyName));
119: }
120:
121: /**
122: * Constructs a <code>PropertyChangeMulticaster</code> object.
123: *
124: * @param sourceBean The bean to be given as the source for any events.
125: * @exception NullPointerException if sourceBean is null
126: */
127:
128: public PropertyChangeMulticaster(Object sourceBean) {
129: if (sourceBean == null) {
130: throw new NullPointerException();
131: }
132:
133: source = sourceBean;
134: }
135:
136: /**
137: * Add a VetoableChangeListener to the listener list.
138: * The listener is registered for all properties.
139: * If the listener is added multiple times, it will
140: * receive multiple change notifications upon any firePropertyChange
141: *
142: * @param listener The PropertyChangeListener to be added
143: * @exception NullPointerException If listener is null
144: */
145:
146: public synchronized void addPropertyChangeListener(
147: PropertyChangeListener listener) {
148: logger.debug("addPropertyChangeListener(listener=" + listener
149: + ") - start");
150:
151: if (listener == null)
152: throw new NullPointerException();
153:
154: int len = listeners.length;
155: PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
156: if (len > 0)
157: System.arraycopy(listeners, 0, newArray, 0, len);
158: newArray[len] = listener;
159: listeners = newArray;
160: }
161:
162: /**
163: * Add a PropertyChangeListener to the listener list if it is
164: * not already present.
165: * The listener is registered for all properties.
166: * The operation maintains Set semantics: If the listener is already
167: * registered, the operation has no effect.
168: *
169: * @param listener The PropertyChangeListener to be added
170: * @exception NullPointerException If listener is null
171: */
172:
173: public synchronized void addPropertyChangeListenerIfAbsent(
174: PropertyChangeListener listener) {
175: logger.debug("addPropertyChangeListenerIfAbsent(listener="
176: + listener + ") - start");
177:
178: if (listener == null)
179: throw new NullPointerException();
180:
181: // Copy while checking if already present.
182: int len = listeners.length;
183: PropertyChangeListener[] newArray = new PropertyChangeListener[len + 1];
184: for (int i = 0; i < len; ++i) {
185: newArray[i] = listeners[i];
186: if (listener.equals(listeners[i]))
187: return; // already present -- throw away copy
188: }
189: newArray[len] = listener;
190: listeners = newArray;
191: }
192:
193: /**
194: * Remove a PropertyChangeListener from the listener list.
195: * It removes at most one occurrence of the given listener.
196: * If the listener was added multiple times it must be removed
197: * mulitple times.
198: * This removes a PropertyChangeListener that was registered
199: * for all properties, and has no effect if registered for only
200: * one or more specified properties.
201: *
202: * @param listener The PropertyChangeListener to be removed
203: */
204:
205: public synchronized void removePropertyChangeListener(
206: PropertyChangeListener listener) {
207: logger.debug("removePropertyChangeListener(listener="
208: + listener + ") - start");
209:
210: int newlen = listeners.length - 1;
211: if (newlen < 0 || listener == null)
212: return;
213:
214: // Copy while searching for element to remove
215:
216: PropertyChangeListener[] newArray = new PropertyChangeListener[newlen];
217:
218: for (int i = 0; i < newlen; ++i) {
219: if (listener.equals(listeners[i])) {
220: // copy remaining and exit
221: for (int k = i + 1; k <= newlen; ++k)
222: newArray[k - 1] = listeners[k];
223: listeners = newArray;
224: return;
225: } else
226: newArray[i] = listeners[i];
227: }
228:
229: // special-case last cell
230: if (listener.equals(listeners[newlen]))
231: listeners = newArray;
232: }
233:
234: /**
235: * Add a PropertyChangeListener for a specific property. The listener
236: * will be invoked only when a call on firePropertyChange names that
237: * specific property. However, if a listener is registered both for all
238: * properties and a specific property, it will receive multiple
239: * notifications upon changes to that property.
240: *
241: * @param propertyName The name of the property to listen on.
242: * @param listener The PropertyChangeListener to be added
243: * @exception NullPointerException If listener is null
244: */
245:
246: public void addPropertyChangeListener(String propertyName,
247: PropertyChangeListener listener) {
248: logger
249: .debug("addPropertyChangeListener(propertyName="
250: + propertyName + ", listener=" + listener
251: + ") - start");
252:
253: if (listener == null)
254: throw new NullPointerException();
255:
256: PropertyChangeMulticaster child = null;
257:
258: synchronized (this ) {
259: if (children == null)
260: children = new HashMap();
261: else
262: child = (PropertyChangeMulticaster) children
263: .get(propertyName);
264:
265: if (child == null) {
266: child = new PropertyChangeMulticaster(source);
267: children.put(propertyName, child);
268: }
269: }
270:
271: child.addPropertyChangeListener(listener);
272: }
273:
274: /**
275: * Add a PropertyChangeListener for a specific property, if it is not
276: * already registered. The listener
277: * will be invoked only when a call on firePropertyChange names that
278: * specific property.
279: *
280: * @param propertyName The name of the property to listen on.
281: * @param listener The PropertyChangeListener to be added
282: * @exception NullPointerException If listener is null
283: */
284:
285: public void addPropertyChangeListenerIfAbsent(String propertyName,
286: PropertyChangeListener listener) {
287: logger
288: .debug("addPropertyChangeListenerIfAbsent(propertyName="
289: + propertyName
290: + ", listener="
291: + listener
292: + ") - start");
293:
294: if (listener == null)
295: throw new NullPointerException();
296:
297: PropertyChangeMulticaster child = null;
298:
299: synchronized (this ) {
300: if (children == null)
301: children = new HashMap();
302: else
303: child = (PropertyChangeMulticaster) children
304: .get(propertyName);
305:
306: if (child == null) {
307: child = new PropertyChangeMulticaster(source);
308: children.put(propertyName, child);
309: }
310: }
311:
312: child.addPropertyChangeListenerIfAbsent(listener);
313: }
314:
315: /**
316: * Remove a PropertyChangeListener 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 PropertyChangeListener to be removed
323: */
324:
325: public void removePropertyChangeListener(String propertyName,
326: PropertyChangeListener listener) {
327: logger
328: .debug("removePropertyChangeListener(propertyName="
329: + propertyName + ", listener=" + listener
330: + ") - start");
331:
332: PropertyChangeMulticaster child = getChild(propertyName);
333: if (child != null)
334: child.removePropertyChangeListener(listener);
335: }
336:
337: /**
338: * Helper method to relay evt to all listeners.
339: * Called by all public firePropertyChange methods.
340: **/
341:
342: protected void multicast(PropertyChangeEvent evt) {
343: logger.debug("multicast(evt=" + evt + ") - start");
344:
345: PropertyChangeListener[] array; // bind in synch block below
346: PropertyChangeMulticaster child = null;
347:
348: synchronized (this ) {
349: array = listeners;
350:
351: if (children != null && evt.getPropertyName() != null)
352: child = (PropertyChangeMulticaster) children.get(evt
353: .getPropertyName());
354: }
355:
356: for (int i = 0; i < array.length; ++i)
357: array[i].propertyChange(evt);
358:
359: if (child != null)
360: child.multicast(evt);
361:
362: }
363:
364: /**
365: * Report a bound property update to any registered listeners.
366: * No event is fired if old and new are equal and non-null.
367: *
368: * @param propertyName The programmatic name of the property
369: * that was changed.
370: * @param oldValue The old value of the property.
371: * @param newValue The new value of the property.
372: */
373: public void firePropertyChange(String propertyName,
374: Object oldValue, Object newValue) {
375: logger.debug("firePropertyChange(propertyName=" + propertyName
376: + ", oldValue=" + oldValue + ", newValue=" + newValue
377: + ") - start");
378:
379: if (oldValue == null || newValue == null
380: || !oldValue.equals(newValue)) {
381: multicast(new PropertyChangeEvent(source, propertyName,
382: oldValue, newValue));
383: }
384:
385: }
386:
387: /**
388: * Report an int bound property update to any registered listeners.
389: * No event is fired if old and new are equal and non-null.
390: * <p>
391: * This is merely a convenience wrapper around the more general
392: * firePropertyChange method that takes Object values.
393: *
394: * @param propertyName The programmatic name of the property
395: * that was changed.
396: * @param oldValue The old value of the property.
397: * @param newValue The new value of the property.
398: */
399: public void firePropertyChange(String propertyName, int oldValue,
400: int newValue) {
401: logger.debug("firePropertyChange(propertyName=" + propertyName
402: + ", oldValue=" + oldValue + ", newValue=" + newValue
403: + ") - start");
404:
405: if (oldValue != newValue) {
406: multicast(new PropertyChangeEvent(source, propertyName,
407: new Integer(oldValue), new Integer(newValue)));
408: }
409: }
410:
411: /**
412: * Report a boolean bound property update to any registered listeners.
413: * No event is fired if old and new are equal and non-null.
414: * <p>
415: * This is merely a convenience wrapper around the more general
416: * firePropertyChange method that takes Object values.
417: *
418: * @param propertyName The programmatic name of the property
419: * that was changed.
420: * @param oldValue The old value of the property.
421: * @param newValue The new value of the property.
422: */
423: public void firePropertyChange(String propertyName,
424: boolean oldValue, boolean newValue) {
425: logger.debug("firePropertyChange(propertyName=" + propertyName
426: + ", oldValue=" + oldValue + ", newValue=" + newValue
427: + ") - start");
428:
429: if (oldValue != newValue) {
430: multicast(new PropertyChangeEvent(source, propertyName,
431: new Boolean(oldValue), new Boolean(newValue)));
432: }
433: }
434:
435: /**
436: * Fire an existing PropertyChangeEvent to any registered listeners.
437: * No event is fired if the given event's old and new values are
438: * equal and non-null.
439: * @param evt The PropertyChangeEvent object.
440: */
441: public void firePropertyChange(PropertyChangeEvent evt) {
442: logger.debug("firePropertyChange(evt=" + evt + ") - start");
443:
444: Object oldValue = evt.getOldValue();
445: Object newValue = evt.getNewValue();
446: if (oldValue == null || newValue == null
447: || !oldValue.equals(newValue))
448: multicast(evt);
449: }
450:
451: /**
452: * Check if there are any listeners for a specific property.
453: * If propertyName is null, return whether there are any listeners at all.
454: *
455: * @param propertyName the property name.
456: * @return true if there are one or more listeners for the given property
457: *
458: */
459: public boolean hasListeners(String propertyName) {
460: logger.debug("hasListeners(propertyName=" + propertyName
461: + ") - start");
462:
463: PropertyChangeMulticaster child;
464:
465: synchronized (this ) {
466: if (listeners.length > 0)
467: return true;
468: else if (propertyName == null || children == null)
469: return false;
470: else {
471: child = (PropertyChangeMulticaster) children
472: .get(propertyName);
473: if (child == null)
474: return false;
475: }
476: }
477:
478: return child.hasListeners(null);
479: }
480:
481: /**
482: * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
483: * <p>
484: * At serialization time we skip non-serializable listeners and
485: * only serialize the serializable listeners.
486: *
487: */
488: private synchronized void writeObject(ObjectOutputStream s)
489: throws IOException {
490: logger.debug("writeObject(s=" + s + ") - start");
491:
492: s.defaultWriteObject();
493:
494: for (int i = 0; i < listeners.length; i++) {
495: if (listeners[i] instanceof Serializable) {
496: s.writeObject(listeners[i]);
497: }
498: }
499: s.writeObject(null);
500: }
501:
502: private void readObject(ObjectInputStream s)
503: throws ClassNotFoundException, IOException {
504: logger.debug("readObject(s=" + s + ") - start");
505:
506: listeners = new PropertyChangeListener[0]; // paranoically reset
507: s.defaultReadObject();
508:
509: Object listenerOrNull;
510: while (null != (listenerOrNull = s.readObject())) {
511: addPropertyChangeListener((PropertyChangeListener) listenerOrNull);
512: }
513: }
514:
515: }
|