1ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka/*
2ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * Copyright (C) 2014 The Android Open Source Project
3ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka *
4ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
5ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * you may not use this file except in compliance with the License.
6ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * You may obtain a copy of the License at
7ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka *
8ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
9ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka *
10ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
11ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
12ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * See the License for the specific language governing permissions and
14ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * limitations under the License.
15ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka */
16ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
17ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokapackage com.android.inputmethod.keyboard.layout.expected;
18ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
19ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
20ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
21ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokaimport com.android.inputmethod.keyboard.internal.MoreKeySpec;
229342484e8d573a40f470b6a593df31c602fa4076Ken Wakasaimport com.android.inputmethod.latin.common.Constants;
234beeb9253a06482299e0c67467531d30436a02fcJean Chalardimport com.android.inputmethod.latin.common.StringUtils;
24ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
25ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokaimport java.util.ArrayList;
265326dcfb7dbdc1a3fc9cfb94046805f18bf3d3d7Tadashi G. Takaokaimport java.util.List;
27ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
2880980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaokaimport javax.annotation.Nonnull;
2980980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaokaimport javax.annotation.Nullable;
3080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka
31ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka/**
32ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka * This class builds an actual keyboard for unit test.
33e6f467c0fe694d93b7f000fcca509587014fb7e8Tadashi G. Takaoka *
34e6f467c0fe694d93b7f000fcca509587014fb7e8Tadashi G. Takaoka * An actual keyboard is an array of rows, and a row consists of an array of {@link Key}s.
35e6f467c0fe694d93b7f000fcca509587014fb7e8Tadashi G. Takaoka * Each row may have different number of {@link Key}s.
36ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka */
37ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaokapublic final class ActualKeyboardBuilder extends AbstractKeyboardBuilder<Key> {
38c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka    private static ArrayList<Key> filterOutSpacer(final List<Key> keys) {
39a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<Key> filteredKeys = new ArrayList<>();
408c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka        for (final Key key : keys) {
418c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka            if (key.isSpacer()) {
428c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka                continue;
438c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka            }
448c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka            filteredKeys.add(key);
458c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka        }
468c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka        return filteredKeys;
478c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka    }
488c6b34e51dfd139d55ad1ea7d6e39a7223117fc3Tadashi G. Takaoka
49ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    /**
50ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * Create the keyboard that consists of the array of rows of the actual keyboard's keys.
51e6f467c0fe694d93b7f000fcca509587014fb7e8Tadashi G. Takaoka     * @param sortedKeys keys list of the actual keyboard that is sorted from top-left to
52e6f467c0fe694d93b7f000fcca509587014fb7e8Tadashi G. Takaoka     * bottom-right.
53ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @return the actual keyboard grouped with rows.
54ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     */
55c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka    public static Key[][] buildKeyboard(final List<Key> sortedKeys) {
56c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        // Filter out spacer to prepare to create rows.
57c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        final ArrayList<Key> filteredSortedKeys = filterOutSpacer(sortedKeys);
58ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
59ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        // Grouping keys into rows.
60a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<ArrayList<Key>> rows = new ArrayList<>();
61a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        ArrayList<Key> elements = new ArrayList<>();
62c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        int lastY = filteredSortedKeys.get(0).getY();
63c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        for (final Key key : filteredSortedKeys) {
64ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (lastY != key.getY()) {
65ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                // A new row is starting.
66ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                lastY = key.getY();
67ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                rows.add(elements);
68a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka                elements = new ArrayList<>();
69ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
70ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            elements.add(key);
71ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
72ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        rows.add(elements); // Add the last row.
73ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
74ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        // Calculate each dimension of rows and create a builder.
75ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        final int[] dimensions = new int[rows.size()];
76ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        for (int rowIndex = 0; rowIndex < dimensions.length; rowIndex++) {
77ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            dimensions[rowIndex] = rows.get(rowIndex).size();
78ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
79cd15cfdaaba7f361f4744bd3ff51ce6cdae1e608Tadashi G. Takaoka        final ActualKeyboardBuilder builder = new ActualKeyboardBuilder();
80ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
81ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        for (int rowIndex = 0; rowIndex < rows.size(); rowIndex++) {
82ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final int row = rowIndex + 1;
83ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final ArrayList<Key> rowKeys = rows.get(rowIndex);
84ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            builder.setRowAt(row, rowKeys.toArray(new Key[rowKeys.size()]));
85ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
86ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        return builder.build();
87ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
88ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
89ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    @Override
90ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    Key defaultElement() { return null; }
91ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
92ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    @Override
93ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    Key[] newArray(final int size) { return new Key[size]; }
94ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
95ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    @Override
96ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    Key[][] newArrayOfArray(final int size) { return new Key[size][]; }
97ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
98ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    // Helper class to create concise representation from the key specification.
99ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    static class MoreKeySpecStringizer extends StringUtils.Stringizer<MoreKeySpec> {
100ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        static final MoreKeySpecStringizer STRINGIZER = new MoreKeySpecStringizer();
101ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
102ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        @Override
103ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        public String stringize(final MoreKeySpec spec) {
10480980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka            if (spec == null) {
10580980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka                return "null";
10680980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka            }
107ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            return toString(spec.mLabel, spec.mIconId, spec.mOutputText, spec.mCode);
108ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
109ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
11080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka        @Nonnull
111ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        static String toString(final String label, final int iconId, final String outputText,
112ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                final int code) {
113ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final String visual = (iconId != KeyboardIconsSet.ICON_UNDEFINED)
114ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                    ? KeyboardIconsSet.getIconName(iconId) : label;
115ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final String output;
116ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (code == Constants.CODE_OUTPUT_TEXT) {
117ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                output = outputText;
118ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            } else if (code < Constants.CODE_SPACE) {
119ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                output = Constants.printableCode(code);
120ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            } else {
121ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                output = StringUtils.newSingleCodePointString(code);
122ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
123ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (visual.equals(output)) {
124ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                return visual;
125ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
126ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            return visual + "|" + output;
127ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
128ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
129ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
130ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    // Helper class to create concise representation from the key.
131ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    static class KeyStringizer extends StringUtils.Stringizer<Key> {
132ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        static final KeyStringizer STRINGIZER = new KeyStringizer();
133ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
134ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        @Override
13580980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka        public String stringize(@Nullable final Key key) {
136ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (key == null) {
137ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                return "NULL";
138ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
139ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (key.isSpacer()) {
140ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                return "SPACER";
141ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
142ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final StringBuilder sb = new StringBuilder();
143ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            sb.append(MoreKeySpecStringizer.toString(
144ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                    key.getLabel(), key.getIconId(), key.getOutputText(), key.getCode()));
145ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            final MoreKeySpec[] moreKeys = key.getMoreKeys();
146ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            if (moreKeys == null) {
147ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka                return sb.toString();
148ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            }
149ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            sb.append("^");
150ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            sb.append(MoreKeySpecStringizer.STRINGIZER.join(moreKeys));
151ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            return sb.toString();
152ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
153ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
154ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
155ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    /**
156ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * Convert the key to human readable string.
157ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @param key the key to be converted to string.
158ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @return the human readable representation of <code>key</code>.
159ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     */
16080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    @Nonnull
16180980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    public static String toString(@Nullable final Key key) {
162ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        return KeyStringizer.STRINGIZER.stringize(key);
163ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
164ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
165ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    /**
166ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * Convert the keyboard row to human readable string.
167ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @param keys the keyboard row to be converted to string.
168ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @return the human readable representation of <code>keys</code>.
169ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     */
17080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    @Nonnull
17180980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    public static String toString(@Nullable final Key[] keys) {
172ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        return KeyStringizer.STRINGIZER.join(keys);
173ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
174ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
175ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    // Helper class to create concise representation from the array of the key.
176ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    static class KeyArrayStringizer extends StringUtils.Stringizer<Key[]> {
177ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        static final KeyArrayStringizer STRINGIZER = new KeyArrayStringizer();
178ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
179ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        @Override
18080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka        public String stringize(@Nullable final Key[] keyArray) {
181ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka            return KeyStringizer.STRINGIZER.join(keyArray);
182ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        }
183ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
184ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka
185ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    /**
186ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * Convert the keyboard to human readable string.
187ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @param rows the keyboard to be converted to string.
188ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     * @return the human readable representation of <code>rows</code>.
189ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka     */
19080980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    @Nonnull
19180980574acfef74f9392da4fdbcba64d911cb66fTadashi G. Takaoka    public static String toString(@Nullable final Key[][] rows) {
192ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka        return KeyArrayStringizer.STRINGIZER.join(rows, "\n" /* delimiter */);
193ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka    }
194ff8405cdfbd575657a6f615a1ac4d86eb1b07f74Tadashi G. Takaoka}
195