1. /*
  2. * @(#)AbstractAction.java 1.48 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 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 java.io.IOException;
  15. import java.io.ObjectInputStream;
  16. import java.io.ObjectOutputStream;
  17. import javax.swing.event.SwingPropertyChangeSupport;
  18. /**
  19. * This class provides default implementations for the JFC <code>Action</code>
  20. * interface. Standard behaviors like the get and set methods for
  21. * <code>Action</code> object properties (icon, text, and enabled) are defined
  22. * here. The developer need only subclass this abstract class and
  23. * define the <code>actionPerformed</code> method.
  24. * <p>
  25. * <strong>Warning:</strong>
  26. * Serialized objects of this class will not be compatible with
  27. * future Swing releases. The current serialization support is
  28. * appropriate for short term storage or RMI between applications running
  29. * the same version of Swing. As of 1.4, support for long term storage
  30. * of all JavaBeans<sup><font size="-2">TM</font></sup>
  31. * has been added to the <code>java.beans</code> package.
  32. * Please see {@link java.beans.XMLEncoder}.
  33. *
  34. * @version 1.48 01/23/03
  35. * @author Georges Saab
  36. * @see Action
  37. */
  38. public abstract class AbstractAction implements Action, Cloneable, Serializable
  39. {
  40. /**
  41. * Specifies whether action is enabled; the default is true.
  42. */
  43. protected boolean enabled = true;
  44. /**
  45. * Contains the array of key bindings.
  46. */
  47. private transient ArrayTable arrayTable;
  48. /**
  49. * Defines an <code>Action</code> object with a default
  50. * description string and default icon.
  51. */
  52. public AbstractAction() {
  53. }
  54. /**
  55. * Defines an <code>Action</code> object with the specified
  56. * description string and a default icon.
  57. */
  58. public AbstractAction(String name) {
  59. putValue(Action.NAME, name);
  60. }
  61. /**
  62. * Defines an <code>Action</code> object with the specified
  63. * description string and a the specified icon.
  64. */
  65. public AbstractAction(String name, Icon icon) {
  66. this(name);
  67. putValue(Action.SMALL_ICON, icon);
  68. }
  69. /**
  70. * Gets the <code>Object</code> associated with the specified key.
  71. *
  72. * @param key a string containing the specified <code>key</code>
  73. * @return the binding <code>Object</code> stored with this key; if there
  74. * are no keys, it will return <code>null</code>
  75. * @see Action#getValue
  76. */
  77. public Object getValue(String key) {
  78. if (arrayTable == null) {
  79. return null;
  80. }
  81. return arrayTable.get(key);
  82. }
  83. /**
  84. * Sets the <code>Value</code> associated with the specified key.
  85. *
  86. * @param key the <code>String</code> that identifies the stored object
  87. * @param newValue the <code>Object</code> to store using this key
  88. * @see Action#putValue
  89. */
  90. public void putValue(String key, Object newValue) {
  91. Object oldValue = null;
  92. if (arrayTable == null) {
  93. arrayTable = new ArrayTable();
  94. }
  95. if (arrayTable.containsKey(key))
  96. oldValue = arrayTable.get(key);
  97. // Remove the entry for key if newValue is null
  98. // else put in the newValue for key.
  99. if (newValue == null) {
  100. arrayTable.remove(key);
  101. } else {
  102. arrayTable.put(key,newValue);
  103. }
  104. firePropertyChange(key, oldValue, newValue);
  105. }
  106. /**
  107. * Returns true if the action is enabled.
  108. *
  109. * @return true if the action is enabled, false otherwise
  110. * @see Action#isEnabled
  111. */
  112. public boolean isEnabled() {
  113. return enabled;
  114. }
  115. /**
  116. * Enables or disables the action.
  117. *
  118. * @param newValue true to enable the action, false to
  119. * disable it
  120. * @see Action#setEnabled
  121. */
  122. public void setEnabled(boolean newValue) {
  123. boolean oldValue = this.enabled;
  124. if (oldValue != newValue) {
  125. this.enabled = newValue;
  126. firePropertyChange("enabled",
  127. Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
  128. }
  129. }
  130. /**
  131. * Returns an array of <code>Object</code>s which are keys for
  132. * which values have been set for this <code>AbstractAction</code>,
  133. * or <code>null</code> if no keys have values set.
  134. * @return an array of key objects, or <code>null</code> if no
  135. * keys have values set
  136. * @since 1.3
  137. */
  138. public Object[] getKeys() {
  139. if (arrayTable == null) {
  140. return null;
  141. }
  142. Object[] keys = new Object[arrayTable.size()];
  143. arrayTable.getKeys(keys);
  144. return keys;
  145. }
  146. /**
  147. * If any <code>PropertyChangeListeners</code> have been registered, the
  148. * <code>changeSupport</code> field describes them.
  149. */
  150. protected SwingPropertyChangeSupport changeSupport;
  151. /**
  152. * Supports reporting bound property changes. This method can be called
  153. * when a bound property has changed and it will send the appropriate
  154. * <code>PropertyChangeEvent</code> to any registered
  155. * <code>PropertyChangeListeners</code>.
  156. */
  157. protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
  158. if (changeSupport == null ||
  159. (oldValue != null && newValue != null && oldValue.equals(newValue))) {
  160. return;
  161. }
  162. changeSupport.firePropertyChange(propertyName, oldValue, newValue);
  163. }
  164. /**
  165. * Adds a <code>PropertyChangeListener</code> to the listener list.
  166. * The listener is registered for all properties.
  167. * <p>
  168. * A <code>PropertyChangeEvent</code> will get fired in response to setting
  169. * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
  170. * or <code>setForeground</code>.
  171. * Note that if the current component is inheriting its foreground,
  172. * background, or font from its container, then no event will be
  173. * fired in response to a change in the inherited property.
  174. *
  175. * @param listener The <code>PropertyChangeListener</code> to be added
  176. *
  177. * @see Action#addPropertyChangeListener
  178. */
  179. public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
  180. if (changeSupport == null) {
  181. changeSupport = new SwingPropertyChangeSupport(this);
  182. }
  183. changeSupport.addPropertyChangeListener(listener);
  184. }
  185. /**
  186. * Removes a <code>PropertyChangeListener</code> from the listener list.
  187. * This removes a <code>PropertyChangeListener</code> that was registered
  188. * for all properties.
  189. *
  190. * @param listener the <code>PropertyChangeListener</code> to be removed
  191. *
  192. * @see Action#removePropertyChangeListener
  193. */
  194. public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
  195. if (changeSupport == null) {
  196. return;
  197. }
  198. changeSupport.removePropertyChangeListener(listener);
  199. }
  200. /**
  201. * Returns an array of all the <code>PropertyChangeListener</code>s added
  202. * to this AbstractAction with addPropertyChangeListener().
  203. *
  204. * @return all of the <code>PropertyChangeListener</code>s added or an empty
  205. * array if no listeners have been added
  206. * @since 1.4
  207. */
  208. public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
  209. if (changeSupport == null) {
  210. return new PropertyChangeListener[0];
  211. }
  212. return changeSupport.getPropertyChangeListeners();
  213. }
  214. /**
  215. * Clones the abstract action. This gives the clone
  216. * its own copy of the key/value list,
  217. * which is not handled for you by <code>Object.clone()</code>.
  218. **/
  219. protected Object clone() throws CloneNotSupportedException {
  220. AbstractAction newAction = (AbstractAction)super.clone();
  221. synchronized(this) {
  222. if (arrayTable != null) {
  223. newAction.arrayTable = (ArrayTable)arrayTable.clone();
  224. }
  225. }
  226. return newAction;
  227. }
  228. private void writeObject(ObjectOutputStream s) throws IOException {
  229. // Store the default fields
  230. s.defaultWriteObject();
  231. // And the keys
  232. ArrayTable.writeArrayTable(s, arrayTable);
  233. }
  234. private void readObject(ObjectInputStream s) throws ClassNotFoundException,
  235. IOException {
  236. s.defaultReadObject();
  237. for (int counter = s.readInt() - 1; counter >= 0; counter--) {
  238. putValue((String)s.readObject(), s.readObject());
  239. }
  240. }
  241. /*
  242. * Private storage mechanism for Action key-value pairs.
  243. * In most cases this will be an array of alternating
  244. * key-value pairs. As it grows larger it is scaled
  245. * up to a Hashtable.
  246. * <p>This is also used by InputMap and ActionMap, this does no
  247. * synchronization, if you need thread safety synchronize on another
  248. * object before calling this.
  249. */
  250. static class ArrayTable implements Cloneable {
  251. // Our field for storage
  252. private Object table = null;
  253. private static final int ARRAY_BOUNDARY = 8;
  254. /*
  255. * Put the key-value pair into storage
  256. */
  257. public void put(Object key, Object value){
  258. if (table==null) {
  259. table = new Object[] {key, value};
  260. } else {
  261. int size = size();
  262. if (size < ARRAY_BOUNDARY) { // We are an array
  263. if (containsKey(key)) {
  264. Object[] tmp = (Object[])table;
  265. for (int i = 0; i<tmp.length-1; i+=2) {
  266. if (tmp[i].equals(key)) {
  267. tmp[i+1]=value;
  268. break;
  269. }
  270. }
  271. } else {
  272. Object[] array = (Object[])table;
  273. int i = array.length;
  274. Object[] tmp = new Object[i+2];
  275. System.arraycopy(array, 0, tmp, 0, i);
  276. tmp[i] = key;
  277. tmp[i+1] = value;
  278. table = tmp;
  279. }
  280. } else { // We are a hashtable
  281. if ((size==ARRAY_BOUNDARY) && isArray()) {
  282. grow();
  283. }
  284. ((Hashtable)table).put(key, value);
  285. }
  286. }
  287. }
  288. /*
  289. * Gets the value for key
  290. */
  291. public Object get(Object key) {
  292. Object value = null;
  293. if (table !=null) {
  294. if (isArray()) {
  295. Object[] array = (Object[])table;
  296. for (int i = 0; i<array.length-1; i+=2) {
  297. if (array[i].equals(key)) {
  298. value = array[i+1];
  299. break;
  300. }
  301. }
  302. } else {
  303. value = ((Hashtable)table).get(key);
  304. }
  305. }
  306. return value;
  307. }
  308. /*
  309. * Returns the number of pairs in storage
  310. */
  311. public int size() {
  312. int size;
  313. if (table==null)
  314. return 0;
  315. if (isArray()) {
  316. size = ((Object[])table).length2;
  317. } else {
  318. size = ((Hashtable)table).size();
  319. }
  320. return size;
  321. }
  322. /*
  323. * Returns true if we have a value for the key
  324. */
  325. public boolean containsKey(Object key) {
  326. boolean contains = false;
  327. if (table !=null) {
  328. if (isArray()) {
  329. Object[] array = (Object[])table;
  330. for (int i = 0; i<array.length-1; i+=2) {
  331. if (array[i].equals(key)) {
  332. contains = true;
  333. break;
  334. }
  335. }
  336. } else {
  337. contains = ((Hashtable)table).containsKey(key);
  338. }
  339. }
  340. return contains;
  341. }
  342. /*
  343. * Removes the key and its value
  344. * Returns the value for the pair removed
  345. */
  346. public Object remove(Object key){
  347. Object value = null;
  348. if (key==null) {
  349. return null;
  350. }
  351. if (table !=null) {
  352. if (isArray()){
  353. // Is key on the list?
  354. int index = -1;
  355. Object[] array = (Object[])table;
  356. for (int i = array.length-2; i>=0; i-=2) {
  357. if (array[i].equals(key)) {
  358. index = i;
  359. value = array[i+1];
  360. break;
  361. }
  362. }
  363. // If so, remove it
  364. if (index != -1) {
  365. Object[] tmp = new Object[array.length-2];
  366. // Copy the list up to index
  367. System.arraycopy(array, 0, tmp, 0, index);
  368. // Copy from two past the index, up to
  369. // the end of tmp (which is two elements
  370. // shorter than the old list)
  371. if (index < tmp.length)
  372. System.arraycopy(array, index+2, tmp, index,
  373. tmp.length - index);
  374. // set the listener array to the new array or null
  375. table = (tmp.length == 0) ? null : tmp;
  376. }
  377. } else {
  378. value = ((Hashtable)table).remove(key);
  379. }
  380. if (size()==7 && !isArray()) {
  381. shrink();
  382. }
  383. }
  384. return value;
  385. }
  386. /**
  387. * Removes all the mappings.
  388. */
  389. public void clear() {
  390. table = null;
  391. }
  392. /*
  393. * Returns a clone of the <code>ArrayTable</code>.
  394. */
  395. public Object clone() {
  396. ArrayTable newArrayTable = new ArrayTable();
  397. if (isArray()) {
  398. Object[] array = (Object[])table;
  399. for (int i = 0 ;i < array.length-1 ; i+=2) {
  400. newArrayTable.put(array[i], array[i+1]);
  401. }
  402. } else {
  403. Hashtable tmp = (Hashtable)table;
  404. Enumeration keys = tmp.keys();
  405. while (keys.hasMoreElements()) {
  406. Object o = keys.nextElement();
  407. newArrayTable.put(o,tmp.get(o));
  408. }
  409. }
  410. return newArrayTable;
  411. }
  412. /**
  413. * Returns the keys of the table, or <code>null</code> if there
  414. * are currently no bindings.
  415. * @param keys array of keys
  416. * @return an array of bindings
  417. */
  418. public Object[] getKeys(Object[] keys) {
  419. if (table == null) {
  420. return null;
  421. }
  422. if (isArray()) {
  423. Object[] array = (Object[])table;
  424. if (keys == null) {
  425. keys = new Object[array.length / 2];
  426. }
  427. for (int i = 0, index = 0 ;i < array.length-1 ; i+=2,
  428. index++) {
  429. keys[index] = array[i];
  430. }
  431. } else {
  432. Hashtable tmp = (Hashtable)table;
  433. Enumeration enum = tmp.keys();
  434. int counter = tmp.size();
  435. if (keys == null) {
  436. keys = new Object[counter];
  437. }
  438. while (counter > 0) {
  439. keys[--counter] = enum.nextElement();
  440. }
  441. }
  442. return keys;
  443. }
  444. /*
  445. * Returns true if the current storage mechanism is
  446. * an array of alternating key-value pairs.
  447. */
  448. private boolean isArray(){
  449. return (table instanceof Object[]);
  450. }
  451. /*
  452. * Grows the storage from an array to a hashtable.
  453. */
  454. private void grow() {
  455. Object[] array = (Object[])table;
  456. Hashtable tmp = new Hashtable(array.length2);
  457. for (int i = 0; i<array.length; i+=2) {
  458. tmp.put(array[i], array[i+1]);
  459. }
  460. table = tmp;
  461. }
  462. /*
  463. * Shrinks the storage from a hashtable to an array.
  464. */
  465. private void shrink() {
  466. Hashtable tmp = (Hashtable)table;
  467. Object[] array = new Object[tmp.size()*2];
  468. Enumeration keys = tmp.keys();
  469. int j = 0;
  470. while (keys.hasMoreElements()) {
  471. Object o = keys.nextElement();
  472. array[j] = o;
  473. array[j+1] = tmp.get(o);
  474. j+=2;
  475. }
  476. table = array;
  477. }
  478. /**
  479. * Writes the passed in ArrayTable to the passed in ObjectOutputStream.
  480. * The data is saved as an integer indicating how many key/value
  481. * pairs are being archived, followed by the the key/value pairs. If
  482. * <code>table</code> is null, 0 will be written to <code>s</code>.
  483. * <p>
  484. * This is a convenience method that ActionMap/InputMap and
  485. * AbstractAction use to avoid having the same code in each class.
  486. */
  487. static void writeArrayTable(ObjectOutputStream s, ArrayTable table) throws IOException {
  488. Object keys[];
  489. if (table == null || (keys = table.getKeys(null)) == null) {
  490. s.writeInt(0);
  491. }
  492. else {
  493. // Determine how many keys have Serializable values, when
  494. // done all non-null values in keys identify the Serializable
  495. // values.
  496. int validCount = 0;
  497. for (int counter = 0; counter < keys.length; counter++) {
  498. if ((keys[counter] instanceof Serializable) &&
  499. (table.get(keys[counter]) instanceof Serializable)) {
  500. validCount++;
  501. }
  502. else {
  503. keys[counter] = null;
  504. }
  505. }
  506. // Write ou the Serializable key/value pairs.
  507. s.writeInt(validCount);
  508. if (validCount > 0) {
  509. for (int counter = 0; counter < keys.length; counter++) {
  510. if (keys[counter] != null) {
  511. s.writeObject(keys[counter]);
  512. s.writeObject(table.get(keys[counter]));
  513. if (--validCount == 0) {
  514. break;
  515. }
  516. }
  517. }
  518. }
  519. }
  520. }
  521. }
  522. }