Home | History | Annotate | Download | only in draft
      1 package org.unicode.cldr.draft;
      2 
      3 import java.util.ArrayList;
      4 import java.util.Collections;
      5 import java.util.EnumSet;
      6 import java.util.HashSet;
      7 import java.util.List;
      8 import java.util.Set;
      9 import java.util.TreeSet;
     10 
     11 import org.unicode.cldr.util.SetComparator;
     12 
     13 /**
     14  * A class which represents a particular modifier combination (or combinations
     15  * of combinations).
     16  * <p>
     17  * For example {@code alt+cmd?} gets transformed into a native format consisting of sets of ON modifiers. In this case
     18  * it would get transformed into {@code altL+cmd, altR+cmd, altL+altR+cmd, altL, altR, altL+altR} .
     19  * <p>
     20  * This definition can be expanded across multiple combinations. For example {@code optR+caps? cmd+shift} gets
     21  * transformed into {@code optR+caps, optR,
     22  * cmd+shiftL, cmd+shiftR, cmd+shiftL+shiftR} .
     23  *
     24  * <h1>Usage</h1>
     25  * <p>
     26  * There is a 1 to 1 relationship between a {@link KeyboardModifierSet} and a particular key map (a mapping from
     27  * physical keys to their output).
     28  *
     29  * <pre>
     30  * {@code
     31  * // Create the set from the XML modifier=".." attribute
     32  * ModifierSet modifierSet = ModifierSet.parseSet(<modifier=".." value from XML>);
     33  * // Test if this set is active for a particular input combination provided by the keyboard
     34  * modifierSet.contains(<some combination to test>);
     35  * }
     36  * </pre>
     37  *
     38  * @author rwainman (at) google.com (Raymond Wainman)
     39  */
     40 public class KeyboardModifierSet {
     41     /**
     42      * Enum of all possible modifier keys.
     43      */
     44     public enum Modifier {
     45         cmd, ctrlL, ctrlR, caps, altL, altR, optL, optR, shiftL, shiftR;
     46     }
     47 
     48     static final SetComparator<Modifier> SINGLETON_COMPARATOR = new SetComparator<Modifier>();
     49 
     50     /** Initial input string */
     51     private final String input;
     52     /** Internal representation of all the possible combination variants */
     53     private final Set<Set<Modifier>> variants;
     54 
     55     /**
     56      * Private constructor. See factory {@link #parseSet} method.
     57      *
     58      * @param variants
     59      *            A set containing all possible variants of the combination
     60      *            provided in the input string.
     61      */
     62     private KeyboardModifierSet(String input, Set<EnumSet<Modifier>> variants) {
     63         this.input = input;
     64         Set<Set<Modifier>> safe = new TreeSet<Set<Modifier>>(SINGLETON_COMPARATOR);
     65         for (EnumSet<Modifier> item : variants) {
     66             safe.add(Collections.unmodifiableSet(item));
     67         }
     68         this.variants = safe;
     69     }
     70 
     71     /**
     72      * Return all possible variants for this combination.
     73      *
     74      * @return Set containing all possible variants.
     75      */
     76     public Set<Set<Modifier>> getVariants() {
     77         return variants;
     78     }
     79 
     80     /**
     81      * Determines if the given combination is valid within this set.
     82      *
     83      * @param combination
     84      *            A combination of Modifier elements.
     85      * @return True if the combination is valid, false otherwise.
     86      */
     87     public boolean contains(EnumSet<Modifier> combination) {
     88         return variants.contains(combination);
     89     }
     90 
     91     public String getInput() {
     92         return input;
     93     }
     94 
     95     @Override
     96     public String toString() {
     97         return input + " => " + variants;
     98     }
     99 
    100     @Override
    101     public boolean equals(Object arg0) {
    102         return arg0 == null ? false : variants.equals(((KeyboardModifierSet) arg0).variants);
    103     }
    104 
    105     @Override
    106     public int hashCode() {
    107         return variants.hashCode();
    108     }
    109 
    110     /**
    111      * Parse a set containing one or more modifier sets. Each modifier set is
    112      * separated by a single space and modifiers within a modifier set are
    113      * separated by a '+'. For example {@code "ctrl+opt?+caps?+shift? alt+caps+cmd?"} has two modifier sets,
    114      * namely:
    115      * <ul>
    116      * <li>{@code "ctrl+opt?+caps?+shift?"}
    117      * <li>{@code "alt+caps+cmd?"}
    118      * </ul>
    119      * <p>
    120      * The '?' symbol appended to some modifiers indicates that this modifier is optional (it can be ON or OFF).
    121      *
    122      * @param input
    123      *            String representing the sets of modifier sets. This string
    124      *            must match the format defined in the LDML Keyboard Standard.
    125      * @return A {@link KeyboardModifierSet} containing all possible variants of
    126      *         the specified combinations.
    127      * @throws IllegalArgumentException
    128      *             if the input string is incorrectly formatted.
    129      */
    130     public static KeyboardModifierSet parseSet(String input) {
    131         if (input == null) {
    132             throw new IllegalArgumentException("Input string cannot be null");
    133         }
    134 
    135         String modifierSetInputs[] = input.trim().split(" ");
    136         Set<EnumSet<Modifier>> variants = new HashSet<EnumSet<Modifier>>();
    137         for (String modifierSetInput : modifierSetInputs) {
    138             variants.addAll(parseSingleSet(modifierSetInput));
    139         }
    140         return new KeyboardModifierSet(input, variants);
    141     }
    142 
    143     /**
    144      * Parse a modifier set. The set typically looks something like {@code ctrl+opt?+caps?+shift?} or
    145      * {@code alt+caps+cmd?} and return a set
    146      * containing all possible variants for that particular modifier set.
    147      * <p>
    148      * For example {@code alt+caps+cmd?} gets expanded into {@code alt+caps+cmd?, alt+caps} .
    149      *
    150      * @param input
    151      *            The input string representing the modifiers. This String must
    152      *            match the format defined in the LDML Keyboard Standard.
    153      * @return {@link KeyboardModifierSet}.
    154      * @throws IllegalArgumentException
    155      *             if the input string is incorrectly formatted.
    156      */
    157     private static Set<EnumSet<Modifier>> parseSingleSet(String input) {
    158         if (input == null) {
    159             throw new IllegalArgumentException("Input string cannot be null");
    160         }
    161         if (input.contains(" ")) {
    162             throw new IllegalArgumentException("Input string contains more than one combination");
    163         }
    164 
    165         String modifiers[] = input.trim().split("\\+");
    166 
    167         List<EnumSet<Modifier>> variants = new ArrayList<EnumSet<Modifier>>();
    168         variants.add(EnumSet.noneOf(Modifier.class)); // Add an initial set
    169                                                       // which is empty
    170 
    171         // Trivial case
    172         if (input.isEmpty()) {
    173             return new HashSet<EnumSet<Modifier>>(variants);
    174         }
    175 
    176         for (String modifier : modifiers) {
    177             String modifierElementString = modifier.replace("?", "");
    178 
    179             // Attempt to parse the modifier as a parent
    180             if (ModifierParent.isParentModifier(modifierElementString)) {
    181                 ModifierParent parentModifier = ModifierParent.valueOf(modifierElementString);
    182 
    183                 // Keep a collection of the new variants that need to be added
    184                 // while iterating over the
    185                 // existing ones
    186                 Set<EnumSet<Modifier>> newVariants = new HashSet<EnumSet<Modifier>>();
    187                 for (EnumSet<Modifier> variant : variants) {
    188                     // A parent key gets exploded into {Left, Right, Left+Right}
    189                     // or {Left, Right, Left+Right,
    190                     // (empty)} if it is a don't care
    191 
    192                     // {Left}
    193                     EnumSet<Modifier> leftVariant = EnumSet.copyOf(variant);
    194                     leftVariant.add(parentModifier.leftChild);
    195                     newVariants.add(leftVariant);
    196 
    197                     // {Right}
    198                     EnumSet<Modifier> rightVariant = EnumSet.copyOf(variant);
    199                     rightVariant.add(parentModifier.rightChild);
    200                     newVariants.add(rightVariant);
    201 
    202                     // {Left+Right}
    203                     // If it is a don't care, we need to leave the empty case
    204                     // {(empty)}
    205                     if (modifier.contains("?")) {
    206                         EnumSet<Modifier> bothChildrenVariant = EnumSet.copyOf(variant);
    207                         bothChildrenVariant.add(parentModifier.rightChild);
    208                         bothChildrenVariant.add(parentModifier.leftChild);
    209                         newVariants.add(bothChildrenVariant);
    210                     }
    211                     // No empty case, it is safe to add to the existing variants
    212                     else {
    213                         variant.add(parentModifier.rightChild);
    214                         variant.add(parentModifier.leftChild);
    215                     }
    216                 }
    217                 variants.addAll(newVariants);
    218             }
    219             // Otherwise, parse as a regular modifier
    220             else {
    221                 Modifier modifierElement = Modifier.valueOf(modifierElementString);
    222                 // On case, add the modifier to all existing variants
    223                 if (!modifier.contains("?")) {
    224                     for (EnumSet<Modifier> variant : variants) {
    225                         variant.add(modifierElement);
    226                     }
    227                 }
    228                 // Don't care case, make a copy of the existing variants and add
    229                 // the new key to it.
    230                 else {
    231                     List<EnumSet<Modifier>> newVariants = new ArrayList<EnumSet<Modifier>>();
    232                     for (EnumSet<Modifier> variant : variants) {
    233                         EnumSet<Modifier> newVariant = EnumSet.copyOf(variant);
    234                         newVariant.add(modifierElement);
    235                         newVariants.add(newVariant);
    236                     }
    237                     variants.addAll(newVariants);
    238                 }
    239             }
    240         }
    241 
    242         return new HashSet<EnumSet<Modifier>>(variants);
    243     }
    244 
    245     /**
    246      * Enum of all parent modifier keys. Defines the relationships with their
    247      * children.
    248      */
    249     private enum ModifierParent {
    250         ctrl(Modifier.ctrlL, Modifier.ctrlR), alt(Modifier.altL, Modifier.altR), opt(
    251             Modifier.optL, Modifier.optR), shift(Modifier.shiftL, Modifier.shiftR);
    252 
    253         private final Modifier leftChild;
    254         private final Modifier rightChild;
    255 
    256         private ModifierParent(Modifier leftChild, Modifier rightChild) {
    257             this.leftChild = leftChild;
    258             this.rightChild = rightChild;
    259         }
    260 
    261         /**
    262          * Determines if the String passed in is a valid parent key.
    263          *
    264          * @param modifier
    265          *            The modifier string to verify.
    266          * @return True if it is a parent key, false otherwise.
    267          */
    268         private static boolean isParentModifier(String modifier) {
    269             try {
    270                 ModifierParent.valueOf(modifier);
    271                 return true;
    272             } catch (IllegalArgumentException e) {
    273                 return false;
    274             }
    275         }
    276     }
    277 
    278     public boolean containsSome(KeyboardModifierSet keyMapModifiers) {
    279         for (Set<Modifier> item : keyMapModifiers.variants) {
    280             if (variants.contains(item)) {
    281                 return true;
    282             }
    283         }
    284         return false;
    285     }
    286 
    287     public String getShortInput() {
    288         int pos = input.indexOf(' ');
    289         if (pos < 0) return input;
    290         return input.substring(0, pos) + "";
    291     }
    292 }
    293