1 /*
   2  * Copyright (c) 2000, 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 java.awt;
  26 
  27 import java.awt.event.KeyEvent;
  28 import sun.awt.AppContext;
  29 import java.awt.event.InputEvent;
  30 import java.util.Collections;
  31 import java.util.HashMap;
  32 import java.util.Map;
  33 import java.util.StringTokenizer;
  34 import java.io.Serializable;
  35 import java.security.AccessController;
  36 import java.security.PrivilegedAction;
  37 import java.lang.reflect.Constructor;
  38 import java.lang.reflect.InvocationTargetException;
  39 import java.lang.reflect.Modifier;
  40 import java.lang.reflect.Field;
  41 
  42 /**
  43  * An <code>AWTKeyStroke</code> represents a key action on the
  44  * keyboard, or equivalent input device. <code>AWTKeyStroke</code>s
  45  * can correspond to only a press or release of a
  46  * particular key, just as <code>KEY_PRESSED</code> and
  47  * <code>KEY_RELEASED</code> <code>KeyEvent</code>s do;
  48  * alternately, they can correspond to typing a specific Java character, just
  49  * as <code>KEY_TYPED</code> <code>KeyEvent</code>s do.
  50  * In all cases, <code>AWTKeyStroke</code>s can specify modifiers
  51  * (alt, shift, control, meta, altGraph, or a combination thereof) which must be present
  52  * during the action for an exact match.
  53  * <p>
  54  * <code>AWTKeyStrokes</code> are immutable, and are intended
  55  * to be unique. Client code should never create an
  56  * <code>AWTKeyStroke</code> on its own, but should instead use
  57  * a variant of <code>getAWTKeyStroke</code>. Client use of these factory
  58  * methods allows the <code>AWTKeyStroke</code> implementation
  59  * to cache and share instances efficiently.
  60  *
  61  * @see #getAWTKeyStroke
  62  *
  63  * @author Arnaud Weber
  64  * @author David Mendenhall
  65  * @since 1.4
  66  */
  67 public class AWTKeyStroke implements Serializable {
  68     static final long serialVersionUID = -6430539691155161871L;
  69 
  70     private static Map<String, Integer> modifierKeywords;
  71     /**
  72      * Associates VK_XXX (as a String) with code (as Integer). This is
  73      * done to avoid the overhead of the reflective call to find the
  74      * constant.
  75      */
  76     private static VKCollection vks;
  77 
  78     //A key for the collection of AWTKeyStrokes within AppContext.
  79     private static Object APP_CONTEXT_CACHE_KEY = new Object();
  80     //A key withing the cache
  81     private static AWTKeyStroke APP_CONTEXT_KEYSTROKE_KEY = new AWTKeyStroke();
  82 
  83     /*
  84      * Reads keystroke class from AppContext and if null, puts there the
  85      * AWTKeyStroke class.
  86      * Must be called under locked AWTKeyStro
  87      */
  88     private static Class<AWTKeyStroke> getAWTKeyStrokeClass() {
  89         @SuppressWarnings("unchecked")
  90         Class<AWTKeyStroke> clazz = (Class<AWTKeyStroke>)AppContext.getAppContext().get(AWTKeyStroke.class);
  91         if (clazz == null) {
  92             clazz = AWTKeyStroke.class;
  93             AppContext.getAppContext().put(AWTKeyStroke.class, AWTKeyStroke.class);
  94         }
  95         return clazz;
  96     }
  97 
  98     private char keyChar = KeyEvent.CHAR_UNDEFINED;
  99     private int keyCode = KeyEvent.VK_UNDEFINED;
 100     private int modifiers;
 101     private boolean onKeyRelease;
 102 
 103     static {
 104         /* ensure that the necessary native libraries are loaded */
 105         Toolkit.loadLibraries();
 106     }
 107 
 108     /**
 109      * Constructs an <code>AWTKeyStroke</code> with default values.
 110      * The default values used are:
 111      * <table border="1" summary="AWTKeyStroke default values">
 112      * <tr><th>Property</th><th>Default Value</th></tr>
 113      * <tr>
 114      *    <td>Key Char</td>
 115      *    <td><code>KeyEvent.CHAR_UNDEFINED</code></td>
 116      * </tr>
 117      * <tr>
 118      *    <td>Key Code</td>
 119      *    <td><code>KeyEvent.VK_UNDEFINED</code></td>
 120      * </tr>
 121      * <tr>
 122      *    <td>Modifiers</td>
 123      *    <td>none</td>
 124      * </tr>
 125      * <tr>
 126      *    <td>On key release?</td>
 127      *    <td><code>false</code></td>
 128      * </tr>
 129      * </table>
 130      *
 131      * <code>AWTKeyStroke</code>s should not be constructed
 132      * by client code. Use a variant of <code>getAWTKeyStroke</code>
 133      * instead.
 134      *
 135      * @see #getAWTKeyStroke
 136      */
 137     protected AWTKeyStroke() {
 138     }
 139 
 140     /**
 141      * Constructs an <code>AWTKeyStroke</code> with the specified
 142      * values. <code>AWTKeyStroke</code>s should not be constructed
 143      * by client code. Use a variant of <code>getAWTKeyStroke</code>
 144      * instead.
 145      *
 146      * @param keyChar the character value for a keyboard key
 147      * @param keyCode the key code for this <code>AWTKeyStroke</code>
 148      * @param modifiers a bitwise-ored combination of any modifiers
 149      * @param onKeyRelease <code>true</code> if this
 150      *        <code>AWTKeyStroke</code> corresponds
 151      *        to a key release; <code>false</code> otherwise
 152      * @see #getAWTKeyStroke
 153      */
 154     protected AWTKeyStroke(char keyChar, int keyCode, int modifiers,
 155                            boolean onKeyRelease) {
 156         this.keyChar = keyChar;
 157         this.keyCode = keyCode;
 158         this.modifiers = modifiers;
 159         this.onKeyRelease = onKeyRelease;
 160     }
 161 
 162     /**
 163      * Registers a new class which the factory methods in
 164      * <code>AWTKeyStroke</code> will use when generating new
 165      * instances of <code>AWTKeyStroke</code>s. After invoking this
 166      * method, the factory methods will return instances of the specified
 167      * Class. The specified Class must be either <code>AWTKeyStroke</code>
 168      * or derived from <code>AWTKeyStroke</code>, and it must have a
 169      * no-arg constructor. The constructor can be of any accessibility,
 170      * including <code>private</code>. This operation
 171      * flushes the current <code>AWTKeyStroke</code> cache.
 172      *
 173      * @param subclass the new Class of which the factory methods should create
 174      *        instances
 175      * @throws IllegalArgumentException if subclass is <code>null</code>,
 176      *         or if subclass does not have a no-arg constructor
 177      * @throws ClassCastException if subclass is not
 178      *         <code>AWTKeyStroke</code>, or a class derived from
 179      *         <code>AWTKeyStroke</code>
 180      */
 181     protected static void registerSubclass(Class<?> subclass) {
 182         if (subclass == null) {
 183             throw new IllegalArgumentException("subclass cannot be null");
 184         }
 185         synchronized (AWTKeyStroke.class) {
 186             @SuppressWarnings("unchecked")
 187             Class<AWTKeyStroke> keyStrokeClass = (Class)AppContext.getAppContext().get(AWTKeyStroke.class);
 188             if (keyStrokeClass != null && keyStrokeClass.equals(subclass)){
 189                 // Already registered
 190                 return;
 191             }
 192         }
 193         if (!AWTKeyStroke.class.isAssignableFrom(subclass)) {
 194             throw new ClassCastException("subclass is not derived from AWTKeyStroke");
 195         }
 196 
 197         Constructor<?> ctor = getCtor(subclass);
 198 
 199         String couldNotInstantiate = "subclass could not be instantiated";
 200 
 201         if (ctor == null) {
 202             throw new IllegalArgumentException(couldNotInstantiate);
 203         }
 204         try {
 205             AWTKeyStroke stroke = (AWTKeyStroke)ctor.newInstance((Object[]) null);
 206             if (stroke == null) {
 207                 throw new IllegalArgumentException(couldNotInstantiate);
 208             }
 209         } catch (NoSuchMethodError e) {
 210             throw new IllegalArgumentException(couldNotInstantiate);
 211         } catch (ExceptionInInitializerError e) {
 212             throw new IllegalArgumentException(couldNotInstantiate);
 213         } catch (InstantiationException e) {
 214             throw new IllegalArgumentException(couldNotInstantiate);
 215         } catch (IllegalAccessException e) {
 216             throw new IllegalArgumentException(couldNotInstantiate);
 217         } catch (InvocationTargetException e) {
 218             throw new IllegalArgumentException(couldNotInstantiate);
 219         }
 220 
 221         synchronized (AWTKeyStroke.class) {
 222             AppContext.getAppContext().put(AWTKeyStroke.class, subclass);
 223             AppContext.getAppContext().remove(APP_CONTEXT_CACHE_KEY);
 224             AppContext.getAppContext().remove(APP_CONTEXT_KEYSTROKE_KEY);
 225         }
 226     }
 227 
 228     /* returns noarg Constructor for class with accessible flag. No security
 229        threat as accessible flag is set only for this Constructor object,
 230        not for Class constructor.
 231      */
 232     private static Constructor<?> getCtor(final Class<?> clazz)
 233     {
 234         Constructor<?> ctor = AccessController.doPrivileged(new PrivilegedAction<Constructor<?>>() {
 235             public Constructor<?> run() {
 236                 try {
 237                     Constructor<?> ctor = clazz.getDeclaredConstructor((Class<?>[]) null);
 238                     if (ctor != null) {
 239                         ctor.setAccessible(true);
 240                     }
 241                     return ctor;
 242                 } catch (SecurityException e) {
 243                 } catch (NoSuchMethodException e) {
 244                 }
 245                 return null;
 246             }
 247         });
 248         return ctor;
 249     }
 250 
 251     private static synchronized AWTKeyStroke getCachedStroke
 252         (char keyChar, int keyCode, int modifiers, boolean onKeyRelease)
 253     {
 254         @SuppressWarnings("unchecked")
 255         Map<AWTKeyStroke, AWTKeyStroke> cache = (Map)AppContext.getAppContext().get(APP_CONTEXT_CACHE_KEY);
 256         AWTKeyStroke cacheKey = (AWTKeyStroke)AppContext.getAppContext().get(APP_CONTEXT_KEYSTROKE_KEY);
 257 
 258         if (cache == null) {
 259             cache = new HashMap<>();
 260             AppContext.getAppContext().put(APP_CONTEXT_CACHE_KEY, cache);
 261         }
 262 
 263         if (cacheKey == null) {
 264             try {
 265                 Class<AWTKeyStroke> clazz = getAWTKeyStrokeClass();
 266                 cacheKey = (AWTKeyStroke)getCtor(clazz).newInstance((Object[]) null);
 267                 AppContext.getAppContext().put(APP_CONTEXT_KEYSTROKE_KEY, cacheKey);
 268             } catch (InstantiationException e) {
 269                 assert(false);
 270             } catch (IllegalAccessException e) {
 271                 assert(false);
 272             } catch (InvocationTargetException e) {
 273                 assert(false);
 274             }
 275         }
 276         cacheKey.keyChar = keyChar;
 277         cacheKey.keyCode = keyCode;
 278         cacheKey.modifiers = mapNewModifiers(mapOldModifiers(modifiers));
 279         cacheKey.onKeyRelease = onKeyRelease;
 280 
 281         AWTKeyStroke stroke = cache.get(cacheKey);
 282         if (stroke == null) {
 283             stroke = cacheKey;
 284             cache.put(stroke, stroke);
 285             AppContext.getAppContext().remove(APP_CONTEXT_KEYSTROKE_KEY);
 286         }
 287         return stroke;
 288     }
 289 
 290     /**
 291      * Returns a shared instance of an <code>AWTKeyStroke</code>
 292      * that represents a <code>KEY_TYPED</code> event for the
 293      * specified character.
 294      *
 295      * @param keyChar the character value for a keyboard key
 296      * @return an <code>AWTKeyStroke</code> object for that key
 297      */
 298     public static AWTKeyStroke getAWTKeyStroke(char keyChar) {
 299         return getCachedStroke(keyChar, KeyEvent.VK_UNDEFINED, 0, false);
 300     }
 301 
 302     /**
 303      * Returns a shared instance of an {@code AWTKeyStroke}
 304      * that represents a {@code KEY_TYPED} event for the
 305      * specified Character object and a set of modifiers. Note
 306      * that the first parameter is of type Character rather than
 307      * char. This is to avoid inadvertent clashes with
 308      * calls to <code>getAWTKeyStroke(int keyCode, int modifiers)</code>.
 309      *
 310      * The modifiers consist of any combination of following:<ul>
 311      * <li>java.awt.event.InputEvent.SHIFT_DOWN_MASK
 312      * <li>java.awt.event.InputEvent.CTRL_DOWN_MASK
 313      * <li>java.awt.event.InputEvent.META_DOWN_MASK
 314      * <li>java.awt.event.InputEvent.ALT_DOWN_MASK
 315      * <li>java.awt.event.InputEvent.ALT_GRAPH_DOWN_MASK
 316      * </ul>
 317      * The old modifiers listed below also can be used, but they are
 318      * mapped to _DOWN_ modifiers. <ul>
 319      * <li>java.awt.event.InputEvent.SHIFT_MASK
 320      * <li>java.awt.event.InputEvent.CTRL_MASK
 321      * <li>java.awt.event.InputEvent.META_MASK
 322      * <li>java.awt.event.InputEvent.ALT_MASK
 323      * <li>java.awt.event.InputEvent.ALT_GRAPH_MASK
 324      * </ul>
 325      * also can be used, but they are mapped to _DOWN_ modifiers.
 326      *
 327      * Since these numbers are all different powers of two, any combination of
 328      * them is an integer in which each bit represents a different modifier
 329      * key. Use 0 to specify no modifiers.
 330      *
 331      * @param keyChar the Character object for a keyboard character
 332      * @param modifiers a bitwise-ored combination of any modifiers
 333      * @return an <code>AWTKeyStroke</code> object for that key
 334      * @throws IllegalArgumentException if <code>keyChar</code> is
 335      *       <code>null</code>
 336      *
 337      * @see java.awt.event.InputEvent
 338      */
 339     public static AWTKeyStroke getAWTKeyStroke(Character keyChar, int modifiers)
 340     {
 341         if (keyChar == null) {
 342             throw new IllegalArgumentException("keyChar cannot be null");
 343         }
 344         return getCachedStroke(keyChar.charValue(), KeyEvent.VK_UNDEFINED,
 345                                modifiers, false);
 346     }
 347 
 348     /**
 349      * Returns a shared instance of an <code>AWTKeyStroke</code>,
 350      * given a numeric key code and a set of modifiers, specifying
 351      * whether the key is activated when it is pressed or released.
 352      * <p>
 353      * The "virtual key" constants defined in
 354      * <code>java.awt.event.KeyEvent</code> can be
 355      * used to specify the key code. For example:<ul>
 356      * <li><code>java.awt.event.KeyEvent.VK_ENTER</code>
 357      * <li><code>java.awt.event.KeyEvent.VK_TAB</code>
 358      * <li><code>java.awt.event.KeyEvent.VK_SPACE</code>
 359      * </ul>
 360      * Alternatively, the key code may be obtained by calling
 361      * <code>java.awt.event.KeyEvent.getExtendedKeyCodeForChar</code>.
 362      *
 363      * The modifiers consist of any combination of:<ul>
 364      * <li>java.awt.event.InputEvent.SHIFT_DOWN_MASK
 365      * <li>java.awt.event.InputEvent.CTRL_DOWN_MASK
 366      * <li>java.awt.event.InputEvent.META_DOWN_MASK
 367      * <li>java.awt.event.InputEvent.ALT_DOWN_MASK
 368      * <li>java.awt.event.InputEvent.ALT_GRAPH_DOWN_MASK
 369      * </ul>
 370      * The old modifiers <ul>
 371      * <li>java.awt.event.InputEvent.SHIFT_MASK
 372      * <li>java.awt.event.InputEvent.CTRL_MASK
 373      * <li>java.awt.event.InputEvent.META_MASK
 374      * <li>java.awt.event.InputEvent.ALT_MASK
 375      * <li>java.awt.event.InputEvent.ALT_GRAPH_MASK
 376      * </ul>
 377      * also can be used, but they are mapped to _DOWN_ modifiers.
 378      *
 379      * Since these numbers are all different powers of two, any combination of
 380      * them is an integer in which each bit represents a different modifier
 381      * key. Use 0 to specify no modifiers.
 382      *
 383      * @param keyCode an int specifying the numeric code for a keyboard key
 384      * @param modifiers a bitwise-ored combination of any modifiers
 385      * @param onKeyRelease <code>true</code> if the <code>AWTKeyStroke</code>
 386      *        should represent a key release; <code>false</code> otherwise
 387      * @return an AWTKeyStroke object for that key
 388      *
 389      * @see java.awt.event.KeyEvent
 390      * @see java.awt.event.InputEvent
 391      */
 392     public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers,
 393                                                boolean onKeyRelease) {
 394         return getCachedStroke(KeyEvent.CHAR_UNDEFINED, keyCode, modifiers,
 395                                onKeyRelease);
 396     }
 397 
 398     /**
 399      * Returns a shared instance of an <code>AWTKeyStroke</code>,
 400      * given a numeric key code and a set of modifiers. The returned
 401      * <code>AWTKeyStroke</code> will correspond to a key press.
 402      * <p>
 403      * The "virtual key" constants defined in
 404      * <code>java.awt.event.KeyEvent</code> can be
 405      * used to specify the key code. For example:<ul>
 406      * <li><code>java.awt.event.KeyEvent.VK_ENTER</code>
 407      * <li><code>java.awt.event.KeyEvent.VK_TAB</code>
 408      * <li><code>java.awt.event.KeyEvent.VK_SPACE</code>
 409      * </ul>
 410      * The modifiers consist of any combination of:<ul>
 411      * <li>java.awt.event.InputEvent.SHIFT_DOWN_MASK
 412      * <li>java.awt.event.InputEvent.CTRL_DOWN_MASK
 413      * <li>java.awt.event.InputEvent.META_DOWN_MASK
 414      * <li>java.awt.event.InputEvent.ALT_DOWN_MASK
 415      * <li>java.awt.event.InputEvent.ALT_GRAPH_DOWN_MASK
 416      * </ul>
 417      * The old modifiers <ul>
 418      * <li>java.awt.event.InputEvent.SHIFT_MASK
 419      * <li>java.awt.event.InputEvent.CTRL_MASK
 420      * <li>java.awt.event.InputEvent.META_MASK
 421      * <li>java.awt.event.InputEvent.ALT_MASK
 422      * <li>java.awt.event.InputEvent.ALT_GRAPH_MASK
 423      * </ul>
 424      * also can be used, but they are mapped to _DOWN_ modifiers.
 425      *
 426      * Since these numbers are all different powers of two, any combination of
 427      * them is an integer in which each bit represents a different modifier
 428      * key. Use 0 to specify no modifiers.
 429      *
 430      * @param keyCode an int specifying the numeric code for a keyboard key
 431      * @param modifiers a bitwise-ored combination of any modifiers
 432      * @return an <code>AWTKeyStroke</code> object for that key
 433      *
 434      * @see java.awt.event.KeyEvent
 435      * @see java.awt.event.InputEvent
 436      */
 437     public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers) {
 438         return getCachedStroke(KeyEvent.CHAR_UNDEFINED, keyCode, modifiers,
 439                                false);
 440     }
 441 
 442     /**
 443      * Returns an <code>AWTKeyStroke</code> which represents the
 444      * stroke which generated a given <code>KeyEvent</code>.
 445      * <p>
 446      * This method obtains the keyChar from a <code>KeyTyped</code>
 447      * event, and the keyCode from a <code>KeyPressed</code> or
 448      * <code>KeyReleased</code> event. The <code>KeyEvent</code> modifiers are
 449      * obtained for all three types of <code>KeyEvent</code>.
 450      *
 451      * @param anEvent the <code>KeyEvent</code> from which to
 452      *      obtain the <code>AWTKeyStroke</code>
 453      * @throws NullPointerException if <code>anEvent</code> is null
 454      * @return the <code>AWTKeyStroke</code> that precipitated the event
 455      */
 456     public static AWTKeyStroke getAWTKeyStrokeForEvent(KeyEvent anEvent) {
 457         int id = anEvent.getID();
 458         switch(id) {
 459           case KeyEvent.KEY_PRESSED:
 460           case KeyEvent.KEY_RELEASED:
 461             return getCachedStroke(KeyEvent.CHAR_UNDEFINED,
 462                                    anEvent.getKeyCode(),
 463                                    anEvent.getModifiers(),
 464                                    (id == KeyEvent.KEY_RELEASED));
 465           case KeyEvent.KEY_TYPED:
 466             return getCachedStroke(anEvent.getKeyChar(),
 467                                    KeyEvent.VK_UNDEFINED,
 468                                    anEvent.getModifiers(),
 469                                    false);
 470           default:
 471             // Invalid ID for this KeyEvent
 472             return null;
 473         }
 474     }
 475 
 476     /**
 477      * Parses a string and returns an <code>AWTKeyStroke</code>.
 478      * The string must have the following syntax:
 479      * <pre>
 480      *    &lt;modifiers&gt;* (&lt;typedID&gt; | &lt;pressedReleasedID&gt;)
 481      *
 482      *    modifiers := shift | control | ctrl | meta | alt | altGraph
 483      *    typedID := typed &lt;typedKey&gt;
 484      *    typedKey := string of length 1 giving Unicode character.
 485      *    pressedReleasedID := (pressed | released) key
 486      *    key := KeyEvent key code name, i.e. the name following "VK_".
 487      * </pre>
 488      * If typed, pressed or released is not specified, pressed is assumed. Here
 489      * are some examples:
 490      * <pre>
 491      *     "INSERT" =&gt; getAWTKeyStroke(KeyEvent.VK_INSERT, 0);
 492      *     "control DELETE" =&gt; getAWTKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);
 493      *     "alt shift X" =&gt; getAWTKeyStroke(KeyEvent.VK_X, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);
 494      *     "alt shift released X" =&gt; getAWTKeyStroke(KeyEvent.VK_X, InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, true);
 495      *     "typed a" =&gt; getAWTKeyStroke('a');
 496      * </pre>
 497      *
 498      * @param s a String formatted as described above
 499      * @return an <code>AWTKeyStroke</code> object for that String
 500      * @throws IllegalArgumentException if <code>s</code> is <code>null</code>,
 501      *        or is formatted incorrectly
 502      */
 503     public static AWTKeyStroke getAWTKeyStroke(String s) {
 504         if (s == null) {
 505             throw new IllegalArgumentException("String cannot be null");
 506         }
 507 
 508         final String errmsg = "String formatted incorrectly";
 509 
 510         StringTokenizer st = new StringTokenizer(s, " ");
 511 
 512         int mask = 0;
 513         boolean released = false;
 514         boolean typed = false;
 515         boolean pressed = false;
 516 
 517         synchronized (AWTKeyStroke.class) {
 518             if (modifierKeywords == null) {
 519                 Map<String, Integer> uninitializedMap = new HashMap<>(8, 1.0f);
 520                 uninitializedMap.put("shift",
 521                                      Integer.valueOf(InputEvent.SHIFT_DOWN_MASK
 522                                                      |InputEvent.SHIFT_MASK));
 523                 uninitializedMap.put("control",
 524                                      Integer.valueOf(InputEvent.CTRL_DOWN_MASK
 525                                                      |InputEvent.CTRL_MASK));
 526                 uninitializedMap.put("ctrl",
 527                                      Integer.valueOf(InputEvent.CTRL_DOWN_MASK
 528                                                      |InputEvent.CTRL_MASK));
 529                 uninitializedMap.put("meta",
 530                                      Integer.valueOf(InputEvent.META_DOWN_MASK
 531                                                      |InputEvent.META_MASK));
 532                 uninitializedMap.put("alt",
 533                                      Integer.valueOf(InputEvent.ALT_DOWN_MASK
 534                                                      |InputEvent.ALT_MASK));
 535                 uninitializedMap.put("altGraph",
 536                                      Integer.valueOf(InputEvent.ALT_GRAPH_DOWN_MASK
 537                                                      |InputEvent.ALT_GRAPH_MASK));
 538                 uninitializedMap.put("button1",
 539                                      Integer.valueOf(InputEvent.BUTTON1_DOWN_MASK));
 540                 uninitializedMap.put("button2",
 541                                      Integer.valueOf(InputEvent.BUTTON2_DOWN_MASK));
 542                 uninitializedMap.put("button3",
 543                                      Integer.valueOf(InputEvent.BUTTON3_DOWN_MASK));
 544                 modifierKeywords =
 545                     Collections.synchronizedMap(uninitializedMap);
 546             }
 547         }
 548 
 549         int count = st.countTokens();
 550 
 551         for (int i = 1; i <= count; i++) {
 552             String token = st.nextToken();
 553 
 554             if (typed) {
 555                 if (token.length() != 1 || i != count) {
 556                     throw new IllegalArgumentException(errmsg);
 557                 }
 558                 return getCachedStroke(token.charAt(0), KeyEvent.VK_UNDEFINED,
 559                                        mask, false);
 560             }
 561 
 562             if (pressed || released || i == count) {
 563                 if (i != count) {
 564                     throw new IllegalArgumentException(errmsg);
 565                 }
 566 
 567                 String keyCodeName = "VK_" + token;
 568                 int keyCode = getVKValue(keyCodeName);
 569 
 570                 return getCachedStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
 571                                        mask, released);
 572             }
 573 
 574             if (token.equals("released")) {
 575                 released = true;
 576                 continue;
 577             }
 578             if (token.equals("pressed")) {
 579                 pressed = true;
 580                 continue;
 581             }
 582             if (token.equals("typed")) {
 583                 typed = true;
 584                 continue;
 585             }
 586 
 587             Integer tokenMask = modifierKeywords.get(token);
 588             if (tokenMask != null) {
 589                 mask |= tokenMask.intValue();
 590             } else {
 591                 throw new IllegalArgumentException(errmsg);
 592             }
 593         }
 594 
 595         throw new IllegalArgumentException(errmsg);
 596     }
 597 
 598     private static VKCollection getVKCollection() {
 599         if (vks == null) {
 600             vks = new VKCollection();
 601         }
 602         return vks;
 603     }
 604     /**
 605      * Returns the integer constant for the KeyEvent.VK field named
 606      * <code>key</code>. This will throw an
 607      * <code>IllegalArgumentException</code> if <code>key</code> is
 608      * not a valid constant.
 609      */
 610     private static int getVKValue(String key) {
 611         VKCollection vkCollect = getVKCollection();
 612 
 613         Integer value = vkCollect.findCode(key);
 614 
 615         if (value == null) {
 616             int keyCode = 0;
 617             final String errmsg = "String formatted incorrectly";
 618 
 619             try {
 620                 keyCode = KeyEvent.class.getField(key).getInt(KeyEvent.class);
 621             } catch (NoSuchFieldException nsfe) {
 622                 throw new IllegalArgumentException(errmsg);
 623             } catch (IllegalAccessException iae) {
 624                 throw new IllegalArgumentException(errmsg);
 625             }
 626             value = Integer.valueOf(keyCode);
 627             vkCollect.put(key, value);
 628         }
 629         return value.intValue();
 630     }
 631 
 632     /**
 633      * Returns the character for this <code>AWTKeyStroke</code>.
 634      *
 635      * @return a char value
 636      * @see #getAWTKeyStroke(char)
 637      * @see KeyEvent#getKeyChar
 638      */
 639     public final char getKeyChar() {
 640         return keyChar;
 641     }
 642 
 643     /**
 644      * Returns the numeric key code for this <code>AWTKeyStroke</code>.
 645      *
 646      * @return an int containing the key code value
 647      * @see #getAWTKeyStroke(int,int)
 648      * @see KeyEvent#getKeyCode
 649      */
 650     public final int getKeyCode() {
 651         return keyCode;
 652     }
 653 
 654     /**
 655      * Returns the modifier keys for this <code>AWTKeyStroke</code>.
 656      *
 657      * @return an int containing the modifiers
 658      * @see #getAWTKeyStroke(int,int)
 659      */
 660     public final int getModifiers() {
 661         return modifiers;
 662     }
 663 
 664     /**
 665      * Returns whether this <code>AWTKeyStroke</code> represents a key release.
 666      *
 667      * @return <code>true</code> if this <code>AWTKeyStroke</code>
 668      *          represents a key release; <code>false</code> otherwise
 669      * @see #getAWTKeyStroke(int,int,boolean)
 670      */
 671     public final boolean isOnKeyRelease() {
 672         return onKeyRelease;
 673     }
 674 
 675     /**
 676      * Returns the type of <code>KeyEvent</code> which corresponds to
 677      * this <code>AWTKeyStroke</code>.
 678      *
 679      * @return <code>KeyEvent.KEY_PRESSED</code>,
 680      *         <code>KeyEvent.KEY_TYPED</code>,
 681      *         or <code>KeyEvent.KEY_RELEASED</code>
 682      * @see java.awt.event.KeyEvent
 683      */
 684     public final int getKeyEventType() {
 685         if (keyCode == KeyEvent.VK_UNDEFINED) {
 686             return KeyEvent.KEY_TYPED;
 687         } else {
 688             return (onKeyRelease)
 689                 ? KeyEvent.KEY_RELEASED
 690                 : KeyEvent.KEY_PRESSED;
 691         }
 692     }
 693 
 694     /**
 695      * Returns a numeric value for this object that is likely to be unique,
 696      * making it a good choice as the index value in a hash table.
 697      *
 698      * @return an int that represents this object
 699      */
 700     public int hashCode() {
 701         return (((int)keyChar) + 1) * (2 * (keyCode + 1)) * (modifiers + 1) +
 702             (onKeyRelease ? 1 : 2);
 703     }
 704 
 705     /**
 706      * Returns true if this object is identical to the specified object.
 707      *
 708      * @param anObject the Object to compare this object to
 709      * @return true if the objects are identical
 710      */
 711     public final boolean equals(Object anObject) {
 712         if (anObject instanceof AWTKeyStroke) {
 713             AWTKeyStroke ks = (AWTKeyStroke)anObject;
 714             return (ks.keyChar == keyChar && ks.keyCode == keyCode &&
 715                     ks.onKeyRelease == onKeyRelease &&
 716                     ks.modifiers == modifiers);
 717         }
 718         return false;
 719     }
 720 
 721     /**
 722      * Returns a string that displays and identifies this object's properties.
 723      * The <code>String</code> returned by this method can be passed
 724      * as a parameter to <code>getAWTKeyStroke(String)</code> to produce
 725      * a key stroke equal to this key stroke.
 726      *
 727      * @return a String representation of this object
 728      * @see #getAWTKeyStroke(String)
 729      */
 730     public String toString() {
 731         if (keyCode == KeyEvent.VK_UNDEFINED) {
 732             return getModifiersText(modifiers) + "typed " + keyChar;
 733         } else {
 734             return getModifiersText(modifiers) +
 735                 (onKeyRelease ? "released" : "pressed") + " " +
 736                 getVKText(keyCode);
 737         }
 738     }
 739 
 740     static String getModifiersText(int modifiers) {
 741         StringBuilder buf = new StringBuilder();
 742 
 743         if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0 ) {
 744             buf.append("shift ");
 745         }
 746         if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0 ) {
 747             buf.append("ctrl ");
 748         }
 749         if ((modifiers & InputEvent.META_DOWN_MASK) != 0 ) {
 750             buf.append("meta ");
 751         }
 752         if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0 ) {
 753             buf.append("alt ");
 754         }
 755         if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0 ) {
 756             buf.append("altGraph ");
 757         }
 758         if ((modifiers & InputEvent.BUTTON1_DOWN_MASK) != 0 ) {
 759             buf.append("button1 ");
 760         }
 761         if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ) {
 762             buf.append("button2 ");
 763         }
 764         if ((modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0 ) {
 765             buf.append("button3 ");
 766         }
 767 
 768         return buf.toString();
 769     }
 770 
 771     static String getVKText(int keyCode) {
 772         VKCollection vkCollect = getVKCollection();
 773         Integer key = Integer.valueOf(keyCode);
 774         String name = vkCollect.findName(key);
 775         if (name != null) {
 776             return name.substring(3);
 777         }
 778         int expected_modifiers =
 779             (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL);
 780 
 781         Field[] fields = KeyEvent.class.getDeclaredFields();
 782         for (int i = 0; i < fields.length; i++) {
 783             try {
 784                 if (fields[i].getModifiers() == expected_modifiers
 785                     && fields[i].getType() == Integer.TYPE
 786                     && fields[i].getName().startsWith("VK_")
 787                     && fields[i].getInt(KeyEvent.class) == keyCode)
 788                 {
 789                     name = fields[i].getName();
 790                     vkCollect.put(name, key);
 791                     return name.substring(3);
 792                 }
 793             } catch (IllegalAccessException e) {
 794                 assert(false);
 795             }
 796         }
 797         return "UNKNOWN";
 798     }
 799 
 800     /**
 801      * Returns a cached instance of <code>AWTKeyStroke</code> (or a subclass of
 802      * <code>AWTKeyStroke</code>) which is equal to this instance.
 803      *
 804      * @return a cached instance which is equal to this instance
 805      */
 806     protected Object readResolve() throws java.io.ObjectStreamException {
 807         synchronized (AWTKeyStroke.class) {
 808             if (getClass().equals(getAWTKeyStrokeClass())) {
 809                 return  getCachedStroke(keyChar, keyCode, modifiers, onKeyRelease);
 810             }
 811         }
 812         return this;
 813     }
 814 
 815     private static int mapOldModifiers(int modifiers) {
 816         if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
 817             modifiers |= InputEvent.SHIFT_DOWN_MASK;
 818         }
 819         if ((modifiers & InputEvent.ALT_MASK) != 0) {
 820             modifiers |= InputEvent.ALT_DOWN_MASK;
 821         }
 822         if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
 823             modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
 824         }
 825         if ((modifiers & InputEvent.CTRL_MASK) != 0) {
 826             modifiers |= InputEvent.CTRL_DOWN_MASK;
 827         }
 828         if ((modifiers & InputEvent.META_MASK) != 0) {
 829             modifiers |= InputEvent.META_DOWN_MASK;
 830         }
 831 
 832         modifiers &= InputEvent.SHIFT_DOWN_MASK
 833             | InputEvent.ALT_DOWN_MASK
 834             | InputEvent.ALT_GRAPH_DOWN_MASK
 835             | InputEvent.CTRL_DOWN_MASK
 836             | InputEvent.META_DOWN_MASK
 837             | InputEvent.BUTTON1_DOWN_MASK
 838             | InputEvent.BUTTON2_DOWN_MASK
 839             | InputEvent.BUTTON3_DOWN_MASK;
 840 
 841         return modifiers;
 842     }
 843 
 844     private static int mapNewModifiers(int modifiers) {
 845         if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0) {
 846             modifiers |= InputEvent.SHIFT_MASK;
 847         }
 848         if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0) {
 849             modifiers |= InputEvent.ALT_MASK;
 850         }
 851         if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0) {
 852             modifiers |= InputEvent.ALT_GRAPH_MASK;
 853         }
 854         if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0) {
 855             modifiers |= InputEvent.CTRL_MASK;
 856         }
 857         if ((modifiers & InputEvent.META_DOWN_MASK) != 0) {
 858             modifiers |= InputEvent.META_MASK;
 859         }
 860 
 861         return modifiers;
 862     }
 863 
 864 }
 865 
 866 class VKCollection {
 867     Map<Integer, String> code2name;
 868     Map<String, Integer> name2code;
 869 
 870     public VKCollection() {
 871         code2name = new HashMap<>();
 872         name2code = new HashMap<>();
 873     }
 874 
 875     public synchronized void put(String name, Integer code) {
 876         assert((name != null) && (code != null));
 877         assert(findName(code) == null);
 878         assert(findCode(name) == null);
 879         code2name.put(code, name);
 880         name2code.put(name, code);
 881     }
 882 
 883     public synchronized Integer findCode(String name) {
 884         assert(name != null);
 885         return name2code.get(name);
 886     }
 887 
 888     public synchronized String findName(Integer code) {
 889         assert(code != null);
 890         return code2name.get(code);
 891     }
 892 }