001: package abbot.tester;
002:
003: import java.lang.reflect.*;
004: import java.awt.event.*;
005: import java.util.*;
006: import java.io.*;
007:
008: import javax.swing.KeyStroke;
009:
010: import abbot.Log;
011: import abbot.Platform;
012:
013: /** Provides read of local-specific mappings for virtual keycode-based
014: KeyStrokes to characters and vice versa.
015: The map format is a properties file with each line containing an entry of
016: the form<br>
017: <code>VKNAME.MOD=VALUE</code><br>
018: The VKNAME is the String suffix of the KeyEvent VK_ keycode. MOD is the
019: integer value of the current modifier mask (assumes only a single modifier
020: has any effect on key output, interesting values are considered to be 0,
021: 1, 2, 8). VALUE is the char value of the KEY_TYPED keyChar
022: corresponding to the VK_ keycode and modifiers, as an integer value.
023: */
024: public class KeyStrokeMap implements KeyStrokeMapProvider {
025:
026: /** Map of Characters to virtual keycode-based KeyStrokes. */
027: private static Map keycodes = getKeyStrokeMap();
028: /** Map of keycode-based KeyStrokes to Characters. */
029: private static Map chars = getCharacterMap();
030:
031: /** Return the keycode-based KeyStroke corresponding to the given
032: * character, as best we can guess it, or null if we don't know how to
033: * generate it.
034: */
035: public static KeyStroke getKeyStroke(char ch) {
036: return (KeyStroke) keycodes.get(new Character(ch));
037: }
038:
039: /** Given a keycode-based KeyStroke, return the equivalent character.
040: * Defined properly for US keyboards only. Please contribute your own.
041: * @return KeyEvent.VK_UNDEFINED if the result is unknown.
042: */
043: public static char getChar(KeyStroke ks) {
044: Character ch = (Character) chars.get(ks);
045: if (ch == null) {
046: // Try again, but strip all modifiers but shift
047: int mask = ks.getModifiers() & ~KeyEvent.SHIFT_MASK;
048: ks = KeyStroke.getKeyStroke(ks.getKeyCode(), mask);
049: ch = (Character) chars.get(ks);
050: if (ch == null)
051: return KeyEvent.CHAR_UNDEFINED;
052: }
053: return ch.charValue();
054: }
055:
056: private static KeyStrokeMapProvider generator = null;
057:
058: /** If available, provide a dedicated class to provide mappings between
059: * keystrokes and generated characters.
060: */
061: private static KeyStrokeMapProvider getGenerator() {
062: if (generator == null) {
063: try {
064: String gname = System.getProperty(
065: "abbot.keystroke_map_generator",
066: "abbot.tester.KeyStrokeMap");
067: if (gname != null) {
068: generator = (KeyStrokeMapProvider) Class.forName(
069: gname).newInstance();
070: }
071: } catch (Exception e) {
072: Log.warn(e);
073: }
074: }
075: return generator;
076: }
077:
078: private static Map getCharacterMap() {
079: KeyStrokeMapProvider generator = getGenerator();
080: Map m = generator != null ? generator.loadCharacterMap() : null;
081: return m != null ? m : generateCharacterMappings();
082: }
083:
084: /** Generate a map from characters to virtual keycode-based KeyStrokes. */
085: private static Map generateCharacterMappings() {
086: Log.debug("Generating default character mappings");
087: Map map = new HashMap();
088: Iterator iter = keycodes.keySet().iterator();
089: while (iter.hasNext()) {
090: Object key = iter.next();
091: map.put(keycodes.get(key), key);
092: }
093: return map;
094: }
095:
096: private static Map getKeyStrokeMap() {
097: KeyStrokeMapProvider generator = getGenerator();
098: Map m = generator != null ? generator.loadKeyStrokeMap() : null;
099: return m != null ? m : generateKeyStrokeMappings();
100: }
101:
102: /**
103: * Generate the mapping between characters and key codes. This is
104: * invoked exactly once per VM invocation.
105: * We don't have complete coverage, so if you use this fallback map in AWT
106: * mode some events may be missing that would otherwise be generated in
107: * robot mode.
108: */
109: private static Map generateKeyStrokeMappings() {
110: Log.debug("Generating default keystroke mappings");
111: // character, keycode, modifiers
112: int shift = InputEvent.SHIFT_MASK;
113: //int alt = InputEvent.ALT_MASK;
114: //int altg = InputEvent.ALT_GRAPH_MASK;
115: int ctrl = InputEvent.CTRL_MASK;
116: //int meta = InputEvent.META_MASK;
117: // These are assumed to be standard across all keyboards (?)
118: int[][] universalMappings = {
119: { '', KeyEvent.VK_ESCAPE, 0 }, // No escape sequence exists
120: { '\b', KeyEvent.VK_BACK_SPACE, 0 },
121: { '', KeyEvent.VK_DELETE, 0 }, // None for this one either
122: { '\n', KeyEvent.VK_ENTER, 0 },
123: { '\r', KeyEvent.VK_ENTER, 0 }, };
124: // Add to these as needed; note that this is based on a US keyboard
125: // mapping, and will likely fail for others.
126: int[][] mappings = {
127: { ' ', KeyEvent.VK_SPACE, 0, },
128: { '\t', KeyEvent.VK_TAB, 0, },
129: { '~', KeyEvent.VK_BACK_QUOTE, shift, },
130: { '`', KeyEvent.VK_BACK_QUOTE, 0, },
131: { '!', KeyEvent.VK_1, shift, },
132: { '@', KeyEvent.VK_2, shift, },
133: { '#', KeyEvent.VK_3, shift, },
134: { '$', KeyEvent.VK_4, shift, },
135: { '%', KeyEvent.VK_5, shift, },
136: { '^', KeyEvent.VK_6, shift, },
137: { '&', KeyEvent.VK_7, shift, },
138: { '*', KeyEvent.VK_8, shift, },
139: { '(', KeyEvent.VK_9, shift, },
140: { ')', KeyEvent.VK_0, shift, },
141: { '-', KeyEvent.VK_MINUS, 0, },
142: { '_', KeyEvent.VK_MINUS, shift, },
143: { '=', KeyEvent.VK_EQUALS, 0, },
144: { '+', KeyEvent.VK_EQUALS, shift, },
145: { '[', KeyEvent.VK_OPEN_BRACKET, 0, },
146: { '{', KeyEvent.VK_OPEN_BRACKET, shift, },
147: // NOTE: The following does NOT produce a left brace
148: //{ '{', KeyEvent.VK_BRACELEFT, 0, },
149: { ']', KeyEvent.VK_CLOSE_BRACKET, 0, },
150: { '}', KeyEvent.VK_CLOSE_BRACKET, shift, },
151: { '|', KeyEvent.VK_BACK_SLASH, shift, },
152: { ';', KeyEvent.VK_SEMICOLON, 0, },
153: { ':', KeyEvent.VK_SEMICOLON, shift, },
154: { ',', KeyEvent.VK_COMMA, 0, },
155: { '<', KeyEvent.VK_COMMA, shift, },
156: { '.', KeyEvent.VK_PERIOD, 0, },
157: { '>', KeyEvent.VK_PERIOD, shift, },
158: { '/', KeyEvent.VK_SLASH, 0, },
159: { '?', KeyEvent.VK_SLASH, shift, },
160: { '\\', KeyEvent.VK_BACK_SLASH, 0, },
161: { '|', KeyEvent.VK_BACK_SLASH, shift, },
162: { '\'', KeyEvent.VK_QUOTE, 0, },
163: { '"', KeyEvent.VK_QUOTE, shift, }, };
164: HashMap map = new HashMap();
165: // Universal mappings
166: for (int i = 0; i < universalMappings.length; i++) {
167: int[] entry = universalMappings[i];
168: KeyStroke stroke = KeyStroke.getKeyStroke(entry[1],
169: entry[2]);
170: map.put(new Character((char) entry[0]), stroke);
171: }
172:
173: // If the locale is not en_US/GB, provide only a very basic map and
174: // rely on key_typed events instead
175: Locale locale = Locale.getDefault();
176: if (!Locale.US.equals(locale) && !Locale.UK.equals(locale)) {
177: Log.debug("Not US: " + locale);
178: return map;
179: }
180:
181: // Basic symbol/punctuation mappings
182: for (int i = 0; i < mappings.length; i++) {
183: int[] entry = mappings[i];
184: KeyStroke stroke = KeyStroke.getKeyStroke(entry[1],
185: entry[2]);
186: map.put(new Character((char) entry[0]), stroke);
187: }
188: // Lowercase
189: for (int i = 'a'; i <= 'z'; i++) {
190: KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_A + i
191: - 'a', 0);
192: map.put(new Character((char) i), stroke);
193: // control characters
194: stroke = KeyStroke.getKeyStroke(KeyEvent.VK_A + i - 'a',
195: ctrl);
196: Character key = new Character((char) (i - 'a' + 1));
197: // Make sure we don't overwrite something already there
198: if (map.get(key) == null) {
199: map.put(key, stroke);
200: }
201: }
202: // Capitals
203: for (int i = 'A'; i <= 'Z'; i++) {
204: KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_A + i
205: - 'A', shift);
206: map.put(new Character((char) i), stroke);
207: }
208: // digits
209: for (int i = '0'; i <= '9'; i++) {
210: KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_0 + i
211: - '0', 0);
212: map.put(new Character((char) i), stroke);
213: }
214: return map;
215: }
216:
217: private static Map characterMap = null;
218: private static Map keyStrokeMap = null;
219: private static boolean loaded = false;
220:
221: private static InputStream findMap() {
222: String[] names = getMapNames();
223: for (int i = 0; i < names.length; i++) {
224: Log.debug("Trying " + names[i]);
225: String name = getFilename(names[i]);
226: InputStream is = KeyStrokeMapProvider.class
227: .getResourceAsStream("keymaps/" + name);
228: if (is != null)
229: return is;
230: }
231: return KeyStrokeMapProvider.class
232: .getResourceAsStream("keymaps/default.map");
233: }
234:
235: private synchronized void loadMaps() {
236: if (loaded)
237: return;
238: Properties props = new Properties();
239: Map cmap = null;
240: Map kmap = null;
241: try {
242: InputStream is = findMap();
243: if (is == null) {
244: Log.debug("No appropriate map file found");
245: loaded = true;
246: return;
247: }
248: props.load(is);
249: Iterator iter = props.keySet().iterator();
250: cmap = new HashMap();
251: kmap = new HashMap();
252: while (iter.hasNext()) {
253: String key = (String) iter.next();
254: Log.debug("Property " + key + "="
255: + props.getProperty(key));
256: try {
257: String codeName = key
258: .substring(0, key.indexOf("."));
259: int mask = Integer.parseInt(key.substring(key
260: .indexOf(".") + 1), 16);
261: int value = Integer.parseInt(
262: props.getProperty(key), 16);
263: Character ch = new Character((char) value);
264: Field field = KeyEvent.class.getField("VK_"
265: + codeName);
266: int code = field.getInt(null);
267: KeyStroke ks = KeyStroke.getKeyStroke(code, mask);
268: // May be more than one KeyStroke mapping to a given key
269: // character; prefer no mask or shift mask over any other
270: // masks.
271: KeyStroke existing = (KeyStroke) kmap.get(ch);
272: if (existing == null
273: || ((existing.getModifiers() != 0 && existing
274: .getModifiers() != KeyEvent.SHIFT_MASK) || (mask == 0 && (existing
275: .getModifiers() != 0 || ks
276: .toString().length() < existing
277: .toString().length())))) {
278: Log.debug("Installing " + ks + " for '" + ch
279: + "'");
280: kmap.put(ch, ks);
281: }
282: cmap.put(ks, ch);
283: } catch (NumberFormatException e) {
284: // ignore invalid entries
285: } catch (Exception e) {
286: Log.warn(e);
287: }
288: }
289: } catch (IOException io) {
290: }
291: Log.debug("Successfully loaded character/keystroke map");
292: characterMap = cmap;
293: keyStrokeMap = kmap;
294: loaded = true;
295: }
296:
297: /** Load a map for the current locale to translate a character into a
298: corresponding virtual keycode-based KeyStroke. */
299: public Map loadCharacterMap() {
300: loadMaps();
301: return characterMap;
302: }
303:
304: /** Load a map for the current locale to translate a virtual keycode into
305: a character-based KeyStroke. */
306: public Map loadKeyStrokeMap() {
307: loadMaps();
308: return keyStrokeMap;
309: }
310:
311: /** Convert a String containing a unique identifier for the map into a
312: * unique filename.
313: */
314: protected static String getFilename(String base) {
315: //return Integer.toHexString(base.hashCode()) + ".map";
316: return base + ".map";
317: }
318:
319: protected static String[] getMapNames() {
320: return getMapStrings(false);
321: }
322:
323: protected static String[] getMapDescriptions() {
324: return getMapStrings(true);
325: }
326:
327: /** Return the keystroke map filenames that should be available for this
328: * locale/OS/VM version/architecture. Assume most changes across locale,
329: * then OS, then VM version, then os version/architecture.
330: */
331: private static String[] getMapStrings(boolean desc) {
332: ArrayList list = new ArrayList();
333: Locale locale = Locale.getDefault();
334: String name = locale.toString();
335: if (desc)
336: name = "locale=" + name;
337: list.add(0, name);
338:
339: String os = "-" + getOSType();
340: if (desc)
341: os = " (os=" + System.getProperty("os.name") + ", "
342: + System.getProperty("os.version") + ")";
343: name += os;
344: list.add(0, name);
345: /*
346: String vm = System.getProperty("java.version");
347: name += " vm=" + vm;
348: list.add(0, name);
349: String version = System.getProperty("os.version");
350: name += " version=" + version;
351: list.add(0, name);
352: String arch = System.getProperty("os.arch");
353: name += " arch=" + arch;
354: list.add(0, name);
355: */
356: return (String[]) list.toArray(new String[list.size()]);
357: }
358:
359: private static String getOSType() {
360: return Platform.isMacintosh() ? "mac"
361: : (Platform.isWindows() ? "w32" : "x11");
362: }
363:
364: /** Return currently available locales. */
365: public static void main(String[] args) {
366: Locale[] available = Locale.getAvailableLocales();
367: System.out.println("Available Locales");
368: for (int i = 0; i < available.length; i++) {
369: System.out.print(available[i].toString());
370: System.out.print(" ");
371: }
372: System.exit(1);
373: }
374: }
|