Merge "Add ExpectedKeyboardBuilder for testing keyboard layouts"

This commit is contained in:
Tadashi G. Takaoka 2014-03-06 12:34:48 +00:00 committed by Android (Google) Code Review
commit eba0af1237
2 changed files with 406 additions and 0 deletions

View file

@ -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 <code>E</code>.
*
* 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 <E> the type of a keyboard element. A keyboard element must be an immutable object.
*/
abstract class AbstractKeyboardBuilder<E> {
// A building array of rows.
private final E[][] mRows;
// Returns an instance of default element.
abstract E defaultElement();
// Returns an <code>E</code> array instance of the <code>size</code>.
abstract E[] newArray(final int size);
// Returns an <code>E[]</code> array instance of the <code>size</code>.
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 <code>rows</rows>.
* @param rows the template keyboard rows. The elements of the <code>rows</code> 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 <code>row</code>.
* @throws {@link RuntimeException} if <code>row</code> 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 <code>elements</code>.
* @param elements the array of elements to set at row number <code>row</code>.
* @throws {@link RuntimeException} if <code>row</code> 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 <code>element</code>.
* @param column the column number to set or insert the <code>element</code>.
* @param element the element to set or insert at <code>row,column</code>.
* @param insert if true, the <code>element</code> is inserted at <code>row,column</code>.
* Otherwise the <code>element</code> replace the element at <code>row,column</code>.
* @throws {@link RuntimeException} if <code>row</code> or <code>column</code> 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 <code>row,column</code>.
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;
}
}

View file

@ -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<ExpectedKey> {
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 <code>keys</code>.
* @param column the column number to insert the <code>keys</code>.
* @param keys the array of keys to insert at <code>row,column</code>.
* @return this builder.
* @throws {@link RuntimeException} if <code>row</code> or <code>column</code> 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 <code>keys</code>.
* @param keys the array of keys to add on the left most of the row.
* @return this builder.
* @throws {@link RuntimeException} if <code>row</code> 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 <code>keys</code>.
* @param keys the array of keys to add on the right most of the row.
* @return this builder.
* @throws {@link RuntimeException} if <code>row</code> 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 <code>newKey</code>.
* @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 <code>newKey</code>.
* @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 <code>rows</code>.
*/
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();
}
}