1. /*
  2. * @(#)VetoableChangeSupport.java 1.44 04/03/04
  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. /**
  17. * This is a utility class that can be used by beans that support constrained
  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. public class VetoableChangeSupport implements java.io.Serializable {
  26. /**
  27. * Constructs a <code>VetoableChangeSupport</code> object.
  28. *
  29. * @param sourceBean The bean to be given as the source for any events.
  30. */
  31. public VetoableChangeSupport(Object sourceBean) {
  32. if (sourceBean == null) {
  33. throw new NullPointerException();
  34. }
  35. source = sourceBean;
  36. }
  37. /**
  38. * Add a VetoableListener to the listener list.
  39. * The listener is registered for all properties.
  40. * The same listener object may be added more than once, and will be called
  41. * as many times as it is added.
  42. * If <code>listener</code> is null, no exception is thrown and no action
  43. * is taken.
  44. *
  45. * @param listener The VetoableChangeListener to be added
  46. */
  47. public synchronized void addVetoableChangeListener(
  48. VetoableChangeListener listener) {
  49. if (listener == null) {
  50. return;
  51. }
  52. if (listener instanceof VetoableChangeListenerProxy) {
  53. VetoableChangeListenerProxy proxy =
  54. (VetoableChangeListenerProxy)listener;
  55. // Call two argument add method.
  56. addVetoableChangeListener(proxy.getPropertyName(),
  57. (VetoableChangeListener)proxy.getListener());
  58. } else {
  59. if (listeners == null) {
  60. listeners = new java.util.Vector();
  61. }
  62. }
  63. listeners.addElement(listener);
  64. }
  65. /**
  66. * Remove a VetoableChangeListener from the listener list.
  67. * This removes a VetoableChangeListener that was registered
  68. * for all properties.
  69. * If <code>listener</code> was added more than once to the same event
  70. * source, it will be notified one less time after being removed.
  71. * If <code>listener</code> is null, or was never added, no exception is
  72. * thrown and no action is taken.
  73. *
  74. * @param listener The VetoableChangeListener to be removed
  75. */
  76. public synchronized void removeVetoableChangeListener(
  77. VetoableChangeListener listener) {
  78. if (listener == null) {
  79. return;
  80. }
  81. if (listener instanceof VetoableChangeListenerProxy) {
  82. VetoableChangeListenerProxy proxy =
  83. (VetoableChangeListenerProxy)listener;
  84. // Call two argument remove method.
  85. removeVetoableChangeListener(proxy.getPropertyName(),
  86. (VetoableChangeListener)proxy.getListener());
  87. } else {
  88. if (listeners == null) {
  89. return;
  90. }
  91. listeners.removeElement(listener);
  92. }
  93. }
  94. /**
  95. * Returns the list of VetoableChangeListeners. If named vetoable change listeners
  96. * were added, then VetoableChangeListenerProxy wrappers will returned
  97. * <p>
  98. * @return List of VetoableChangeListeners and VetoableChangeListenerProxys
  99. * if named property change listeners were added.
  100. * @since 1.4
  101. */
  102. public synchronized VetoableChangeListener[] getVetoableChangeListeners(){
  103. List returnList = new ArrayList();
  104. // Add all the VetoableChangeListeners
  105. if (listeners != null) {
  106. returnList.addAll(listeners);
  107. }
  108. // Add all the VetoableChangeListenerProxys
  109. if (children != null) {
  110. Iterator iterator = children.keySet().iterator();
  111. while (iterator.hasNext()) {
  112. String key = (String)iterator.next();
  113. VetoableChangeSupport child =
  114. (VetoableChangeSupport)children.get(key);
  115. VetoableChangeListener[] childListeners =
  116. child.getVetoableChangeListeners();
  117. for (int index = childListeners.length - 1; index >= 0;
  118. index--) {
  119. returnList.add(new VetoableChangeListenerProxy(
  120. key, childListeners[index]));
  121. }
  122. }
  123. }
  124. return (VetoableChangeListener[])(returnList.toArray(
  125. new VetoableChangeListener[0]));
  126. }
  127. /**
  128. * Add a VetoableChangeListener for a specific property. The listener
  129. * will be invoked only when a call on fireVetoableChange names that
  130. * specific property.
  131. * The same listener object may be added more than once. For each
  132. * property, the listener will be invoked the number of times it was added
  133. * for that property.
  134. * If <code>propertyName</code> or <code>listener</code> is null, no
  135. * exception is thrown and no action is taken.
  136. *
  137. * @param propertyName The name of the property to listen on.
  138. * @param listener The VetoableChangeListener to be added
  139. */
  140. public synchronized void addVetoableChangeListener(
  141. String propertyName,
  142. VetoableChangeListener listener) {
  143. if (listener == null || propertyName == null) {
  144. return;
  145. }
  146. if (children == null) {
  147. children = new java.util.Hashtable();
  148. }
  149. VetoableChangeSupport child = (VetoableChangeSupport)children.get(propertyName);
  150. if (child == null) {
  151. child = new VetoableChangeSupport(source);
  152. children.put(propertyName, child);
  153. }
  154. child.addVetoableChangeListener(listener);
  155. }
  156. /**
  157. * Remove a VetoableChangeListener for a specific property.
  158. * If <code>listener</code> was added more than once to the same event
  159. * source for the specified property, it will be notified one less time
  160. * after being removed.
  161. * If <code>propertyName</code> is null, no exception is thrown and no
  162. * action is taken.
  163. * If <code>listener</code> is null, or was never added for the specified
  164. * property, no exception is thrown and no action is taken.
  165. *
  166. * @param propertyName The name of the property that was listened on.
  167. * @param listener The VetoableChangeListener to be removed
  168. */
  169. public synchronized void removeVetoableChangeListener(
  170. String propertyName,
  171. VetoableChangeListener listener) {
  172. if (listener == null || propertyName == null) {
  173. return;
  174. }
  175. if (children == null) {
  176. return;
  177. }
  178. VetoableChangeSupport child = (VetoableChangeSupport)children.get(propertyName);
  179. if (child == null) {
  180. return;
  181. }
  182. child.removeVetoableChangeListener(listener);
  183. }
  184. /**
  185. * Returns an array of all the listeners which have been associated
  186. * with the named property.
  187. *
  188. * @param propertyName The name of the property being listened to
  189. * @return all the <code>VetoableChangeListeners</code> associated with
  190. * the named property. If no such listeners have been added,
  191. * or if <code>propertyName</code> is null, an empty array is
  192. * returned.
  193. */
  194. public synchronized VetoableChangeListener[] getVetoableChangeListeners(
  195. String propertyName) {
  196. List returnList = new ArrayList();
  197. if (children != null && propertyName != null) {
  198. VetoableChangeSupport support =
  199. (VetoableChangeSupport)children.get(propertyName);
  200. if (support != null) {
  201. returnList.addAll(
  202. Arrays.asList(support.getVetoableChangeListeners()));
  203. }
  204. }
  205. return (VetoableChangeListener[])(returnList.toArray(new
  206. VetoableChangeListener[0]));
  207. }
  208. /**
  209. * Report a vetoable property update to any registered listeners. If
  210. * anyone vetos the change, then fire a new event reverting everyone to
  211. * the old value and then rethrow the PropertyVetoException.
  212. * <p>
  213. * No event is fired if old and new are equal and non-null.
  214. *
  215. * @param propertyName The programmatic name of the property
  216. * that is about to change..
  217. * @param oldValue The old value of the property.
  218. * @param newValue The new value of the property.
  219. * @exception PropertyVetoException if the recipient wishes the property
  220. * change to be rolled back.
  221. */
  222. public void fireVetoableChange(String propertyName,
  223. Object oldValue, Object newValue)
  224. throws PropertyVetoException {
  225. if (listeners == null && children == null) {
  226. return;
  227. }
  228. PropertyChangeEvent evt = new PropertyChangeEvent(source, propertyName,
  229. oldValue, newValue);
  230. fireVetoableChange(evt);
  231. }
  232. /**
  233. * Report a int vetoable property update to any registered listeners.
  234. * No event is fired if old and new are equal and non-null.
  235. * <p>
  236. * This is merely a convenience wrapper around the more general
  237. * fireVetoableChange method that takes Object values.
  238. *
  239. * @param propertyName The programmatic name of the property
  240. * that is about to change.
  241. * @param oldValue The old value of the property.
  242. * @param newValue The new value of the property.
  243. */
  244. public void fireVetoableChange(String propertyName,
  245. int oldValue, int newValue)
  246. throws PropertyVetoException {
  247. if (oldValue == newValue) {
  248. return;
  249. }
  250. fireVetoableChange(propertyName, new Integer(oldValue), new Integer(newValue));
  251. }
  252. /**
  253. * Report a boolean vetoable 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. * fireVetoableChange method that takes Object values.
  258. *
  259. * @param propertyName The programmatic name of the property
  260. * that is about to change.
  261. * @param oldValue The old value of the property.
  262. * @param newValue The new value of the property.
  263. */
  264. public void fireVetoableChange(String propertyName,
  265. boolean oldValue, boolean newValue)
  266. throws PropertyVetoException {
  267. if (oldValue == newValue) {
  268. return;
  269. }
  270. fireVetoableChange(propertyName, Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  271. }
  272. /**
  273. * Fire a vetoable property update to any registered listeners. If
  274. * anyone vetos the change, then fire a new event reverting everyone to
  275. * the old value and then rethrow the PropertyVetoException.
  276. * <p>
  277. * No event is fired if old and new are equal and non-null.
  278. *
  279. * @param evt The PropertyChangeEvent to be fired.
  280. * @exception PropertyVetoException if the recipient wishes the property
  281. * change to be rolled back.
  282. */
  283. public void fireVetoableChange(PropertyChangeEvent evt)
  284. throws PropertyVetoException {
  285. Object oldValue = evt.getOldValue();
  286. Object newValue = evt.getNewValue();
  287. String propertyName = evt.getPropertyName();
  288. if (oldValue != null && newValue != null && oldValue.equals(newValue)) {
  289. return;
  290. }
  291. java.util.Vector targets = null;
  292. VetoableChangeSupport child = null;
  293. synchronized (this) {
  294. if (listeners != null) {
  295. targets = (java.util.Vector) listeners.clone();
  296. }
  297. if (children != null && propertyName != null) {
  298. child = (VetoableChangeSupport)children.get(propertyName);
  299. }
  300. }
  301. if (listeners != null) {
  302. try {
  303. for (int i = 0; i < targets.size(); i++) {
  304. VetoableChangeListener target =
  305. (VetoableChangeListener)targets.elementAt(i);
  306. target.vetoableChange(evt);
  307. }
  308. } catch (PropertyVetoException veto) {
  309. // Create an event to revert everyone to the old value.
  310. evt = new PropertyChangeEvent(source, propertyName, newValue, oldValue);
  311. for (int i = 0; i < targets.size(); i++) {
  312. try {
  313. VetoableChangeListener target =
  314. (VetoableChangeListener)targets.elementAt(i);
  315. target.vetoableChange(evt);
  316. } catch (PropertyVetoException ex) {
  317. // We just ignore exceptions that occur during reversions.
  318. }
  319. }
  320. // And now rethrow the PropertyVetoException.
  321. throw veto;
  322. }
  323. }
  324. if (child != null) {
  325. child.fireVetoableChange(evt);
  326. }
  327. }
  328. /**
  329. * Check if there are any listeners for a specific property, including
  330. * those registered on all properties. If <code>propertyName</code>
  331. * is null, only check for listeners registered on all properties.
  332. *
  333. * @param propertyName the property name.
  334. * @return true if there are one or more listeners for the given property
  335. */
  336. public synchronized boolean hasListeners(String propertyName) {
  337. if (listeners != null && !listeners.isEmpty()) {
  338. // there is a generic listener
  339. return true;
  340. }
  341. if (children != null && propertyName != null) {
  342. VetoableChangeSupport child = (VetoableChangeSupport)children.get(propertyName);
  343. if (child != null && child.listeners != null) {
  344. return !child.listeners.isEmpty();
  345. }
  346. }
  347. return false;
  348. }
  349. /**
  350. * @serialData Null terminated list of <code>VetoableChangeListeners</code>.
  351. * <p>
  352. * At serialization time we skip non-serializable listeners and
  353. * only serialize the serializable listeners.
  354. *
  355. */
  356. private void writeObject(ObjectOutputStream s) throws IOException {
  357. s.defaultWriteObject();
  358. java.util.Vector v = null;
  359. synchronized (this) {
  360. if (listeners != null) {
  361. v = (java.util.Vector) listeners.clone();
  362. }
  363. }
  364. if (v != null) {
  365. for(int i = 0; i < v.size(); i++) {
  366. VetoableChangeListener l = (VetoableChangeListener)v.elementAt(i);
  367. if (l instanceof Serializable) {
  368. s.writeObject(l);
  369. }
  370. }
  371. }
  372. s.writeObject(null);
  373. }
  374. private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {
  375. s.defaultReadObject();
  376. Object listenerOrNull;
  377. while(null != (listenerOrNull = s.readObject())) {
  378. addVetoableChangeListener((VetoableChangeListener)listenerOrNull);
  379. }
  380. }
  381. /**
  382. * "listeners" lists all the generic listeners.
  383. *
  384. * This is transient - its state is written in the writeObject method.
  385. */
  386. transient private java.util.Vector listeners;
  387. /**
  388. * Hashtable for managing listeners for specific properties.
  389. * Maps property names to VetoableChangeSupport objects.
  390. * @serial
  391. * @since 1.2
  392. */
  393. private java.util.Hashtable children;
  394. /**
  395. * The object to be provided as the "source" for any generated events.
  396. * @serial
  397. */
  398. private Object source;
  399. /**
  400. * Internal version number
  401. * @serial
  402. */
  403. private int vetoableChangeSupportSerializedDataVersion = 2;
  404. /**
  405. * Serialization version ID, so we're compatible with JDK 1.1
  406. */
  407. static final long serialVersionUID = -5090210921595982017L;
  408. }