View Javadoc
1   /*
2    * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  package javax.swing;
26  
27  import java.awt.*;
28  import java.awt.event.*;
29  import java.beans.*;
30  import java.util.Hashtable;
31  import java.util.Enumeration;
32  import java.io.Serializable;
33  import java.io.IOException;
34  import java.io.ObjectInputStream;
35  import java.io.ObjectOutputStream;
36  import java.security.AccessController;
37  import javax.swing.event.SwingPropertyChangeSupport;
38  import sun.security.action.GetPropertyAction;
39  
40  /**
41   * This class provides default implementations for the JFC <code>Action</code>
42   * interface. Standard behaviors like the get and set methods for
43   * <code>Action</code> object properties (icon, text, and enabled) are defined
44   * here. The developer need only subclass this abstract class and
45   * define the <code>actionPerformed</code> method.
46   * <p>
47   * <strong>Warning:</strong>
48   * Serialized objects of this class will not be compatible with
49   * future Swing releases. The current serialization support is
50   * appropriate for short term storage or RMI between applications running
51   * the same version of Swing.  As of 1.4, support for long term storage
52   * of all JavaBeans&trade;
53   * has been added to the <code>java.beans</code> package.
54   * Please see {@link java.beans.XMLEncoder}.
55   *
56   * @author Georges Saab
57   * @see Action
58   */
59  public abstract class AbstractAction implements Action, Cloneable, Serializable
60  {
61      /**
62       * Whether or not actions should reconfigure all properties on null.
63       */
64      private static Boolean RECONFIGURE_ON_NULL;
65  
66      /**
67       * Specifies whether action is enabled; the default is true.
68       */
69      protected boolean enabled = true;
70  
71  
72      /**
73       * Contains the array of key bindings.
74       */
75      private transient ArrayTable arrayTable;
76  
77      /**
78       * Whether or not to reconfigure all action properties from the
79       * specified event.
80       */
81      static boolean shouldReconfigure(PropertyChangeEvent e) {
82          if (e.getPropertyName() == null) {
83              synchronized(AbstractAction.class) {
84                  if (RECONFIGURE_ON_NULL == null) {
85                      RECONFIGURE_ON_NULL = Boolean.valueOf(
86                          AccessController.doPrivileged(new GetPropertyAction(
87                          "swing.actions.reconfigureOnNull", "false")));
88                  }
89                  return RECONFIGURE_ON_NULL;
90              }
91          }
92          return false;
93      }
94  
95      /**
96       * Sets the enabled state of a component from an Action.
97       *
98       * @param c the Component to set the enabled state on
99       * @param a the Action to set the enabled state from, may be null
100      */
101     static void setEnabledFromAction(JComponent c, Action a) {
102         c.setEnabled((a != null) ? a.isEnabled() : true);
103     }
104 
105     /**
106      * Sets the tooltip text of a component from an Action.
107      *
108      * @param c the Component to set the tooltip text on
109      * @param a the Action to set the tooltip text from, may be null
110      */
111     static void setToolTipTextFromAction(JComponent c, Action a) {
112         c.setToolTipText(a != null ?
113                          (String)a.getValue(Action.SHORT_DESCRIPTION) : null);
114     }
115 
116     static boolean hasSelectedKey(Action a) {
117         return (a != null && a.getValue(Action.SELECTED_KEY) != null);
118     }
119 
120     static boolean isSelected(Action a) {
121         return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY));
122     }
123 
124 
125 
126     /**
127      * Creates an {@code Action}.
128      */
129     public AbstractAction() {
130     }
131 
132     /**
133      * Creates an {@code Action} with the specified name.
134      *
135      * @param name the name ({@code Action.NAME}) for the action; a
136      *        value of {@code null} is ignored
137      */
138     public AbstractAction(String name) {
139         putValue(Action.NAME, name);
140     }
141 
142     /**
143      * Creates an {@code Action} with the specified name and small icon.
144      *
145      * @param name the name ({@code Action.NAME}) for the action; a
146      *        value of {@code null} is ignored
147      * @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a
148      *        value of {@code null} is ignored
149      */
150     public AbstractAction(String name, Icon icon) {
151         this(name);
152         putValue(Action.SMALL_ICON, icon);
153     }
154 
155     /**
156      * Gets the <code>Object</code> associated with the specified key.
157      *
158      * @param key a string containing the specified <code>key</code>
159      * @return the binding <code>Object</code> stored with this key; if there
160      *          are no keys, it will return <code>null</code>
161      * @see Action#getValue
162      */
163     public Object getValue(String key) {
164         if (key == "enabled") {
165             return enabled;
166         }
167         if (arrayTable == null) {
168             return null;
169         }
170         return arrayTable.get(key);
171     }
172 
173     /**
174      * Sets the <code>Value</code> associated with the specified key.
175      *
176      * @param key  the <code>String</code> that identifies the stored object
177      * @param newValue the <code>Object</code> to store using this key
178      * @see Action#putValue
179      */
180     public void putValue(String key, Object newValue) {
181         Object oldValue = null;
182         if (key == "enabled") {
183             // Treat putValue("enabled") the same way as a call to setEnabled.
184             // If we don't do this it means the two may get out of sync, and a
185             // bogus property change notification would be sent.
186             //
187             // To avoid dependencies between putValue & setEnabled this
188             // directly changes enabled. If we instead called setEnabled
189             // to change enabled, it would be possible for stack
190             // overflow in the case where a developer implemented setEnabled
191             // in terms of putValue.
192             if (newValue == null || !(newValue instanceof Boolean)) {
193                 newValue = false;
194             }
195             oldValue = enabled;
196             enabled = (Boolean)newValue;
197         } else {
198             if (arrayTable == null) {
199                 arrayTable = new ArrayTable();
200             }
201             if (arrayTable.containsKey(key))
202                 oldValue = arrayTable.get(key);
203             // Remove the entry for key if newValue is null
204             // else put in the newValue for key.
205             if (newValue == null) {
206                 arrayTable.remove(key);
207             } else {
208                 arrayTable.put(key,newValue);
209             }
210         }
211         firePropertyChange(key, oldValue, newValue);
212     }
213 
214     /**
215      * Returns true if the action is enabled.
216      *
217      * @return true if the action is enabled, false otherwise
218      * @see Action#isEnabled
219      */
220     public boolean isEnabled() {
221         return enabled;
222     }
223 
224     /**
225      * Sets whether the {@code Action} is enabled. The default is {@code true}.
226      *
227      * @param newValue  {@code true} to enable the action, {@code false} to
228      *                  disable it
229      * @see Action#setEnabled
230      */
231     public void setEnabled(boolean newValue) {
232         boolean oldValue = this.enabled;
233 
234         if (oldValue != newValue) {
235             this.enabled = newValue;
236             firePropertyChange("enabled",
237                                Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
238         }
239     }
240 
241 
242     /**
243      * Returns an array of <code>Object</code>s which are keys for
244      * which values have been set for this <code>AbstractAction</code>,
245      * or <code>null</code> if no keys have values set.
246      * @return an array of key objects, or <code>null</code> if no
247      *                  keys have values set
248      * @since 1.3
249      */
250     public Object[] getKeys() {
251         if (arrayTable == null) {
252             return null;
253         }
254         Object[] keys = new Object[arrayTable.size()];
255         arrayTable.getKeys(keys);
256         return keys;
257     }
258 
259     /**
260      * If any <code>PropertyChangeListeners</code> have been registered, the
261      * <code>changeSupport</code> field describes them.
262      */
263     protected SwingPropertyChangeSupport changeSupport;
264 
265     /**
266      * Supports reporting bound property changes.  This method can be called
267      * when a bound property has changed and it will send the appropriate
268      * <code>PropertyChangeEvent</code> to any registered
269      * <code>PropertyChangeListeners</code>.
270      */
271     protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
272         if (changeSupport == null ||
273             (oldValue != null && newValue != null && oldValue.equals(newValue))) {
274             return;
275         }
276         changeSupport.firePropertyChange(propertyName, oldValue, newValue);
277     }
278 
279 
280     /**
281      * Adds a <code>PropertyChangeListener</code> to the listener list.
282      * The listener is registered for all properties.
283      * <p>
284      * A <code>PropertyChangeEvent</code> will get fired in response to setting
285      * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
286      * or <code>setForeground</code>.
287      * Note that if the current component is inheriting its foreground,
288      * background, or font from its container, then no event will be
289      * fired in response to a change in the inherited property.
290      *
291      * @param listener  The <code>PropertyChangeListener</code> to be added
292      *
293      * @see Action#addPropertyChangeListener
294      */
295     public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
296         if (changeSupport == null) {
297             changeSupport = new SwingPropertyChangeSupport(this);
298         }
299         changeSupport.addPropertyChangeListener(listener);
300     }
301 
302 
303     /**
304      * Removes a <code>PropertyChangeListener</code> from the listener list.
305      * This removes a <code>PropertyChangeListener</code> that was registered
306      * for all properties.
307      *
308      * @param listener  the <code>PropertyChangeListener</code> to be removed
309      *
310      * @see Action#removePropertyChangeListener
311      */
312     public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
313         if (changeSupport == null) {
314             return;
315         }
316         changeSupport.removePropertyChangeListener(listener);
317     }
318 
319 
320     /**
321      * Returns an array of all the <code>PropertyChangeListener</code>s added
322      * to this AbstractAction with addPropertyChangeListener().
323      *
324      * @return all of the <code>PropertyChangeListener</code>s added or an empty
325      *         array if no listeners have been added
326      * @since 1.4
327      */
328     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
329         if (changeSupport == null) {
330             return new PropertyChangeListener[0];
331         }
332         return changeSupport.getPropertyChangeListeners();
333     }
334 
335 
336     /**
337      * Clones the abstract action. This gives the clone
338      * its own copy of the key/value list,
339      * which is not handled for you by <code>Object.clone()</code>.
340      **/
341 
342     protected Object clone() throws CloneNotSupportedException {
343         AbstractAction newAction = (AbstractAction)super.clone();
344         synchronized(this) {
345             if (arrayTable != null) {
346                 newAction.arrayTable = (ArrayTable)arrayTable.clone();
347             }
348         }
349         return newAction;
350     }
351 
352     private void writeObject(ObjectOutputStream s) throws IOException {
353         // Store the default fields
354         s.defaultWriteObject();
355 
356         // And the keys
357         ArrayTable.writeArrayTable(s, arrayTable);
358     }
359 
360     private void readObject(ObjectInputStream s) throws ClassNotFoundException,
361         IOException {
362         s.defaultReadObject();
363         for (int counter = s.readInt() - 1; counter >= 0; counter--) {
364             putValue((String)s.readObject(), s.readObject());
365         }
366     }
367 }