001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings.util;
014:
015: import java.beans.PropertyChangeEvent;
016: import java.beans.PropertyChangeListener;
017: import java.lang.ref.ReferenceQueue;
018: import java.lang.ref.WeakReference;
019: import java.io.Serializable;
020:
021: /**
022: * This is a utility class that can be used by beans that support bound
023: * properties. You can use an instance of this class as a member field
024: * of your bean and delegate various work to it.
025: * <p/>
026: * This class is serializable. When it is serialized it will save
027: * (and restore) any listeners that are themselves serializable. Any
028: * non-serializable listeners will be skipped during serialization.
029: *
030: * @author <a href="mailto:haaf@mercatis.de">Holger Engels</a>
031: */
032: public class WeakPropertyChangeSupport implements Serializable {
033: /**
034: * Constructs a <code>WeakPropertyChangeSupport</code> object.
035: *
036: * @param sourceBean The bean to be given as the source for any events.
037: */
038: public WeakPropertyChangeSupport(Object sourceBean) {
039: if (sourceBean == null) {
040: throw new NullPointerException();
041: }
042: source = sourceBean;
043: }
044:
045: /**
046: * Add a PropertyChangeListener to the listener list.
047: * The listener is registered for all properties.
048: *
049: * @param listener The PropertyChangeListener to be added
050: */
051: public synchronized void addPropertyChangeListener(
052: PropertyChangeListener listener) {
053: if (listeners == null) {
054: listeners = new java.util.LinkedList();
055: } else
056: processQueue();
057: listeners.add(WeakEntry.create(listener, queue));
058: }
059:
060: /**
061: * Remove a PropertyChangeListener from the listener list.
062: * This removes a PropertyChangeListener that was registered
063: * for all properties.
064: *
065: * @param listener The PropertyChangeListener to be removed
066: */
067: public synchronized void removePropertyChangeListener(
068: PropertyChangeListener listener) {
069: if (listeners == null) {
070: return;
071: } else
072: processQueue();
073: listeners.remove(WeakEntry.create(listener));
074: }
075:
076: /**
077: * Add a PropertyChangeListener for a specific property. The listener
078: * will be invoked only when a call on firePropertyChange names that
079: * specific property.
080: *
081: * @param propertyName The name of the property to listen on.
082: * @param listener The PropertyChangeListener to be added
083: */
084: public synchronized void addPropertyChangeListener(
085: String propertyName, PropertyChangeListener listener) {
086: if (children == null) {
087: children = new java.util.WeakHashMap();
088: }
089: WeakPropertyChangeSupport child = (WeakPropertyChangeSupport) children
090: .get(propertyName);
091: if (child == null) {
092: child = new WeakPropertyChangeSupport(source);
093: children.put(propertyName, child);
094: }
095: child.addPropertyChangeListener(listener);
096: }
097:
098: /**
099: * Remove a PropertyChangeListener for a specific property.
100: *
101: * @param propertyName The name of the property that was listened on.
102: * @param listener The PropertyChangeListener to be removed
103: */
104: public synchronized void removePropertyChangeListener(
105: String propertyName, PropertyChangeListener listener) {
106: if (children == null) {
107: return;
108: }
109: WeakPropertyChangeSupport child = (WeakPropertyChangeSupport) children
110: .get(propertyName);
111: if (child == null) {
112: return;
113: }
114: child.removePropertyChangeListener(listener);
115: }
116:
117: /**
118: * Report a bound property update to any registered listeners.
119: * No event is fired if old and new are equal and non-null.
120: *
121: * @param propertyName The programmatic name of the property
122: * that was changed.
123: * @param oldValue The old value of the property.
124: * @param newValue The new value of the property.
125: */
126: public void firePropertyChange(String propertyName,
127: Object oldValue, Object newValue) {
128: if (oldValue != null && newValue != null
129: && oldValue.equals(newValue)) {
130: return;
131: }
132:
133: java.util.LinkedList targets = null;
134: WeakPropertyChangeSupport child = null;
135: synchronized (this ) {
136: if (listeners != null) {
137: targets = (java.util.LinkedList) listeners.clone();
138: }
139: if (children != null && propertyName != null) {
140: child = (WeakPropertyChangeSupport) children
141: .get(propertyName);
142: }
143: }
144:
145: PropertyChangeEvent evt = new PropertyChangeEvent(source,
146: propertyName, oldValue, newValue);
147:
148: if (targets != null) {
149: for (int i = 0; i < targets.size(); i++) {
150: WeakEntry entry = (WeakEntry) targets.get(i);
151: PropertyChangeListener target = (PropertyChangeListener) entry
152: .get();
153: if (target != null)
154: target.propertyChange(evt);
155: }
156: }
157:
158: if (child != null) {
159: child.firePropertyChange(evt);
160: }
161: }
162:
163: /**
164: * Report an int bound property update to any registered listeners.
165: * No event is fired if old and new are equal and non-null.
166: * <p/>
167: * This is merely a convenience wrapper around the more general
168: * firePropertyChange method that takes Object values.
169: *
170: * @param propertyName The programmatic name of the property
171: * that was changed.
172: * @param oldValue The old value of the property.
173: * @param newValue The new value of the property.
174: */
175: public void firePropertyChange(String propertyName, int oldValue,
176: int newValue) {
177: if (oldValue == newValue) {
178: return;
179: }
180: firePropertyChange(propertyName, new Integer(oldValue),
181: new Integer(newValue));
182: }
183:
184: /**
185: * Report a boolean bound property update to any registered listeners.
186: * No event is fired if old and new are equal and non-null.
187: * <p/>
188: * This is merely a convenience wrapper around the more general
189: * firePropertyChange method that takes Object values.
190: *
191: * @param propertyName The programmatic name of the property
192: * that was changed.
193: * @param oldValue The old value of the property.
194: * @param newValue The new value of the property.
195: */
196: public void firePropertyChange(String propertyName,
197: boolean oldValue, boolean newValue) {
198: if (oldValue == newValue) {
199: return;
200: }
201: firePropertyChange(propertyName, Boolean.valueOf(oldValue),
202: Boolean.valueOf(newValue));
203: }
204:
205: /**
206: * Fire an existing PropertyChangeEvent to any registered listeners.
207: * No event is fired if the given event's old and new values are
208: * equal and non-null.
209: *
210: * @param evt The PropertyChangeEvent object.
211: */
212: public void firePropertyChange(PropertyChangeEvent evt) {
213: Object oldValue = evt.getOldValue();
214: Object newValue = evt.getNewValue();
215: String propertyName = evt.getPropertyName();
216: if (oldValue != null && newValue != null
217: && oldValue.equals(newValue)) {
218: return;
219: }
220:
221: java.util.LinkedList targets = null;
222: WeakPropertyChangeSupport child = null;
223: synchronized (this ) {
224: if (listeners != null) {
225: targets = (java.util.LinkedList) listeners.clone();
226: }
227: if (children != null && propertyName != null) {
228: child = (WeakPropertyChangeSupport) children
229: .get(propertyName);
230: }
231: }
232:
233: if (targets != null) {
234: for (int i = 0; i < targets.size(); i++) {
235: WeakEntry entry = (WeakEntry) targets.get(i);
236: PropertyChangeListener target = (PropertyChangeListener) entry
237: .get();
238: if (target != null)
239: target.propertyChange(evt);
240: }
241: }
242: if (child != null) {
243: child.firePropertyChange(evt);
244: }
245: }
246:
247: /**
248: * Check if there are any listeners for a specific property.
249: *
250: * @param propertyName the property name.
251: * @return true if there are ore or more listeners for the given property
252: */
253: public synchronized boolean hasListeners(String propertyName) {
254: if (listeners != null && !listeners.isEmpty()) {
255: // there is a generic listener
256: return true;
257: }
258: if (children != null) {
259: WeakPropertyChangeSupport child = (WeakPropertyChangeSupport) children
260: .get(propertyName);
261: if (child != null && child.listeners != null) {
262: return !child.listeners.isEmpty();
263: }
264: }
265: return false;
266: }
267:
268: /**
269: * "listeners" lists all the generic listeners.
270: * <p/>
271: * This is transient - its state is written in the writeObject method.
272: */
273: transient private java.util.LinkedList listeners;
274:
275: /**
276: * Hashtable for managing listeners for specific properties.
277: * Maps property names to WeakPropertyChangeSupport objects.
278: */
279: private transient java.util.WeakHashMap children;
280:
281: /**
282: * The object to be provided as the "source" for any generated events.
283: */
284: private Object source;
285:
286: private transient ReferenceQueue queue = new ReferenceQueue();
287:
288: /**
289: * Remove all invalidated entries from the map, that is, remove all entries
290: * whose keys have been discarded. This method should be invoked once by
291: * each public mutator in this class. We don't invoke this method in
292: * public accessors because that can lead to surprising
293: * ConcurrentModificationExceptions.
294: */
295: private void processQueue() {
296: WeakEntry wk;
297: while ((wk = (WeakEntry) queue.poll()) != null) {
298: listeners.remove(wk);
299: }
300: }
301:
302: static private class WeakEntry extends WeakReference {
303: private int hash; /* Hashcode of key, stored here since the key
304: may be tossed by the GC */
305:
306: private WeakEntry(Object k) {
307: super (k);
308: hash = k.hashCode();
309: }
310:
311: private static WeakEntry create(Object k) {
312: if (k == null)
313: return null;
314: else
315: return new WeakEntry(k);
316: }
317:
318: private WeakEntry(Object k, ReferenceQueue q) {
319: super (k, q);
320: hash = k.hashCode();
321: }
322:
323: private static WeakEntry create(Object k, ReferenceQueue q) {
324: if (k == null)
325: return null;
326: else
327: return new WeakEntry(k, q);
328: }
329:
330: /* A WeakEntry is equal to another WeakEntry iff they both refer to objects
331: * that are, in turn, equal according to their own equals methods */
332: public boolean equals(Object o) {
333: if (this == o)
334: return true;
335: if (!(o instanceof WeakEntry))
336: return false;
337: Object t = this .get();
338: Object u = ((WeakEntry) o).get();
339: if ((t == null) || (u == null))
340: return false;
341: if (t == u)
342: return true;
343: return t.equals(u);
344: }
345:
346: public int hashCode() {
347: return hash;
348: }
349: }
350: }
|