1/*
2 * Copyright (c) 1997, 2014, 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 */
25package javax.swing;
26
27import java.awt.*;
28import java.awt.event.*;
29import java.beans.*;
30import java.util.Hashtable;
31import java.util.Enumeration;
32import java.io.Serializable;
33import java.io.IOException;
34import java.io.ObjectInputStream;
35import java.io.ObjectOutputStream;
36import java.security.AccessController;
37import javax.swing.event.SwingPropertyChangeSupport;
38import 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 * @since 1.2
59 */
60@SuppressWarnings("serial") // Same-version serialization only
61public abstract class AbstractAction implements Action, Cloneable, Serializable
62{
63    /**
64     * Whether or not actions should reconfigure all properties on null.
65     */
66    private static Boolean RECONFIGURE_ON_NULL;
67
68    /**
69     * Specifies whether action is enabled; the default is true.
70     */
71    protected boolean enabled = true;
72
73
74    /**
75     * Contains the array of key bindings.
76     */
77    private transient ArrayTable arrayTable;
78
79    /**
80     * Whether or not to reconfigure all action properties from the
81     * specified event.
82     */
83    static boolean shouldReconfigure(PropertyChangeEvent e) {
84        if (e.getPropertyName() == null) {
85            synchronized(AbstractAction.class) {
86                if (RECONFIGURE_ON_NULL == null) {
87                    RECONFIGURE_ON_NULL = Boolean.valueOf(
88                        AccessController.doPrivileged(new GetPropertyAction(
89                        "swing.actions.reconfigureOnNull", "false")));
90                }
91                return RECONFIGURE_ON_NULL;
92            }
93        }
94        return false;
95    }
96
97    /**
98     * Sets the enabled state of a component from an Action.
99     *
100     * @param c the Component to set the enabled state on
101     * @param a the Action to set the enabled state from, may be null
102     */
103    static void setEnabledFromAction(JComponent c, Action a) {
104        c.setEnabled((a != null) ? a.isEnabled() : true);
105    }
106
107    /**
108     * Sets the tooltip text of a component from an Action.
109     *
110     * @param c the Component to set the tooltip text on
111     * @param a the Action to set the tooltip text from, may be null
112     */
113    static void setToolTipTextFromAction(JComponent c, Action a) {
114        c.setToolTipText(a != null ?
115                         (String)a.getValue(Action.SHORT_DESCRIPTION) : null);
116    }
117
118    static boolean hasSelectedKey(Action a) {
119        return (a != null && a.getValue(Action.SELECTED_KEY) != null);
120    }
121
122    static boolean isSelected(Action a) {
123        return Boolean.TRUE.equals(a.getValue(Action.SELECTED_KEY));
124    }
125
126
127
128    /**
129     * Creates an {@code Action}.
130     */
131    public AbstractAction() {
132    }
133
134    /**
135     * Creates an {@code Action} with the specified name.
136     *
137     * @param name the name ({@code Action.NAME}) for the action; a
138     *        value of {@code null} is ignored
139     */
140    public AbstractAction(String name) {
141        putValue(Action.NAME, name);
142    }
143
144    /**
145     * Creates an {@code Action} with the specified name and small icon.
146     *
147     * @param name the name ({@code Action.NAME}) for the action; a
148     *        value of {@code null} is ignored
149     * @param icon the small icon ({@code Action.SMALL_ICON}) for the action; a
150     *        value of {@code null} is ignored
151     */
152    public AbstractAction(String name, Icon icon) {
153        this(name);
154        putValue(Action.SMALL_ICON, icon);
155    }
156
157    /**
158     * Gets the <code>Object</code> associated with the specified key.
159     *
160     * @param key a string containing the specified <code>key</code>
161     * @return the binding <code>Object</code> stored with this key; if there
162     *          are no keys, it will return <code>null</code>
163     * @see Action#getValue
164     */
165    public Object getValue(String key) {
166        if (key == "enabled") {
167            return enabled;
168        }
169        if (arrayTable == null) {
170            return null;
171        }
172        return arrayTable.get(key);
173    }
174
175    /**
176     * Sets the <code>Value</code> associated with the specified key.
177     *
178     * @param key  the <code>String</code> that identifies the stored object
179     * @param newValue the <code>Object</code> to store using this key
180     * @see Action#putValue
181     */
182    public void putValue(String key, Object newValue) {
183        Object oldValue = null;
184        if (key == "enabled") {
185            // Treat putValue("enabled") the same way as a call to setEnabled.
186            // If we don't do this it means the two may get out of sync, and a
187            // bogus property change notification would be sent.
188            //
189            // To avoid dependencies between putValue & setEnabled this
190            // directly changes enabled. If we instead called setEnabled
191            // to change enabled, it would be possible for stack
192            // overflow in the case where a developer implemented setEnabled
193            // in terms of putValue.
194            if (newValue == null || !(newValue instanceof Boolean)) {
195                newValue = false;
196            }
197            oldValue = enabled;
198            enabled = (Boolean)newValue;
199        } else {
200            if (arrayTable == null) {
201                arrayTable = new ArrayTable();
202            }
203            if (arrayTable.containsKey(key))
204                oldValue = arrayTable.get(key);
205            // Remove the entry for key if newValue is null
206            // else put in the newValue for key.
207            if (newValue == null) {
208                arrayTable.remove(key);
209            } else {
210                arrayTable.put(key,newValue);
211            }
212        }
213        firePropertyChange(key, oldValue, newValue);
214    }
215
216    /**
217     * Returns true if the action is enabled.
218     *
219     * @return true if the action is enabled, false otherwise
220     * @see Action#isEnabled
221     */
222    public boolean isEnabled() {
223        return enabled;
224    }
225
226    /**
227     * Sets whether the {@code Action} is enabled. The default is {@code true}.
228     *
229     * @param newValue  {@code true} to enable the action, {@code false} to
230     *                  disable it
231     * @see Action#setEnabled
232     */
233    public void setEnabled(boolean newValue) {
234        boolean oldValue = this.enabled;
235
236        if (oldValue != newValue) {
237            this.enabled = newValue;
238            firePropertyChange("enabled",
239                               Boolean.valueOf(oldValue), Boolean.valueOf(newValue));
240        }
241    }
242
243
244    /**
245     * Returns an array of <code>Object</code>s which are keys for
246     * which values have been set for this <code>AbstractAction</code>,
247     * or <code>null</code> if no keys have values set.
248     * @return an array of key objects, or <code>null</code> if no
249     *                  keys have values set
250     * @since 1.3
251     */
252    public Object[] getKeys() {
253        if (arrayTable == null) {
254            return null;
255        }
256        Object[] keys = new Object[arrayTable.size()];
257        arrayTable.getKeys(keys);
258        return keys;
259    }
260
261    /**
262     * If any <code>PropertyChangeListeners</code> have been registered, the
263     * <code>changeSupport</code> field describes them.
264     */
265    protected SwingPropertyChangeSupport changeSupport;
266
267    /**
268     * Supports reporting bound property changes.  This method can be called
269     * when a bound property has changed and it will send the appropriate
270     * <code>PropertyChangeEvent</code> to any registered
271     * <code>PropertyChangeListeners</code>.
272     *
273     * @param propertyName  the name of the property that has changed
274     * @param oldValue  the old value of the property
275     * @param newValue  the new value of the property
276     */
277    protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
278        if (changeSupport == null ||
279            (oldValue != null && newValue != null && oldValue.equals(newValue))) {
280            return;
281        }
282        changeSupport.firePropertyChange(propertyName, oldValue, newValue);
283    }
284
285
286    /**
287     * Adds a <code>PropertyChangeListener</code> to the listener list.
288     * The listener is registered for all properties.
289     * <p>
290     * A <code>PropertyChangeEvent</code> will get fired in response to setting
291     * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
292     * or <code>setForeground</code>.
293     * Note that if the current component is inheriting its foreground,
294     * background, or font from its container, then no event will be
295     * fired in response to a change in the inherited property.
296     *
297     * @param listener  The <code>PropertyChangeListener</code> to be added
298     *
299     * @see Action#addPropertyChangeListener
300     */
301    public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
302        if (changeSupport == null) {
303            changeSupport = new SwingPropertyChangeSupport(this);
304        }
305        changeSupport.addPropertyChangeListener(listener);
306    }
307
308
309    /**
310     * Removes a <code>PropertyChangeListener</code> from the listener list.
311     * This removes a <code>PropertyChangeListener</code> that was registered
312     * for all properties.
313     *
314     * @param listener  the <code>PropertyChangeListener</code> to be removed
315     *
316     * @see Action#removePropertyChangeListener
317     */
318    public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
319        if (changeSupport == null) {
320            return;
321        }
322        changeSupport.removePropertyChangeListener(listener);
323    }
324
325
326    /**
327     * Returns an array of all the <code>PropertyChangeListener</code>s added
328     * to this AbstractAction with addPropertyChangeListener().
329     *
330     * @return all of the <code>PropertyChangeListener</code>s added or an empty
331     *         array if no listeners have been added
332     * @since 1.4
333     */
334    public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
335        if (changeSupport == null) {
336            return new PropertyChangeListener[0];
337        }
338        return changeSupport.getPropertyChangeListeners();
339    }
340
341
342    /**
343     * Clones the abstract action. This gives the clone
344     * its own copy of the key/value list,
345     * which is not handled for you by <code>Object.clone()</code>.
346     **/
347
348    protected Object clone() throws CloneNotSupportedException {
349        AbstractAction newAction = (AbstractAction)super.clone();
350        synchronized(this) {
351            if (arrayTable != null) {
352                newAction.arrayTable = (ArrayTable)arrayTable.clone();
353            }
354        }
355        return newAction;
356    }
357
358    private void writeObject(ObjectOutputStream s) throws IOException {
359        // Store the default fields
360        s.defaultWriteObject();
361
362        // And the keys
363        ArrayTable.writeArrayTable(s, arrayTable);
364    }
365
366    private void readObject(ObjectInputStream s) throws ClassNotFoundException,
367        IOException {
368        s.defaultReadObject();
369        for (int counter = s.readInt() - 1; counter >= 0; counter--) {
370            putValue((String)s.readObject(), s.readObject());
371        }
372    }
373}
374