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 }