Add ActualKeyboardBuilder for testing keyboard layouts

Bug: 13017434
Change-Id: I38b1f5c261ea1f3ff312c780d7d9b5fe3fba8aaf
diff --git a/java/src/com/android/inputmethod/latin/utils/StringUtils.java b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
index e7932b5..b9d526b 100644
--- a/java/src/com/android/inputmethod/latin/utils/StringUtils.java
+++ b/java/src/com/android/inputmethod/latin/utils/StringUtils.java
@@ -507,4 +507,44 @@
         return codePointCount(casedText) == 1
                 ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
     }
+
+    @UsedForTesting
+    public static class Stringizer<E> {
+        public String stringize(final E element) {
+            return element != null ? element.toString() : "null";
+        }
+
+        @UsedForTesting
+        public final String join(final E[] array) {
+            return joinStringArray(toStringArray(array), null /* delimiter */);
+        }
+
+        @UsedForTesting
+        public final String join(final E[] array, final String delimiter) {
+            return joinStringArray(toStringArray(array), delimiter);
+        }
+
+        protected String[] toStringArray(final E[] array) {
+            final String[] stringArray = new String[array.length];
+            for (int index = 0; index < array.length; index++) {
+                stringArray[index] = stringize(array[index]);
+            }
+            return stringArray;
+        }
+
+        protected String joinStringArray(final String[] stringArray, final String delimiter) {
+            if (stringArray == null) {
+                return "null";
+            }
+            if (delimiter == null) {
+                return Arrays.toString(stringArray);
+            }
+            final StringBuilder sb = new StringBuilder();
+            for (int index = 0; index < stringArray.length; index++) {
+                sb.append(index == 0 ? "[" : delimiter);
+                sb.append(stringArray[index]);
+            }
+            return sb + "]";
+        }
+    }
 }
