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