1 /* AWTKeyStroke.java -- an immutable key stroke
2    Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation
3 
4 This file is part of GNU Classpath.
5 
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10 
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20 
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25 
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37 
38 
39 package java.awt;
40 
41 import java.awt.event.InputEvent;
42 import java.awt.event.KeyEvent;
43 import java.io.ObjectStreamException;
44 import java.io.Serializable;
45 import java.lang.reflect.Constructor;
46 import java.lang.reflect.Field;
47 import java.lang.reflect.InvocationTargetException;
48 import java.security.AccessController;
49 import java.security.PrivilegedAction;
50 import java.security.PrivilegedActionException;
51 import java.security.PrivilegedExceptionAction;
52 import java.util.HashMap;
53 import java.util.LinkedHashMap;
54 import java.util.Map;
55 import java.util.StringTokenizer;
56 
57 /**
58  * This class mirrors KeyEvents, representing both low-level key presses and
59  * key releases, and high level key typed inputs. However, this class forms
60  * immutable strokes, and can be efficiently reused via the factory methods
61  * for creating them.
62  *
63  * <p>For backwards compatibility with Swing, this supports a way to build
64  * instances of a subclass, using reflection, provided the subclass has a
65  * no-arg constructor (of any accessibility).
66  *
67  * @author Eric Blake (ebb9@email.byu.edu)
68  * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
69  * @see #getAWTKeyStroke(char)
70  * @since 1.4
71  * @status updated to 1.4
72  */
73 public class AWTKeyStroke implements Serializable
74 {
75   /**
76    * Compatible with JDK 1.4+.
77    */
78   private static final long serialVersionUID = -6430539691155161871L;
79 
80   /** The mask for modifiers. */
81   private static final int MODIFIERS_MASK = 0x3fef;
82 
83   /**
84    * The cache of recently created keystrokes. This maps KeyStrokes to
85    * KeyStrokes in a cache which removes the least recently accessed entry,
86    * under the assumption that garbage collection of a new keystroke is
87    * easy when we find the old one that it matches in the cache.
88    */
89   private static final LinkedHashMap<AWTKeyStroke,AWTKeyStroke> cache =
90     new LinkedHashMap<AWTKeyStroke,AWTKeyStroke>(11, 0.75f, true)
91   {
92     /** The largest the keystroke cache can grow. */
93     private static final int MAX_CACHE_SIZE = 2048;
94 
95     /** Prune stale entries. */
96     protected boolean removeEldestEntry(Map.Entry<AWTKeyStroke,AWTKeyStroke>
97                                         eldest)
98     {
99       return size() > MAX_CACHE_SIZE;
100     }
101   };
102 
103   /** The most recently generated keystroke, or null. */
104   private static AWTKeyStroke recent;
105 
106   /**
107    * The no-arg constructor of a subclass, or null to use AWTKeyStroke. Note
108    * that this will be left accessible, to get around private access; but
109    * it should not be a security risk as it is highly unlikely that creating
110    * protected instances of the subclass via reflection will do much damage.
111    */
112   private static Constructor ctor;
113 
114   /**
115    * A table of keyCode names to values.  This is package-private to
116    * avoid an accessor method.
117    *
118    * @see #getAWTKeyStroke(String)
119    */
120   static final HashMap<String,Object> vktable = new HashMap<String,Object>();
121   static
122   {
123     // Using reflection saves the hassle of keeping this in sync with KeyEvent,
124     // at the price of an expensive initialization.
AccessController.doPrivileged(new PrivilegedAction() { public Object run() { Field[] fields = KeyEvent.class.getFields(); int i = fields.length; try { while (--i >= 0) { Field f = fields[i]; String name = f.getName(); if (name.startsWith(R)) vktable.put(name.substring(3), f.get(null)); } } catch (Exception e) { throw (Error) new InternalError().initCause(e); } return null; } })125     AccessController.doPrivileged(new PrivilegedAction()
126       {
127         public Object run()
128         {
129           Field[] fields = KeyEvent.class.getFields();
130           int i = fields.length;
131           try
132             {
133               while (--i >= 0)
134                 {
135                   Field f = fields[i];
136                   String name = f.getName();
137                   if (name.startsWith("VK_"))
138                     vktable.put(name.substring(3), f.get(null));
139                 }
140             }
141           catch (Exception e)
142             {
143               throw (Error) new InternalError().initCause(e);
144             }
145           return null;
146         }
147       });
148   }
149 
150   /**
151    * The typed character, or CHAR_UNDEFINED for key presses and releases.
152    *
153    * @serial the keyChar
154    */
155   private char keyChar;
156 
157   /**
158    * The virtual key code, or VK_UNDEFINED for key typed. Package visible for
159    * use by Component.
160    *
161    * @serial the keyCode
162    */
163   int keyCode;
164 
165   /**
166    * The modifiers in effect. To match Sun, this stores the old style masks
167    * for shift, control, alt, meta, and alt-graph (but not button1); as well
168    * as the new style of extended modifiers for all modifiers.
169    *
170    * @serial bitwise or of the *_DOWN_MASK modifiers
171    */
172   private int modifiers;
173 
174   /**
175    * True if this is a key release; should only be true if keyChar is
176    * CHAR_UNDEFINED.
177    *
178    * @serial true to distinguish key pressed from key released
179    */
180   private boolean onKeyRelease;
181 
182   /**
183    * Construct a keystroke with default values: it will be interpreted as a
184    * key typed event with an invalid character and no modifiers. Client code
185    * should use the factory methods instead.
186    *
187    * @see #getAWTKeyStroke(char)
188    * @see #getAWTKeyStroke(Character, int)
189    * @see #getAWTKeyStroke(int, int, boolean)
190    * @see #getAWTKeyStroke(int, int)
191    * @see #getAWTKeyStrokeForEvent(KeyEvent)
192    * @see #getAWTKeyStroke(String)
193    */
AWTKeyStroke()194   protected AWTKeyStroke()
195   {
196     keyChar = KeyEvent.CHAR_UNDEFINED;
197   }
198 
199   /**
200    * Construct a keystroke with the given values. Client code should use the
201    * factory methods instead.
202    *
203    * @param keyChar the character entered, if this is a key typed
204    * @param keyCode the key pressed or released, or VK_UNDEFINED for key typed
205    * @param modifiers the modifier keys for the keystroke, in old or new style
206    * @param onKeyRelease true if this is a key release instead of a press
207    * @see #getAWTKeyStroke(char)
208    * @see #getAWTKeyStroke(Character, int)
209    * @see #getAWTKeyStroke(int, int, boolean)
210    * @see #getAWTKeyStroke(int, int)
211    * @see #getAWTKeyStrokeForEvent(KeyEvent)
212    * @see #getAWTKeyStroke(String)
213    */
AWTKeyStroke(char keyChar, int keyCode, int modifiers, boolean onKeyRelease)214   protected AWTKeyStroke(char keyChar, int keyCode, int modifiers,
215                          boolean onKeyRelease)
216   {
217     this.keyChar = keyChar;
218     this.keyCode = keyCode;
219     // No need to call extend(), as only trusted code calls this constructor.
220     this.modifiers = modifiers;
221     this.onKeyRelease = onKeyRelease;
222   }
223 
224   /**
225    * Registers a new subclass as being the type of keystrokes to generate in
226    * the factory methods. This operation flushes the cache of stored keystrokes
227    * if the class differs from the current one. The new class must be
228    * AWTKeyStroke or a subclass, and must have a no-arg constructor (which may
229    * be private).
230    *
231    * @param subclass the new runtime type of generated keystrokes
232    * @throws IllegalArgumentException subclass doesn't have no-arg constructor
233    * @throws ClassCastException subclass doesn't extend AWTKeyStroke
234    */
registerSubclass(final Class<?> subclass)235   protected static void registerSubclass(final Class<?> subclass)
236   {
237     if (subclass == null)
238       throw new IllegalArgumentException();
239     if (subclass.equals(ctor == null ? AWTKeyStroke.class
240                         : ctor.getDeclaringClass()))
241       return;
242     if (subclass.equals(AWTKeyStroke.class))
243        {
244          cache.clear();
245          recent = null;
246          ctor = null;
247          return;
248        }
249     try
250       {
251         ctor = (Constructor) AccessController.doPrivileged
252           (new PrivilegedExceptionAction()
253             {
254               public Object run()
255                 throws NoSuchMethodException, InstantiationException,
256                        IllegalAccessException, InvocationTargetException
257               {
258                 Constructor<?> c =
259                   subclass.getDeclaredConstructor((Class<?>[])null);
260                 c.setAccessible(true);
261                 // Create a new instance, to make sure that we can, and
262                 // to cause any ClassCastException.
263                 AWTKeyStroke dummy = (AWTKeyStroke) c.newInstance();
264                 return c;
265               }
266             });
267       }
268     catch (PrivilegedActionException e)
269       {
270         // e.getCause() will not ever be ClassCastException; that should
271         // escape on its own.
272         throw (RuntimeException)
273           new IllegalArgumentException().initCause(e.getCause());
274       }
275     cache.clear();
276     recent = null;
277   }
278 
279   /**
280    * Returns a keystroke representing a typed character.
281    *
282    * @param keyChar the typed character
283    * @return the specified keystroke
284    */
getAWTKeyStroke(char keyChar)285   public static AWTKeyStroke getAWTKeyStroke(char keyChar)
286   {
287     return getAWTKeyStroke(keyChar, KeyEvent.VK_UNDEFINED, 0, false);
288   }
289 
290   /**
291    * Returns a keystroke representing a typed character with the given
292    * modifiers. Note that keyChar is a <code>Character</code> instead of a
293    * <code>char</code> to avoid accidental ambiguity with
294    * <code>getAWTKeyStroke(int, int)</code>. The modifiers are the bitwise
295    * or of the masks found in {@link InputEvent}; the new style (*_DOWN_MASK)
296    * is preferred, but the old style will work.
297    *
298    * @param keyChar the typed character
299    * @param modifiers the modifiers, or 0
300    * @return the specified keystroke
301    * @throws IllegalArgumentException if keyChar is null
302    */
getAWTKeyStroke(Character keyChar, int modifiers)303   public static AWTKeyStroke getAWTKeyStroke(Character keyChar, int modifiers)
304   {
305     if (keyChar == null)
306       throw new IllegalArgumentException();
307     return getAWTKeyStroke(keyChar.charValue(), KeyEvent.VK_UNDEFINED,
308                            extend(modifiers), false);
309   }
310 
311   /**
312    * Returns a keystroke representing a pressed or released key event, with
313    * the given modifiers. The "virtual key" should be one of the VK_*
314    * constants in {@link KeyEvent}. The modifiers are the bitwise or of the
315    * masks found in {@link InputEvent}; the new style (*_DOWN_MASK) is
316    * preferred, but the old style will work.
317    *
318    * @param keyCode the virtual key
319    * @param modifiers the modifiers, or 0
320    * @param release true if this is a key release instead of a key press
321    * @return the specified keystroke
322    */
getAWTKeyStroke(int keyCode, int modifiers, boolean release)323   public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers,
324                                              boolean release)
325   {
326     return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
327                            extend(modifiers), release);
328   }
329 
330   /**
331    * Returns a keystroke representing a pressed key event, with the given
332    * modifiers. The "virtual key" should be one of the VK_* constants in
333    * {@link KeyEvent}. The modifiers are the bitwise or of the masks found
334    * in {@link InputEvent}; the new style (*_DOWN_MASK) is preferred, but the
335    * old style will work.
336    *
337    * @param keyCode the virtual key
338    * @param modifiers the modifiers, or 0
339    * @return the specified keystroke
340    */
getAWTKeyStroke(int keyCode, int modifiers)341   public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers)
342   {
343     return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
344                            extend(modifiers), false);
345   }
346 
347   /**
348    * Returns a keystroke representing what caused the key event.
349    *
350    * @param event the key event to convert
351    * @return the specified keystroke, or null if the event is invalid
352    * @throws NullPointerException if event is null
353    */
getAWTKeyStrokeForEvent(KeyEvent event)354   public static AWTKeyStroke getAWTKeyStrokeForEvent(KeyEvent event)
355   {
356     switch (event.id)
357       {
358       case KeyEvent.KEY_TYPED:
359         return getAWTKeyStroke(event.getKeyChar(), KeyEvent.VK_UNDEFINED,
360                                extend(event.getModifiersEx()), false);
361       case KeyEvent.KEY_PRESSED:
362         return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
363                                extend(event.getModifiersEx()), false);
364       case KeyEvent.KEY_RELEASED:
365         return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
366                                extend(event.getModifiersEx()), true);
367       default:
368         return null;
369       }
370   }
371 
372   /**
373    * Parses a string and returns the keystroke that it represents. The syntax
374    * for keystrokes is listed below, with tokens separated by an arbitrary
375    * number of spaces:
376    * <pre>
377    * keyStroke := &lt;modifiers&gt;* ( &lt;typedID&gt; | &lt;codeID&gt; )
378    * modifiers := ( shift | control | ctrl | meta | alt
379    *                | button1 | button2 | button3 )
380    * typedID := typed &lt;single Unicode character&gt;
381    * codeID := ( pressed | released )? &lt;name&gt;
382    * name := &lt;the KeyEvent field name less the leading "VK_"&gt;
383    * </pre>
384    *
385    * <p>Note that the grammar is rather weak, and not all valid keystrokes
386    * can be generated in this manner (for example, a typed space, or anything
387    * with the alt-graph modifier!). The output of AWTKeyStroke.toString()
388    * will not meet the grammar. If pressed or released is not specified,
389    * pressed is assumed. Examples:<br>
390    * <code>
391    * "INSERT" =&gt; getAWTKeyStroke(KeyEvent.VK_INSERT, 0);<br>
392    * "control DELETE" =&gt;
393    *    getAWTKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);<br>
394    * "alt shift X" =&gt; getAWTKeyStroke(KeyEvent.VK_X,
395    *    InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);<br>
396    * "alt shift released X" =&gt; getAWTKeyStroke(KeyEvent.VK_X,
397    *    InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, true);<br>
398    * "typed a" =&gt; getAWTKeyStroke('a');
399    * </code>
400    *
401    * @param s the string to parse
402    * @throws IllegalArgumentException if s is null or cannot be parsed
403    * @return the specified keystroke
404    */
getAWTKeyStroke(String s)405   public static AWTKeyStroke getAWTKeyStroke(String s)
406   {
407     if (s == null)
408       throw new IllegalArgumentException("null argument");
409     StringTokenizer t = new StringTokenizer(s, " ");
410     if (! t.hasMoreTokens())
411       throw new IllegalArgumentException("no tokens '" + s + "'");
412     int modifiers = 0;
413     boolean released = false;
414     String token = null;
415     do
416       {
417         token = t.nextToken();
418         if ("shift".equals(token))
419           {
420             modifiers |= KeyEvent.SHIFT_MASK;
421             modifiers |= KeyEvent.SHIFT_DOWN_MASK;
422           }
423         else if ("ctrl".equals(token) || "control".equals(token))
424           {
425             modifiers |= KeyEvent.CTRL_MASK;
426             modifiers |= KeyEvent.CTRL_DOWN_MASK;
427           }
428         else if ("meta".equals(token))
429           {
430             modifiers |= KeyEvent.META_MASK;
431             modifiers |= KeyEvent.META_DOWN_MASK;
432           }
433         else if ("alt".equals(token))
434           {
435             modifiers |= KeyEvent.ALT_MASK;
436             modifiers |= KeyEvent.ALT_DOWN_MASK;
437           }
438         else if ("button1".equals(token))
439           modifiers |= KeyEvent.BUTTON1_DOWN_MASK;
440         else if ("button2".equals(token))
441           modifiers |= KeyEvent.BUTTON2_DOWN_MASK;
442         else if ("button3".equals(token))
443           modifiers |= KeyEvent.BUTTON3_DOWN_MASK;
444         else if ("typed".equals(token))
445           {
446             if (t.hasMoreTokens())
447               {
448                 token = t.nextToken();
449                 if (! t.hasMoreTokens() && token.length() == 1)
450                   return getAWTKeyStroke(token.charAt(0),
451                                          KeyEvent.VK_UNDEFINED, modifiers,
452                                          false);
453               }
454             throw new IllegalArgumentException("Invalid 'typed' argument '"
455                                                + s + "'");
456           }
457         else if ("pressed".equals(token))
458           {
459             if (t.hasMoreTokens())
460               token = t.nextToken();
461             break;
462           }
463         else if ("released".equals(token))
464           {
465             released = true;
466             if (t.hasMoreTokens())
467               token = t.nextToken();
468             break;
469           }
470         else
471           break;
472       }
473     while (t.hasMoreTokens());
474     // Now token contains the VK name we must parse.
475     Integer code = (Integer) vktable.get(token);
476     if (code == null)
477       throw new IllegalArgumentException("Unknown token '" + token
478                                          + "' in '" + s + "'");
479     if (t.hasMoreTokens())
480       throw new IllegalArgumentException("Too many tokens: " + s);
481     return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, code.intValue(),
482                            modifiers, released);
483   }
484 
485   /**
486    * Returns the character of this keystroke, if it was typed.
487    *
488    * @return the character value, or CHAR_UNDEFINED
489    * @see #getAWTKeyStroke(char)
490    */
getKeyChar()491   public final char getKeyChar()
492   {
493     return keyChar;
494   }
495 
496   /**
497    * Returns the virtual key code of this keystroke, if it was pressed or
498    * released. This will be a VK_* constant from KeyEvent.
499    *
500    * @return the virtual key code value, or VK_UNDEFINED
501    * @see #getAWTKeyStroke(int, int)
502    */
getKeyCode()503   public final int getKeyCode()
504   {
505     return keyCode;
506   }
507 
508   /**
509    * Returns the modifiers for this keystroke. This will be a bitwise or of
510    * constants from InputEvent; it includes the old style masks for shift,
511    * control, alt, meta, and alt-graph (but not button1); as well as the new
512    * style of extended modifiers for all modifiers.
513    *
514    * @return the modifiers
515    * @see #getAWTKeyStroke(Character, int)
516    * @see #getAWTKeyStroke(int, int)
517    */
getModifiers()518   public final int getModifiers()
519   {
520     return modifiers;
521   }
522 
523   /**
524    * Tests if this keystroke is a key release.
525    *
526    * @return true if this is a key release
527    * @see #getAWTKeyStroke(int, int, boolean)
528    */
isOnKeyRelease()529   public final boolean isOnKeyRelease()
530   {
531     return onKeyRelease;
532   }
533 
534   /**
535    * Returns the AWT event type of this keystroke. This is one of
536    * {@link KeyEvent#KEY_TYPED}, {@link KeyEvent#KEY_PRESSED}, or
537    * {@link KeyEvent#KEY_RELEASED}.
538    *
539    * @return the key event type
540    */
getKeyEventType()541   public final int getKeyEventType()
542   {
543     return keyCode == KeyEvent.VK_UNDEFINED ? KeyEvent.KEY_TYPED
544       : onKeyRelease ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED;
545   }
546 
547   /**
548    * Returns a hashcode for this key event. It is not documented, but appears
549    * to be: <code>(getKeyChar() + 1) * (getKeyCode() + 1)
550    * * (getModifiers() + 1) * 2 + (isOnKeyRelease() ? 1 : 2)</code>.
551    *
552    * @return the hashcode
553    */
hashCode()554   public int hashCode()
555   {
556     return (keyChar + 1) * (keyCode + 1) * (modifiers + 1) * 2
557       + (onKeyRelease ? 1 : 2);
558   }
559 
560   /**
561    * Tests two keystrokes for equality.
562    *
563    * @param o the object to test
564    * @return true if it is equal
565    */
equals(Object o)566   public final boolean equals(Object o)
567   {
568     if (! (o instanceof AWTKeyStroke))
569       return false;
570     AWTKeyStroke s = (AWTKeyStroke) o;
571     return this == o || (keyChar == s.keyChar && keyCode == s.keyCode
572                          && modifiers == s.modifiers
573                          && onKeyRelease == s.onKeyRelease);
574   }
575 
576   /**
577    * Returns a string representation of this keystroke. For typed keystrokes,
578    * this is <code>"keyChar " + KeyEvent.getKeyModifiersText(getModifiers())
579    + getKeyChar()</code>; for pressed and released keystrokes, this is
580    * <code>"keyCode " + KeyEvent.getKeyModifiersText(getModifiers())
581    * + KeyEvent.getKeyText(getKeyCode())
582    * + (isOnKeyRelease() ? "-R" : "-P")</code>.
583    *
584    * @return a string representation
585    */
toString()586   public String toString()
587   {
588     if (keyCode == KeyEvent.VK_UNDEFINED)
589       return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + keyChar;
590     return "keyCode " + KeyEvent.getKeyModifiersText(modifiers)
591       + KeyEvent.getKeyText(keyCode) + (onKeyRelease ? "-R" : "-P");
592   }
593 
594   /**
595    * Returns a cached version of the deserialized keystroke, if available.
596    *
597    * @return a cached replacement
598    * @throws ObjectStreamException if something goes wrong
599    */
readResolve()600   protected Object readResolve() throws ObjectStreamException
601   {
602     AWTKeyStroke s = cache.get(this);
603     if (s != null)
604       return s;
605     cache.put(this, this);
606     return this;
607   }
608 
609   /**
610    * Gets the appropriate keystroke, creating one if necessary.
611    *
612    * @param keyChar the keyChar
613    * @param keyCode the keyCode
614    * @param modifiers the modifiers
615    * @param release true for key release
616    * @return the specified keystroke
617    */
getAWTKeyStroke(char keyChar, int keyCode, int modifiers, boolean release)618   private static AWTKeyStroke getAWTKeyStroke(char keyChar, int keyCode,
619                                               int modifiers, boolean release)
620   {
621     // Check level 0 cache.
622     AWTKeyStroke stroke = recent; // Avoid thread races.
623     if (stroke != null && stroke.keyChar == keyChar
624         && stroke.keyCode == keyCode && stroke.modifiers == modifiers
625         && stroke.onKeyRelease == release)
626       return stroke;
627     // Create a new object, on the assumption that if it has a match in the
628     // cache, the VM can easily garbage collect it as it is temporary.
629     Constructor c = ctor; // Avoid thread races.
630     if (c == null)
631       stroke = new AWTKeyStroke(keyChar, keyCode, modifiers, release);
632     else
633       try
634         {
635           stroke = (AWTKeyStroke) c.newInstance();
636           stroke.keyChar = keyChar;
637           stroke.keyCode = keyCode;
638           stroke.modifiers = modifiers;
639           stroke.onKeyRelease = release;
640         }
641       catch (Exception e)
642         {
643           throw (Error) new InternalError().initCause(e);
644         }
645     // Check level 1 cache.
646     AWTKeyStroke cached = cache.get(stroke);
647     if (cached == null)
648       cache.put(stroke, stroke);
649     else
650       stroke = cached;
651     return recent = stroke;
652   }
653 
654   /**
655    * Converts the modifiers to the appropriate format.
656    *
657    * @param mod the modifiers to convert
658    * @return the adjusted modifiers
659    */
extend(int mod)660   private static int extend(int mod)
661   {
662     if ((mod & (KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK)) != 0)
663       mod |= KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK;
664     if ((mod & (KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK)) != 0)
665       mod |= KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK;
666     if ((mod & (KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK)) != 0)
667       mod |= KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK;
668     if ((mod & (KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK)) != 0)
669       mod |= KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK;
670     if ((mod & (KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK)) != 0)
671       mod |= KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK;
672     if ((mod & KeyEvent.BUTTON1_MASK) != 0)
673       mod |= KeyEvent.BUTTON1_DOWN_MASK;
674     return mod & MODIFIERS_MASK;
675   }
676 } // class AWTKeyStroke
677