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., 51 Franklin Street, Fifth Floor, 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.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;
55 import java.util.StringTokenizer;
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
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).
67 * @author Eric Blake (ebb9@email.byu.edu)
68 * @see #getAWTKeyStroke(char)
70 * @status updated to 1.4
72 public class AWTKeyStroke implements Serializable
75 * Compatible with JDK 1.4+.
77 private static final long serialVersionUID = -6430539691155161871L;
79 /** The mask for modifiers. */
80 private static final int MODIFIERS_MASK = 0x3fef;
83 * The cache of recently created keystrokes. This maps KeyStrokes to
84 * KeyStrokes in a cache which removes the least recently accessed entry,
85 * under the assumption that garbage collection of a new keystroke is
86 * easy when we find the old one that it matches in the cache.
88 private static final LinkedHashMap cache = new LinkedHashMap(11, 0.75f, true)
90 /** The largest the keystroke cache can grow. */
91 private static final int MAX_CACHE_SIZE = 2048;
93 /** Prune stale entries. */
94 protected boolean removeEldestEntry(Map.Entry eldest)
95 { // XXX - FIXME Use Map.Entry, not just Entry as gcj 3.1 workaround.
96 return size() > MAX_CACHE_SIZE;
100 /** The most recently generated keystroke, or null. */
101 private static AWTKeyStroke recent;
104 * The no-arg constructor of a subclass, or null to use AWTKeyStroke. Note
105 * that this will be left accessible, to get around private access; but
106 * it should not be a security risk as it is highly unlikely that creating
107 * protected instances of the subclass via reflection will do much damage.
109 private static Constructor ctor;
112 * A table of keyCode names to values. This is package-private to
113 * avoid an accessor method.
115 * @see #getAWTKeyStroke(String)
117 static final HashMap vktable = new HashMap();
120 // Using reflection saves the hassle of keeping this in sync with KeyEvent,
121 // at the price of an expensive initialization.
122 AccessController.doPrivileged(new PrivilegedAction()
126 Field[] fields = KeyEvent.class.getFields();
127 int i = fields.length;
133 String name = f.getName();
134 if (name.startsWith("VK_"))
135 vktable.put(name.substring(3), f.get(null));
140 throw (Error) new InternalError().initCause(e);
148 * The typed character, or CHAR_UNDEFINED for key presses and releases.
150 * @serial the keyChar
152 private char keyChar;
155 * The virtual key code, or VK_UNDEFINED for key typed. Package visible for
158 * @serial the keyCode
163 * The modifiers in effect. To match Sun, this stores the old style masks
164 * for shift, control, alt, meta, and alt-graph (but not button1); as well
165 * as the new style of extended modifiers for all modifiers.
167 * @serial bitwise or of the *_DOWN_MASK modifiers
169 private int modifiers;
172 * True if this is a key release; should only be true if keyChar is
175 * @serial true to distinguish key pressed from key released
177 private boolean onKeyRelease;
180 * Construct a keystroke with default values: it will be interpreted as a
181 * key typed event with an invalid character and no modifiers. Client code
182 * should use the factory methods instead.
184 * @see #getAWTKeyStroke(char)
185 * @see #getAWTKeyStroke(Character, int)
186 * @see #getAWTKeyStroke(int, int, boolean)
187 * @see #getAWTKeyStroke(int, int)
188 * @see #getAWTKeyStrokeForEvent(KeyEvent)
189 * @see #getAWTKeyStroke(String)
191 protected AWTKeyStroke()
193 keyChar = KeyEvent.CHAR_UNDEFINED;
197 * Construct a keystroke with the given values. Client code should use the
198 * factory methods instead.
200 * @param keyChar the character entered, if this is a key typed
201 * @param keyCode the key pressed or released, or VK_UNDEFINED for key typed
202 * @param modifiers the modifier keys for the keystroke, in old or new style
203 * @param onKeyRelease true if this is a key release instead of a press
204 * @see #getAWTKeyStroke(char)
205 * @see #getAWTKeyStroke(Character, int)
206 * @see #getAWTKeyStroke(int, int, boolean)
207 * @see #getAWTKeyStroke(int, int)
208 * @see #getAWTKeyStrokeForEvent(KeyEvent)
209 * @see #getAWTKeyStroke(String)
211 protected AWTKeyStroke(char keyChar, int keyCode, int modifiers,
212 boolean onKeyRelease)
214 this.keyChar = keyChar;
215 this.keyCode = keyCode;
216 // No need to call extend(), as only trusted code calls this constructor.
217 this.modifiers = modifiers;
218 this.onKeyRelease = onKeyRelease;
222 * Registers a new subclass as being the type of keystrokes to generate in
223 * the factory methods. This operation flushes the cache of stored keystrokes
224 * if the class differs from the current one. The new class must be
225 * AWTKeyStroke or a subclass, and must have a no-arg constructor (which may
228 * @param subclass the new runtime type of generated keystrokes
229 * @throws IllegalArgumentException subclass doesn't have no-arg constructor
230 * @throws ClassCastException subclass doesn't extend AWTKeyStroke
232 protected static void registerSubclass(final Class subclass)
234 if (subclass == null)
235 throw new IllegalArgumentException();
236 if (subclass.equals(ctor == null ? AWTKeyStroke.class
237 : ctor.getDeclaringClass()))
239 if (subclass.equals(AWTKeyStroke.class))
248 ctor = (Constructor) AccessController.doPrivileged
249 (new PrivilegedExceptionAction()
252 throws NoSuchMethodException, InstantiationException,
253 IllegalAccessException, InvocationTargetException
255 Constructor c = subclass.getDeclaredConstructor(null);
256 c.setAccessible(true);
257 // Create a new instance, to make sure that we can, and
258 // to cause any ClassCastException.
259 AWTKeyStroke dummy = (AWTKeyStroke) c.newInstance(null);
264 catch (PrivilegedActionException e)
266 // e.getCause() will not ever be ClassCastException; that should
267 // escape on its own.
268 throw (RuntimeException)
269 new IllegalArgumentException().initCause(e.getCause());
276 * Returns a keystroke representing a typed character.
278 * @param keyChar the typed character
279 * @return the specified keystroke
281 public static AWTKeyStroke getAWTKeyStroke(char keyChar)
283 return getAWTKeyStroke(keyChar, KeyEvent.VK_UNDEFINED, 0, false);
287 * Returns a keystroke representing a typed character with the given
288 * modifiers. Note that keyChar is a <code>Character</code> instead of a
289 * <code>char</code> to avoid accidental ambiguity with
290 * <code>getAWTKeyStroke(int, int)</code>. The modifiers are the bitwise
291 * or of the masks found in {@link InputEvent}; the new style (*_DOWN_MASK)
292 * is preferred, but the old style will work.
294 * @param keyChar the typed character
295 * @param modifiers the modifiers, or 0
296 * @return the specified keystroke
297 * @throws IllegalArgumentException if keyChar is null
299 public static AWTKeyStroke getAWTKeyStroke(Character keyChar, int modifiers)
302 throw new IllegalArgumentException();
303 return getAWTKeyStroke(keyChar.charValue(), KeyEvent.VK_UNDEFINED,
304 extend(modifiers), false);
308 * Returns a keystroke representing a pressed or released key event, with
309 * the given modifiers. The "virtual key" should be one of the VK_*
310 * constants in {@link KeyEvent}. The modifiers are the bitwise or of the
311 * masks found in {@link InputEvent}; the new style (*_DOWN_MASK) is
312 * preferred, but the old style will work.
314 * @param keyCode the virtual key
315 * @param modifiers the modifiers, or 0
316 * @param release true if this is a key release instead of a key press
317 * @return the specified keystroke
319 public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers,
322 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
323 extend(modifiers), release);
327 * Returns a keystroke representing a pressed key event, with the given
328 * modifiers. The "virtual key" should be one of the VK_* constants in
329 * {@link KeyEvent}. The modifiers are the bitwise or of the masks found
330 * in {@link InputEvent}; the new style (*_DOWN_MASK) is preferred, but the
331 * old style will work.
333 * @param keyCode the virtual key
334 * @param modifiers the modifiers, or 0
335 * @return the specified keystroke
337 public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers)
339 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
340 extend(modifiers), false);
344 * Returns a keystroke representing what caused the key event.
346 * @param event the key event to convert
347 * @return the specified keystroke, or null if the event is invalid
348 * @throws NullPointerException if event is null
350 public static AWTKeyStroke getAWTKeyStrokeForEvent(KeyEvent event)
354 case KeyEvent.KEY_TYPED:
355 return getAWTKeyStroke(event.getKeyChar(), KeyEvent.VK_UNDEFINED,
356 extend(event.getModifiersEx()), false);
357 case KeyEvent.KEY_PRESSED:
358 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
359 extend(event.getModifiersEx()), false);
360 case KeyEvent.KEY_RELEASED:
361 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
362 extend(event.getModifiersEx()), true);
369 * Parses a string and returns the keystroke that it represents. The syntax
370 * for keystrokes is listed below, with tokens separated by an arbitrary
373 * keyStroke := <modifiers>* ( <typedID> | <codeID> )
374 * modifiers := ( shift | control | ctrl | meta | alt
375 * | button1 | button2 | button3 )
376 * typedID := typed <single Unicode character>
377 * codeID := ( pressed | released )? <name>
378 * name := <the KeyEvent field name less the leading "VK_">
381 * <p>Note that the grammar is rather weak, and not all valid keystrokes
382 * can be generated in this manner (for example, a typed space, or anything
383 * with the alt-graph modifier!). The output of AWTKeyStroke.toString()
384 * will not meet the grammar. If pressed or released is not specified,
385 * pressed is assumed. Examples:<br>
387 * "INSERT" => getAWTKeyStroke(KeyEvent.VK_INSERT, 0);<br>
388 * "control DELETE" =>
389 * getAWTKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);<br>
390 * "alt shift X" => getAWTKeyStroke(KeyEvent.VK_X,
391 * InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);<br>
392 * "alt shift released X" => getAWTKeyStroke(KeyEvent.VK_X,
393 * InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, true);<br>
394 * "typed a" => getAWTKeyStroke('a');
397 * @param s the string to parse
398 * @throws IllegalArgumentException if s is null or cannot be parsed
399 * @return the specified keystroke
401 public static AWTKeyStroke getAWTKeyStroke(String s)
404 throw new IllegalArgumentException("null argument");
405 StringTokenizer t = new StringTokenizer(s, " ");
406 if (! t.hasMoreTokens())
407 throw new IllegalArgumentException("no tokens '" + s + "'");
409 boolean released = false;
413 token = t.nextToken();
414 if ("shift".equals(token))
416 modifiers |= KeyEvent.SHIFT_MASK;
417 modifiers |= KeyEvent.SHIFT_DOWN_MASK;
419 else if ("ctrl".equals(token) || "control".equals(token))
421 modifiers |= KeyEvent.CTRL_MASK;
422 modifiers |= KeyEvent.CTRL_DOWN_MASK;
424 else if ("meta".equals(token))
426 modifiers |= KeyEvent.META_MASK;
427 modifiers |= KeyEvent.META_DOWN_MASK;
429 else if ("alt".equals(token))
431 modifiers |= KeyEvent.ALT_MASK;
432 modifiers |= KeyEvent.ALT_DOWN_MASK;
434 else if ("button1".equals(token))
435 modifiers |= KeyEvent.BUTTON1_DOWN_MASK;
436 else if ("button2".equals(token))
437 modifiers |= KeyEvent.BUTTON2_DOWN_MASK;
438 else if ("button3".equals(token))
439 modifiers |= KeyEvent.BUTTON3_DOWN_MASK;
440 else if ("typed".equals(token))
442 if (t.hasMoreTokens())
444 token = t.nextToken();
445 if (! t.hasMoreTokens() && token.length() == 1)
446 return getAWTKeyStroke(token.charAt(0),
447 KeyEvent.VK_UNDEFINED, modifiers,
450 throw new IllegalArgumentException("Invalid 'typed' argument '"
453 else if ("pressed".equals(token))
455 if (t.hasMoreTokens())
456 token = t.nextToken();
459 else if ("released".equals(token))
462 if (t.hasMoreTokens())
463 token = t.nextToken();
469 while (t.hasMoreTokens());
470 // Now token contains the VK name we must parse.
471 Integer code = (Integer) vktable.get(token);
473 throw new IllegalArgumentException("Unknown token '" + token
474 + "' in '" + s + "'");
475 if (t.hasMoreTokens())
476 throw new IllegalArgumentException("Too many tokens: " + s);
477 return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, code.intValue(),
478 modifiers, released);
482 * Returns the character of this keystroke, if it was typed.
484 * @return the character value, or CHAR_UNDEFINED
485 * @see #getAWTKeyStroke(char)
487 public final char getKeyChar()
493 * Returns the virtual key code of this keystroke, if it was pressed or
494 * released. This will be a VK_* constant from KeyEvent.
496 * @return the virtual key code value, or VK_UNDEFINED
497 * @see #getAWTKeyStroke(int, int)
499 public final int getKeyCode()
505 * Returns the modifiers for this keystroke. This will be a bitwise or of
506 * constants from InputEvent; it includes the old style masks for shift,
507 * control, alt, meta, and alt-graph (but not button1); as well as the new
508 * style of extended modifiers for all modifiers.
510 * @return the modifiers
511 * @see #getAWTKeyStroke(Character, int)
512 * @see #getAWTKeyStroke(int, int)
514 public final int getModifiers()
520 * Tests if this keystroke is a key release.
522 * @return true if this is a key release
523 * @see #getAWTKeyStroke(int, int, boolean)
525 public final boolean isOnKeyRelease()
531 * Returns the AWT event type of this keystroke. This is one of
532 * {@link KeyEvent#KEY_TYPED}, {@link KeyEvent#KEY_PRESSED}, or
533 * {@link KeyEvent#KEY_RELEASED}.
535 * @return the key event type
537 public final int getKeyEventType()
539 return keyCode == KeyEvent.VK_UNDEFINED ? KeyEvent.KEY_TYPED
540 : onKeyRelease ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED;
544 * Returns a hashcode for this key event. It is not documented, but appears
545 * to be: <code>(getKeyChar() + 1) * (getKeyCode() + 1)
546 * * (getModifiers() + 1) * 2 + (isOnKeyRelease() ? 1 : 2)</code>.
548 * @return the hashcode
550 public int hashCode()
552 return (keyChar + 1) * (keyCode + 1) * (modifiers + 1) * 2
553 + (onKeyRelease ? 1 : 2);
557 * Tests two keystrokes for equality.
559 * @param o the object to test
560 * @return true if it is equal
562 public final boolean equals(Object o)
564 if (! (o instanceof AWTKeyStroke))
566 AWTKeyStroke s = (AWTKeyStroke) o;
567 return this == o || (keyChar == s.keyChar && keyCode == s.keyCode
568 && modifiers == s.modifiers
569 && onKeyRelease == s.onKeyRelease);
573 * Returns a string representation of this keystroke. For typed keystrokes,
574 * this is <code>"keyChar " + KeyEvent.getKeyModifiersText(getModifiers())
575 + getKeyChar()</code>; for pressed and released keystrokes, this is
576 * <code>"keyCode " + KeyEvent.getKeyModifiersText(getModifiers())
577 * + KeyEvent.getKeyText(getKeyCode())
578 * + (isOnKeyRelease() ? "-R" : "-P")</code>.
580 * @return a string representation
582 public String toString()
584 if (keyCode == KeyEvent.VK_UNDEFINED)
585 return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + keyChar;
586 return "keyCode " + KeyEvent.getKeyModifiersText(modifiers)
587 + KeyEvent.getKeyText(keyCode) + (onKeyRelease ? "-R" : "-P");
591 * Returns a cached version of the deserialized keystroke, if available.
593 * @return a cached replacement
594 * @throws ObjectStreamException if something goes wrong
596 protected Object readResolve() throws ObjectStreamException
598 AWTKeyStroke s = (AWTKeyStroke) cache.get(this);
601 cache.put(this, this);
606 * Gets the appropriate keystroke, creating one if necessary.
608 * @param keyChar the keyChar
609 * @param keyCode the keyCode
610 * @param modifiers the modifiers
611 * @param release true for key release
612 * @return the specified keystroke
614 private static AWTKeyStroke getAWTKeyStroke(char keyChar, int keyCode,
615 int modifiers, boolean release)
617 // Check level 0 cache.
618 AWTKeyStroke stroke = recent; // Avoid thread races.
619 if (stroke != null && stroke.keyChar == keyChar
620 && stroke.keyCode == keyCode && stroke.modifiers == modifiers
621 && stroke.onKeyRelease == release)
623 // Create a new object, on the assumption that if it has a match in the
624 // cache, the VM can easily garbage collect it as it is temporary.
625 Constructor c = ctor; // Avoid thread races.
627 stroke = new AWTKeyStroke(keyChar, keyCode, modifiers, release);
631 stroke = (AWTKeyStroke) c.newInstance(null);
632 stroke.keyChar = keyChar;
633 stroke.keyCode = keyCode;
634 stroke.modifiers = modifiers;
635 stroke.onKeyRelease = release;
639 throw (Error) new InternalError().initCause(e);
641 // Check level 1 cache.
642 AWTKeyStroke cached = (AWTKeyStroke) cache.get(stroke);
644 cache.put(stroke, stroke);
647 return recent = stroke;
651 * Converts the modifiers to the appropriate format.
653 * @param mod the modifiers to convert
654 * @return the adjusted modifiers
656 private static int extend(int mod)
658 if ((mod & (KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK)) != 0)
659 mod |= KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK;
660 if ((mod & (KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK)) != 0)
661 mod |= KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK;
662 if ((mod & (KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK)) != 0)
663 mod |= KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK;
664 if ((mod & (KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK)) != 0)
665 mod |= KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK;
666 if ((mod & (KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK)) != 0)
667 mod |= KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK;
668 if ((mod & KeyEvent.BUTTON1_MASK) != 0)
669 mod |= KeyEvent.BUTTON1_DOWN_MASK;
670 return mod & MODIFIERS_MASK;
672 } // class AWTKeyStroke