1. /*
  2. * @(#)AbstractAction.java 1.33 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.awt.*;
  9. import java.awt.event.*;
  10. import java.beans.*;
  11. import java.util.Hashtable;
  12. import java.util.Enumeration;
  13. import java.io.Serializable;
  14. import javax.swing.event.SwingPropertyChangeSupport;
  15. /**
  16. * This class provides default implementations for the JFC Action
  17. * interface. Standard behaviors like the get and set methods for
  18. * Action object properties (icon, text, and enabled) are defined
  19. * here. The developer need only subclass this abstract class and
  20. * define the <code>actionPerformed</code> method.
  21. * <p>
  22. * <strong>Warning:</strong>
  23. * Serialized objects of this class will not be compatible with
  24. * future Swing releases. The current serialization support is appropriate
  25. * for short term storage or RMI between applications running the same
  26. * version of Swing. A future release of Swing will provide support for
  27. * long term persistence.
  28. *
  29. * @version 1.33 11/29/01
  30. * @author Georges Saab
  31. * @see Action
  32. */
  33. public abstract class AbstractAction implements Action, Cloneable, Serializable
  34. {
  35. protected boolean enabled = true;
  36. private ArrayTable arrayTable = new ArrayTable();
  37. /**
  38. * Defines an Action object with a default description string
  39. * and default icon.
  40. */
  41. public AbstractAction() {
  42. }
  43. /**
  44. * Defines an Action object with the specified description string
  45. * and a default icon.
  46. */
  47. public AbstractAction(String name) {
  48. putValue(Action.NAME, name);
  49. }
  50. /**
  51. * Defines an Action object with the specified description string
  52. * and a the specified icon.
  53. */
  54. public AbstractAction(String name, Icon icon) {
  55. this(name);
  56. putValue(Action.SMALL_ICON, icon);
  57. }
  58. /**
  59. * Gets the Object associated with the specified key.
  60. *
  61. * @return the Object stored with this key
  62. * @see Action#getValue
  63. */
  64. public Object getValue(String key) {
  65. return arrayTable.get(key);
  66. }
  67. /**
  68. * Sets the Value associated with the specified key.
  69. *
  70. * @param key the String that identifies the stored object
  71. * @param newValue the Object to store using this key
  72. * @see Action#putValue
  73. */
  74. public synchronized void putValue(String key, Object newValue) {
  75. Object oldValue = null;
  76. /*
  77. if (keyTable.containsKey(key))
  78. oldValue = keyTable.get(key);
  79. // Remove the entry for key if newValue is null
  80. // else put in the newValue for key.
  81. if (newValue == null) {
  82. keyTable.remove(key);
  83. } else {
  84. keyTable.put(key,newValue);
  85. }
  86. */
  87. if (arrayTable.containsKey(key))
  88. oldValue = arrayTable.get(key);
  89. // Remove the entry for key if newValue is null
  90. // else put in the newValue for key.
  91. if (newValue == null) {
  92. arrayTable.remove(key);
  93. } else {
  94. arrayTable.put(key,newValue);
  95. }
  96. firePropertyChange(key, oldValue, newValue);
  97. }
  98. /**
  99. * Returns true if the action is enabled.
  100. *
  101. * @return true if the action is enabled
  102. * @see Action#isEnabled
  103. */
  104. public boolean isEnabled() {
  105. return enabled;
  106. }
  107. /**
  108. * Enables or disables the action.
  109. *
  110. * @param newValue true to enable the action, false to
  111. * disable it
  112. * @see Action#setEnabled
  113. */
  114. public synchronized void setEnabled(boolean newValue) {
  115. boolean oldValue = this.enabled;
  116. this.enabled = newValue;
  117. firePropertyChange("enabled",
  118. new Boolean(oldValue), new Boolean(newValue));
  119. }
  120. /*
  121. * If any PropertyChangeListeners have been registered, the
  122. * changeSupport field describes them.
  123. */
  124. protected SwingPropertyChangeSupport changeSupport;
  125. /**
  126. * Support for reporting bound property changes. This method can be called
  127. * when a bound property has changed and it will send the appropriate
  128. * PropertyChangeEvent to any registered PropertyChangeListeners.
  129. */
  130. protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
  131. if (changeSupport == null) {
  132. return;
  133. }
  134. changeSupport.firePropertyChange(propertyName, oldValue, newValue);
  135. }
  136. /**
  137. * Add a PropertyChangeListener to the listener list.
  138. * The listener is registered for all properties.
  139. * <p>
  140. * A PropertyChangeEvent will get fired in response to setting
  141. * a bound property, e.g. setFont, setBackground, or setForeground.
  142. * Note that if the current component is inheriting its foreground,
  143. * background, or font from its container, then no event will be
  144. * fired in response to a change in the inherited property.
  145. *
  146. * @param listener The PropertyChangeListener to be added
  147. *
  148. * @see Action#addPropertyChangeListener
  149. */
  150. public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
  151. if (changeSupport == null) {
  152. changeSupport = new SwingPropertyChangeSupport(this);
  153. }
  154. changeSupport.addPropertyChangeListener(listener);
  155. }
  156. /**
  157. * Remove a PropertyChangeListener from the listener list.
  158. * This removes a PropertyChangeListener that was registered
  159. * for all properties.
  160. *
  161. * @param listener The PropertyChangeListener to be removed
  162. *
  163. * @see Action#removePropertyChangeListener
  164. */
  165. public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
  166. if (changeSupport == null) {
  167. return;
  168. }
  169. changeSupport.removePropertyChangeListener(listener);
  170. }
  171. /**
  172. * Clone the abstract action. This gives the clone
  173. * its own copy of the key/value list,
  174. * which is not handled for you by Object.clone()
  175. **/
  176. protected Object clone() throws CloneNotSupportedException {
  177. AbstractAction newAction = (AbstractAction)super.clone();
  178. newAction.arrayTable = (ArrayTable)arrayTable.clone();
  179. return newAction;
  180. }
  181. /*
  182. * Private storage mechanism for Action key-value pairs.
  183. * In most cases this will be an array of alternating
  184. * key-value pairs. As it grows larger it is scaled
  185. * up to a Hashtable.
  186. */
  187. private class ArrayTable implements Cloneable, Serializable {
  188. // Our field for storage
  189. private Object table = null;
  190. private static final int ARRAY_BOUNDARY = 8;
  191. /*
  192. * Put the key-value pair into storage
  193. */
  194. public synchronized void put(Object key, Object value){
  195. if (table==null) {
  196. table = new Object[] {key, value};
  197. } else {
  198. int size = size();
  199. if (size < ARRAY_BOUNDARY) { // We are an array
  200. if (containsKey(key)) {
  201. Object[] tmp = (Object[])table;
  202. for (int i = 0; i<tmp.length-1; i+=2) {
  203. if (tmp[i].equals(key)) {
  204. tmp[i+1]=value;
  205. break;
  206. }
  207. }
  208. } else {
  209. Object[] array = (Object[])table;
  210. int i = array.length;
  211. Object[] tmp = new Object[i+2];
  212. System.arraycopy(array, 0, tmp, 0, i);
  213. tmp[i] = key;
  214. tmp[i+1] = value;
  215. table = tmp;
  216. }
  217. } else { // We are a hashtable
  218. if ((size==ARRAY_BOUNDARY) && isArray()) {
  219. grow();
  220. }
  221. ((Hashtable)table).put(key, value);
  222. }
  223. }
  224. }
  225. /*
  226. * Get the value for key
  227. */
  228. public Object get(Object key) {
  229. Object value = null;
  230. if (table !=null) {
  231. if (isArray()) {
  232. Object[] array = (Object[])table;
  233. for (int i = 0; i<array.length-1; i+=2) {
  234. if (array[i].equals(key)) {
  235. value = array[i+1];
  236. break;
  237. }
  238. }
  239. } else {
  240. value = ((Hashtable)table).get(key);
  241. }
  242. }
  243. return value;
  244. }
  245. /*
  246. * Return the number of pairs in storage
  247. */
  248. public int size() {
  249. int size;
  250. if (table==null)
  251. return 0;
  252. if (isArray()) {
  253. size = ((Object[])table).length2;
  254. } else {
  255. size = ((Hashtable)table).size();
  256. }
  257. return size;
  258. }
  259. /*
  260. * Returns true if we have a value for the key
  261. */
  262. public boolean containsKey(Object key) {
  263. boolean contains = false;
  264. if (table !=null) {
  265. if (isArray()) {
  266. Object[] array = (Object[])table;
  267. for (int i = 0; i<array.length-1; i+=2) {
  268. if (array[i].equals(key)) {
  269. contains = true;
  270. break;
  271. }
  272. }
  273. } else {
  274. contains = ((Hashtable)table).containsKey(key);
  275. }
  276. }
  277. return contains;
  278. }
  279. /*
  280. * Remove the key and its value
  281. * Returns the value for the pair removed
  282. */
  283. public synchronized Object remove(Object key){
  284. Object value = null;
  285. if (key==null) {
  286. return null;
  287. }
  288. if (table !=null) {
  289. if (isArray()){
  290. // Is key on the list?
  291. int index = -1;
  292. Object[] array = (Object[])table;
  293. for (int i = array.length-2; i>=0; i-=2) {
  294. if (array[i].equals(key)) {
  295. index = i;
  296. value = array[i+1];
  297. break;
  298. }
  299. }
  300. // If so, remove it
  301. if (index != -1) {
  302. Object[] tmp = new Object[array.length-2];
  303. // Copy the list up to index
  304. System.arraycopy(array, 0, tmp, 0, index);
  305. // Copy from two past the index, up to
  306. // the end of tmp (which is two elements
  307. // shorter than the old list)
  308. if (index < tmp.length)
  309. System.arraycopy(array, index+2, tmp, index,
  310. tmp.length - index);
  311. // set the listener array to the new array or null
  312. table = (tmp.length == 0) ? null : tmp;
  313. }
  314. } else {
  315. value = ((Hashtable)table).remove(key);
  316. }
  317. if (size()==7 && !isArray()) {
  318. shrink();
  319. }
  320. }
  321. return value;
  322. }
  323. /*
  324. * Return a clone of the ArrayTable
  325. */
  326. public synchronized Object clone() {
  327. ArrayTable newArrayTable = new ArrayTable();
  328. if (isArray()) {
  329. Object[] array = (Object[])table;
  330. for (int i = 0 ;i < array.length-1 ; i+=2) {
  331. newArrayTable.put(array[i], array[i+1]);
  332. }
  333. } else {
  334. Hashtable tmp = (Hashtable)table;
  335. Enumeration keys = tmp.keys();
  336. while (keys.hasMoreElements()) {
  337. Object o = keys.nextElement();
  338. newArrayTable.put(o,tmp.get(o));
  339. }
  340. }
  341. return newArrayTable;
  342. }
  343. /*
  344. * Return true if the current storage mechanism is
  345. * an array of alternating key-value pairs
  346. */
  347. private boolean isArray(){
  348. return (table instanceof Object[]);
  349. }
  350. /*
  351. * Grow the storage from an array to a hashtable
  352. */
  353. private void grow() {
  354. Object[] array = (Object[])table;
  355. Hashtable tmp = new Hashtable(array.length2);
  356. for (int i = 0; i<array.length; i+=2) {
  357. tmp.put(array[i], array[i+1]);
  358. }
  359. table = tmp;
  360. }
  361. /*
  362. * Shrink the storage from a hashtable to an array
  363. */
  364. private void shrink() {
  365. Hashtable tmp = (Hashtable)table;
  366. Object[] array = new Object[tmp.size()*2];
  367. Enumeration keys = tmp.keys();
  368. int j = 0;
  369. while (keys.hasMoreElements()) {
  370. Object o = keys.nextElement();
  371. array[j] = o;
  372. array[j+1] = tmp.get(o);
  373. j+=2;
  374. }
  375. table = array;
  376. }
  377. }
  378. }