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