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