JDK-4370733 : AWTKeyStroke's getAWTKeyStroke(String) and toString() method aren't symmetric
  • Type: Enhancement
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 1.3.0,1.3.1,1.4.0
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • OS: generic,windows_nt
  • CPU: generic,x86
  • Submitted: 2000-09-13
  • Updated: 2017-05-16
  • Resolved: 2003-05-25
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availabitlity Release.

To download the current JDK release, click here.
Other
5.0 tigerFixed
Related Reports
Duplicate :  
Duplicate :  
Duplicate :  
Description

Name: rmT116609			Date: 09/13/2000


java version "1.3.0"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.0-C)
Java HotSpot(TM) Client VM (build 1.3.0-C, mixed mode)

KeyStroke.getKeyStroke(String s) should accept the string returned by
KeyStroke.toString(). In particular, if there exists a <code>KeyStroke
a</code>, then <code>KeyStroke.getKeyStroke(a.toString)</code> should return.

Currently, KeyStroke.toString() does not return a string that matches the
documented grammar for KeyStroke.getKeyStroke(String). This makes the KeyStroke
class's toString method fairly useless.

In my particular project, I would like to be able to use the KeyStroke's
toString() method as a serialization mechanism in a properties file. Let's say
that I have an application that has user-defined accelerator keys (a.k.a.
hotkeys, shortcut keys). I have a Print bean in class org.brianlsmith.PrintBean
and I want to store the information for the PrintBean's shortcut key in a
property file (actually, a property-backed resource bundle). I need to have a
way to convert from a KeyStroke to a String and back, but there is no way to do
that short of writing my own parser for KeyStoke.toString()'s format (which is
unspecified) or by reimplementing KeyStroke.toString().

Since KeyStroke.toString()'s format is undocumented, there should be no
political problems in changing it to match KeyStroke.getKeyStroke(String)'s
documented grammer.

In the test program below, The output should be:
shift F1
shift F1
1st Copy equals original: true
shift F1
2nd Copy equals original: true

// BEGIN A.java
import java.awt.event.KeyEvent;
import javax.swing.KeyStroke;

public class A {
	public static void main(String [] args) {
		KeyStroke original = KeyStroke.getKeyStroke(KeyEvent.VK_F1,KeyEvent.SHIFT_MASK);
		System.out.println(original);

		KeyStroke firstCopy = KeyStroke.getKeyStroke(original.toString());
		System.out.println(firstCopy);
		System.out.println("1st Copy equals original: " + (original ==firstCopy));

		KeyStroke secondCopy = KeyStroke.getKeyStroke("shift F1");
		System.out.println(secondCopy);
		System.out.println("2nd Copy equals original: " + (original == secondCopy));
	}
}
// END A.java
(Review ID: 108792) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: tiger FIXED IN: tiger INTEGRATED IN: tiger tiger-b08
2004-08-24

WORK AROUND Name: rmT116609 Date: 09/13/2000 Overriding KeyStroke.getString() or providing a correctly-behaving KeyStroke- >String converter is the best way to work around this issue. Also look at using the numeric values for the key code and the shift mask (although this would require some very simple parsing to split the two numbers apart when they are written as a pair of strings). ======================================================================
2004-08-24

