1 /* AWTKeyStroke.java -- an immutable key stroke
2 Copyright (C) 2002, 2004, 2005 Free Software Foundation
4 This file is part of GNU Classpath.
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)
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.
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., 59 Temple Place, Suite 330, Boston, MA
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
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. */
41 import java.awt.event.KeyEvent;
42 import java.io.ObjectStreamException;
43 import java.io.Serializable;
44 import java.lang.reflect.Constructor;
45 import java.lang.reflect.Field;
46 import java.lang.reflect.InvocationTargetException;
47 import java.security.AccessController;
48 import java.security.PrivilegedAction;
49 import java.security.PrivilegedActionException;
50 import java.security.PrivilegedExceptionAction;
51 import java.util.HashMap;
52 import java.util.LinkedHashMap;
54 import java.util.StringTokenizer;
57 * This class mirrors KeyEvents, representing both low-level key presses and
58 * key releases, and high level key typed inputs. However, this class forms
59 * immutable strokes, and can be efficiently reused via the factory methods
62 * <p>For backwards compatibility with Swing, this supports a way to build
63 * instances of a subclass, using reflection, provided the subclass has a
64 * no-arg constructor (of any accessibility).
66 * @author Eric Blake (ebb9@email.byu.edu)
67 * @see #getAWTKeyStroke(char)
69 * @status updated to 1.4
71 public class AWTKeyStroke implements Serializable
74 * Compatible with JDK 1.4+.
76 private static final long serialVersionUID = -6430539691155161871L;
78 /** The mask for modifiers. */
79 private static final int MODIFIERS_MASK = 0x3fef;
82 * The cache of recently created keystrokes. This maps KeyStrokes to
83 * KeyStrokes in a cache which removes the least recently accessed entry,
84 * under the assumption that garbage collection of a new keystroke is
85 * easy when we find the old one that it matches in the cache.
87 private static final LinkedHashMap cache = new LinkedHashMap(11, 0.75f, true)
89 /** The largest the keystroke cache can grow. */
90 private static final int MAX_CACHE_SIZE = 2048;
92 /** Prune stale entries. */
93 protected boolean removeEldestEntry(Map.Entry eldest)
94 { // XXX - FIXME Use Map.Entry, not just Entry as gcj 3.1 workaround.
95 return size() > MAX_CACHE_SIZE;
99 /** The most recently generated keystroke, or null. */
100 private static AWTKeyStroke recent;
103 * The no-arg constructor of a subclass, or null to use AWTKeyStroke. Note
104 * that this will be left accessible, to get around private access; but
105 * it should not be a security risk as it is highly unlikely that creating
106 * protected instances of the subclass via reflection will do much damage.
108 private static Constructor ctor;
111 * A table of keyCode names to values. This is package-private to
112 * avoid an accessor method.
114 * @see #getAWTKeyStroke(String)
116 static final HashMap vktable = new HashMap();
119 // Using reflection saves the hassle of keeping this in sync with KeyEvent,
120 // at the price of an expensive initialization.
121 AccessController.doPrivileged(new PrivilegedAction()
125 Field[] fields = KeyEvent.class.getFields();
126 int i = fields.length;
132 String name = f.getName();
133 if (name.startsWith("VK_"))
134 vktable.put(name.substring(3), f.get(null));
139 throw (Error) new InternalError().initCause(e);
147 * The typed character, or CHAR_UNDEFINED for key presses and releases.
149 * @serial the keyChar
151 private char keyChar;
154 * The virtual key code, or VK_UNDEFINED for key typed. Package visible for
157 * @serial the keyCode
162 * The modifiers in effect. To match Sun, this stores the old style masks
163 * for shift, control, alt, meta, and alt-graph (but not button1); as well
164 * as the new style of extended modifiers for all modifiers.
166 * @serial bitwise or of the *_DOWN_MASK modifiers
168 private int modifiers;
171 * True if this is a key release; should only be true if keyChar is
174 * @serial true to distinguish key pressed from key released
176 private boolean onKeyRelease;
179 * Construct a keystroke with default values: it will be interpreted as a
180 * key typed event with an invalid character and no modifiers. Client code
181 * should use the factory methods instead.
183 * @see #getAWTKeyStroke(char)
184 * @see #getAWTKeyStroke(Character, int)
185 * @see #getAWTKeyStroke(int, int, boolean)
186 * @see #getAWTKeyStroke(int, int)
187 * @see #getAWTKeyStrokeForEvent(KeyEvent)
188 * @see #getAWTKeyStroke(String)
190 protected AWTKeyStroke()
192 keyChar = KeyEvent.CHAR_UNDEFINED;
196 * Construct a keystroke with the given values. Client code should use the
197 * factory methods instead.
199 * @param keyChar the character entered, if this is a key typed
200 * @param keyCode the key pressed or released, or VK_UNDEFINED for key typed
201 * @param modifiers the modifier keys for the keystroke, in old or new style
202 * @param onKeyRelease true if this is a key release instead of a press
203 * @see #getAWTKeyStroke(char)
204 * @see #getAWTKeyStroke(Character, int)
205 * @see #getAWTKeyStroke(int, int, boolean)
206 * @see #getAWTKeyStroke(int, int)
207 * @see #getAWTKeyStrokeForEvent(KeyEvent)
208 * @see #getAWTKeyStroke(String)
210 protected AWTKeyStroke(char keyChar, int keyCode, int modifiers,
211 boolean onKeyRelease)
213 this.keyChar = keyChar;
214 this.keyCode = keyCode;
215 // No need to call extend(), as only trusted code calls this constructor.
216 this.modifiers = modifiers;
217 this.onKeyRelease = onKeyRelease;
221 * Registers a new subclass as being the type of keystrokes to generate in
222 * the factory methods. This operation flushes the cache of stored keystrokes
223 * if the class differs from the current one. The new class must be
224 * AWTKeyStroke or a subclass, and must have a no-arg constructor (which may
227 * @param subclass the new runtime type of generated keystrokes
228 * @throws IllegalArgumentException subclass doesn't have no-arg constructor
229 * @throws ClassCastException subclass doesn't extend AWTKeyStroke
231 protected static void registerSubclass(final Class subclass)
233 if (subclass == null)
234 throw new IllegalArgumentException();
235 if (subclass.equals(ctor == null ? AWTKeyStroke.class
236 : ctor.getDeclaringClass()))
238 if (subclass.equals(AWTKeyStroke.class))
247 ctor = (Constructor) AccessController.doPrivileged
248 (new PrivilegedExceptionAction()
251 throws NoSuchMethodException, InstantiationException,
252 IllegalAccessException, InvocationTargetException
254 Constructor c = subclass.getDeclaredConstructor(null);
255 c.setAccessible(true);
256 // Create a new instance, to make sure that we can, and
257 // to cause any ClassCastException.
258 AWTKeyStroke dummy = (AWTKeyStroke) c.newInstance(null);
263 catch (PrivilegedActionException e)
265 // e.getCause() will not ever be ClassCastException; that should
266 // escape on its own.
267 throw (RuntimeException)
268 new IllegalArgumentException().initCause(e.getCause());
275 * Returns a keystroke representing a typed character.
277 * @param keyChar the typed character
278 * @return the specified keystroke
280 public static AWTKeyStroke getAWTKeyStroke(char keyChar)
282 return getAWTKeyStroke(keyChar, KeyEvent.VK_UNDEFINED, 0, false);
286 * Returns a keystroke representing a typed character with the given
287 * modifiers. Note that keyChar is a <code>Character</code> instead of a
288 * <code>char</code> to avoid accidental ambiguity with
289 * <code>getAWTKeyStroke(int, int)</code>. The modifiers are the bitwise
290 * or of the masks found in {@link InputEvent}; the new style (*_DOWN_MASK)
291 * is preferred, but the old style will work.
293 * @param keyChar the typed character
294 * @param modifiers the modifiers, or 0
295 * @return the specified keystroke
296 * @throws IllegalArgumentException if keyChar is null
298 public static AWTKeyStroke getAWTKeyStroke(Character keyChar, int modifiers)
301 throw new IllegalArgumentException();
302 return getAWTKeyStroke(keyChar.charValue(), KeyEvent.VK_UNDEFINED,
303 extend(modifiers), false);
307 * Returns a keystroke representing a pressed or released key event, with
308 * the given modifiers. The "virtual key" should be one of the VK_*
309 * constants in {@link KeyEvent}. The modifiers are the bitwise or of the
310 * masks found in {@link InputEvent}; the new style (*_DOWN_MASK) is
311 * preferred, but the old style will work.
313 * @param keyCode the virtual key
314 * @param modifiers the modifiers, or 0
315 * @param release true if this is a key release instead of a key press
316 * @return the specified keystroke
318 public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers,
321 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
322 extend(modifiers), release);
326 * Returns a keystroke representing a pressed key event, with the given
327 * modifiers. The "virtual key" should be one of the VK_* constants in
328 * {@link KeyEvent}. The modifiers are the bitwise or of the masks found
329 * in {@link InputEvent}; the new style (*_DOWN_MASK) is preferred, but the
330 * old style will work.
332 * @param keyCode the virtual key
333 * @param modifiers the modifiers, or 0
334 * @return the specified keystroke
336 public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers)
338 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
339 extend(modifiers), false);
343 * Returns a keystroke representing what caused the key event.
345 * @param event the key event to convert
346 * @return the specified keystroke, or null if the event is invalid
347 * @throws NullPointerException if event is null
349 public static AWTKeyStroke getAWTKeyStrokeForEvent(KeyEvent event)
353 case KeyEvent.KEY_TYPED:
354 return getAWTKeyStroke(event.getKeyChar(), KeyEvent.VK_UNDEFINED,
355 extend(event.getModifiersEx()), false);
356 case KeyEvent.KEY_PRESSED:
357 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
358 extend(event.getModifiersEx()), false);
359 case KeyEvent.KEY_RELEASED:
360 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
361 extend(event.getModifiersEx()), true);
368 * Parses a string and returns the keystroke that it represents. The syntax
369 * for keystrokes is listed below, with tokens separated by an arbitrary
372 * keyStroke := <modifiers>* ( <typedID> | <codeID> )
373 * modifiers := ( shift | control | ctrl | meta | alt
374 * | button1 | button2 | button3 )
375 * typedID := typed <single Unicode character>
376 * codeID := ( pressed | released )? <name>
377 * name := <the KeyEvent field name less the leading "VK_">
380 * <p>Note that the grammar is rather weak, and not all valid keystrokes
381 * can be generated in this manner (for example, a typed space, or anything
382 * with the alt-graph modifier!). The output of AWTKeyStroke.toString()
383 * will not meet the grammar. If pressed or released is not specified,
384 * pressed is assumed. Examples:<br>
386 * "INSERT" => getAWTKeyStroke(KeyEvent.VK_INSERT, 0);<br>
387 * "control DELETE" =>
388 * getAWTKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);<br>
389 * "alt shift X" => getAWTKeyStroke(KeyEvent.VK_X,
390 * InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);<br>
391 * "alt shift released X" => getAWTKeyStroke(KeyEvent.VK_X,
392 * InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, true);<br>
393 * "typed a" => getAWTKeyStroke('a');
396 * @param s the string to parse
397 * @throws IllegalArgumentException if s is null or cannot be parsed
398 * @return the specified keystroke
400 public static AWTKeyStroke getAWTKeyStroke(String s)
403 throw new IllegalArgumentException("null argument");
404 StringTokenizer t = new StringTokenizer(s, " ");
405 if (! t.hasMoreTokens())
406 throw new IllegalArgumentException("no tokens '" + s + "'");
408 boolean released = false;
412 token = t.nextToken();
413 if ("shift".equals(token))
414 modifiers |= KeyEvent.SHIFT_DOWN_MASK;
415 else if ("ctrl".equals(token) || "control".equals(token))
416 modifiers |= KeyEvent.CTRL_DOWN_MASK;
417 else if ("meta".equals(token))
418 modifiers |= KeyEvent.META_DOWN_MASK;
419 else if ("alt".equals(token))
420 modifiers |= KeyEvent.ALT_DOWN_MASK;
421 else if ("button1".equals(token))
422 modifiers |= KeyEvent.BUTTON1_DOWN_MASK;
423 else if ("button2".equals(token))
424 modifiers |= KeyEvent.BUTTON2_DOWN_MASK;
425 else if ("button3".equals(token))
426 modifiers |= KeyEvent.BUTTON3_DOWN_MASK;
427 else if ("typed".equals(token))
429 if (t.hasMoreTokens())
431 token = t.nextToken();
432 if (! t.hasMoreTokens() && token.length() == 1)
433 return getAWTKeyStroke(token.charAt(0),
434 KeyEvent.VK_UNDEFINED, modifiers,
437 throw new IllegalArgumentException("Invalid 'typed' argument '"
440 else if ("pressed".equals(token))
442 if (t.hasMoreTokens())
443 token = t.nextToken();
446 else if ("released".equals(token))
449 if (t.hasMoreTokens())
450 token = t.nextToken();
456 while (t.hasMoreTokens());
457 // Now token contains the VK name we must parse.
458 Integer code = (Integer) vktable.get(token);
460 throw new IllegalArgumentException("Unknown token '" + token
461 + "' in '" + s + "'");
462 if (t.hasMoreTokens())
463 throw new IllegalArgumentException("Too many tokens: " + s);
464 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, code.intValue(),
465 modifiers, released);
469 * Returns the character of this keystroke, if it was typed.
471 * @return the character value, or CHAR_UNDEFINED
472 * @see #getAWTKeyStroke(char)
474 public final char getKeyChar()
480 * Returns the virtual key code of this keystroke, if it was pressed or
481 * released. This will be a VK_* constant from KeyEvent.
483 * @return the virtual key code value, or VK_UNDEFINED
484 * @see #getAWTKeyStroke(int, int)
486 public final int getKeyCode()
492 * Returns the modifiers for this keystroke. This will be a bitwise or of
493 * constants from InputEvent; it includes the old style masks for shift,
494 * control, alt, meta, and alt-graph (but not button1); as well as the new
495 * style of extended modifiers for all modifiers.
497 * @return the modifiers
498 * @see #getAWTKeyStroke(Character, int)
499 * @see #getAWTKeyStroke(int, int)
501 public final int getModifiers()
507 * Tests if this keystroke is a key release.
509 * @return true if this is a key release
510 * @see #getAWTKeyStroke(int, int, boolean)
512 public final boolean isOnKeyRelease()
518 * Returns the AWT event type of this keystroke. This is one of
519 * {@link KeyEvent#KEY_TYPED}, {@link KeyEvent#KEY_PRESSED}, or
520 * {@link KeyEvent#KEY_RELEASED}.
522 * @return the key event type
524 public final int getKeyEventType()
526 return keyCode == KeyEvent.VK_UNDEFINED ? KeyEvent.KEY_TYPED
527 : onKeyRelease ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED;
531 * Returns a hashcode for this key event. It is not documented, but appears
532 * to be: <code>(getKeyChar() + 1) * (getKeyCode() + 1)
533 * * (getModifiers() + 1) * 2 + (isOnKeyRelease() ? 1 : 2)</code>.
535 * @return the hashcode
537 public int hashCode()
539 return (keyChar + 1) * (keyCode + 1) * (modifiers + 1) * 2
540 + (onKeyRelease ? 1 : 2);
544 * Tests two keystrokes for equality.
546 * @param o the object to test
547 * @return true if it is equal
549 public final boolean equals(Object o)
551 if (! (o instanceof AWTKeyStroke))
553 AWTKeyStroke s = (AWTKeyStroke) o;
554 return this == o || (keyChar == s.keyChar && keyCode == s.keyCode
555 && modifiers == s.modifiers
556 && onKeyRelease == s.onKeyRelease);
560 * Returns a string representation of this keystroke. For typed keystrokes,
561 * this is <code>"keyChar " + KeyEvent.getKeyModifiersText(getModifiers())
562 + getKeyChar()</code>; for pressed and released keystrokes, this is
563 * <code>"keyCode " + KeyEvent.getKeyModifiersText(getModifiers())
564 * + KeyEvent.getKeyText(getKeyCode())
565 * + (isOnKeyRelease() ? "-R" : "-P")</code>.
567 * @return a string representation
569 public String toString()
571 if (keyCode == KeyEvent.VK_UNDEFINED)
572 return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + keyChar;
573 return "keyCode " + KeyEvent.getKeyModifiersText(modifiers)
574 + KeyEvent.getKeyText(keyCode) + (onKeyRelease ? "-R" : "-P");
578 * Returns a cached version of the deserialized keystroke, if available.
580 * @return a cached replacement
581 * @throws ObjectStreamException if something goes wrong
583 protected Object readResolve() throws ObjectStreamException
585 AWTKeyStroke s = (AWTKeyStroke) cache.get(this);
588 cache.put(this, this);
593 * Gets the appropriate keystroke, creating one if necessary.
595 * @param keyChar the keyChar
596 * @param keyCode the keyCode
597 * @param modifiers the modifiers
598 * @param release true for key release
599 * @return the specified keystroke
601 private static AWTKeyStroke getAWTKeyStroke(char keyChar, int keyCode,
602 int modifiers, boolean release)
604 // Check level 0 cache.
605 AWTKeyStroke stroke = recent; // Avoid thread races.
606 if (stroke != null && stroke.keyChar == keyChar
607 && stroke.keyCode == keyCode && stroke.modifiers == modifiers
608 && stroke.onKeyRelease == release)
610 // Create a new object, on the assumption that if it has a match in the
611 // cache, the VM can easily garbage collect it as it is temporary.
612 Constructor c = ctor; // Avoid thread races.
614 stroke = new AWTKeyStroke(keyChar, keyCode, modifiers, release);
618 stroke = (AWTKeyStroke) c.newInstance(null);
619 stroke.keyChar = keyChar;
620 stroke.keyCode = keyCode;
621 stroke.modifiers = modifiers;
622 stroke.onKeyRelease = release;
626 throw (Error) new InternalError().initCause(e);
628 // Check level 1 cache.
629 AWTKeyStroke cached = (AWTKeyStroke) cache.get(stroke);
631 cache.put(stroke, stroke);
634 return recent = stroke;
638 * Converts the modifiers to the appropriate format.
640 * @param mod the modifiers to convert
641 * @return the adjusted modifiers
643 private static int extend(int mod)
645 if ((mod & (KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK)) != 0)
646 mod |= KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK;
647 if ((mod & (KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK)) != 0)
648 mod |= KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK;
649 if ((mod & (KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK)) != 0)
650 mod |= KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK;
651 if ((mod & (KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK)) != 0)
652 mod |= KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK;
653 if ((mod & (KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK)) != 0)
654 mod |= KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK;
655 if ((mod & KeyEvent.BUTTON1_MASK) != 0)
656 mod |= KeyEvent.BUTTON1_DOWN_MASK;
657 return mod & MODIFIERS_MASK;
659 } // class AWTKeyStroke