KeySpecParser.java revision 2fe68b9616ebdeb24daf043fbc590ea6a11f10a0
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
199b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaokaimport android.content.res.Resources;
209b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaokaimport android.text.TextUtils;
21c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaokaimport android.util.Log;
22c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka
23c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
24c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaokaimport com.android.inputmethod.latin.R;
259b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
262fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaokaimport java.util.ArrayList;
272fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
289b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka/**
299b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * String parser of popupCharacters attribute of Key.
309b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * The string is comma separated texts each of which represents one popup key.
319b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * Each popup key text is one of the following:
329b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * - A single letter (Letter)
339b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
34c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka * - Icon followed by keyOutputText or code (@icon/icon_number|@integer/key_code)
359b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\'
369b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * character.
379b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka * Note that the character '@' and '\' are also parsed by XML parser and CSV parser as well.
38c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka * See {@link KeyboardIconsSet} about icon_number.
399b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka */
409b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaokapublic class PopupCharactersParser {
41c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka    private static final String TAG = PopupCharactersParser.class.getSimpleName();
42c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka
439b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static final char ESCAPE = '\\';
449b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static final String LABEL_END = "|";
459b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static final String PREFIX_AT = "@";
46c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka    private static final String PREFIX_ICON = PREFIX_AT + "icon/";
479b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static final String PREFIX_CODE = PREFIX_AT + "integer/";
489b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
499b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private PopupCharactersParser() {
509b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        // Intentional empty constructor for utility class.
519b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
529b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
539b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static boolean hasIcon(String popupSpec) {
549b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (popupSpec.startsWith(PREFIX_ICON)) {
559b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final int end = indexOfLabelEnd(popupSpec, 0);
569b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (end > 0)
579b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                return true;
589b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            throw new PopupCharactersParserError("outputText or code not specified: " + popupSpec);
599b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
609b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return false;
619b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
629b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
639b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static boolean hasCode(String popupSpec) {
649b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int end = indexOfLabelEnd(popupSpec, 0);
659b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (end > 0 && end + 1 < popupSpec.length()
669b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                && popupSpec.substring(end + 1).startsWith(PREFIX_CODE)) {
679b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return true;
689b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
699b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return false;
709b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
719b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
729b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static String parseEscape(String text) {
739b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (text.indexOf(ESCAPE) < 0)
749b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return text;
759b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int length = text.length();
769b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final StringBuilder sb = new StringBuilder();
779b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        for (int pos = 0; pos < length; pos++) {
789b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final char c = text.charAt(pos);
799b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (c == ESCAPE && pos + 1 < length) {
809b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                sb.append(text.charAt(++pos));
819b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            } else {
829b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                sb.append(c);
839b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            }
849b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
859b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return sb.toString();
869b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
879b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
889b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static int indexOfLabelEnd(String popupSpec, int start) {
899b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (popupSpec.indexOf(ESCAPE, start) < 0) {
909b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final int end = popupSpec.indexOf(LABEL_END, start);
919b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (end == 0)
929b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                throw new PopupCharactersParserError(LABEL_END + " at " + start + ": " + popupSpec);
939b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return end;
949b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
959b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int length = popupSpec.length();
969b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        for (int pos = start; pos < length; pos++) {
979b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final char c = popupSpec.charAt(pos);
989b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (c == ESCAPE && pos + 1 < length) {
999b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                pos++;
1009b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            } else if (popupSpec.startsWith(LABEL_END, pos)) {
1019b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                return pos;
1029b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            }
1039b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1049b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return -1;
1059b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1069b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1079b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    public static String getLabel(String popupSpec) {
1089b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (hasIcon(popupSpec))
1099b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return null;
1109b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int end = indexOfLabelEnd(popupSpec, 0);
1119b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final String label = (end > 0) ? parseEscape(popupSpec.substring(0, end))
1129b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                : parseEscape(popupSpec);
1139b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (TextUtils.isEmpty(label))
1149b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            throw new PopupCharactersParserError("Empty label: " + popupSpec);
1159b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return label;
1169b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1179b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1189b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    public static String getOutputText(String popupSpec) {
1199b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (hasCode(popupSpec))
1209b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return null;
1219b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final int end = indexOfLabelEnd(popupSpec, 0);
1229b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (end > 0) {
1239b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (indexOfLabelEnd(popupSpec, end + 1) >= 0)
1249b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                    throw new PopupCharactersParserError("Multiple " + LABEL_END + ": "
1259b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                            + popupSpec);
1269b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final String outputText = parseEscape(popupSpec.substring(end + LABEL_END.length()));
1279b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (!TextUtils.isEmpty(outputText))
1289b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                return outputText;
1299b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            throw new PopupCharactersParserError("Empty outputText: " + popupSpec);
1309b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1319b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final String label = getLabel(popupSpec);
1329b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (label == null)
1339b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            throw new PopupCharactersParserError("Empty label: " + popupSpec);
134c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        // Code is automatically generated for one letter label. See {@link getCode()}.
1359b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (label.length() == 1)
1369b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            return null;
1379b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return label;
1389b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1399b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
140c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka    public static int getCode(Resources res, String popupSpec) {
1419b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (hasCode(popupSpec)) {
1429b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final int end = indexOfLabelEnd(popupSpec, 0);
1439b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            if (indexOfLabelEnd(popupSpec, end + 1) >= 0)
1449b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                throw new PopupCharactersParserError("Multiple " + LABEL_END + ": " + popupSpec);
1459b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final int resId = getResourceId(res,
1469b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka                    popupSpec.substring(end + LABEL_END.length() + PREFIX_AT.length()));
1479b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            final int code = res.getInteger(resId);
148c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka            return code;
1499b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1509b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (indexOfLabelEnd(popupSpec, 0) > 0)
151c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka            return Keyboard.CODE_DUMMY;
1529b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        final String label = getLabel(popupSpec);
1539b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        // Code is automatically generated for one letter label.
1549b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (label != null && label.length() == 1)
155c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka            return label.charAt(0);
156c4f71668d7b8203dc66f0f04c089a363189eb4ceTadashi G. Takaoka        return Keyboard.CODE_DUMMY;
1579b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1589b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
159c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka    public static int getIconId(String popupSpec) {
1609b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (hasIcon(popupSpec)) {
1619b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            int end = popupSpec.indexOf(LABEL_END, PREFIX_ICON.length() + 1);
162c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka            final String iconId = popupSpec.substring(PREFIX_ICON.length(), end);
163c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka            try {
164c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka                return Integer.valueOf(iconId);
165c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka            } catch (NumberFormatException e) {
166c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka                Log.w(TAG, "illegal icon id specified: " + iconId);
167c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka                return KeyboardIconsSet.ICON_UNDEFINED;
168c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka            }
1699b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
170c2a21786e526cc32e48a577a55b1b7e72ae1a6ddTadashi G. Takaoka        return KeyboardIconsSet.ICON_UNDEFINED;
1719b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1729b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1739b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    private static int getResourceId(Resources res, String name) {
1749b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        String packageName = res.getResourcePackageName(R.string.english_ime_name);
1759b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        int resId = res.getIdentifier(name, null, packageName);
1769b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        if (resId == 0)
1779b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            throw new PopupCharactersParserError("Unknown resource: " + name);
1789b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        return resId;
1799b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1809b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka
1819b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    @SuppressWarnings("serial")
1829b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    public static class PopupCharactersParserError extends RuntimeException {
1839b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        public PopupCharactersParserError(String message) {
1849b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka            super(message);
1859b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka        }
1869b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka    }
1872fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
1882fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    public interface CodeFilter {
1892fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        public boolean shouldFilterOut(int code);
1902fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    }
1912fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
1922fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    public static final CodeFilter DIGIT_FILTER = new CodeFilter() {
1932fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        @Override
1942fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        public boolean shouldFilterOut(int code) {
1952fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return Character.isDigit(code);
1962fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
1972fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    };
1982fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
1992fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    public static final CodeFilter NON_ASCII_FILTER = new CodeFilter() {
2002fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        @Override
2012fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        public boolean shouldFilterOut(int code) {
2022fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return code < 0x20 || code > 0x7e;
2032fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2042fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    };
2052fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka
2062fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    public static CharSequence[] filterOut(Resources res, CharSequence[] popupCharacters,
2072fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            CodeFilter filter) {
2082fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        if (popupCharacters == null || popupCharacters.length < 1) {
2092fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return null;
2102fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2112fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        if (popupCharacters.length == 1
2122fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                && filter.shouldFilterOut(getCode(res, popupCharacters[0].toString()))) {
2132fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return null;
2142fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2152fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        ArrayList<CharSequence> filtered = null;
2162fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        for (int i = 0; i < popupCharacters.length; i++) {
2172fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            final CharSequence popupSpec = popupCharacters[i];
2182fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            if (filter.shouldFilterOut(getCode(res, popupSpec.toString()))) {
2192fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                if (filtered == null) {
2202fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                    filtered = new ArrayList<CharSequence>();
2212fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                    for (int j = 0; j < i; j++) {
2222fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                        filtered.add(popupCharacters[j]);
2232fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                    }
2242fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                }
2252fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            } else if (filtered != null) {
2262fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka                filtered.add(popupSpec);
2272fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            }
2282fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2292fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        if (filtered == null) {
2302fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return popupCharacters;
2312fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2322fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        if (filtered.size() == 0) {
2332fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka            return null;
2342fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        }
2352fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka        return filtered.toArray(new CharSequence[filtered.size()]);
2362fe68b9616ebdeb24daf043fbc590ea6a11f10a0Tadashi G. Takaoka    }
2379b6d1d52d91f8f18952ae3841f4bb0d7309bfc0eTadashi G. Takaoka}
238