001 /*
002 * @(#)SwingPropertyChangeSupport.java 1.18 03/01/23
003 *
004 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. SUN
005 * PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
006 */
007
008 package org.springframework.binding.value.support;
009
010 import java.beans.PropertyChangeEvent;
011 import java.beans.PropertyChangeListener;
012 import java.beans.PropertyChangeListenerProxy;
013 import java.io.IOException;
014 import java.io.ObjectInputStream;
015 import java.io.ObjectOutputStream;
016 import java.io.Serializable;
017 import java.util.ArrayList;
018 import java.util.Arrays;
019 import java.util.HashMap;
020 import java.util.Iterator;
021 import java.util.List;
022 import java.util.Map;
023
024 import javax.swing.event.EventListenerList;
025
026 import org.springframework.util.ObjectUtils;
027
028 /**
029 * Convenience class that provides propertyChange support. Listeners can be
030 * (un)registered on specific properties while firing of
031 * {@link PropertyChangeEvent}s is eased.
032 */
033 public final class PropertyChangeSupport implements Serializable {
034
035 /** Lists all the generic listeners. */
036 transient private EventListenerList listeners;
037
038 /** Contains SwingPropertyChangeSupports for individual properties. */
039 private Map children;
040
041 /** Source of the events. */
042 private Object source;
043
044 // Serialization version ID
045 static final long serialVersionUID = 7162625831330845068L;
046
047 /**
048 * Constructs a SwingPropertyChangeSupport object.
049 *
050 * @param sourceBean The bean to be given as the source for any events.
051 */
052 public PropertyChangeSupport(Object sourceBean) {
053 if (sourceBean == null) {
054 throw new NullPointerException();
055 }
056 this.source = sourceBean;
057 }
058
059 /**
060 * Add a PropertyChangeListener to the listener list. The listener is
061 * registered for all properties.
062 *
063 * @param listener The PropertyChangeListener to be added
064 */
065 public void addPropertyChangeListener(PropertyChangeListener listener) {
066 if (listener instanceof PropertyChangeListenerProxy) {
067 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
068 addPropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener());
069 }
070 else {
071 if (listeners == null) {
072 listeners = new EventListenerList();
073 }
074 listeners.add(PropertyChangeListener.class, listener);
075 }
076 }
077
078 /**
079 * Remove a PropertyChangeListener from the listener list. This removes a
080 * PropertyChangeListener that was registered for all properties.
081 *
082 * @param listener The PropertyChangeListener to be removed
083 */
084 public void removePropertyChangeListener(PropertyChangeListener listener) {
085 if (listener instanceof PropertyChangeListenerProxy) {
086 PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listener;
087 removePropertyChangeListener(proxy.getPropertyName(), (PropertyChangeListener) proxy.getListener());
088 }
089 else {
090 if (listeners == null) {
091 return;
092 }
093 listeners.remove(PropertyChangeListener.class, listener);
094 }
095 }
096
097 /**
098 * Returns an array of all the listeners that were added to the
099 * SwingPropertyChangeSupport object with addPropertyChangeListener().
100 * <p>
101 * If some listeners have been added with a named property, then the
102 * returned array will be a mixture of PropertyChangeListeners and
103 * <code>PropertyChangeListenerProxy</code>s. If the calling method is
104 * interested in distinguishing the listeners then it must test each element
105 * to see if it's a <code>PropertyChangeListenerProxy</code> perform the
106 * cast and examine the parameter.
107 *
108 * <pre>
109 * PropertyChangeListener[] listeners = support.getPropertyChangeListeners();
110 * for (int i = 0; i < listeners.length; i++) {
111 * if (listeners[i] instanceof PropertyChangeListenerProxy) {
112 * PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listeners[i];
113 * if (proxy.getPropertyName().equals("foo")) {
114 * // proxy is a PropertyChangeListener which was associated
115 * // with the property named "foo"
116 * }
117 * }
118 * }
119 * </pre>
120 *
121 * @see java.beans.PropertyChangeListenerProxy
122 * @see java.beans.PropertyChangeSupport#getPropertyChangeListeners
123 * @return all of the <code>PropertyChangeListener</code> s added or an
124 * empty array if no listeners have been added
125 * @since 1.4
126 */
127 public PropertyChangeListener[] getPropertyChangeListeners() {
128 List returnList = new ArrayList();
129
130 // Add all the PropertyChangeListeners
131 if (listeners != null) {
132 returnList.addAll(Arrays.asList(listeners.getListeners(PropertyChangeListener.class)));
133 }
134
135 // Add all the PropertyChangeListenerProxys
136 if (children != null) {
137 Iterator iterator = children.keySet().iterator();
138 while (iterator.hasNext()) {
139 String key = (String) iterator.next();
140 PropertyChangeSupport child = (PropertyChangeSupport) children.get(key);
141 PropertyChangeListener[] childListeners = child.getPropertyChangeListeners();
142 for (int index = childListeners.length - 1; index >= 0; index--) {
143 returnList.add(new PropertyChangeListenerProxy(key, childListeners[index]));
144 }
145 }
146 }
147 return (PropertyChangeListener[]) returnList.toArray(new PropertyChangeListener[returnList.size()]);
148 }
149
150 /**
151 * Add a PropertyChangeListener for a specific property. The listener will
152 * be invoked only when a call on firePropertyChange names that specific
153 * property.
154 *
155 * @param propertyName The name of the property to listen on.
156 * @param listener The PropertyChangeListener to be added
157 */
158 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
159 if (children == null) {
160 children = new HashMap();
161 }
162 PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName);
163 if (child == null) {
164 child = new PropertyChangeSupport(source);
165 children.put(propertyName, child);
166 }
167 child.addPropertyChangeListener(listener);
168 }
169
170 /**
171 * Remove a PropertyChangeListener for a specific property.
172 *
173 * @param propertyName The name of the property that was listened on.
174 * @param listener The PropertyChangeListener to be removed
175 */
176 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
177 if (children == null) {
178 return;
179 }
180 PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName);
181 if (child == null) {
182 return;
183 }
184 child.removePropertyChangeListener(listener);
185 }
186
187 /**
188 * Returns an array of all the listeners which have been associated with the
189 * named property.
190 *
191 * @return all of the <code>PropertyChangeListeners</code> associated with
192 * the named property or an empty array if no listeners have been added
193 */
194 public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
195 List returnList = new ArrayList();
196
197 if (children != null) {
198 PropertyChangeSupport support = (PropertyChangeSupport) children.get(propertyName);
199 if (support != null) {
200 returnList.addAll(Arrays.asList(support.getPropertyChangeListeners()));
201 }
202 }
203 return (PropertyChangeListener[]) (returnList.toArray(new PropertyChangeListener[0]));
204 }
205
206 /**
207 * Report a bound property update to any registered listeners. No event is
208 * fired if old and new are equal and non-null.
209 *
210 * @param propertyName The programmatic name of the property that was
211 * changed.
212 * @param oldValue The old value of the property.
213 * @param newValue The new value of the property.
214 */
215 public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
216 firePropertyChange(new PropertyChangeEvent(source, propertyName, oldValue, newValue));
217 }
218
219 /**
220 * Fire an existing PropertyChangeEvent to any registered listeners. No
221 * event is fired if the given event's old and new values are equal and
222 * non-null.
223 *
224 * @param evt The PropertyChangeEvent object.
225 */
226 public void firePropertyChange(PropertyChangeEvent evt) {
227 Object oldValue = evt.getOldValue();
228 Object newValue = evt.getNewValue();
229 if (ObjectUtils.nullSafeEquals(oldValue, newValue)) {
230 return;
231 }
232
233 String propertyName = evt.getPropertyName();
234 PropertyChangeSupport child = null;
235 if (children != null) {
236 if (children != null && propertyName != null) {
237 child = (PropertyChangeSupport) children.get(propertyName);
238 }
239 }
240
241 if (listeners != null) {
242 Object[] listenerList = listeners.getListenerList();
243 for (int i = 0; i <= listenerList.length - 2; i += 2) {
244 if (listenerList[i] == PropertyChangeListener.class) {
245 ((PropertyChangeListener) listenerList[i + 1]).propertyChange(evt);
246 }
247 }
248 }
249
250 if (child != null) {
251 child.firePropertyChange(evt);
252 }
253 }
254
255 /**
256 * Check if there are any listeners for a specific property.
257 *
258 * @param propertyName the property name.
259 * @return true if there are ore or more listeners for the given property
260 */
261 public boolean hasListeners(String propertyName) {
262 if (listeners != null && listeners.getListenerCount(PropertyChangeListener.class) > 0) {
263 // there is a generic listener
264 return true;
265 }
266 if (children != null) {
267 PropertyChangeSupport child = (PropertyChangeSupport) children.get(propertyName);
268 if (child != null) {
269 // The child will always have a listeners ArrayList.
270 return child.hasListeners(propertyName);
271 }
272 }
273 return false;
274 }
275
276 private void writeObject(ObjectOutputStream s) throws IOException {
277 s.defaultWriteObject();
278
279 if (listeners != null) {
280 Object[] listenerList = listeners.getListenerList();
281 for (int i = 0; i <= listenerList.length - 2; i += 2) {
282 if (listenerList[i] == PropertyChangeListener.class
283 && (PropertyChangeListener) listenerList[i + 1] instanceof Serializable) {
284 s.writeObject(listenerList[i + 1]);
285 }
286 }
287 }
288 s.writeObject(null);
289 }
290
291 private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
292 s.defaultReadObject();
293
294 Object listenerOrNull;
295 while (null != (listenerOrNull = s.readObject())) {
296 addPropertyChangeListener((PropertyChangeListener) listenerOrNull);
297 }
298 }
299
300 }