SUGGESTED FIX Name: osR10079 Date: 04/27/2003 ###@###.### 2003-04-28 ------- AWTKeyStroke.java ------- *** /tmp/sccs.zgaqyy Wed Apr 9 13:16:38 2003 --- AWTKeyStroke.java Wed Apr 9 13:04:01 2003 *************** *** 17,22 **** --- 17,24 ---- import java.security.PrivilegedAction; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; + import java.lang.reflect.Modifier; + import java.lang.reflect.Field; /** * An <code>AWTKeyStroke</code> represents a key action on the *************** *** 45,59 **** * @since 1.4 */ public class AWTKeyStroke implements Serializable { private static Map cache; private static AWTKeyStroke cacheKey; private static Constructor ctor = getCtor(AWTKeyStroke.class); private static Map modifierKeywords; /** ! * Maps from VK_XXX (as a String) to an Integer. This is done to ! * avoid the overhead of the reflective call to find the constant. */ ! private static Map vkMap; private char keyChar = KeyEvent.CHAR_UNDEFINED; private int keyCode = KeyEvent.VK_UNDEFINED; --- 47,64 ---- * @since 1.4 */ public class AWTKeyStroke implements Serializable { + static final long serialVersionUID = -6430539691155161871L; + private static Map cache; private static AWTKeyStroke cacheKey; private static Constructor ctor = getCtor(AWTKeyStroke.class); private static Map modifierKeywords; /** ! * Associates VK_XXX (as a String) with code (as Integer). This is ! * done to avoid the overhead of the reflective call to find the ! * constant. */ ! private static VKCollection vks; private char keyChar = KeyEvent.CHAR_UNDEFINED; private int keyCode = KeyEvent.VK_UNDEFINED; *************** *** 441,447 **** * <pre> * &lt;modifiers&gt;* (&lt;typedID&gt; | &lt;pressedReleasedID&gt;) * ! * modifiers := shift | control | ctrl | meta | alt | button1 | button2 | button3 * typedID := typed &lt;typedKey&gt; * typedKey := string of length 1 giving Unicode character. * pressedReleasedID := (pressed | released) key --- 446,452 ---- * <pre> * &lt;modifiers&gt;* (&lt;typedID&gt; | &lt;pressedReleasedID&gt;) * ! * modifiers := shift | control | ctrl | meta | alt | altGraph | button1 | button2 | button3 * typedID := typed &lt;typedKey&gt; * typedKey := string of length 1 giving Unicode character. * pressedReleasedID := (pressed | released) key *************** *** 559,564 **** --- 564,575 ---- throw new IllegalArgumentException(errmsg); } + private static VKCollection getVKCollection() { + if (vks == null) { + vks = new VKCollection(); + } + return vks; + } /** * Returns the integer constant for the KeyEvent.VK field named * <code>key</code>. This will throw an *************** *** 566,576 **** * not a valid constant. */ private static int getVKValue(String key) { ! if (vkMap == null) { ! vkMap = Collections.synchronizedMap(new HashMap()); ! } ! Integer value = (Integer)vkMap.get(key); if (value == null) { int keyCode = 0; --- 577,585 ---- * not a valid constant. */ private static int getVKValue(String key) { ! VKCollection vkCollect = getVKCollection(); ! Integer value = vkCollect.findCode(key); if (value == null) { int keyCode = 0; *************** *** 584,595 **** throw new IllegalArgumentException(errmsg); } value = new Integer(keyCode); ! vkMap.put(key, value); } return value.intValue(); } - /** * Returns the character for this <code>AWTKeyStroke</code>. * --- 593,603 ---- throw new IllegalArgumentException(errmsg); } value = new Integer(keyCode); ! vkCollect.put(key, value); } return value.intValue(); } /** * Returns the character for this <code>AWTKeyStroke</code>. * *************** *** 679,697 **** /** * Returns a string that displays and identifies this object's properties. * * @return a String representation of this object */ public String toString() { if (keyCode == KeyEvent.VK_UNDEFINED) { ! return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + ! keyChar; ! } else { ! return "keyCode " + KeyEvent.getKeyModifiersText(modifiers) + ! KeyEvent.getKeyText(keyCode) + (onKeyRelease ? "-R" : "-P"); ! } } /** * Returns a cached instance of <code>AWTKeyStroke</code> (or a subclass of * <code>AWTKeyStroke</code>) which is equal to this instance. --- 687,769 ---- /** * Returns a string that displays and identifies this object's properties. + * The <code>String</code> returned by this method can be passed + * as a parameter to <code>getAWTKeyStroke(String)</code> to produce + * a key stroke equal to this key stroke. * * @return a String representation of this object + * @see #getAWTKeyStroke(String) */ public String toString() { if (keyCode == KeyEvent.VK_UNDEFINED) { ! return getModifiersText(modifiers) + "typed " + keyChar; ! } else { ! return getModifiersText(modifiers) + ! (onKeyRelease ? "released" : "pressed") + " " + ! getVKText(keyCode); ! } } + static String getModifiersText(int modifiers) { + StringBuffer buf = new StringBuffer(); + + if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0 ) { + buf.append("shift "); + } + if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0 ) { + buf.append("ctrl "); + } + if ((modifiers & InputEvent.META_DOWN_MASK) != 0 ) { + buf.append("meta "); + } + if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0 ) { + buf.append("alt "); + } + if ((modifiers & InputEvent.ALT_GRAPH_DOWN_MASK) != 0 ) { + buf.append("altGraph "); + } + if ((modifiers & InputEvent.BUTTON1_DOWN_MASK) != 0 ) { + buf.append("button1 "); + } + if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0 ) { + buf.append("button2 "); + } + if ((modifiers & InputEvent.BUTTON3_DOWN_MASK) != 0 ) { + buf.append("button3 "); + } + + return buf.toString(); + } + + static String getVKText(int keyCode) { + VKCollection vkCollect = getVKCollection(); + Integer key = new Integer(keyCode); + String name = vkCollect.findName(key); + if (name != null) { + return name.substring(3); + } + int expected_modifiers = + (Modifier.PUBLIC | Modifier.STATIC | Modifier.FINAL); + + Field[] fields = KeyEvent.class.getDeclaredFields(); + for (int i = 0; i < fields.length; i++) { + try { + if (fields[i].getModifiers() == expected_modifiers + && fields[i].getType() == Integer.TYPE + && fields[i].getName().startsWith("VK_") + && fields[i].getInt(KeyEvent.class) == keyCode) + { + name = fields[i].getName(); + vkCollect.put(name, key); + return name.substring(3); + } + } catch (IllegalAccessException e) { + assert(false); + } + } + return "UNKNOWN"; + } + /** * Returns a cached instance of <code>AWTKeyStroke</code> (or a subclass of * <code>AWTKeyStroke</code>) which is equal to this instance. *************** *** 762,764 **** --- 834,863 ---- } + class VKCollection { + Map code2name; + Map name2code; + + public VKCollection() { + code2name = new HashMap(); + name2code = new HashMap(); + } + + public synchronized void put(String name, Integer code) { + assert((name != null) && (code != null)); + assert(findName(code) == null); + assert(findCode(name) == null); + code2name.put(code, name); + name2code.put(name, code); + } + + public synchronized Integer findCode(String name) { + assert(name != null); + return (Integer)name2code.get(name); + } + + public synchronized String findName(Integer code) { + assert(code != null); + return (String)code2name.get(code); + } + } ------- KeyStroke.java ------- *** /tmp/d3Ba4RY Mon Mar 17 14:42:30 2003 --- KeyStroke.java Thu Feb 27 13:07:07 2003 *************** *** 217,223 **** * <pre> * &lt;modifiers&gt;* (&lt;typedID&gt; | &lt;pressedReleasedID&gt;) * ! * modifiers := shift | control | ctrl | meta | alt | button1 | button2 | button3 * typedID := typed &lt;typedKey&gt; * typedKey := string of length 1 giving Unicode character. * pressedReleasedID := (pressed | released) key --- 217,223 ---- * <pre> * &lt;modifiers&gt;* (&lt;typedID&gt; | &lt;pressedReleasedID&gt;) * ! * modifiers := shift | control | ctrl | meta | alt | altGraph | button1 | button2 | button3 * typedID := typed &lt;typedKey&gt; * typedKey := string of length 1 giving Unicode character. * pressedReleasedID := (pressed | released) key ======================================================================
2004-08-24

EVALUATION KeyStroke is currently a swing class, so I'm assigning this rfe to them for evaluation. eric.hawkes@eng 2000-09-13 This is a reasonable suggestion, and we should consider it for a future release. Any change should apply to both java.awt.AWTKeyStroke and javax.swing.KeyStroke. david.mendenhall@east 2001-07-13
2001-07-13