1. /*
  2. * @(#)PropertyChangeSupport.java 1.49 04/05/11
  3. *
  4. * Copyright 2004 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. import sun.awt.EventListenerAggregate;
  17. /**
  18. * This is a utility class that can be used by beans that support bound
  19. * properties. You can use an instance of this class as a member field
  20. * of your bean and delegate various work to it.
  21. *
  22. * This class is serializable. When it is serialized it will save
  23. * (and restore) any listeners that are themselves serializable. Any
  24. * non-serializable listeners will be skipped during serialization.
  25. *
  26. */
  27. public class PropertyChangeSupport implements java.io.Serializable {
  28. // Manages the listener list.
  29. private transient EventListenerAggregate listeners;
  30. /**
  31. * Constructs a <code>PropertyChangeSupport</code> object.
  32. *
  33. * @param sourceBean The bean to be given as the source for any events.
  34. */
  35. public PropertyChangeSupport(Object sourceBean) {
  36. if (sourceBean == null) {
  37. throw new NullPointerException();
  38. }
  39. source = sourceBean;
  40. }
  41. /**
  42. * Add a PropertyChangeListener to the listener list.
  43. * The listener is registered for all properties.
  44. * The same listener object may be added more than once, and will be called
  45. * as many times as it is added.
  46. * If <code>listener</code> is null, no exception is thrown and no action
  47. * is taken.
  48. *
  49. * @param listener The PropertyChangeListener to be added
  50. */
  51. public synchronized void addPropertyChangeListener(
  52. PropertyChangeListener listener) {
  53. if (listener == null) {
  54. return;
  55. }
  56. if (listener instanceof PropertyChangeListenerProxy) {
  57. PropertyChangeListenerProxy proxy =
  58. (PropertyChangeListenerProxy)listener;
  59. // Call two argument add method.
  60. addPropertyChangeListener(proxy.getPropertyName(),
  61. (PropertyChangeListener)proxy.getListener());
  62. } else {
  63. if (listeners == null) {
  64. listeners = new EventListenerAggregate(PropertyChangeListener.class);
  65. }
  66. listeners.add(listener);
  67. }
  68. }
  69. /**
  70. * Remove a PropertyChangeListener from the listener list.
  71. * This removes a PropertyChangeListener that was registered
  72. * for all properties.
  73. * If <code>listener</code> was added more than once to the same event
  74. * source, it will be notified one less time after being removed.
  75. * If <code>listener</code> is null, or was never added, no exception is
  76. * thrown and no action is taken.
  77. *
  78. * @param listener The PropertyChangeListener to be removed
  79. */
  80. public synchronized void removePropertyChangeListener(
  81. PropertyChangeListener listener) {
  82. if (listener == null) {
  83. return;
  84. }
  85. if (listener instanceof PropertyChangeListenerProxy) {
  86. PropertyChangeListenerProxy proxy =
  87. (PropertyChangeListenerProxy)listener;
  88. // Call two argument remove method.
  89. removePropertyChangeListener(proxy.getPropertyName(),
  90. (PropertyChangeListener)proxy.getListener());
  91. } else {
  92. if (listeners == null) {
  93. return;
  94. }
  95. listeners.remove(listener);
  96. }
  97. }
  98. /**
  99. * Returns an array of all the listeners that were added to the
  100. * PropertyChangeSupport object with addPropertyChangeListener().
  101. * <p>
  102. * If some listeners have been added with a named property, then
  103. * the returned array will be a mixture of PropertyChangeListeners
  104. * and <code>PropertyChangeListenerProxy</code>s. If the calling
  105. * method is interested in distinguishing the listeners then it must
  106. * test each element to see if it's a
  107. * <code>PropertyChangeListenerProxy</code>, perform the cast, and examine
  108. * the parameter.
  109. *
  110. * <pre>
  111. * PropertyChangeListener[] listeners = bean.getPropertyChangeListeners();
  112. * for (int i = 0; i < listeners.length; i++) {
  113. * if (listeners[i] instanceof PropertyChangeListenerProxy) {
  114. * PropertyChangeListenerProxy proxy =
  115. * (PropertyChangeListenerProxy)listeners[i];
  116. * if (proxy.getPropertyName().equals("foo")) {
  117. * // proxy is a PropertyChangeListener which was associated
  118. * // with the property named "foo"
  119. * }
  120. * }
  121. * }
  122. *</pre>
  123. *
  124. * @see PropertyChangeListenerProxy
  125. * @return all of the <code>PropertyChangeListeners</code> added or an
  126. * empty array if no listeners have been added
  127. * @since 1.4
  128. */
  129. public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
  130. List returnList = new ArrayList();
  131. // Add all the PropertyChangeListeners
  132. if (listeners != null) {
  133. returnList.addAll(Arrays.asList(listeners.getListenersInternal()));
  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 =
  141. (PropertyChangeSupport)children.get(key);
  142. PropertyChangeListener[] childListeners =
  143. child.getPropertyChangeListeners();
  144. for (int index = childListeners.length - 1; index >= 0;
  145. index--) {
  146. returnList.add(new PropertyChangeListenerProxy(
  147. key, childListeners[index]));
  148. }
  149. }
  150. }
  151. return (PropertyChangeListener[])(returnList.toArray(
  152. new PropertyChangeListener[0]));
  153. }
  154. /**
  155. * Add a PropertyChangeListener for a specific property. The listener
  156. * will be invoked only when a call on firePropertyChange names that
  157. * specific property.
  158. * The same listener object may be added more than once. For each
  159. * property, the listener will be invoked the number of times it was added
  160. * for that property.
  161. * If <code>propertyName</code> or <code>listener</code> is null, no
  162. * exception is thrown and no action is taken.
  163. *
  164. * @param propertyName The name of the property to listen on.
  165. * @param listener The PropertyChangeListener to be added
  166. */
  167. public synchronized void addPropertyChangeListener(
  168. String propertyName,
  169. PropertyChangeListener listener) {
  170. if (listener == null || propertyName == null) {
  171. return;
  172. }
  173. if (children == null) {
  174. children = new java.util.Hashtable();
  175. }
  176. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  177. if (child == null) {
  178. child = new PropertyChangeSupport(source);
  179. children.put(propertyName, child);
  180. }
  181. child.addPropertyChangeListener(listener);
  182. }
  183. /**
  184. * Remove a PropertyChangeListener for a specific property.
  185. * If <code>listener</code> was added more than once to the same event
  186. * source for the specified property, it will be notified one less time
  187. * after being removed.
  188. * If <code>propertyName</code> is null, no exception is thrown and no
  189. * action is taken.
  190. * If <code>listener</code> is null, or was never added for the specified
  191. * property, no exception is thrown and no action is taken.
  192. *
  193. * @param propertyName The name of the property that was listened on.
  194. * @param listener The PropertyChangeListener to be removed
  195. */
  196. public synchronized void removePropertyChangeListener(
  197. String propertyName,
  198. PropertyChangeListener listener) {
  199. if (listener == null || propertyName == null) {
  200. return;
  201. }
  202. if (children == null) {
  203. return;
  204. }
  205. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  206. if (child == null) {
  207. return;
  208. }
  209. child.removePropertyChangeListener(listener);
  210. }
  211. /**
  212. * Returns an array of all the listeners which have been associated
  213. * with the named property.
  214. *
  215. * @param propertyName The name of the property being listened to
  216. * @return all of the <code>PropertyChangeListeners</code> associated with
  217. * the named property. If no such listeners have been added,
  218. * or if <code>propertyName</code> is null, an empty array is
  219. * returned.
  220. */
  221. public synchronized PropertyChangeListener[] getPropertyChangeListeners(
  222. String propertyName) {
  223. ArrayList returnList = new ArrayList();
  224. if (children != null && propertyName != null) {
  225. PropertyChangeSupport support =
  226. (PropertyChangeSupport)children.get(propertyName);
  227. if (support != null) {
  228. returnList.addAll(
  229. Arrays.asList(support.getPropertyChangeListeners()));
  230. }
  231. }
  232. return (PropertyChangeListener[])(returnList.toArray(
  233. new PropertyChangeListener[0]));
  234. }
  235. /**
  236. * Report a bound property update to any registered listeners.
  237. * No event is fired if old and new are equal and non-null.
  238. *
  239. * @param propertyName The programmatic name of the property
  240. * that was changed.
  241. * @param oldValue The old value of the property.
  242. * @param newValue The new value of the property.
  243. */
  244. public void firePropertyChange(String propertyName,
  245. Object oldValue, Object newValue) {
  246. if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
  247. return;
  248. }
  249. firePropertyChange(new PropertyChangeEvent(source, propertyName,
  250. oldValue, newValue));
  251. }
  252. /**
  253. * Report an int bound property update to any registered listeners.
  254. * No event is fired if old and new are equal and non-null.
  255. * <p>
  256. * This is merely a convenience wrapper around the more general
  257. * firePropertyChange method that takes Object values.
  258. *
  259. * @param propertyName The programmatic name of the property
  260. * that was changed.
  261. * @param oldValue The old value of the property.
  262. * @param newValue The new value of the property.
  263. */
  264. public void firePropertyChange(String propertyName,
  265. int oldValue, int newValue) {
  266. if (oldValue == newValue) {
  267. return;
  268. }
  269. firePropertyChange(propertyName, new Integer(oldValue), new Integer(newValue));
  270. }
  271. /**
  272. * Report a boolean bound property update to any registered listeners.
  273. * No event is fired if old and new are equal and non-null.
  274. * <p>
  275. * This is merely a convenience wrapper around the more general
  276. * firePropertyChange method that takes Object values.
  277. *
  278. * @param propertyName The programmatic name of the property
  279. * that was changed.
  280. * @param oldValue The old value of the property.
  281. * @param newValue The new value of the property.
  282. */
  283. public void firePropertyChange(String propertyName,
  284. boolean oldValue, boolean newValue) {
  285. if (oldValue == newValue) {
  286. return;
  287. }
  288. firePropertyChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  289. }
  290. /**
  291. * Fire an existing PropertyChangeEvent to any registered listeners.
  292. * No event is fired if the given event's old and new values are
  293. * equal and non-null.
  294. * @param evt The PropertyChangeEvent object.
  295. */
  296. public void firePropertyChange(PropertyChangeEvent evt) {
  297. Object oldValue = evt.getOldValue();
  298. Object newValue = evt.getNewValue();
  299. String propertyName = evt.getPropertyName();
  300. if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
  301. return;
  302. }
  303. if (listeners != null) {
  304. Object[] list = listeners.getListenersInternal();
  305. for (int i = 0; i < list.length; i++) {
  306. PropertyChangeListener target = (PropertyChangeListener)list[i];
  307. target.propertyChange(evt);
  308. }
  309. }
  310. if (children != null && propertyName != null) {
  311. PropertyChangeSupport child = null;
  312. child = (PropertyChangeSupport)children.get(propertyName);
  313. if (child != null) {
  314. child.firePropertyChange(evt);
  315. }
  316. }
  317. }
  318. /**
  319. * Report a bound indexed property update to any registered
  320. * listeners.
  321. * <p>
  322. * No event is fired if old and new values are equal
  323. * and non-null.
  324. *
  325. * @param propertyName The programmatic name of the property that
  326. * was changed.
  327. * @param index index of the property element that was changed.
  328. * @param oldValue The old value of the property.
  329. * @param newValue The new value of the property.
  330. * @since 1.5
  331. */
  332. public void fireIndexedPropertyChange(String propertyName, int index,
  333. Object oldValue, Object newValue) {
  334. firePropertyChange(new IndexedPropertyChangeEvent
  335. (source, propertyName, oldValue, newValue, index));
  336. }
  337. /**
  338. * Report an <code>int</code> bound indexed property update to any registered
  339. * listeners.
  340. * <p>
  341. * No event is fired if old and new values are equal
  342. * and non-null.
  343. * <p>
  344. * This is merely a convenience wrapper around the more general
  345. * fireIndexedPropertyChange method which takes Object values.
  346. *
  347. * @param propertyName The programmatic name of the property that
  348. * was changed.
  349. * @param index index of the property element that was changed.
  350. * @param oldValue The old value of the property.
  351. * @param newValue The new value of the property.
  352. * @since 1.5
  353. */
  354. public void fireIndexedPropertyChange(String propertyName, int index,
  355. int oldValue, int newValue) {
  356. if (oldValue == newValue) {
  357. return;
  358. }
  359. fireIndexedPropertyChange(propertyName, index,
  360. new Integer(oldValue),
  361. new Integer(newValue));
  362. }
  363. /**
  364. * Report a <code>boolean</code> bound indexed property update to any
  365. * registered listeners.
  366. * <p>
  367. * No event is fired if old and new values are equal and non-null.
  368. * <p>
  369. * This is merely a convenience wrapper around the more general
  370. * fireIndexedPropertyChange method which takes Object values.
  371. *
  372. * @param propertyName The programmatic name of the property that
  373. * was changed.
  374. * @param index index of the property element that was changed.
  375. * @param oldValue The old value of the property.
  376. * @param newValue The new value of the property.
  377. * @since 1.5
  378. */
  379. public void fireIndexedPropertyChange(String propertyName, int index,
  380. boolean oldValue, boolean newValue) {
  381. if (oldValue == newValue) {
  382. return;
  383. }
  384. fireIndexedPropertyChange(propertyName, index, Boolean.valueOf(oldValue),
  385. Boolean.valueOf(newValue));
  386. }
  387. /**
  388. * Check if there are any listeners for a specific property, including
  389. * those registered on all properties. If <code>propertyName</code>
  390. * is null, only check for listeners registered on all properties.
  391. *
  392. * @param propertyName the property name.
  393. * @return true if there are one or more listeners for the given property
  394. */
  395. public synchronized boolean hasListeners(String propertyName) {
  396. if (listeners != null && !listeners.isEmpty()) {
  397. // there is a generic listener
  398. return true;
  399. }
  400. if (children != null && propertyName != null) {
  401. PropertyChangeSupport child = (PropertyChangeSupport)children.get(propertyName);
  402. if (child != null && child.listeners != null) {
  403. return !child.listeners.isEmpty();
  404. }
  405. }
  406. return false;
  407. }
  408. /**
  409. * @serialData Null terminated list of <code>PropertyChangeListeners</code>.
  410. * <p>
  411. * At serialization time we skip non-serializable listeners and
  412. * only serialize the serializable listeners.
  413. *
  414. */
  415. private void writeObject(ObjectOutputStream s) throws IOException {
  416. s.defaultWriteObject();
  417. if (listeners != null) {
  418. Object[] list = listeners.getListenersCopy();
  419. for (int i = 0; i < list.length; i++) {
  420. PropertyChangeListener l = (PropertyChangeListener)list[i];
  421. if (l instanceof Serializable) {
  422. s.writeObject(l);
  423. }
  424. }
  425. }
  426. s.writeObject(null);
  427. }
  428. private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
  429. s.defaultReadObject();
  430. Object listenerOrNull;
  431. while (null != (listenerOrNull = s.readObject())) {
  432. addPropertyChangeListener((PropertyChangeListener)listenerOrNull);
  433. }
  434. }
  435. /**
  436. * Hashtable for managing listeners for specific properties.
  437. * Maps property names to PropertyChangeSupport objects.
  438. * @serial
  439. * @since 1.2
  440. */
  441. private java.util.Hashtable children;
  442. /**
  443. * The object to be provided as the "source" for any generated events.
  444. * @serial
  445. */
  446. private Object source;
  447. /**
  448. * Internal version number
  449. * @serial
  450. * @since
  451. */
  452. private int propertyChangeSupportSerializedDataVersion = 2;
  453. /**
  454. * Serialization version ID, so we're compatible with JDK 1.1
  455. */
  456. static final long serialVersionUID = 6401253773779951803L;
  457. }