Home | History | Annotate | Download | only in expected
      1 /*
      2  * Copyright (C) 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.inputmethod.keyboard.layout.expected;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Arrays;
     21 import java.util.Locale;
     22 
     23 /**
     24  * This class builds an expected keyboard for unit test.
     25  *
     26  * An expected keyboard is an array of rows, and a row consists of an array of {@link ExpectedKey}s.
     27  * Each row may have different number of {@link ExpectedKey}s. While building an expected keyboard,
     28  * an {@link ExpectedKey} can be specified by a row number and a column number, both numbers starts
     29  * from 1.
     30  */
     31 public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder<ExpectedKey> {
     32     public ExpectedKeyboardBuilder() {
     33         super();
     34     }
     35 
     36     public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) {
     37         super(rows);
     38     }
     39 
     40     @Override
     41     protected ExpectedKey defaultElement() {
     42         return ExpectedKey.EMPTY_KEY;
     43     }
     44 
     45     @Override
     46     ExpectedKey[] newArray(final int size) {
     47         return new ExpectedKey[size];
     48     }
     49 
     50     @Override
     51     ExpectedKey[][] newArrayOfArray(final int size) {
     52         return new ExpectedKey[size][];
     53     }
     54 
     55     @Override
     56     public ExpectedKey[][] build() {
     57         return super.build();
     58     }
     59 
     60     // A replacement job to be performed.
     61     private interface ReplaceJob {
     62         // Returns a {@link ExpectedKey} objects to replace.
     63         ExpectedKey[] replacingKeys(final ExpectedKey oldKey);
     64         // Return true if replacing should be stopped at first occurrence.
     65         boolean stopAtFirstOccurrence();
     66     }
     67 
     68     private static ExpectedKey[] replaceKeyAt(final ExpectedKey[] keys, final int columnIndex,
     69             final ExpectedKey[] replacingKeys) {
     70         // Optimization for replacing a key with another key.
     71         if (replacingKeys.length == 1) {
     72             keys[columnIndex] = replacingKeys[0];
     73             return keys;
     74         }
     75         final int newLength = keys.length - 1 + replacingKeys.length;
     76         // Remove the key at columnIndex.
     77         final ExpectedKey[] newKeys = Arrays.copyOf(keys, newLength);
     78         System.arraycopy(keys, columnIndex + 1, newKeys, columnIndex + replacingKeys.length,
     79                 keys.length - 1 - columnIndex);
     80         // Insert replacing keys at columnIndex.
     81         System.arraycopy(replacingKeys, 0, newKeys, columnIndex, replacingKeys.length);
     82         return newKeys;
     83 
     84     }
     85 
     86     // Replace key(s) that has the specified visual.
     87     private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) {
     88         int replacedCount = 0;
     89         final int rowCount = getRowCount();
     90         for (int row = 1; row <= rowCount; row++) {
     91             ExpectedKey[] keys = getRowAt(row);
     92             for (int columnIndex = 0; columnIndex < keys.length; /* nothing */) {
     93                 final ExpectedKey currentKey = keys[columnIndex];
     94                 if (!currentKey.getVisual().equalsTo(visual)) {
     95                     columnIndex++;
     96                     continue;
     97                 }
     98                 final ExpectedKey[] replacingKeys = job.replacingKeys(currentKey);
     99                 keys = replaceKeyAt(keys, columnIndex, replacingKeys);
    100                 columnIndex += replacingKeys.length;
    101                 setRowAt(row, keys);
    102                 replacedCount++;
    103                 if (job.stopAtFirstOccurrence()) {
    104                     return;
    105                 }
    106             }
    107         }
    108         if (replacedCount == 0) {
    109             throw new RuntimeException(
    110                     "Can't find key that has visual: " + visual + " in\n" + this);
    111         }
    112     }
    113 
    114     // Helper method to create {@link ExpectedKey} array by joining {@link ExpectedKey},
    115     // {@link ExpectedKey} array, and {@link String}.
    116     static ExpectedKey[] joinKeys(final Object ... keys) {
    117         final ArrayList<ExpectedKey> list = new ArrayList<>();
    118         for (final Object key : keys) {
    119             if (key instanceof ExpectedKey) {
    120                 list.add((ExpectedKey)key);
    121             } else if (key instanceof ExpectedKey[]) {
    122                 list.addAll(Arrays.asList((ExpectedKey[])key));
    123             } else if (key instanceof String) {
    124                 list.add(ExpectedKey.newInstance((String)key));
    125             } else {
    126                 throw new RuntimeException("Unknown expected key type: " + key);
    127             }
    128         }
    129         return list.toArray(new ExpectedKey[list.size()]);
    130     }
    131 
    132     /**
    133      * Set the row with specified keys.
    134      * @param row the row number to set keys.
    135      * @param keys the keys to be set at <code>row</code>. Each key can be {@link ExpectedKey},
    136      *        {@link ExpectedKey} array, and {@link String}.
    137      * @return this builder.
    138      */
    139     public ExpectedKeyboardBuilder setKeysOfRow(final int row, final Object ... keys) {
    140         setRowAt(row, joinKeys(keys));
    141         return this;
    142     }
    143 
    144     /**
    145      * Set the "more keys" of the key that has the specified label.
    146      * @param label the label of the key to set the "more keys".
    147      * @param moreKeys the array of "more key" to be set. Each "more key" can be
    148      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
    149      * @return this builder.
    150      */
    151     public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final Object ... moreKeys) {
    152         setMoreKeysOf(ExpectedKeyVisual.newInstance(label), joinKeys(moreKeys));
    153         return this;
    154     }
    155 
    156     /**
    157      * Set the "more keys" of the key that has the specified icon.
    158      * @param iconId the icon id of the key to set the "more keys".
    159      * @param moreKeys the array of "more key" to be set. Each "more key" can be
    160      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
    161      * @return this builder.
    162      */
    163     public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final Object ... moreKeys) {
    164         setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), joinKeys(moreKeys));
    165         return this;
    166     }
    167 
    168     private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) {
    169         replaceKeyOf(visual, new ReplaceJob() {
    170             @Override
    171             public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
    172                 return new ExpectedKey[] { oldKey.setMoreKeys(moreKeys) };
    173             }
    174             @Override
    175             public boolean stopAtFirstOccurrence() {
    176                 return true;
    177             }
    178         });
    179     }
    180 
    181     /**
    182      * Set the "additional more keys position" of the key that has the specified label.
    183      * @param label the label of the key to set the "additional more keys".
    184      * @param additionalMoreKeysPosition the position in the "more keys" where
    185      *        "additional more keys" will be merged. The position starts from 1.
    186      * @return this builder.
    187      */
    188     public ExpectedKeyboardBuilder setAdditionalMoreKeysPositionOf(final String label,
    189             final int additionalMoreKeysPosition) {
    190         final int additionalMoreKeysIndex = additionalMoreKeysPosition - 1;
    191         if (additionalMoreKeysIndex < 0) {
    192             throw new RuntimeException("Illegal additional more keys position: "
    193                     + additionalMoreKeysPosition);
    194         }
    195         final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
    196         replaceKeyOf(visual, new ReplaceJob() {
    197             @Override
    198             public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
    199                 return new ExpectedKey[] {
    200                         oldKey.setAdditionalMoreKeysIndex(additionalMoreKeysIndex)
    201                 };
    202             }
    203             @Override
    204             public boolean stopAtFirstOccurrence() {
    205                 return true;
    206             }
    207         });
    208         return this;
    209     }
    210 
    211     /**
    212      * Insert the keys at specified position.
    213      * @param row the row number to insert the <code>keys</code>.
    214      * @param column the column number to insert the <code>keys</code>.
    215      * @param keys the array of keys to insert at <code>row,column</code>. Each key can be
    216      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
    217      * @return this builder.
    218      * @throws RuntimeException if <code>row</code> or <code>column</code> is illegal.
    219      */
    220     public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column,
    221             final Object ... keys) {
    222         final ExpectedKey[] expectedKeys = joinKeys(keys);
    223         for (int index = 0; index < keys.length; index++) {
    224             setElementAt(row, column + index, expectedKeys[index], true /* insert */);
    225         }
    226         return this;
    227     }
    228 
    229     /**
    230      * Add the keys on the left most of the row.
    231      * @param row the row number to add the <code>keys</code>.
    232      * @param keys the array of keys to add on the left most of the row. Each key can be
    233      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
    234      * @return this builder.
    235      * @throws RuntimeException if <code>row</code> is illegal.
    236      */
    237     public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row,
    238             final Object ... keys) {
    239         final ExpectedKey[] expectedKeys = joinKeys(keys);
    240         // Keys should be inserted from the last to preserve the order.
    241         for (int index = keys.length - 1; index >= 0; index--) {
    242             setElementAt(row, 1, expectedKeys[index], true /* insert */);
    243         }
    244         return this;
    245     }
    246 
    247     /**
    248      * Add the keys on the right most of the row.
    249      * @param row the row number to add the <code>keys</code>.
    250      * @param keys the array of keys to add on the right most of the row. Each key can be
    251      *        {@link ExpectedKey}, {@link ExpectedKey} array, and {@link String}.
    252      * @return this builder.
    253      * @throws RuntimeException if <code>row</code> is illegal.
    254      */
    255     public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row,
    256             final Object ... keys) {
    257         final int rightEnd = getRowAt(row).length + 1;
    258         insertKeysAtRow(row, rightEnd, keys);
    259         return this;
    260     }
    261 
    262     /**
    263      * Replace the most top-left key that has the specified label with the new keys.
    264      * @param label the label of the key to set <code>newKeys</code>.
    265      * @param newKeys the keys to be set. Each key can be {@link ExpectedKey}, {@link ExpectedKey}
    266      *        array, and {@link String}.
    267      * @return this builder.
    268      */
    269     public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label,
    270             final Object ... newKeys) {
    271         final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label);
    272         replaceKeyOf(visual, new ReplaceJob() {
    273             @Override
    274             public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
    275                 return joinKeys(newKeys);
    276             }
    277             @Override
    278             public boolean stopAtFirstOccurrence() {
    279                 return true;
    280             }
    281         });
    282         return this;
    283     }
    284 
    285     /**
    286      * Replace the all specified keys with the new keys.
    287      * @param key the key to be replaced by <code>newKeys</code>.
    288      * @param newKeys the keys to be set. Each key can be {@link ExpectedKey}, {@link ExpectedKey}
    289      *        array, and {@link String}.
    290      * @return this builder.
    291      */
    292     public ExpectedKeyboardBuilder replaceKeysOfAll(final ExpectedKey key,
    293             final Object ... newKeys) {
    294         replaceKeyOf(key.getVisual(), new ReplaceJob() {
    295             @Override
    296             public ExpectedKey[] replacingKeys(final ExpectedKey oldKey) {
    297                 return joinKeys(newKeys);
    298             }
    299             @Override
    300             public boolean stopAtFirstOccurrence() {
    301                 return false;
    302             }
    303         });
    304         return this;
    305     }
    306 
    307     /**
    308      * Convert all keys of this keyboard builder to upper case keys.
    309      * @param locale the locale used to convert cases.
    310      * @return this builder
    311      */
    312     public ExpectedKeyboardBuilder toUpperCase(final Locale locale) {
    313         final int rowCount = getRowCount();
    314         for (int row = 1; row <= rowCount; row++) {
    315             final ExpectedKey[] lowerCaseKeys = getRowAt(row);
    316             final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length];
    317             for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) {
    318                 upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale);
    319             }
    320             setRowAt(row, upperCaseKeys);
    321         }
    322         return this;
    323     }
    324 
    325     @Override
    326     public String toString() {
    327         return toString(build());
    328     }
    329 
    330     /**
    331      * Convert the keyboard to human readable string.
    332      * @param rows the keyboard to be converted to string.
    333      * @return the human readable representation of <code>rows</code>.
    334      */
    335     public static String toString(final ExpectedKey[][] rows) {
    336         final StringBuilder sb = new StringBuilder();
    337         for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) {
    338             if (rowIndex > 0) {
    339                 sb.append("\n");
    340             }
    341             sb.append(Arrays.toString(rows[rowIndex]));
    342         }
    343         return sb.toString();
    344     }
    345 }
    346