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