diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java new file mode 100644 index 000000000..45449b762 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/AbstractKeyboardBuilder.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import java.util.Arrays; + +/** + * This class builds a keyboard that is a two dimensional array of elements E. + * + * A keyboard consists of array of rows, and a row consists of array of elements. Each row may have + * different number of elements. A element of a keyboard can be specified by a row number and a + * column number, both numbers starts from 1. + * + * @param the type of a keyboard element. A keyboard element must be an immutable object. + */ +abstract class AbstractKeyboardBuilder { + // A building array of rows. + private final E[][] mRows; + + // Returns an instance of default element. + abstract E defaultElement(); + // Returns an E array instance of the size. + abstract E[] newArray(final int size); + // Returns an E[] array instance of the size. + abstract E[][] newArrayOfArray(final int size); + + /** + * Construct a builder filled with the default element. + * @param dimensions the integer array of each row's size. + */ + AbstractKeyboardBuilder(final int ... dimensions) { + mRows = newArrayOfArray(dimensions.length); + for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) { + mRows[rowIndex] = newArray(dimensions[rowIndex]); + Arrays.fill(mRows[rowIndex], defaultElement()); + } + } + + /** + * Construct a builder from template keyboard. This builder has the same dimensions and + * elements of rows. + * @param rows the template keyboard rows. The elements of the rows will be + * shared with this builder. Therefore a element must be an immutable object. + */ + AbstractKeyboardBuilder(final E[][] rows) { + mRows = newArrayOfArray(rows.length); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final E[] row = rows[rowIndex]; + mRows[rowIndex] = Arrays.copyOf(row, row.length); + } + } + + /** + * Return current constructing keyboard. + * @return the array of the array of the element being constructed. + */ + E[][] build() { + return mRows; + } + + /** + * Get the current contents of the specified row. + * @param row the row number to get the contents. + * @return the array of elements at row number row. + * @throws {@link RuntimeException} if row is illegal. + */ + E[] getRowAt(final int row) { + final int rowIndex = row - 1; + if (rowIndex < 0 || rowIndex >= mRows.length) { + throw new RuntimeException("Illegal row number: " + row); + } + return mRows[rowIndex]; + } + + /** + * Set an array of elements to the specified row. + * @param row the row number to set elements. + * @param elements the array of elements to set at row number row. + * @throws {@link RuntimeException} if row is illegal. + */ + void setRowAt(final int row, final E[] elements) { + final int rowIndex = row - 1; + if (rowIndex < 0 || rowIndex >= mRows.length) { + throw new RuntimeException("Illegal row number: " + row); + } + mRows[rowIndex] = elements; + } + + /** + * Set or insert an element at specified position. + * @param row the row number to set or insert the element. + * @param column the column number to set or insert the element. + * @param element the element to set or insert at row,column. + * @param insert if true, the element is inserted at row,column. + * Otherwise the element replace the element at row,column. + * @throws {@link RuntimeException} if row or column is illegal. + */ + void setElementAt(final int row, final int column, final E element, final boolean insert) { + final E[] elements = getRowAt(row); + final int columnIndex = column - 1; + if (insert) { + if (columnIndex < 0 || columnIndex >= elements.length + 1) { + throw new RuntimeException("Illegal column number: " + column); + } + final E[] newElements = Arrays.copyOf(elements, elements.length + 1); + // Shift the remaining elements. + System.arraycopy(newElements, columnIndex, newElements, columnIndex + 1, + elements.length - columnIndex); + // Insert the element at row,column. + newElements[columnIndex] = element; + // Replace the current row with one. + setRowAt(row, newElements); + return; + } + if (columnIndex < 0 || columnIndex >= elements.length) { + throw new RuntimeException("Illegal column number: " + column); + } + elements[columnIndex] = element; + } +} diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java new file mode 100644 index 000000000..61288f048 --- /dev/null +++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ExpectedKeyboardBuilder.java @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.keyboard.layout.expected; + +import java.util.Arrays; +import java.util.Locale; + +/** + * This class builds an expected keyboard for unit test. + */ +public final class ExpectedKeyboardBuilder extends AbstractKeyboardBuilder { + public ExpectedKeyboardBuilder(final int ... dimensions) { + super(dimensions); + } + + public ExpectedKeyboardBuilder(final ExpectedKey[][] rows) { + super(rows); + } + + @Override + protected ExpectedKey defaultElement() { + return ExpectedKey.EMPTY_KEY; + } + + @Override + ExpectedKey[] newArray(final int size) { + return new ExpectedKey[size]; + } + + @Override + ExpectedKey[][] newArrayOfArray(final int size) { + return new ExpectedKey[size][]; + } + + @Override + public ExpectedKey[][] build() { + return super.build(); + } + + // A replacement job to be performed. + interface ReplaceJob { + // Returns a {@link ExpectedKey} object to replace. + ExpectedKey replace(final ExpectedKey oldKey); + // Return true if replacing should be stopped at first occurrence. + boolean stopAtFirstOccurrence(); + } + + // Replace key(s) that has the specified visual. + private void replaceKeyOf(final ExpectedKeyVisual visual, final ReplaceJob job) { + int replacedCount = 0; + final ExpectedKey[][] rows = build(); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final ExpectedKey[] keys = rows[rowIndex]; + for (int columnIndex = 0; columnIndex < keys.length; columnIndex++) { + if (keys[columnIndex].getVisual().equalsTo(visual)) { + keys[columnIndex] = job.replace(keys[columnIndex]); + replacedCount++; + if (job.stopAtFirstOccurrence()) { + return; + } + } + } + } + if (replacedCount == 0) { + throw new RuntimeException( + "Can't find key that has visual: " + visual + " in\n" + toString(rows)); + } + } + + /** + * Set the row with specified keys that have specified labels. + * @param row the row number to set keys. + * @param labels the label texts of the keys. + * @return this builder. + */ + public ExpectedKeyboardBuilder setLabelsOfRow(final int row, final String ... labels) { + final ExpectedKey[] keys = new ExpectedKey[labels.length]; + for (int columnIndex = 0; columnIndex < labels.length; columnIndex++) { + keys[columnIndex] = ExpectedKey.newInstance(labels[columnIndex]); + } + setRowAt(row, keys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified label. + * @param label the label of the key to set the "more keys". + * @param moreKeys the array of labels of the "more keys" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final String label, final String ... moreKeys) { + final ExpectedKey[] expectedMoreKeys = new ExpectedKey[moreKeys.length]; + for (int index = 0; index < moreKeys.length; index++) { + expectedMoreKeys[index] = ExpectedKey.newInstance(moreKeys[index]); + } + setMoreKeysOf(label, expectedMoreKeys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified label. + * @param label the label of the key to set the "more keys". + * @param moreKeys the array of "more key" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final String label, + final ExpectedKey ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(label), moreKeys); + return this; + } + + /** + * Set the "more keys" of the key that has the specified icon. + * @param iconId the icon id of the key to set the "more keys". + * @param moreKeys the array of "more key" to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder setMoreKeysOf(final int iconId, final ExpectedKey ... moreKeys) { + setMoreKeysOf(ExpectedKeyVisual.newInstance(iconId), moreKeys); + return this; + } + + private void setMoreKeysOf(final ExpectedKeyVisual visual, final ExpectedKey[] moreKeys) { + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return ExpectedKey.newInstance(oldKey.getVisual(), oldKey.getOutput(), moreKeys); + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + } + + /** + * Insert the keys at specified position. + * @param row the row number to insert the keys. + * @param column the column number to insert the keys. + * @param keys the array of keys to insert at row,column. + * @return this builder. + * @throws {@link RuntimeException} if row or column is illegal. + */ + public ExpectedKeyboardBuilder insertKeysAtRow(final int row, final int column, + final ExpectedKey ... keys) { + for (int index = 0; index < keys.length; index++) { + setElementAt(row, column + index, keys[index], true /* insert */); + } + return this; + } + + /** + * Add the keys on the left most of the row. + * @param row the row number to add the keys. + * @param keys the array of keys to add on the left most of the row. + * @return this builder. + * @throws {@link RuntimeException} if row is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheLeftOfRow(final int row, + final ExpectedKey ... keys) { + // Keys should be inserted from the last to preserve the order. + for (int index = keys.length - 1; index >= 0; index--) { + setElementAt(row, 1, keys[index], true /* insert */); + } + return this; + } + + /** + * Add the keys on the right most of the row. + * @param row the row number to add the keys. + * @param keys the array of keys to add on the right most of the row. + * @return this builder. + * @throws {@link RuntimeException} if row is illegal. + */ + public ExpectedKeyboardBuilder addKeysOnTheRightOfRow(final int row, + final ExpectedKey ... keys) { + final int rightEnd = getRowAt(row).length + 1; + insertKeysAtRow(row, rightEnd, keys); + return this; + } + + /** + * Replace the most top-left key that has the specified label with the new key. + * @param label the label of the key to set newKey. + * @param newKey the key to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeyOfLabel(final String label, final ExpectedKey newKey) { + final ExpectedKeyVisual visual = ExpectedKeyVisual.newInstance(label); + replaceKeyOf(visual, new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return newKey; + } + @Override + public boolean stopAtFirstOccurrence() { + return true; + } + }); + return this; + } + + /** + * Replace the all specified keys with the new key. + * @param key the key to be replaced by newKey. + * @param newKey the key to be set. + * @return this builder. + */ + public ExpectedKeyboardBuilder replaceKeyOfAll(final ExpectedKey key, + final ExpectedKey newKey) { + replaceKeyOf(key.getVisual(), new ReplaceJob() { + @Override + public ExpectedKey replace(final ExpectedKey oldKey) { + return newKey; + } + @Override + public boolean stopAtFirstOccurrence() { + return false; + } + }); + return this; + } + + /** + * Returns new keyboard instance that has upper case keys of the specified keyboard. + * @param rows the lower case keyboard. + * @param locale the locale used to convert cases. + * @return the upper case keyboard. + */ + public static ExpectedKey[][] toUpperCase(final ExpectedKey[][] rows, final Locale locale) { + final ExpectedKey[][] upperCaseRows = new ExpectedKey[rows.length][]; + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + final ExpectedKey[] lowerCaseKeys = rows[rowIndex]; + final ExpectedKey[] upperCaseKeys = new ExpectedKey[lowerCaseKeys.length]; + for (int columnIndex = 0; columnIndex < lowerCaseKeys.length; columnIndex++) { + upperCaseKeys[columnIndex] = lowerCaseKeys[columnIndex].toUpperCase(locale); + } + upperCaseRows[rowIndex] = upperCaseKeys; + } + return upperCaseRows; + } + + /** + * Convert the keyboard to human readable string. + * @param rows the keyboard to be converted to string. + * @return the human readable representation of rows. + */ + public static String toString(final ExpectedKey[][] rows) { + final StringBuilder sb = new StringBuilder(); + for (int rowIndex = 0; rowIndex < rows.length; rowIndex++) { + if (rowIndex > 0) { + sb.append("\n"); + } + sb.append(Arrays.toString(rows[rowIndex])); + } + return sb.toString(); + } +}