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
17package com.android.inputmethod.keyboard.layout.expected;
18
19import com.android.inputmethod.keyboard.Key;
20import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
21import com.android.inputmethod.keyboard.internal.MoreKeySpec;
22import com.android.inputmethod.latin.common.Constants;
23import com.android.inputmethod.latin.common.StringUtils;
24
25import java.util.ArrayList;
26import java.util.List;
27
28import javax.annotation.Nonnull;
29import javax.annotation.Nullable;
30
31/**
32 * This class builds an actual keyboard for unit test.
33 *
34 * An actual keyboard is an array of rows, and a row consists of an array of {@link Key}s.
35 * Each row may have different number of {@link Key}s.
36 */
37public final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> {
38    private static ArrayList<Key> filterOutSpacer(final List<Key> keys) {
39        final ArrayList<Key> filteredKeys = new ArrayList<>();
40        for (final Key key : keys) {
41            if (key.isSpacer()) {
42                continue;
43            }
44            filteredKeys.add(key);
45        }
46        return filteredKeys;
47    }
48
49    /**
50     * Create the keyboard that consists of the array of rows of the actual keyboard's keys.
51     * @param sortedKeys keys list of the actual keyboard that is sorted from top-left to
52     * bottom-right.
53     * @return the actual keyboard grouped with rows.
54     */
55    public static Key[][] buildKeyboard(final List<Key> sortedKeys) {
56        // Filter out spacer to prepare to create rows.
57        final ArrayList<Key> filteredSortedKeys = filterOutSpacer(sortedKeys);
58
59        // Grouping keys into rows.
60        final ArrayList<ArrayList<Key>> rows = new ArrayList<>();
61        ArrayList<Key> elements = new ArrayList<>();
62        int lastY = filteredSortedKeys.get(0).getY();
63        for (final Key key : filteredSortedKeys) {
64            if (lastY != key.getY()) {
65                // A new row is starting.
66                lastY = key.getY();
67                rows.add(elements);
68                elements = new ArrayList<>();
69            }
70            elements.add(key);
71        }
72        rows.add(elements); // Add the last row.
73
74        // Calculate each dimension of rows and create a builder.
75        final int[] dimensions = new int[rows.size()];
76        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
77            dimensions[rowIndex] = rows.get(rowIndex).size();
78        }
79        final ActualKeyboardBuilder builder = new ActualKeyboardBuilder();
80
81        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
82            final int row = rowIndex + 1;
83            final ArrayList<Key> rowKeys = rows.get(rowIndex);
84            builder.setRowAt(row, rowKeys.toArray(new Key[rowKeys.size()]));
85        }
86        return builder.build();
87    }
88
89    @Override
90    Key defaultElement() { return null; }
91
92    @Override
93    Key[] newArray(final int size) { return new Key[size]; }
94
95    @Override
96    Key[][] newArrayOfArray(final int size) { return new Key[size][]; }
97
98    // Helper class to create concise representation from the key specification.
99    static class MoreKeySpecStringizer extends StringUtils.Stringizer<MoreKeySpec> {
100        static final MoreKeySpecStringizer STRINGIZER = new MoreKeySpecStringizer();
101
102        @Override
103        public String stringize(final MoreKeySpec spec) {
104            if (spec == null) {
105                return "null";
106            }
107            return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode);
108        }
109
110        @Nonnull
111        static String toString(final String label, final int iconId, final String outputText,
112                final int code) {
113            final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED)
114                    ? KeyboardIconsSet.getIconName(iconId) : label;
115            final String output;
116            if (code == Constants.CODE_OUTPUT_TEXT) {
117                output = outputText;
118            } else if (code < Constants.CODE_SPACE) {
119                output = Constants.printableCode(code);
120            } else {
121                output = StringUtils.newSingleCodePointString(code);
122            }
123            if (visual.equals(output)) {
124                return visual;
125            }
126            return visual + "|" + output;
127        }
128    }
129
130    // Helper class to create concise representation from the key.
131    static class KeyStringizer extends StringUtils.Stringizer<Key> {
132        static final KeyStringizer STRINGIZER = new KeyStringizer();
133
134        @Override
135        public String stringize(@Nullable final Key key) {
136            if (key == null) {
137                return "NULL";
138            }
139            if (key.isSpacer()) {
140                return "SPACER";
141            }
142            final StringBuilder sb = new StringBuilder();
143            sb.append(MoreKeySpecStringizer.toString(
144                    key.getLabel(), key.getIconId(), key.getOutputText(), key.getCode()));
145            final MoreKeySpec[] moreKeys = key.getMoreKeys();
146            if (moreKeys == null) {
147                return sb.toString();
148            }
149            sb.append("^");
150            sb.append(MoreKeySpecStringizer.STRINGIZER.join(moreKeys));
151            return sb.toString();
152        }
153    }
154
155    /**
156     * Convert the key to human readable string.
157     * @param key the key to be converted to string.
158     * @return the human readable representation of <code>key</code>.
159     */
160    @Nonnull
161    public static String toString(@Nullable final Key key) {
162        return KeyStringizer.STRINGIZER.stringize(key);
163    }
164
165    /**
166     * Convert the keyboard row to human readable string.
167     * @param keys the keyboard row to be converted to string.
168     * @return the human readable representation of <code>keys</code>.
169     */
170    @Nonnull
171    public static String toString(@Nullable final Key[] keys) {
172        return KeyStringizer.STRINGIZER.join(keys);
173    }
174
175    // Helper class to create concise representation from the array of the key.
176    static class KeyArrayStringizer extends StringUtils.Stringizer<Key[]> {
177        static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer();
178
179        @Override
180        public String stringize(@Nullable final Key[] keyArray) {
181            return KeyStringizer.STRINGIZER.join(keyArray);
182        }
183    }
184
185    /**
186     * Convert the keyboard to human readable string.
187     * @param rows the keyboard to be converted to string.
188     * @return the human readable representation of <code>rows</code>.
189     */
190    @Nonnull
191    public static String toString(@Nullable final Key[][] rows) {
192        return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */);
193    }
194}
195