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