diff --git a/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
new file mode 100644
index 0000000..b918d47
--- /dev/null
+++ b/tests/src/com/android/inputmethod/keyboard/layout/expected/ActualKeyboardBuilder.java
@@ -0,0 +1,189 @@
+/*
+ * 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 com.android.inputmethod.keyboard.Key;
+import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
+import com.android.inputmethod.keyboard.internal.MoreKeySpec;
+import com.android.inputmethod.latin.Constants;
+import com.android.inputmethod.latin.utils.CollectionUtils;
+import com.android.inputmethod.latin.utils.StringUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * This class builds an actual keyboard for unit test.
+ */
+public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> {
+    // Comparator to sort {@link Key}s from top-left to bottom-right order.
+    private static final Comparator<Key> ROW_COLUMN_COMPARATOR = new Comparator<Key>() {
+        @Override
+        public int compare(final Key lhs, final Key rhs) {
+            if (lhs.getY() < rhs.getY()) return -1;
+            if (lhs.getY() > rhs.getY()) return 1;
+            if (lhs.getX() < rhs.getX()) return -1;
+            if (lhs.getX() > rhs.getX()) return 1;
+            return 0;
+        }
+    };
+
+    /**
+     * Create the keyboard that consists of the array of rows of the actual keyboard's keys.
+     * @param keys the array of keys of the actual keyboard.
+     * @return the actual keyboard grouped with rows.
+     */
+    public static Key[][] buildKeyboard(final Key[] keys) {
+        // Sort keys from top-left to bottom-right order to prepare to create rows.
+        final ArrayList<Key> sortedKeys = CollectionUtils.newArrayList(Arrays.asList(keys));
+        Collections.sort(sortedKeys, ROW_COLUMN_COMPARATOR);
+
+        // Grouping keys into rows.
+        final ArrayList<ArrayList<Key>> rows = CollectionUtils.newArrayList();
+        ArrayList<Key> elements = CollectionUtils.newArrayList();
+        int lastY = sortedKeys.get(0).getY();
+        for (final Key key : sortedKeys) {
+            if (lastY != key.getY()) {
+                // A new row is starting.
+                lastY = key.getY();
+                rows.add(elements);
+                elements = CollectionUtils.newArrayList();
+            }
+            elements.add(key);
+        }
+        rows.add(elements); // Add the last row.
+
+        // Calculate each dimension of rows and create a builder.
+        final int[] dimensions = new int[rows.size()];
+        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
+            dimensions[rowIndex] = rows.get(rowIndex).size();
+        }
+        final ActualKeyboardBuilder builder = new ActualKeyboardBuilder(dimensions);
+
+        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
+            final int row = rowIndex + 1;
+            final ArrayList<Key> rowKeys = rows.get(rowIndex);
+            builder.setRowAt(row, rowKeys.toArray(new Key[rowKeys.size()]));
+        }
+        return builder.build();
+    }
+
+    private ActualKeyboardBuilder(final int ... dimensions) {
+        super(dimensions);
+    }
+
+    @Override
+    Key defaultElement() { return null; }
+
+    @Override
+    Key[] newArray(final int size) { return new Key[size]; }
+
+    @Override
+    Key[][] newArrayOfArray(final int size) { return new Key[size][]; }
+
+    // Helper class to create concise representation from the key specification.
+    static class MoreKeySpecStringizer extends StringUtils.Stringizer<MoreKeySpec> {
+        static final MoreKeySpecStringizer STRINGIZER = new MoreKeySpecStringizer();
+
+        @Override
+        public String stringize(final MoreKeySpec spec) {
+            return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode);
+        }
+
+        static String toString(final String label, final int iconId, final String outputText,
+                final int code) {
+            final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED)
+                    ? KeyboardIconsSet.getIconName(iconId) : label;
+            final String output;
+            if (code == Constants.CODE_OUTPUT_TEXT) {
+                output = outputText;
+            } else if (code < Constants.CODE_SPACE) {
+                output = Constants.printableCode(code);
+            } else {
+                output = StringUtils.newSingleCodePointString(code);
+            }
+            if (visual.equals(output)) {
+                return visual;
+            }
+            return visual + "|" + output;
+        }
+    }
+
+    // Helper class to create concise representation from the key.
+    static class KeyStringizer extends StringUtils.Stringizer<Key> {
+        static final KeyStringizer STRINGIZER = new KeyStringizer();
+
+        @Override
+        public String stringize(final Key key) {
+            if (key == null) {
+                return "NULL";
+            }
+            if (key.isSpacer()) {
+                return "SPACER";
+            }
+            final StringBuilder sb = new StringBuilder();
+            sb.append(MoreKeySpecStringizer.toString(
+                    key.getLabel(), key.getIconId(), key.getOutputText(), key.getCode()));
+            final MoreKeySpec[] moreKeys = key.getMoreKeys();
+            if (moreKeys == null) {
+                return sb.toString();
+            }
+            sb.append("^");
+            sb.append(MoreKeySpecStringizer.STRINGIZER.join(moreKeys));
+            return sb.toString();
+        }
+    }
+
+    /**
+     * Convert the key to human readable string.
+     * @param key the key to be converted to string.
+     * @return the human readable representation of <code>key</code>.
+     */
+    public static String toString(final Key key) {
+        return KeyStringizer.STRINGIZER.stringize(key);
+    }
+
+    /**
+     * Convert the keyboard row to human readable string.
+     * @param keys the keyboard row to be converted to string.
+     * @return the human readable representation of <code>keys</code>.
+     */
+    public static String toString(final Key[] keys) {
+        return KeyStringizer.STRINGIZER.join(keys);
+    }
+
+    // Helper class to create concise representation from the array of the key.
+    static class KeyArrayStringizer extends StringUtils.Stringizer<Key[]> {
+        static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer();
+
+        @Override
+        public String stringize(final Key[] keyArray) {
+            return KeyStringizer.STRINGIZER.join(keyArray);
+        }
+    }
+
+    /**
+     * 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 Key[][] rows) {
+        return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */);
+    }
+}