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