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  */
  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  * @since 1.2
  59  */
  60 @SuppressWarnings("serial") // Same-version serialization only
  61 public 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     protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
 274         if (changeSupport == null ||
 275             (oldValue != null && newValue != null && oldValue.equals(newValue))) {
 276             return;
 277         }
 278         changeSupport.firePropertyChange(propertyName, oldValue, newValue);
 279     }
 280 
 281 
 282     /**
 283      * Adds a <code>PropertyChangeListener</code> to the listener list.
 284      * The listener is registered for all properties.
 285      * <p>
 286      * A <code>PropertyChangeEvent</code> will get fired in response to setting
 287      * a bound property, e.g. <code>setFont</code>, <code>setBackground</code>,
 288      * or <code>setForeground</code>.
 289      * Note that if the current component is inheriting its foreground,
 290      * background, or font from its container, then no event will be
 291      * fired in response to a change in the inherited property.
 292      *
 293      * @param listener  The <code>PropertyChangeListener</code> to be added
 294      *
 295      * @see Action#addPropertyChangeListener
 296      */
 297     public synchronized void addPropertyChangeListener(PropertyChangeListener listener) {
 298         if (changeSupport == null) {
 299             changeSupport = new SwingPropertyChangeSupport(this);
 300         }
 301         changeSupport.addPropertyChangeListener(listener);
 302     }
 303 
 304 
 305     /**
 306      * Removes a <code>PropertyChangeListener</code> from the listener list.
 307      * This removes a <code>PropertyChangeListener</code> that was registered
 308      * for all properties.
 309      *
 310      * @param listener  the <code>PropertyChangeListener</code> to be removed
 311      *
 312      * @see Action#removePropertyChangeListener
 313      */
 314     public synchronized void removePropertyChangeListener(PropertyChangeListener listener) {
 315         if (changeSupport == null) {
 316             return;
 317         }
 318         changeSupport.removePropertyChangeListener(listener);
 319     }
 320 
 321 
 322     /**
 323      * Returns an array of all the <code>PropertyChangeListener</code>s added
 324      * to this AbstractAction with addPropertyChangeListener().
 325      *
 326      * @return all of the <code>PropertyChangeListener</code>s added or an empty
 327      *         array if no listeners have been added
 328      * @since 1.4
 329      */
 330     public synchronized PropertyChangeListener[] getPropertyChangeListeners() {
 331         if (changeSupport == null) {
 332             return new PropertyChangeListener[0];
 333         }
 334         return changeSupport.getPropertyChangeListeners();
 335     }
 336 
 337 
 338     /**
 339      * Clones the abstract action. This gives the clone
 340      * its own copy of the key/value list,
 341      * which is not handled for you by <code>Object.clone()</code>.
 342      **/
 343 
 344     protected Object clone() throws CloneNotSupportedException {
 345         AbstractAction newAction = (AbstractAction)super.clone();
 346         synchronized(this) {
 347             if (arrayTable != null) {
 348                 newAction.arrayTable = (ArrayTable)arrayTable.clone();
 349             }
 350         }
 351         return newAction;
 352     }
 353 
 354     private void writeObject(ObjectOutputStream s) throws IOException {
 355         // Store the default fields
 356         s.defaultWriteObject();
 357 
 358         // And the keys
 359         ArrayTable.writeArrayTable(s, arrayTable);
 360     }
 361 
 362     private void readObject(ObjectInputStream s) throws ClassNotFoundException,
 363         IOException {
 364         s.defaultReadObject();
 365         for (int counter = s.readInt() - 1; counter >= 0; counter--) {
 366             putValue((String)s.readObject(), s.readObject());
 367         }
 368     }
 369 }