Home | History | Annotate | Download | only in keyboard
      1 package org.unicode.cldr.draft.keyboard;
      2 
      3 import static com.google.common.base.Preconditions.checkArgument;
      4 import static com.google.common.base.Preconditions.checkNotNull;
      5 
      6 import java.util.EnumSet;
      7 import java.util.Iterator;
      8 import java.util.List;
      9 import java.util.Set;
     10 import java.util.TreeSet;
     11 
     12 import com.google.common.base.Joiner;
     13 import com.google.common.collect.ImmutableList;
     14 import com.google.common.collect.ImmutableMap;
     15 import com.google.common.collect.ImmutableSet;
     16 import com.google.common.collect.ImmutableTable;
     17 import com.google.common.collect.Lists;
     18 import com.google.common.collect.Sets;
     19 import com.google.common.collect.Sets.SetView;
     20 
     21 /**
     22  * A helper class which helps simplify single key combinations. That is, keys which come in a parent
     23  * and child variants.
     24  *
     25  * The strategy used to build this simplification table was to minimize the number of terms in the
     26  * boolean algebra by simplifying most keys into a "don't care (?)" state as much as possible. For
     27  * example, to represent an empty combination of keys, we use "Parent = 0, Left Child = ?, Right
     28  * Child = ?" as opposed to "Parent = 0, Left Child = 0, Right Child = 0". (See the table above for
     29  * more details). Both forms are functionally equivalent but we feel that the first form is much
     30  * simpler to represent.
     31  */
     32 public final class ModifierKeySimplifier {
     33     /**
     34      * A mapping from input (given by a ON set and a DON'T CARE set) to the internal representation of
     35      * the combination. The order is always {@code <PARENT><LEFT_CHILD><RIGHT_CHILD>}.
     36      * <p>
     37      * Notation:
     38      * <ul>
     39      * <li>"-" = Missing (not in any set)
     40      * <li>"1" = In the ON set
     41      * <li>"?" = In the DON'T CARE set
     42      * <li>"0" = In the OFF set
     43      * <ul>
     44      */
     45     private static final ImmutableMap<String, String> INPUT_COMBINATION_TO_INTERNAL = ImmutableMap
     46         .<String, String> builder().put("---", "0??").put("--1", "?01").put("--?", "?0?")
     47         .put("-1-", "?10").put("-11", "?11").put("-1?", "?1?").put("-?-", "??0").put("-?1", "??1")
     48         .put("-??", "???").put("1--", "1??").put("1-1", "??1").put("1-?", "1??").put("11-", "?1?")
     49         .put("111", "?11").put("11?", "?1?").put("1?-", "1??").put("1?1", "??1").put("1??", "1??")
     50         .put("?--", "???").put("?-1", "??1").put("?-?", "?0?").put("?1-", "?1?").put("?11", "?11")
     51         .put("?1?", "?1?").put("??-", "??0").put("??1", "??1").put("???", "???").build();
     52 
     53     /**
     54      * A mapping which maps the result of an OR between two combinations. Takes two combinations
     55      * (represented in the internal notation) and returns the simplified combination (also in the
     56      * internal notation).
     57      *
     58      * <p>
     59      * For example, "A? AL" simplifies to "A?", "AL AL+AR" simplifies to "AL+AR?" and so on. The
     60      * equivalence table is included in the document linked in the class header.
     61      *
     62      * <p>
     63      * Notation:
     64      * <ul>
     65      * <li>"%" = No simplification possible, both combinations must stay.
     66      * <li>"1" = In the ON set
     67      * <li>"?" = In the DON'T CARE set
     68      * <li>"0" = In the OFF set
     69      * <ul>
     70      */
     71     private static final ImmutableTable<String, String, String> COMBINATIONS_TO_SIMPLIFCATION = ImmutableTable
     72         .<String, String, String> builder().put("1??", "0??", "???").put("?10", "0??", "??0")
     73         .put("?1?", "0??", "%").put("??0", "0??", "??0").put("?11", "0??", "%")
     74         .put("?01", "0??", "?0?").put("??1", "0??", "%").put("?0?", "0??", "?0?")
     75         .put("???", "0??", "???").put("?10", "1??", "1??").put("?1?", "1??", "1??")
     76         .put("??0", "1??", "???").put("?11", "1??", "1??").put("?01", "1??", "1??")
     77         .put("??1", "1??", "1??").put("?0?", "1??", "???").put("???", "1??", "???")
     78         .put("?1?", "?10", "?1?").put("??0", "?10", "??0").put("?11", "?10", "?1?")
     79         .put("?01", "?10", "%").put("??1", "?10", "%").put("?0?", "?10", "%")
     80         .put("???", "?10", "???").put("??0", "?1?", "%").put("?11", "?1?", "?1?")
     81         .put("?01", "?1?", "%").put("??1", "?1?", "1??").put("?0?", "?1?", "???")
     82         .put("???", "?1?", "???").put("?11", "??0", "%").put("?01", "??0", "%")
     83         .put("??1", "??0", "???").put("?0?", "??0", "%").put("???", "??0", "???")
     84         .put("?01", "?11", "??1").put("??1", "?11", "??1").put("?0?", "?11", "%")
     85         .put("???", "?11", "???").put("??1", "?01", "??1").put("?0?", "?01", "?0?")
     86         .put("???", "?01", "???").put("?0?", "??1", "%").put("???", "??1", "???")
     87         .put("???", "?0?", "???").build();
     88 
     89     /**
     90      * Given a set of ON keys and DON'T CARE keys, simplify and determine the internal representation
     91      * of the combination.
     92      */
     93     public static ModifierKeyCombination simplifyInput(Set<ModifierKey> onKeys, Set<ModifierKey> dontCareKeys) {
     94         checkArgument(Sets.intersection(onKeys, dontCareKeys).size() == 0);
     95         ImmutableSet.Builder<ModifierKey> onKeysBuilder = ImmutableSet.builder();
     96         ImmutableSet.Builder<ModifierKey> offKeysBuilder = ImmutableSet.builder();
     97         // Add parent keys and their children.
     98         for (ModifierKey parentKey : ModifierKey.parents()) {
     99             StringBuilder inputRepresentation = new StringBuilder();
    100             // Parent key.
    101             inputRepresentation.append(getInputKeyState(parentKey, onKeys, dontCareKeys));
    102             // Children.
    103             for (ModifierKey child : parentKey.children()) {
    104                 inputRepresentation.append(getInputKeyState(child, onKeys, dontCareKeys));
    105             }
    106             // Get the internal representation
    107             String result = INPUT_COMBINATION_TO_INTERNAL.get(inputRepresentation.toString());
    108             checkNotNull(result, "No internal mapping for %s", inputRepresentation);
    109             // Transform the String representation into the internal representation and add them to the ON
    110             // and OFF sets.
    111             addInternalRepresentationFromString(parentKey, result, onKeysBuilder, offKeysBuilder);
    112         }
    113         // Add single keys.
    114         for (ModifierKey singleKey : ModifierKey.singles()) {
    115             if (onKeys.contains(singleKey)) {
    116                 onKeysBuilder.add(singleKey);
    117             } else if (!dontCareKeys.contains(singleKey)) {
    118                 offKeysBuilder.add(singleKey);
    119             }
    120         }
    121         return ModifierKeyCombination.of(onKeysBuilder.build(), offKeysBuilder.build());
    122     }
    123 
    124     /** Find the state of the given modifier key by evaluating the given sets. */
    125     private static char getInputKeyState(ModifierKey modifierKey, Set<ModifierKey> onKeys,
    126         Set<ModifierKey> dontCareKeys) {
    127         return onKeys.contains(modifierKey) ? '1' : dontCareKeys.contains(modifierKey) ? '?' : '-';
    128     }
    129 
    130     private static Joiner PLUS_JOINER = Joiner.on('+');
    131 
    132     /**
    133      * Given a set of ON keys and OFF keys in the internal representation, simplify the combination
    134      * and produce a string representing the combination in the format defined by the LDML Keyboard
    135      * Standard.
    136      * <p>
    137      * Namely:
    138      * <ul>
    139      * <li>All keys are separated by a '+'.
    140      * <li>All don't care keys are suffixed by a '?'.
    141      * <li>ON keys are grouped together and are displayed first, followed by the don't care keys.
    142      * <li>The modifier keys should be in the order defined in the standard within a group.
    143      * <li>The combination should be in its simplest form.
    144      * </ul>
    145      */
    146     public static String simplifyToString(ModifierKeyCombination combination) {
    147         ImmutableSet<ModifierKey> onKeys = combination.onKeys();
    148         ImmutableSet<ModifierKey> offKeys = combination.offKeys();
    149         TreeSet<ModifierKey> onKeysForOutput = Sets.newTreeSet();
    150         TreeSet<ModifierKey> dontCareKeysForOutput = Sets.newTreeSet();
    151         for (ModifierKey parentKey : ModifierKey.parents()) {
    152             String result = getStringFromInternalRepresentation(parentKey, onKeys, offKeys);
    153             char parentState = result.charAt(0);
    154             char leftChildState = result.charAt(1);
    155             char rightChildState = result.charAt(2);
    156             // If both children are don't cares, output the parent only in its state (don't output the OFF
    157             // ones).
    158             if (leftChildState == '?' && rightChildState == '?') {
    159                 if (parentState == '1') {
    160                     onKeysForOutput.add(parentKey);
    161                 } else if (parentState == '?') {
    162                     dontCareKeysForOutput.add(parentKey);
    163                 }
    164             }
    165             // Otherwise, add the child keys in their states (don't output the OFF ones).
    166             else {
    167                 ImmutableList<ModifierKey> children = parentKey.children();
    168                 if (leftChildState == '1') {
    169                     onKeysForOutput.add(children.get(0));
    170                 } else if (leftChildState == '?') {
    171                     dontCareKeysForOutput.add(children.get(0));
    172                 }
    173                 if (rightChildState == '1') {
    174                     onKeysForOutput.add(children.get(1));
    175                 } else if (rightChildState == '?') {
    176                     dontCareKeysForOutput.add(children.get(1));
    177                 }
    178             }
    179         }
    180         // Add single keys
    181         for (ModifierKey singleKey : ModifierKey.singles()) {
    182             if (onKeys.contains(singleKey)) {
    183                 onKeysForOutput.add(singleKey);
    184             } else if (!offKeys.contains(singleKey)) {
    185                 dontCareKeysForOutput.add(singleKey);
    186             }
    187         }
    188         // Join on-keys.
    189         String onKeysString = PLUS_JOINER.join(onKeysForOutput);
    190         // Join don't care keys.
    191         List<String> dontCareKeysList = Lists.newArrayList();
    192         for (ModifierKey dontCareKey : dontCareKeysForOutput) {
    193             dontCareKeysList.add(dontCareKey.toString() + "?");
    194         }
    195         String dontCareKeysString = PLUS_JOINER.join(dontCareKeysList);
    196         return dontCareKeysString.isEmpty() ? onKeysString
    197             : onKeysString.isEmpty() ? dontCareKeysString
    198                 : PLUS_JOINER.join(onKeysString, dontCareKeysString);
    199     }
    200 
    201     /** Find the state of the given modifier key by evaluating the given sets. */
    202     private static char getInternalKeyState(ModifierKey modifierKey, Set<ModifierKey> onKeys,
    203         Set<ModifierKey> offKeys) {
    204         return onKeys.contains(modifierKey) ? '1' : offKeys.contains(modifierKey) ? '0' : '?';
    205     }
    206 
    207     /**
    208      * Simplifies the set of combinations into its most simple forms and returns a modifier key
    209      * combination set.
    210      */
    211     public static ImmutableSet<ModifierKeyCombination> simplifySet(Set<ModifierKeyCombination> combinations) {
    212         // Make a defensive copy of the input.
    213         Set<ModifierKeyCombination> finalCombinations = Sets.newHashSet(combinations);
    214         // Keep simplifying the combination until a stable version is attained.
    215         int sizeChange = Integer.MAX_VALUE;
    216         while (sizeChange != 0) {
    217             int initialSize = finalCombinations.size();
    218             finalCombinations = simplifyCombinationsOnePass(finalCombinations);
    219             sizeChange = initialSize - finalCombinations.size();
    220         }
    221         return ImmutableSet.copyOf(finalCombinations);
    222     }
    223 
    224     /**
    225      * Make a single pass over the set of combinations to attempt to simplify them. Multiple calls to
    226      * this method are necessary to achieve the simplest form.
    227      */
    228     private static Set<ModifierKeyCombination> simplifyCombinationsOnePass(
    229         Set<ModifierKeyCombination> combinations) {
    230         if (combinations.size() < 2) {
    231             return combinations;
    232         }
    233         Iterator<ModifierKeyCombination> iterator = Sets.newTreeSet(combinations).iterator();
    234         Set<ModifierKeyCombination> finalCombinations = Sets.newHashSet();
    235         // Take two consecutive objects in the sorted set and attempt to simplify them.
    236         ModifierKeyCombination combination1 = iterator.next();
    237         while (iterator.hasNext()) {
    238             ModifierKeyCombination combination2 = iterator.next();
    239             Set<ModifierKeyCombination> result = simplifyTwoCombinations(combination1, combination2);
    240             // If the simplification was successful, use it as a new pointer.
    241             if (result.size() == 1) {
    242                 combination1 = result.iterator().next();
    243             } else {
    244                 finalCombinations.add(combination1);
    245                 combination1 = combination2;
    246             }
    247         }
    248         finalCombinations.add(combination1);
    249         return finalCombinations;
    250     }
    251 
    252     /**
    253      * Given two modifier key combinations, attempt to simplify them into a single combination. If no
    254      * simplification is possible, the method simply returns a set containing the two original
    255      * combinations.
    256      */
    257     private static ImmutableSet<ModifierKeyCombination> simplifyTwoCombinations(
    258         ModifierKeyCombination combination1, ModifierKeyCombination combination2) {
    259         // If the combinations are identical, the simplification is trivial.
    260         if (combination1.equals(combination2)) {
    261             return ImmutableSet.of(combination1);
    262         }
    263         SetView<ModifierKey> onKeyDifferences = Sets.symmetricDifference(combination1.onKeys(),
    264             combination2.onKeys());
    265         SetView<ModifierKey> offKeyDifferences = Sets.symmetricDifference(combination1.offKeys(),
    266             combination2.offKeys());
    267         // Simplification is only possible if there is some sort of relationship between the keys (and
    268         // even then, simplification is not guaranteed.
    269         if (!keysAreRelated(onKeyDifferences, offKeyDifferences)) {
    270             return ImmutableSet.of(combination1, combination2);
    271         }
    272         // Get the modifier key parent in question.
    273         ModifierKey key = null;
    274         if (onKeyDifferences.size() > 0) {
    275             key = onKeyDifferences.iterator().next();
    276         } else {
    277             key = offKeyDifferences.iterator().next();
    278         }
    279         ModifierKey parentKey = key.parent();
    280         // Set up a new combination with all the common keys from the two combinations.
    281         Sets.SetView<ModifierKey> onKeysIntersect = Sets.intersection(combination1.onKeys(),
    282             combination2.onKeys());
    283         EnumSet<ModifierKey> onKeys = onKeysIntersect.isEmpty() ? EnumSet.noneOf(ModifierKey.class)
    284             : EnumSet.copyOf(onKeysIntersect);
    285         Sets.SetView<ModifierKey> offKeysIntersect = Sets.intersection(combination1.offKeys(), combination2.offKeys());
    286         EnumSet<ModifierKey> offKeys = offKeysIntersect.isEmpty() ? EnumSet.noneOf(ModifierKey.class)
    287             : EnumSet.copyOf(offKeysIntersect);
    288         // Get the internal state of both combinations for this particular modifier key
    289         String combination1States = getStringFromInternalRepresentation(parentKey,
    290             combination1.onKeys(), combination1.offKeys());
    291         String combination2States = getStringFromInternalRepresentation(parentKey,
    292             combination2.onKeys(), combination2.offKeys());
    293         // Attempt to get simplification (may need to reverse the col/row keys because we are just
    294         // storing a triangular matrix with the simplification codes).
    295         String result = COMBINATIONS_TO_SIMPLIFCATION.get(combination1States, combination2States);
    296         if (result == null) {
    297             result = COMBINATIONS_TO_SIMPLIFCATION.get(combination2States, combination1States);
    298         }
    299         checkNotNull(result, "Unknown combination %s", combination1States + "," + combination2States);
    300         // The "%" return code means that the two combinations cannot be combined.
    301         if (result.equals("%")) {
    302             return ImmutableSet.of(combination1, combination2);
    303         }
    304         // Transform the String representation into the internal representation and add them to the ON
    305         // and OFF sets.
    306         ImmutableSet.Builder<ModifierKey> onKeysBuilder = ImmutableSet.builder();
    307         ImmutableSet.Builder<ModifierKey> offKeysBuilder = ImmutableSet.builder();
    308         addInternalRepresentationFromString(parentKey, result, onKeysBuilder, offKeysBuilder);
    309         onKeysBuilder.addAll(onKeys);
    310         offKeysBuilder.addAll(offKeys);
    311         return ImmutableSet
    312             .of(ModifierKeyCombination.of(onKeysBuilder.build(), offKeysBuilder.build()));
    313     }
    314 
    315     /**
    316      * Given the set difference between two combinations ON keys and OFF keys, determine if the
    317      * differences in both sets are related (simplification is only possible if there is a
    318      * relationship between the different keys).
    319      */
    320     private static boolean keysAreRelated(Set<ModifierKey> onKeys, Set<ModifierKey> offKeys) {
    321         // Combine all keys.
    322         Set<ModifierKey> allKeys = EnumSet.noneOf(ModifierKey.class);
    323         allKeys.addAll(onKeys);
    324         allKeys.addAll(offKeys);
    325         // Get a test key.
    326         ModifierKey testKey = allKeys.iterator().next();
    327         // Remove all keys which have some sort of relationship to the test key from all keys.
    328         allKeys.remove(testKey);
    329         allKeys.remove(testKey.parent());
    330         allKeys.remove(testKey.sibling());
    331         allKeys.removeAll(testKey.children());
    332         // Check that set is empty, if it isn't there are some extra keys.
    333         return allKeys.size() == 0;
    334     }
    335 
    336     /**
    337      * Return a length 3 String representing the state of a parent key and its two children in their
    338      * internal representation given a set of ON keys and OFF keys (in internal representation).
    339      */
    340     private static String getStringFromInternalRepresentation(
    341         ModifierKey parentKey, Set<ModifierKey> onKeys, Set<ModifierKey> offKeys) {
    342         StringBuilder internalRepresentationBuilder = new StringBuilder();
    343         internalRepresentationBuilder.append(getInternalKeyState(parentKey, onKeys, offKeys));
    344         // Children
    345         ImmutableList<ModifierKey> children = parentKey.children();
    346         // If there are no children, mark them as ?? (effectively removing them from the boolean
    347         // equation).
    348         if (children.size() == 0) {
    349             internalRepresentationBuilder.append("??");
    350         } else {
    351             internalRepresentationBuilder.append(getInternalKeyState(children.get(0), onKeys, offKeys));
    352             internalRepresentationBuilder.append(getInternalKeyState(children.get(1), onKeys, offKeys));
    353         }
    354         return internalRepresentationBuilder.toString();
    355     }
    356 
    357     /**
    358      * Transform a length 3 String containing the state of a modifier key and its children and add it
    359      * to the onKeys and offKeys builders.
    360      */
    361     private static void addInternalRepresentationFromString(
    362         ModifierKey parentKey,
    363         String modifierKeyState,
    364         ImmutableSet.Builder<ModifierKey> onKeysOut,
    365         ImmutableSet.Builder<ModifierKey> offKeysOut) {
    366         checkArgument(modifierKeyState.length() == 3, modifierKeyState);
    367         ImmutableList<ModifierKey> children = parentKey.children();
    368         List<ModifierKey> keys = children.isEmpty()
    369             ? Lists.newArrayList(parentKey, parentKey, parentKey)
    370             : Lists.newArrayList(parentKey, children.get(0), children.get(1));
    371         for (int i = 0; i < modifierKeyState.length(); i++) {
    372             char state = modifierKeyState.charAt(i);
    373             ModifierKey key = keys.get(i);
    374             if (state == '1') {
    375                 onKeysOut.add(key);
    376             } else if (state == '0') {
    377                 offKeysOut.add(key);
    378             }
    379         }
    380     }
    381 
    382     private ModifierKeySimplifier() {
    383     }
    384 }
    385