001    /* AWTKeyStroke.java -- an immutable key stroke
002       Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.awt;
040    
041    import java.awt.event.InputEvent;
042    import java.awt.event.KeyEvent;
043    import java.io.ObjectStreamException;
044    import java.io.Serializable;
045    import java.lang.reflect.Constructor;
046    import java.lang.reflect.Field;
047    import java.lang.reflect.InvocationTargetException;
048    import java.security.AccessController;
049    import java.security.PrivilegedAction;
050    import java.security.PrivilegedActionException;
051    import java.security.PrivilegedExceptionAction;
052    import java.util.HashMap;
053    import java.util.LinkedHashMap;
054    import java.util.Map;
055    import java.util.StringTokenizer;
056    
057    /**
058     * This class mirrors KeyEvents, representing both low-level key presses and
059     * key releases, and high level key typed inputs. However, this class forms
060     * immutable strokes, and can be efficiently reused via the factory methods
061     * for creating them.
062     *
063     * <p>For backwards compatibility with Swing, this supports a way to build
064     * instances of a subclass, using reflection, provided the subclass has a
065     * no-arg constructor (of any accessibility).
066     *
067     * @author Eric Blake (ebb9@email.byu.edu)
068     * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
069     * @see #getAWTKeyStroke(char)
070     * @since 1.4
071     * @status updated to 1.4
072     */
073    public class AWTKeyStroke implements Serializable
074    {
075      /**
076       * Compatible with JDK 1.4+.
077       */
078      private static final long serialVersionUID = -6430539691155161871L;
079    
080      /** The mask for modifiers. */
081      private static final int MODIFIERS_MASK = 0x3fef;
082    
083      /**
084       * The cache of recently created keystrokes. This maps KeyStrokes to
085       * KeyStrokes in a cache which removes the least recently accessed entry,
086       * under the assumption that garbage collection of a new keystroke is
087       * easy when we find the old one that it matches in the cache.
088       */
089      private static final LinkedHashMap<AWTKeyStroke,AWTKeyStroke> cache =
090        new LinkedHashMap<AWTKeyStroke,AWTKeyStroke>(11, 0.75f, true)
091      {
092        /** The largest the keystroke cache can grow. */
093        private static final int MAX_CACHE_SIZE = 2048;
094    
095        /** Prune stale entries. */
096        protected boolean removeEldestEntry(Map.Entry<AWTKeyStroke,AWTKeyStroke>
097                                            eldest)
098        {
099          return size() > MAX_CACHE_SIZE;
100        }
101      };
102    
103      /** The most recently generated keystroke, or null. */
104      private static AWTKeyStroke recent;
105    
106      /**
107       * The no-arg constructor of a subclass, or null to use AWTKeyStroke. Note
108       * that this will be left accessible, to get around private access; but
109       * it should not be a security risk as it is highly unlikely that creating
110       * protected instances of the subclass via reflection will do much damage.
111       */
112      private static Constructor ctor;
113    
114      /**
115       * A table of keyCode names to values.  This is package-private to
116       * avoid an accessor method.
117       *
118       * @see #getAWTKeyStroke(String)
119       */
120      static final HashMap<String,Object> vktable = new HashMap<String,Object>();
121      static
122      {
123        // Using reflection saves the hassle of keeping this in sync with KeyEvent,
124        // at the price of an expensive initialization.
125        AccessController.doPrivileged(new PrivilegedAction()
126          {
127            public Object run()
128            {
129              Field[] fields = KeyEvent.class.getFields();
130              int i = fields.length;
131              try
132                {
133                  while (--i >= 0)
134                    {
135                      Field f = fields[i];
136                      String name = f.getName();
137                      if (name.startsWith("VK_"))
138                        vktable.put(name.substring(3), f.get(null));
139                    }
140                }
141              catch (Exception e)
142                {
143                  throw (Error) new InternalError().initCause(e);
144                }
145              return null;
146            }
147          });
148      }
149    
150      /**
151       * The typed character, or CHAR_UNDEFINED for key presses and releases.
152       *
153       * @serial the keyChar
154       */
155      private char keyChar;
156    
157      /**
158       * The virtual key code, or VK_UNDEFINED for key typed. Package visible for
159       * use by Component.
160       *
161       * @serial the keyCode
162       */
163      int keyCode;
164    
165      /**
166       * The modifiers in effect. To match Sun, this stores the old style masks
167       * for shift, control, alt, meta, and alt-graph (but not button1); as well
168       * as the new style of extended modifiers for all modifiers.
169       *
170       * @serial bitwise or of the *_DOWN_MASK modifiers
171       */
172      private int modifiers;
173    
174      /**
175       * True if this is a key release; should only be true if keyChar is
176       * CHAR_UNDEFINED.
177       *
178       * @serial true to distinguish key pressed from key released
179       */
180      private boolean onKeyRelease;
181    
182      /**
183       * Construct a keystroke with default values: it will be interpreted as a
184       * key typed event with an invalid character and no modifiers. Client code
185       * should use the factory methods instead.
186       *
187       * @see #getAWTKeyStroke(char)
188       * @see #getAWTKeyStroke(Character, int)
189       * @see #getAWTKeyStroke(int, int, boolean)
190       * @see #getAWTKeyStroke(int, int)
191       * @see #getAWTKeyStrokeForEvent(KeyEvent)
192       * @see #getAWTKeyStroke(String)
193       */
194      protected AWTKeyStroke()
195      {
196        keyChar = KeyEvent.CHAR_UNDEFINED;
197      }
198    
199      /**
200       * Construct a keystroke with the given values. Client code should use the
201       * factory methods instead.
202       *
203       * @param keyChar the character entered, if this is a key typed
204       * @param keyCode the key pressed or released, or VK_UNDEFINED for key typed
205       * @param modifiers the modifier keys for the keystroke, in old or new style
206       * @param onKeyRelease true if this is a key release instead of a press
207       * @see #getAWTKeyStroke(char)
208       * @see #getAWTKeyStroke(Character, int)
209       * @see #getAWTKeyStroke(int, int, boolean)
210       * @see #getAWTKeyStroke(int, int)
211       * @see #getAWTKeyStrokeForEvent(KeyEvent)
212       * @see #getAWTKeyStroke(String)
213       */
214      protected AWTKeyStroke(char keyChar, int keyCode, int modifiers,
215                             boolean onKeyRelease)
216      {
217        this.keyChar = keyChar;
218        this.keyCode = keyCode;
219        // No need to call extend(), as only trusted code calls this constructor.
220        this.modifiers = modifiers;
221        this.onKeyRelease = onKeyRelease;
222      }
223    
224      /**
225       * Registers a new subclass as being the type of keystrokes to generate in
226       * the factory methods. This operation flushes the cache of stored keystrokes
227       * if the class differs from the current one. The new class must be
228       * AWTKeyStroke or a subclass, and must have a no-arg constructor (which may
229       * be private).
230       *
231       * @param subclass the new runtime type of generated keystrokes
232       * @throws IllegalArgumentException subclass doesn't have no-arg constructor
233       * @throws ClassCastException subclass doesn't extend AWTKeyStroke
234       */
235      protected static void registerSubclass(final Class<?> subclass)
236      {
237        if (subclass == null)
238          throw new IllegalArgumentException();
239        if (subclass.equals(ctor == null ? AWTKeyStroke.class
240                            : ctor.getDeclaringClass()))
241          return;
242        if (subclass.equals(AWTKeyStroke.class))
243           {
244             cache.clear();
245             recent = null;
246             ctor = null;
247             return;
248           }
249        try
250          {
251            ctor = (Constructor) AccessController.doPrivileged
252              (new PrivilegedExceptionAction()
253                {
254                  public Object run()
255                    throws NoSuchMethodException, InstantiationException,
256                           IllegalAccessException, InvocationTargetException
257                  {
258                    Constructor<?> c =
259                      subclass.getDeclaredConstructor((Class<?>[])null);
260                    c.setAccessible(true);
261                    // Create a new instance, to make sure that we can, and
262                    // to cause any ClassCastException.
263                    AWTKeyStroke dummy = (AWTKeyStroke) c.newInstance();
264                    return c;
265                  }
266                });
267          }
268        catch (PrivilegedActionException e)
269          {
270            // e.getCause() will not ever be ClassCastException; that should
271            // escape on its own.
272            throw (RuntimeException)
273              new IllegalArgumentException().initCause(e.getCause());
274          }
275        cache.clear();
276        recent = null;
277      }
278    
279      /**
280       * Returns a keystroke representing a typed character.
281       *
282       * @param keyChar the typed character
283       * @return the specified keystroke
284       */
285      public static AWTKeyStroke getAWTKeyStroke(char keyChar)
286      {
287        return getAWTKeyStroke(keyChar, KeyEvent.VK_UNDEFINED, 0, false);
288      }
289    
290      /**
291       * Returns a keystroke representing a typed character with the given
292       * modifiers. Note that keyChar is a <code>Character</code> instead of a
293       * <code>char</code> to avoid accidental ambiguity with
294       * <code>getAWTKeyStroke(int, int)</code>. The modifiers are the bitwise
295       * or of the masks found in {@link InputEvent}; the new style (*_DOWN_MASK)
296       * is preferred, but the old style will work.
297       *
298       * @param keyChar the typed character
299       * @param modifiers the modifiers, or 0
300       * @return the specified keystroke
301       * @throws IllegalArgumentException if keyChar is null
302       */
303      public static AWTKeyStroke getAWTKeyStroke(Character keyChar, int modifiers)
304      {
305        if (keyChar == null)
306          throw new IllegalArgumentException();
307        return getAWTKeyStroke(keyChar.charValue(), KeyEvent.VK_UNDEFINED,
308                               extend(modifiers), false);
309      }
310    
311      /**
312       * Returns a keystroke representing a pressed or released key event, with
313       * the given modifiers. The "virtual key" should be one of the VK_*
314       * constants in {@link KeyEvent}. The modifiers are the bitwise or of the
315       * masks found in {@link InputEvent}; the new style (*_DOWN_MASK) is
316       * preferred, but the old style will work.
317       *
318       * @param keyCode the virtual key
319       * @param modifiers the modifiers, or 0
320       * @param release true if this is a key release instead of a key press
321       * @return the specified keystroke
322       */
323      public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers,
324                                                 boolean release)
325      {
326        return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
327                               extend(modifiers), release);
328      }
329    
330      /**
331       * Returns a keystroke representing a pressed key event, with the given
332       * modifiers. The "virtual key" should be one of the VK_* constants in
333       * {@link KeyEvent}. The modifiers are the bitwise or of the masks found
334       * in {@link InputEvent}; the new style (*_DOWN_MASK) is preferred, but the
335       * old style will work.
336       *
337       * @param keyCode the virtual key
338       * @param modifiers the modifiers, or 0
339       * @return the specified keystroke
340       */
341      public static AWTKeyStroke getAWTKeyStroke(int keyCode, int modifiers)
342      {
343        return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, keyCode,
344                               extend(modifiers), false);
345      }
346    
347      /**
348       * Returns a keystroke representing what caused the key event.
349       *
350       * @param event the key event to convert
351       * @return the specified keystroke, or null if the event is invalid
352       * @throws NullPointerException if event is null
353       */
354      public static AWTKeyStroke getAWTKeyStrokeForEvent(KeyEvent event)
355      {
356        switch (event.id)
357          {
358          case KeyEvent.KEY_TYPED:
359            return getAWTKeyStroke(event.getKeyChar(), KeyEvent.VK_UNDEFINED,
360                                   extend(event.getModifiersEx()), false);
361          case KeyEvent.KEY_PRESSED:
362            return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
363                                   extend(event.getModifiersEx()), false);
364          case KeyEvent.KEY_RELEASED:
365            return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, event.getKeyCode(),
366                                   extend(event.getModifiersEx()), true);
367          default:
368            return null;
369          }
370      }
371    
372      /**
373       * Parses a string and returns the keystroke that it represents. The syntax
374       * for keystrokes is listed below, with tokens separated by an arbitrary
375       * number of spaces:
376       * <pre>
377       * keyStroke := &lt;modifiers&gt;* ( &lt;typedID&gt; | &lt;codeID&gt; )
378       * modifiers := ( shift | control | ctrl | meta | alt
379       *                | button1 | button2 | button3 )
380       * typedID := typed &lt;single Unicode character&gt;
381       * codeID := ( pressed | released )? &lt;name&gt;
382       * name := &lt;the KeyEvent field name less the leading "VK_"&gt;
383       * </pre>
384       *
385       * <p>Note that the grammar is rather weak, and not all valid keystrokes
386       * can be generated in this manner (for example, a typed space, or anything
387       * with the alt-graph modifier!). The output of AWTKeyStroke.toString()
388       * will not meet the grammar. If pressed or released is not specified,
389       * pressed is assumed. Examples:<br>
390       * <code>
391       * "INSERT" =&gt; getAWTKeyStroke(KeyEvent.VK_INSERT, 0);<br>
392       * "control DELETE" =&gt;
393       *    getAWTKeyStroke(KeyEvent.VK_DELETE, InputEvent.CTRL_MASK);<br>
394       * "alt shift X" =&gt; getAWTKeyStroke(KeyEvent.VK_X,
395       *    InputEvent.ALT_MASK | InputEvent.SHIFT_MASK);<br>
396       * "alt shift released X" =&gt; getAWTKeyStroke(KeyEvent.VK_X,
397       *    InputEvent.ALT_MASK | InputEvent.SHIFT_MASK, true);<br>
398       * "typed a" =&gt; getAWTKeyStroke('a');
399       * </code>
400       *
401       * @param s the string to parse
402       * @throws IllegalArgumentException if s is null or cannot be parsed
403       * @return the specified keystroke
404       */
405      public static AWTKeyStroke getAWTKeyStroke(String s)
406      {
407        if (s == null)
408          throw new IllegalArgumentException("null argument");
409        StringTokenizer t = new StringTokenizer(s, " ");
410        if (! t.hasMoreTokens())
411          throw new IllegalArgumentException("no tokens '" + s + "'");
412        int modifiers = 0;
413        boolean released = false;
414        String token = null;
415        do
416          {
417            token = t.nextToken();
418            if ("shift".equals(token))
419              {
420                modifiers |= KeyEvent.SHIFT_MASK;
421                modifiers |= KeyEvent.SHIFT_DOWN_MASK;
422              }
423            else if ("ctrl".equals(token) || "control".equals(token))
424              {
425                modifiers |= KeyEvent.CTRL_MASK;
426                modifiers |= KeyEvent.CTRL_DOWN_MASK;
427              }
428            else if ("meta".equals(token))
429              {
430                modifiers |= KeyEvent.META_MASK;
431                modifiers |= KeyEvent.META_DOWN_MASK;
432              }
433            else if ("alt".equals(token))
434              {
435                modifiers |= KeyEvent.ALT_MASK;
436                modifiers |= KeyEvent.ALT_DOWN_MASK;
437              }
438            else if ("button1".equals(token))
439              modifiers |= KeyEvent.BUTTON1_DOWN_MASK;
440            else if ("button2".equals(token))
441              modifiers |= KeyEvent.BUTTON2_DOWN_MASK;
442            else if ("button3".equals(token))
443              modifiers |= KeyEvent.BUTTON3_DOWN_MASK;
444            else if ("typed".equals(token))
445              {
446                if (t.hasMoreTokens())
447                  {
448                    token = t.nextToken();
449                    if (! t.hasMoreTokens() && token.length() == 1)
450                      return getAWTKeyStroke(token.charAt(0),
451                                             KeyEvent.VK_UNDEFINED, modifiers,
452                                             false);
453                  }
454                throw new IllegalArgumentException("Invalid 'typed' argument '"
455                                                   + s + "'");
456              }
457            else if ("pressed".equals(token))
458              {
459                if (t.hasMoreTokens())
460                  token = t.nextToken();
461                break;
462              }
463            else if ("released".equals(token))
464              {
465                released = true;
466                if (t.hasMoreTokens())
467                  token = t.nextToken();
468                break;
469              }
470            else
471              break;
472          }
473        while (t.hasMoreTokens());
474        // Now token contains the VK name we must parse.
475        Integer code = (Integer) vktable.get(token);
476        if (code == null)
477          throw new IllegalArgumentException("Unknown token '" + token
478                                             + "' in '" + s + "'");
479        if (t.hasMoreTokens())
480          throw new IllegalArgumentException("Too many tokens: " + s);
481        return getAWTKeyStroke(KeyEvent.CHAR_UNDEFINED, code.intValue(),
482                               modifiers, released);
483      }
484    
485      /**
486       * Returns the character of this keystroke, if it was typed.
487       *
488       * @return the character value, or CHAR_UNDEFINED
489       * @see #getAWTKeyStroke(char)
490       */
491      public final char getKeyChar()
492      {
493        return keyChar;
494      }
495    
496      /**
497       * Returns the virtual key code of this keystroke, if it was pressed or
498       * released. This will be a VK_* constant from KeyEvent.
499       *
500       * @return the virtual key code value, or VK_UNDEFINED
501       * @see #getAWTKeyStroke(int, int)
502       */
503      public final int getKeyCode()
504      {
505        return keyCode;
506      }
507    
508      /**
509       * Returns the modifiers for this keystroke. This will be a bitwise or of
510       * constants from InputEvent; it includes the old style masks for shift,
511       * control, alt, meta, and alt-graph (but not button1); as well as the new
512       * style of extended modifiers for all modifiers.
513       *
514       * @return the modifiers
515       * @see #getAWTKeyStroke(Character, int)
516       * @see #getAWTKeyStroke(int, int)
517       */
518      public final int getModifiers()
519      {
520        return modifiers;
521      }
522    
523      /**
524       * Tests if this keystroke is a key release.
525       *
526       * @return true if this is a key release
527       * @see #getAWTKeyStroke(int, int, boolean)
528       */
529      public final boolean isOnKeyRelease()
530      {
531        return onKeyRelease;
532      }
533    
534      /**
535       * Returns the AWT event type of this keystroke. This is one of
536       * {@link KeyEvent#KEY_TYPED}, {@link KeyEvent#KEY_PRESSED}, or
537       * {@link KeyEvent#KEY_RELEASED}.
538       *
539       * @return the key event type
540       */
541      public final int getKeyEventType()
542      {
543        return keyCode == KeyEvent.VK_UNDEFINED ? KeyEvent.KEY_TYPED
544          : onKeyRelease ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED;
545      }
546    
547      /**
548       * Returns a hashcode for this key event. It is not documented, but appears
549       * to be: <code>(getKeyChar() + 1) * (getKeyCode() + 1)
550       * * (getModifiers() + 1) * 2 + (isOnKeyRelease() ? 1 : 2)</code>.
551       *
552       * @return the hashcode
553       */
554      public int hashCode()
555      {
556        return (keyChar + 1) * (keyCode + 1) * (modifiers + 1) * 2
557          + (onKeyRelease ? 1 : 2);
558      }
559    
560      /**
561       * Tests two keystrokes for equality.
562       *
563       * @param o the object to test
564       * @return true if it is equal
565       */
566      public final boolean equals(Object o)
567      {
568        if (! (o instanceof AWTKeyStroke))
569          return false;
570        AWTKeyStroke s = (AWTKeyStroke) o;
571        return this == o || (keyChar == s.keyChar && keyCode == s.keyCode
572                             && modifiers == s.modifiers
573                             && onKeyRelease == s.onKeyRelease);
574      }
575    
576      /**
577       * Returns a string representation of this keystroke. For typed keystrokes,
578       * this is <code>"keyChar " + KeyEvent.getKeyModifiersText(getModifiers())
579       + getKeyChar()</code>; for pressed and released keystrokes, this is
580       * <code>"keyCode " + KeyEvent.getKeyModifiersText(getModifiers())
581       * + KeyEvent.getKeyText(getKeyCode())
582       * + (isOnKeyRelease() ? "-R" : "-P")</code>.
583       *
584       * @return a string representation
585       */
586      public String toString()
587      {
588        if (keyCode == KeyEvent.VK_UNDEFINED)
589          return "keyChar " + KeyEvent.getKeyModifiersText(modifiers) + keyChar;
590        return "keyCode " + KeyEvent.getKeyModifiersText(modifiers)
591          + KeyEvent.getKeyText(keyCode) + (onKeyRelease ? "-R" : "-P");
592      }
593    
594      /**
595       * Returns a cached version of the deserialized keystroke, if available.
596       *
597       * @return a cached replacement
598       * @throws ObjectStreamException if something goes wrong
599       */
600      protected Object readResolve() throws ObjectStreamException
601      {
602        AWTKeyStroke s = cache.get(this);
603        if (s != null)
604          return s;
605        cache.put(this, this);
606        return this;
607      }
608    
609      /**
610       * Gets the appropriate keystroke, creating one if necessary.
611       *
612       * @param keyChar the keyChar
613       * @param keyCode the keyCode
614       * @param modifiers the modifiers
615       * @param release true for key release
616       * @return the specified keystroke
617       */
618      private static AWTKeyStroke getAWTKeyStroke(char keyChar, int keyCode,
619                                                  int modifiers, boolean release)
620      {
621        // Check level 0 cache.
622        AWTKeyStroke stroke = recent; // Avoid thread races.
623        if (stroke != null && stroke.keyChar == keyChar
624            && stroke.keyCode == keyCode && stroke.modifiers == modifiers
625            && stroke.onKeyRelease == release)
626          return stroke;
627        // Create a new object, on the assumption that if it has a match in the
628        // cache, the VM can easily garbage collect it as it is temporary.
629        Constructor c = ctor; // Avoid thread races.
630        if (c == null)
631          stroke = new AWTKeyStroke(keyChar, keyCode, modifiers, release);
632        else
633          try
634            {
635              stroke = (AWTKeyStroke) c.newInstance();
636              stroke.keyChar = keyChar;
637              stroke.keyCode = keyCode;
638              stroke.modifiers = modifiers;
639              stroke.onKeyRelease = release;
640            }
641          catch (Exception e)
642            {
643              throw (Error) new InternalError().initCause(e);
644            }
645        // Check level 1 cache.
646        AWTKeyStroke cached = cache.get(stroke);
647        if (cached == null)
648          cache.put(stroke, stroke);
649        else
650          stroke = cached;
651        return recent = stroke;
652      }
653    
654      /**
655       * Converts the modifiers to the appropriate format.
656       *
657       * @param mod the modifiers to convert
658       * @return the adjusted modifiers
659       */
660      private static int extend(int mod)
661      {
662        if ((mod & (KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK)) != 0)
663          mod |= KeyEvent.SHIFT_MASK | KeyEvent.SHIFT_DOWN_MASK;
664        if ((mod & (KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK)) != 0)
665          mod |= KeyEvent.CTRL_MASK | KeyEvent.CTRL_DOWN_MASK;
666        if ((mod & (KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK)) != 0)
667          mod |= KeyEvent.META_MASK | KeyEvent.META_DOWN_MASK;
668        if ((mod & (KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK)) != 0)
669          mod |= KeyEvent.ALT_MASK | KeyEvent.ALT_DOWN_MASK;
670        if ((mod & (KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK)) != 0)
671          mod |= KeyEvent.ALT_GRAPH_MASK | KeyEvent.ALT_GRAPH_DOWN_MASK;
672        if ((mod & KeyEvent.BUTTON1_MASK) != 0)
673          mod |= KeyEvent.BUTTON1_DOWN_MASK;
674        return mod & MODIFIERS_MASK;
675      }
676    } // class AWTKeyStroke