United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4370733 : AWTKeyStroke's getAWTKeyStroke(String) and toString() method aren't symmetric

Details
Type:
Enhancement
Submit Date:
2000-09-13
Status:
Resolved
Updated Date:
2003-05-25
Project Name:
JDK
Resolved Date:
2003-05-25
Component:
client-libs
OS:
windows_nt,generic
Sub-Component:
java.awt
CPU:
x86,generic
Priority:
P4
Resolution:
Fixed
Affected Versions:
1.3.0,1.3.1,1.4.0
Fixed Versions:
5.0 (tiger)

Related Reports
Duplicate:
Duplicate:
Duplicate:

Sub Tasks

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
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
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
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
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
tiger

FIXED IN:
tiger

INTEGRATED IN:
tiger
tiger-b08


                                     
2004-08-24



Hardware and Software, Engineered to Work Together