19b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka/*
28632bff2d5a8e1160989008dea6eff4b94b065ddTadashi G. Takaoka * Copyright (C) 2010 The Android Open Source Project
39b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka *
49b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License"); you may not
59b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * use this file except in compliance with the License. You may obtain a copy of
69b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * the License at
79b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka *
89b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * http://www.apache.org/licenses/LICENSE-2.0
99b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka *
109b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
119b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
129b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
139b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * License for the specific language governing permissions and limitations under
149b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * the License.
159b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka */
169b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1772934bd5967d0127f71fd4d66158b18b4e6ceefeTadashi G. Takaokapackage com.android.inputmethod.keyboard.internal;
189b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
197ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaokaimport static com.android.inputmethod.keyboard.Keyboard.CODE_UNSPECIFIED;
207ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka
219b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaokaimport android.text.TextUtils;
22c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka
23c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
245f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaokaimport com.android.inputmethod.latin.CollectionUtils;
2515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaokaimport com.android.inputmethod.latin.LatinImeLogger;
26cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaokaimport com.android.inputmethod.latin.StringUtils;
279b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
282fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaokaimport java.util.ArrayList;
2915c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaokaimport java.util.Arrays;
307ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaokaimport java.util.Locale;
312fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
329b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka/**
332be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka * The string parser of more keys specification.
342be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka * The specification is comma separated texts each of which represents one "more key".
352be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka * The specification might have label or string resource reference in it. These references are
362be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka * expanded before parsing comma.
376bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka * - Label reference should be a string representation of label (!text/label_name)
386bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka * - String resource reference should be a string representation of resource (!text/resource_name)
399d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka * Each "more key" specification is one of the following:
409b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
417f0c503afc9a130da73503e97108d2ca5ae328adTadashi G. Takaoka * - Icon followed by keyOutputText or code (!icon/icon_name|!code/code_name)
427f0c503afc9a130da73503e97108d2ca5ae328adTadashi G. Takaoka *   - Icon should be a string representation of icon (!icon/icon_name).
432be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka *   - Code should be a code point presented by hexadecimal string prefixed with "0x", or a string
442be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka *     representation of code (!code/code_name).
450086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character.
462be51f4fd0c5cd70c7a2757558ffe45e703700cfTadashi G. Takaoka * Note that the '\' is also parsed by XML parser and CSV parser as well.
470086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka * See {@link KeyboardIconsSet} about icon_name.
489b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka */
49a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaokapublic final class KeySpecParser {
5015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka    private static final boolean DEBUG = LatinImeLogger.sDBG;
510086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
52aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka    private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;
53aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka
540086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    // Constants for parsing.
550086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    private static int COMMA = ',';
560086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    private static final char ESCAPE_CHAR = '\\';
57e54a4005d569cddbf8610dfd3e9afaec540fa060Tadashi G. Takaoka    private static final char LABEL_END = '|';
586bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka    private static final String PREFIX_TEXT = "!text/";
5935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    static final String PREFIX_ICON = "!icon/";
60ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka    private static final String PREFIX_CODE = "!code/";
61ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka    private static final String PREFIX_HEX = "0x";
6215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka    private static final String ADDITIONAL_MORE_KEY_MARKER = "%";
639b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
64e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka    private KeySpecParser() {
659b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        // Intentional empty constructor for utility class.
669b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
679b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
6835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static boolean hasIcon(final String moreKeySpec) {
690a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka        return moreKeySpec.startsWith(PREFIX_ICON);
709b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
719b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
7235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static boolean hasCode(final String moreKeySpec) {
739d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final int end = indexOfLabelEnd(moreKeySpec, 0);
740a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka        if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
750a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka                PREFIX_CODE, end + 1)) {
769b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return true;
779b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
789b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return false;
799b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
809b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
8135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static String parseEscape(final String text) {
820086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        if (text.indexOf(ESCAPE_CHAR) < 0) {
839b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return text;
8423bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
859b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int length = text.length();
869b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final StringBuilder sb = new StringBuilder();
879b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        for (int pos = 0; pos < length; pos++) {
889b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final char c = text.charAt(pos);
890086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            if (c == ESCAPE_CHAR && pos + 1 < length) {
90e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka                // Skip escape char
91e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka                pos++;
92e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka                sb.append(text.charAt(pos));
939b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            } else {
949b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                sb.append(c);
959b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            }
969b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
979b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return sb.toString();
989b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
999b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
10035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
1010086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        if (moreKeySpec.indexOf(ESCAPE_CHAR, start) < 0) {
1029d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            final int end = moreKeySpec.indexOf(LABEL_END, start);
10323bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            if (end == 0) {
1040086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                throw new KeySpecParserError(LABEL_END + " at " + start + ": " + moreKeySpec);
10523bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            }
1069b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return end;
1079b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1089d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final int length = moreKeySpec.length();
1099b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        for (int pos = start; pos < length; pos++) {
1109d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            final char c = moreKeySpec.charAt(pos);
1110086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            if (c == ESCAPE_CHAR && pos + 1 < length) {
112e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka                // Skip escape char
1139b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                pos++;
114e54a4005d569cddbf8610dfd3e9afaec540fa060Tadashi G. Takaoka            } else if (c == LABEL_END) {
1159b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                return pos;
1169b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            }
1179b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1189b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return -1;
1199b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1209b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
12135ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static String getLabel(final String moreKeySpec) {
12223bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        if (hasIcon(moreKeySpec)) {
1239b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return null;
12423bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
1259d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final int end = indexOfLabelEnd(moreKeySpec, 0);
1269d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
1279d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka                : parseEscape(moreKeySpec);
12823bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        if (TextUtils.isEmpty(label)) {
1290086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            throw new KeySpecParserError("Empty label: " + moreKeySpec);
13023bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
1319b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return label;
1329b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1339b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
13435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static String getOutputTextInternal(final String moreKeySpec) {
135c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        final int end = indexOfLabelEnd(moreKeySpec, 0);
136c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        if (end <= 0) {
137c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka            return null;
138c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        }
139c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
140c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka            throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
141c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        }
142c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        return parseEscape(moreKeySpec.substring(end + /* LABEL_END */1));
143c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka    }
144c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka
14535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    static String getOutputText(final String moreKeySpec) {
14623bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        if (hasCode(moreKeySpec)) {
1479b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return null;
14823bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
149c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        final String outputText = getOutputTextInternal(moreKeySpec);
150c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        if (outputText != null) {
151cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka            if (StringUtils.codePointCount(outputText) == 1) {
152c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka                // If output text is one code point, it should be treated as a code.
153c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka                // See {@link #getCode(Resources, String)}.
154c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka                return null;
15523bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            }
15623bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            if (!TextUtils.isEmpty(outputText)) {
1579b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                return outputText;
15823bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            }
1590086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            throw new KeySpecParserError("Empty outputText: " + moreKeySpec);
1609b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1619d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final String label = getLabel(moreKeySpec);
16223bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        if (label == null) {
1630086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            throw new KeySpecParserError("Empty label: " + moreKeySpec);
16423bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
165c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        // Code is automatically generated for one letter label. See {@link getCode()}.
166cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka        return (StringUtils.codePointCount(label) == 1) ? null : label;
1679b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1689b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
16935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
1709d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        if (hasCode(moreKeySpec)) {
1719d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            final int end = indexOfLabelEnd(moreKeySpec, 0);
17223bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
1730086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                throw new KeySpecParserError("Multiple " + LABEL_END + ": " + moreKeySpec);
17423bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            }
175ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka            return parseCode(moreKeySpec.substring(end + 1), codesSet, Keyboard.CODE_UNSPECIFIED);
1769b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
177c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        final String outputText = getOutputTextInternal(moreKeySpec);
178c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka        if (outputText != null) {
179c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka            // If output text is one code point, it should be treated as a code.
180c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka            // See {@link #getOutputText(String)}.
181cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka            if (StringUtils.codePointCount(outputText) == 1) {
182c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka                return outputText.codePointAt(0);
183c217dc9237e5d1e1e721b9007139d771dcb41145Tadashi G. Takaoka            }
18423bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka            return Keyboard.CODE_OUTPUT_TEXT;
18523bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
1869d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka        final String label = getLabel(moreKeySpec);
1879b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        // Code is automatically generated for one letter label.
188cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka        if (StringUtils.codePointCount(label) == 1) {
189e01d272603f3643ce613e61dd3204379f4f4fb73Tadashi G. Takaoka            return label.codePointAt(0);
19023bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        }
19123bacdb6a58cf22535aea8d22d3b6e14ea23667eTadashi G. Takaoka        return Keyboard.CODE_OUTPUT_TEXT;
1929b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1939b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
19435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static int parseCode(final String text, final KeyboardCodesSet codesSet,
19535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final int defCode) {
196ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka        if (text == null) return defCode;
1970a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka        if (text.startsWith(PREFIX_CODE)) {
198ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka            return codesSet.getCode(text.substring(PREFIX_CODE.length()));
1990a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka        } else if (text.startsWith(PREFIX_HEX)) {
200ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka            return Integer.parseInt(text.substring(PREFIX_HEX.length()), 16);
201ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka        } else {
202ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka            return Integer.parseInt(text);
203ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka        }
204ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka    }
205ed3bac91f242850c6d1833a5f8981b9cc208c5ddTadashi G. Takaoka
20635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static int getIconId(final String moreKeySpec) {
207cf41aff251ecc94b729307ede05208a104fcd8b0Tadashi G. Takaoka        if (moreKeySpec != null && hasIcon(moreKeySpec)) {
208b009a24b838b560bd093ff295c99c0cf5fe27c81Tadashi G. Takaoka            final int end = moreKeySpec.indexOf(LABEL_END, PREFIX_ICON.length());
209cf41aff251ecc94b729307ede05208a104fcd8b0Tadashi G. Takaoka            final String name = (end < 0) ? moreKeySpec.substring(PREFIX_ICON.length())
210cf41aff251ecc94b729307ede05208a104fcd8b0Tadashi G. Takaoka                    : moreKeySpec.substring(PREFIX_ICON.length(), end);
211e7948a8692a102c89aca70a79ca62573d77efde3Tadashi G. Takaoka            return KeyboardIconsSet.getIconId(name);
2129b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
213c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka        return KeyboardIconsSet.ICON_UNDEFINED;
2149b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
2159b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
21635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static <T> ArrayList<T> arrayAsList(final T[] array, final int start, final int end) {
217aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        if (array == null) {
218aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            throw new NullPointerException();
219aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
220aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        if (start < 0 || start > end || end > array.length) {
221aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            throw new IllegalArgumentException();
222aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
223aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
2245f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka        final ArrayList<T> list = CollectionUtils.newArrayList(end - start);
225aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        for (int i = start; i < end; i++) {
226aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            list.add(array[i]);
227aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
228aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        return list;
229aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka    }
230aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
231aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka    private static final String[] EMPTY_STRING_ARRAY = new String[0];
232aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
23335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static String[] filterOutEmptyString(final String[] array) {
234aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        if (array == null) {
235aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            return EMPTY_STRING_ARRAY;
236aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
237aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        ArrayList<String> out = null;
238aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        for (int i = 0; i < array.length; i++) {
239aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            final String entry = array[i];
240aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            if (TextUtils.isEmpty(entry)) {
241aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                if (out == null) {
242aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                    out = arrayAsList(array, 0, i);
243aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                }
244aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            } else if (out != null) {
245aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                out.add(entry);
246aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            }
247aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
248aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        if (out == null) {
249aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            return array;
250aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        }
2517ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        return out.toArray(new String[out.size()]);
252aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka    }
253aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka
25435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
25535ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final String[] additionalMoreKeySpecs) {
256aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
257aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
258aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        final int moreKeysCount = moreKeys.length;
259aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        final int additionalCount = additionalMoreKeys.length;
26015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        ArrayList<String> out = null;
26115c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        int additionalIndex = 0;
26215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
26315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            final String moreKeySpec = moreKeys[moreKeyIndex];
26415c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
26515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                if (additionalIndex < additionalCount) {
26615c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    // Replace '%' marker with additional more key specification.
26715c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    final String additionalMoreKey = additionalMoreKeys[additionalIndex];
26815c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    if (out != null) {
26915c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        out.add(additionalMoreKey);
27015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    } else {
27115c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        moreKeys[moreKeyIndex] = additionalMoreKey;
27215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    }
27315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    additionalIndex++;
27415c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                } else {
27515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    // Filter out excessive '%' marker.
27615c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    if (out == null) {
277aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka                        out = arrayAsList(moreKeys, 0, moreKeyIndex);
2782fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                    }
2792fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                }
28015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            } else {
28115c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                if (out != null) {
28215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                    out.add(moreKeySpec);
28315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                }
28415c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            }
28515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        }
28615c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        if (additionalCount > 0 && additionalIndex == 0) {
28715c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            // No '%' marker is found in more keys.
28815c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            // Insert all additional more keys to the head of more keys.
28915c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            if (DEBUG && out != null) {
29015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                throw new RuntimeException("Internal logic error:"
29115c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        + " moreKeys=" + Arrays.toString(moreKeys)
29215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
29315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            }
294aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            out = arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
29515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            for (int i = 0; i < moreKeysCount; i++) {
29615c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                out.add(moreKeys[i]);
29715c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            }
29815c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka        } else if (additionalIndex < additionalCount) {
29915c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            // The number of '%' markers are less than additional more keys.
30015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            // Append remained additional more keys to the tail of more keys.
30115c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            if (DEBUG && out != null) {
30215c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                throw new RuntimeException("Internal logic error:"
30315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        + " moreKeys=" + Arrays.toString(moreKeys)
30415c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
30515c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            }
306aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            out = arrayAsList(moreKeys, 0, moreKeysCount);
30715c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            for (int i = additionalIndex; i < additionalCount; i++) {
30815c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka                out.add(additionalMoreKeys[additionalIndex]);
3092fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            }
3102fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
311aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        if (out == null && moreKeysCount > 0) {
3129d5601e9013c5ec9a7ac75db16f4a0a8218b02bfTadashi G. Takaoka            return moreKeys;
313aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        } else if (out != null && out.size() > 0) {
314aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            return out.toArray(new String[out.size()]);
315aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka        } else {
316aeeed758480b0fac848f4556884d978f3004555bTadashi G. Takaoka            return null;
3172fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
31815c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka    }
31915c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka
32015c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka    @SuppressWarnings("serial")
321a28a05e971cc242b338331a3b78276fa95188d19Tadashi G. Takaoka    public static final class KeySpecParserError extends RuntimeException {
32235ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka        public KeySpecParserError(final String message) {
32315c99e9f1a7776b95325d36cf9e38b6d674e483bTadashi G. Takaoka            super(message);
3242fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
3252fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    }
3260086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
32735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static String resolveTextReference(final String rawText,
32835ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final KeyboardTextsSet textsSet) {
329aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        int level = 0;
330aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        String text = rawText;
331aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        StringBuilder sb;
332aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        do {
333aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            level++;
334aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            if (level >= MAX_STRING_REFERENCE_INDIRECTION) {
335aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                throw new RuntimeException("too many @string/resource indirection: " + text);
336aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            }
3370086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
3386bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka            final int prefixLen = PREFIX_TEXT.length();
339aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            final int size = text.length();
3402f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka            if (size < prefixLen) {
341aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                return text;
342aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            }
343aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka
344aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            sb = null;
345aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            for (int pos = 0; pos < size; pos++) {
346aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                final char c = text.charAt(pos);
3470a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka                if (text.startsWith(PREFIX_TEXT, pos) && textsSet != null) {
348aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    if (sb == null) {
349aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                        sb = new StringBuilder(text.substring(0, pos));
350aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    }
3516bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka                    final int end = searchTextNameEnd(text, pos + prefixLen);
3522f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka                    final String name = text.substring(pos + prefixLen, end);
3536bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka                    sb.append(textsSet.getText(name));
354aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    pos = end - 1;
355aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                } else if (c == ESCAPE_CHAR) {
356aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    if (sb != null) {
357aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                        // Append both escape character and escaped character.
358aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                        sb.append(text.substring(pos, Math.min(pos + 2, size)));
359aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    }
360aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    pos++;
361aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                } else if (sb != null) {
362aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                    sb.append(c);
3630086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                }
3640086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            }
365aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka
366aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            if (sb != null) {
367aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka                text = sb.toString();
368aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka            }
369aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        } while (sb != null);
370aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka
371aca8870128caeec74ed4426f3c1e23ab60597453Tadashi G. Takaoka        return text;
3720086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    }
3730086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
37435ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    private static int searchTextNameEnd(final String text, final int start) {
3750086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        final int size = text.length();
3765852a2594f4cce518e0b18069c895c2f8561d093Tadashi G. Takaoka        for (int pos = start; pos < size; pos++) {
3770086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            final char c = text.charAt(pos);
3782f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka            // Label name should be consisted of [a-zA-Z_0-9].
3790a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka            if ((c >= 'a' && c <= 'z') || c == '_' || (c >= '0' && c <= '9')) {
3800086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                continue;
3810086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            }
3820086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            return pos;
3830086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        }
3840086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        return size;
3850086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    }
3860086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
38735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static String[] parseCsvString(final String rawText, final KeyboardTextsSet textsSet) {
3886bfd5f631908c4afd893c9b25b353e5e16c5fc0cTadashi G. Takaoka        final String text = resolveTextReference(rawText, textsSet);
3890086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        final int size = text.length();
3900086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        if (size == 0) {
3910086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            return null;
3920086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        }
393cc8c8b99bd0463f5977dea82f5e2379ea1dd4e73Tadashi G. Takaoka        if (StringUtils.codePointCount(text) == 1) {
39424cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka            return text.codePointAt(0) == COMMA ? null : new String[] { text };
3950086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        }
3960086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka
3970086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        ArrayList<String> list = null;
3980086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        int start = 0;
3990086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        for (int pos = 0; pos < size; pos++) {
4000086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            final char c = text.charAt(pos);
4010086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            if (c == COMMA) {
40224cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka                // Skip empty entry.
40324cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka                if (pos - start > 0) {
40424cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka                    if (list == null) {
4055f282ea9e4a4590fcbab6e27d5fca7dacbb40a6aTadashi G. Takaoka                        list = CollectionUtils.newArrayList();
40624cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka                    }
40724cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka                    list.add(text.substring(start, pos));
4080086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                }
4090086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                // Skip comma
4100086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka                start = pos + 1;
4115852a2594f4cce518e0b18069c895c2f8561d093Tadashi G. Takaoka            } else if (c == ESCAPE_CHAR) {
4125852a2594f4cce518e0b18069c895c2f8561d093Tadashi G. Takaoka                // Skip escape character and escaped character.
4135852a2594f4cce518e0b18069c895c2f8561d093Tadashi G. Takaoka                pos++;
4140086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka            }
4150086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        }
41624cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka        final String remain = (size - start > 0) ? text.substring(start) : null;
4170086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        if (list == null) {
41824cd2617f527d3d52c7fa36463002c47024083c8Tadashi G. Takaoka            return remain != null ? new String[] { remain } : null;
4190086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka        }
4207ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        if (remain != null) {
4217ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka            list.add(remain);
4227ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        }
4237ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        return list.toArray(new String[list.size()]);
4240086861d5a2d16f86e2130ee9a7ec46ca533fadbTadashi G. Takaoka    }
42542fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka
42635ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static int getIntValue(final String[] moreKeys, final String key,
42735ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final int defaultValue) {
42842fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        if (moreKeys == null) {
42942fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            return defaultValue;
43042fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        }
4312f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka        final int keyLen = key.length();
43242fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        boolean foundValue = false;
43342fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        int value = defaultValue;
43442fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        for (int i = 0; i < moreKeys.length; i++) {
43542fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            final String moreKeySpec = moreKeys[i];
4360a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka            if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
43742fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka                continue;
43842fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            }
43942fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            moreKeys[i] = null;
44042fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            try {
44142fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka                if (!foundValue) {
4422f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka                    value = Integer.parseInt(moreKeySpec.substring(keyLen));
4432f16fd40faab7287dfcae4899050b9df360d0c29Tadashi G. Takaoka                    foundValue = true;
44442fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka                }
44542fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            } catch (NumberFormatException e) {
44642fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka                throw new RuntimeException(
44742fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka                        "integer should follow after " + key + ": " + moreKeySpec);
44842fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka            }
44942fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        }
45042fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka        return value;
45142fd1d2d72c097b2227d4b22f0f824dbb34a4d0cTadashi G. Takaoka    }
452e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka
45335ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static boolean getBooleanValue(final String[] moreKeys, final String key) {
454e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        if (moreKeys == null) {
455e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka            return false;
456e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        }
457e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        boolean value = false;
458e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        for (int i = 0; i < moreKeys.length; i++) {
459e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka            final String moreKeySpec = moreKeys[i];
4600a3362d26474cbd60bf870be7ad1413359e4fad6Tadashi G. Takaoka            if (moreKeySpec == null || !moreKeySpec.equals(key)) {
461e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka                continue;
462e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka            }
463e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka            moreKeys[i] = null;
464e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka            value = true;
465e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        }
466e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka        return value;
467e4c45c6ef920b9cd1754f345446f53c504a64c5fTadashi G. Takaoka    }
4687ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka
46935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static int toUpperCaseOfCodeForLocale(final int code, final boolean needsToUpperCase,
47035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final Locale locale) {
4717ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        if (!Keyboard.isLetterCode(code) || !needsToUpperCase) return code;
4727ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        final String text = new String(new int[] { code } , 0, 1);
4737ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        final String casedText = KeySpecParser.toUpperCaseOfStringForLocale(
4747ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka                text, needsToUpperCase, locale);
4757ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        return StringUtils.codePointCount(casedText) == 1
4767ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka                ? casedText.codePointAt(0) : CODE_UNSPECIFIED;
4777ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka    }
4787ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka
47935ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka    public static String toUpperCaseOfStringForLocale(final String text,
48035ff94547c16c84c5b6fafdae0b4a683be782b97Tadashi G. Takaoka            final boolean needsToUpperCase, final Locale locale) {
4817ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        if (text == null || !needsToUpperCase) return text;
4827ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka        return text.toUpperCase(locale);
4837ab7f66c2d0f4a0b2e29be718b310ccaf368a4f4Tadashi G. Takaoka    }
4849b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka}
485