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