1. /*
  2. * @(#)PropertyChangeSupport.java 1.39 03/01/23
  3. *
  4. * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.beans;
  8. import java.io.Serializable;
  9. import java.io.ObjectOutputStream;
  10. import java.io.ObjectInputStream;
  11. import java.io.IOException;
  12. import java.util.Arrays;
  13. import java.util.ArrayList;
  14. import java.util.Iterator;
  15. import java.util.List;
  16. /**
  17. * This is a utility class that can be used by beans that support bound
  18. * properties. You can use an instance of this class as a member field
  19. * of your bean and delegate various work to it.
  20. *
  21. * This class is serializable. When it is serialized it will save
  22. * (and restore) any listeners that are themselves serializable. Any
  23. * non-serializable listeners will be skipped during serialization.
  24. *
  25. */
  26. public class PropertyChangeSupport implements java.io.Serializable {
  27. /**
  28. * Constructs a <code>PropertyChangeSupport</code> object.
  29. *
  30. * @param sourceBean The bean to be given as the source for any events.
  31. */
  32. public PropertyChangeSupport(Object sourceBean) {
  33. if (sourceBean == null) {
  34. throw new NullPointerException();
  35. }
  36. source = sourceBean;
  37. }
  38. /**
  39. * Add a PropertyChangeListener to the listener list.
  40. * The listener is registered for all properties.
  41. *
  42. * @param listener The PropertyChangeListener to be added
  43. */
  44. public synchronized void addPropertyChangeListener(
  45. PropertyChangeListener listener) {
  46. if (listener instanceof PropertyChangeListenerProxy) {
  47. PropertyChangeListenerProxy proxy =
  48. (PropertyChangeListenerProxy)listener;
  49. // Call two argument add method.
  50. addPropertyChangeListener(proxy.getPropertyName(),
  51. (PropertyChangeListener)proxy.getListener());
  52. } else {
  53. if (listeners == null) {
  54. listeners = new java.util.Vector();
  55. }
  56. listeners.addElement(listener);
  57. }
  58. }
  59. /**
  60. * Remove a PropertyChangeListener from the listener list.
  61. * This removes a PropertyChangeListener that was registered
  62. * for all properties.
  63. *
  64. * @param listener The PropertyChangeListener to be removed
  65. */
  66. public synchronized void removePropertyChangeListener(
  67. PropertyChangeListener listener) {
  68. if (listener instanceof PropertyChangeListenerProxy) {
  69. PropertyChangeListenerProxy proxy =
  70. (PropertyChangeListenerProxy)listener;
  71. // Call two argument remove method.
  72. removePropertyChangeListener(proxy.getPropertyName(),
  73. (PropertyChangeListener)proxy.getListener());
  74. } else {
  75. if (listeners == null) {
  76. return;
  77. }
  78. listeners.removeElement(listener);
  79. }
  80. }
  81. /**
  82. * Returns an array of all the listeners that were added to the
  83. * PropertyChangeSupport object with addPropertyChangeListener().
  84. * <p>
  85. * If some listeners have been added with a named property, then
  86. * the returned array will be a mixture of PropertyChangeListeners
  87. * and <code>PropertyChangeListenerProxy</code>s. If the calling
  88. * method is interested in distinguishing the listeners then it must
  89. * test each element to see if it's a
  90. * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
  91. * the parameter.
  92. *
  93. * <pre>
  94. * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
  95. * for (int i = 0; i < listeners.length; i++) {
  96. * if (listeners[i] instanceof PropertyChangeListenerProxy) {
  97. * PropertyChangeListenerProxy proxy =
  98. * (PropertyChangeListenerProxy)listeners[i];
  99. * if (proxy.getPropertyName().equals("foo")) {
  100. * // proxy is a PropertyChangeListener which was associated
  101. * // with the property named "foo"
  102. * }
  103. * }
  104. * }
  105. *</pre>
  106. *
  107. * @see PropertyChangeListenerProxy
  108. * @return all of the <code>PropertyChangeListeners</code> added or an
  109. * empty array if no listeners have been added
  110. * @since 1.4
  111. */
  112. public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
  113. List returnList = new ArrayList();
  114. // Add all the PropertyChangeListeners
  115. if (listeners != null) {
  116. returnList.addAll(listeners);
  117. }
  118. // Add all the PropertyChangeListenerProxys
  119. if (children != null) {
  120. Iterator iterator = children.keySet().iterator();
  121. while (iterator.hasNext()) {
  122. String key = (String)iterator.next();
  123. PropertyChangeSupport child =
  124. (PropertyChangeSupport)children.get(key);
  125. PropertyChangeListener[] childListeners =
  126. child.getPropertyChangeListeners();
  127. for (int index = childListeners.length - 1; index >= 0;
  128. index--) {
  129. returnList.add(new PropertyChangeListenerProxy(
  130. key, childListeners[index]));
  131. }
  132. }
  133. }
  134. return (PropertyChangeListener[])(returnList.toArray(
  135. new PropertyChangeListener[0]));
  136. }
  137. /**
  138. * Add a PropertyChangeListener for a specific property. The listener
  139. * will be invoked only when a call on firePropertyChange names that
  140. * specific property.
  141. *
  142. * @param propertyName The name of the property to listen on.
  143. * @param listener The PropertyChangeListener to be added
  144. */
  145. public synchronized void addPropertyChangeListener(
  146. String propertyName,
  147. PropertyChangeListener listener) {
  148. if (children == null) {
  149. children = new java.util.Hashtable();
  150. }
  151. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  152. if (child == null) {
  153. child = new PropertyChangeSupport(source);
  154. children.put(propertyName, child);
  155. }
  156. child.addPropertyChangeListener(listener);
  157. }
  158. /**
  159. * Remove a PropertyChangeListener for a specific property.
  160. *
  161. * @param propertyName The name of the property that was listened on.
  162. * @param listener The PropertyChangeListener to be removed
  163. */
  164. public synchronized void removePropertyChangeListener(
  165. String propertyName,
  166. PropertyChangeListener listener) {
  167. if (children == null) {
  168. return;
  169. }
  170. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  171. if (child == null) {
  172. return;
  173. }
  174. child.removePropertyChangeListener(listener);
  175. }
  176. /**
  177. * Returns an array of all the listeners which have been associated
  178. * with the named property.
  179. *
  180. * @return all of the <code>PropertyChangeListeners</code> associated with
  181. * the named property or an empty array if no listeners have
  182. * been added
  183. */
  184. public synchronized PropertyChangeListener[] getPropertyChangeListeners(
  185. String propertyName) {
  186. ArrayList returnList = new ArrayList();
  187. if (children != null) {
  188. PropertyChangeSupport support =
  189. (PropertyChangeSupport)children.get(propertyName);
  190. if (support != null) {
  191. returnList.addAll(
  192. Arrays.asList(support.getPropertyChangeListeners()));
  193. }
  194. }
  195. return (PropertyChangeListener[])(returnList.toArray(
  196. new PropertyChangeListener[0]));
  197. }
  198. /**
  199. * Report a bound property update to any registered listeners.
  200. * No event is fired if old and new are equal and non-null.
  201. *
  202. * @param propertyName The programmatic name of the property
  203. * that was changed.
  204. * @param oldValue The old value of the property.
  205. * @param newValue The new value of the property.
  206. */
  207. public void firePropertyChange(String propertyName,
  208. Object oldValue, Object newValue) {
  209. if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
  210. return;
  211. }
  212. java.util.Vector targets = null;
  213. PropertyChangeSupport child = null;
  214. synchronized (this) {
  215. if (listeners != null) {
  216. targets = (java.util.Vector) listeners.clone();
  217. }
  218. if (children != null && propertyName != null) {
  219. child = (PropertyChangeSupport)children.get(propertyName);
  220. }
  221. }
  222. PropertyChangeEvent evt = new PropertyChangeEvent(source,
  223. propertyName, oldValue, newValue);
  224. if (targets != null) {
  225. for (int i = 0; i < targets.size(); i++) {
  226. PropertyChangeListener target = (PropertyChangeListener)targets.elementAt(i);
  227. target.propertyChange(evt);
  228. }
  229. }
  230. if (child != null) {
  231. child.firePropertyChange(evt);
  232. }
  233. }
  234. /**
  235. * Report an int bound property update to any registered listeners.
  236. * No event is fired if old and new are equal and non-null.
  237. * <p>
  238. * This is merely a convenience wrapper around the more general
  239. * firePropertyChange method that takes Object values.
  240. *
  241. * @param propertyName The programmatic name of the property
  242. * that was changed.
  243. * @param oldValue The old value of the property.
  244. * @param newValue The new value of the property.
  245. */
  246. public void firePropertyChange(String propertyName,
  247. int oldValue, int newValue) {
  248. if (oldValue == newValue) {
  249. return;
  250. }
  251. firePropertyChange(propertyName, new Integer(oldValue), new Integer(newValue));
  252. }
  253. /**
  254. * Report a boolean bound property update to any registered listeners.
  255. * No event is fired if old and new are equal and non-null.
  256. * <p>
  257. * This is merely a convenience wrapper around the more general
  258. * firePropertyChange method that takes Object values.
  259. *
  260. * @param propertyName The programmatic name of the property
  261. * that was changed.
  262. * @param oldValue The old value of the property.
  263. * @param newValue The new value of the property.
  264. */
  265. public void firePropertyChange(String propertyName,
  266. boolean oldValue, boolean newValue) {
  267. if (oldValue == newValue) {
  268. return;
  269. }
  270. firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  271. }
  272. /**
  273. * Fire an existing PropertyChangeEvent to any registered listeners.
  274. * No event is fired if the given event's old and new values are
  275. * equal and non-null.
  276. * @param evt The PropertyChangeEvent object.
  277. */
  278. public void firePropertyChange(PropertyChangeEvent evt) {
  279. Object oldValue = evt.getOldValue();
  280. Object newValue = evt.getNewValue();
  281. String propertyName = evt.getPropertyName();
  282. if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
  283. return;
  284. }
  285. java.util.Vector targets = null;
  286. PropertyChangeSupport child = null;
  287. synchronized (this) {
  288. if (listeners != null) {
  289. targets = (java.util.Vector) listeners.clone();
  290. }
  291. if (children != null && propertyName != null) {
  292. child = (PropertyChangeSupport)children.get(propertyName);
  293. }
  294. }
  295. if (targets != null) {
  296. for (int i = 0; i < targets.size(); i++) {
  297. PropertyChangeListener target = (PropertyChangeListener)targets.elementAt(i);
  298. target.propertyChange(evt);
  299. }
  300. }
  301. if (child != null) {
  302. child.firePropertyChange(evt);
  303. }
  304. }
  305. /**
  306. * Check if there are any listeners for a specific property.
  307. *
  308. * @param propertyName the property name.
  309. * @return true if there are ore or more listeners for the given property
  310. */
  311. public synchronized boolean hasListeners(String propertyName) {
  312. if (listeners != null && !listeners.isEmpty()) {
  313. // there is a generic listener
  314. return true;
  315. }
  316. if (children != null) {
  317. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  318. if (child != null && child.listeners != null) {
  319. return !child.listeners.isEmpty();
  320. }
  321. }
  322. return false;
  323. }
  324. /**
  325. * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
  326. * <p>
  327. * At serialization time we skip non-serializable listeners and
  328. * only serialize the serializable listeners.
  329. *
  330. */
  331. private void writeObject(ObjectOutputStream s) throws IOException {
  332. s.defaultWriteObject();
  333. java.util.Vector v = null;
  334. synchronized (this) {
  335. if (listeners != null) {
  336. v = (java.util.Vector) listeners.clone();
  337. }
  338. }
  339. if (v != null) {
  340. for (int i = 0; i < v.size(); i++) {
  341. PropertyChangeListener l = (PropertyChangeListener)v.elementAt(i);
  342. if (l instanceof Serializable) {
  343. s.writeObject(l);
  344. }
  345. }
  346. }
  347. s.writeObject(null);
  348. }
  349. private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
  350. s.defaultReadObject();
  351. Object listenerOrNull;
  352. while (null != (listenerOrNull = s.readObject())) {
  353. addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
  354. }
  355. }
  356. /**
  357. * "listeners" lists all the generic listeners.
  358. *
  359. * This is transient - its state is written in the writeObject method.
  360. */
  361. transient private java.util.Vector listeners;
  362. /**
  363. * Hashtable for managing listeners for specific properties.
  364. * Maps property names to PropertyChangeSupport objects.
  365. * @serial
  366. * @since 1.2
  367. */
  368. private java.util.Hashtable children;
  369. /**
  370. * The object to be provided as the "source" for any generated events.
  371. * @serial
  372. */
  373. private Object source;
  374. /**
  375. * Internal version number
  376. * @serial
  377. * @since
  378. */
  379. private int propertyChangeSupportSerializedDataVersion = 2;
  380. /**
  381. * Serialization version ID, so we're compatible with JDK 1.1
  382. */
  383. static final long serialVersionUID = 6401253773779951803L;
  384. }