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 &lt; listeners.length; i++) {
111             *      if (listeners[i] instanceof PropertyChangeListenerProxy) {
112             *              PropertyChangeListenerProxy proxy = (PropertyChangeListenerProxy) listeners[i];
113             *              if (proxy.getPropertyName().equals(&quot;foo&quot;)) {
114             *                      // proxy is a PropertyChangeListener which was associated
115             *                      // with the property named &quot;foo&quot;
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    }