Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ae7e53c authored by Tadashi G. Takaoka's avatar Tadashi G. Takaoka Committed by Android (Google) Code Review
Browse files

Merge "Move some methods to MoreKeySpec class"

parents 35ff07c7 e855093f
Loading
Loading
Loading
Loading
+6 −6
Original line number Diff line number Diff line
@@ -284,19 +284,19 @@ public class Key implements Comparable<Key> {
        int moreKeysColumn = style.getInt(keyAttr,
                R.styleable.Keyboard_Key_maxMoreKeysColumn, params.mMaxMoreKeysKeyboardColumn);
        int value;
        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
        if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_AUTO_COLUMN_ORDER, -1)) > 0) {
            moreKeysColumn = value & MORE_KEYS_COLUMN_MASK;
        }
        if ((value = KeySpecParser.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
        if ((value = MoreKeySpec.getIntValue(moreKeys, MORE_KEYS_FIXED_COLUMN_ORDER, -1)) > 0) {
            moreKeysColumn = MORE_KEYS_FLAGS_FIXED_COLUMN_ORDER | (value & MORE_KEYS_COLUMN_MASK);
        }
        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_HAS_LABELS)) {
            moreKeysColumn |= MORE_KEYS_FLAGS_HAS_LABELS;
        }
        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NEEDS_DIVIDERS)) {
            moreKeysColumn |= MORE_KEYS_FLAGS_NEEDS_DIVIDERS;
        }
        if (KeySpecParser.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
        if (MoreKeySpec.getBooleanValue(moreKeys, MORE_KEYS_NO_PANEL_AUTO_MORE_KEY)) {
            moreKeysColumn |= MORE_KEYS_FLAGS_NO_PANEL_AUTO_MORE_KEY;
        }
        mMoreKeysColumnAndFlags = moreKeysColumn;
@@ -308,7 +308,7 @@ public class Key implements Comparable<Key> {
            additionalMoreKeys = style.getStringArray(keyAttr,
                    R.styleable.Keyboard_Key_additionalMoreKeys);
        }
        moreKeys = KeySpecParser.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
        moreKeys = MoreKeySpec.insertAdditionalMoreKeys(moreKeys, additionalMoreKeys);
        if (moreKeys != null) {
            actionFlags |= ACTION_FLAGS_ENABLE_LONG_PRESS;
            mMoreKeys = new MoreKeySpec[moreKeys.length];
+11 −11
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.inputmethod.keyboard.internal;

import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.StringUtils;

import android.text.TextUtils;

@@ -29,15 +30,16 @@ import android.text.TextUtils;
 * marker. An output text may consist of multiple code points separated by comma.
 * The format of the codesArray element should be:
 * <pre>
 *   codePointInHex[,codePoint2InHex]*(|outputTextCodePointInHex[,outputTextCodePoint2InHex]*)?
 *   label1[,label2]*(|outputText1[,outputText2]*(|minSupportSdkVersion)?)?
 * </pre>
 */
// TODO: Write unit tests for this class.
public final class CodesArrayParser {
    // Constants for parsing.
    private static final char COMMA = ',';
    private static final String VERTICAL_BAR_STRING = "\\|";
    private static final String COMMA_STRING = ",";
    private static final char COMMA = Constants.CODE_COMMA;
    private static final String COMMA_REGEX = StringUtils.newSingleCodePointString(COMMA);
    private static final String VERTICAL_BAR_REGEX = // "\\|"
            new String(new char[] { Constants.CODE_BACKSLASH, Constants.CODE_VERTICAL_BAR });
    private static final int BASE_HEX = 16;

    private CodesArrayParser() {
@@ -45,7 +47,7 @@ public final class CodesArrayParser {
    }

    private static String getLabelSpec(final String codesArraySpec) {
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
        if (strs.length <= 1) {
            return codesArraySpec;
        }
@@ -55,7 +57,7 @@ public final class CodesArrayParser {
    public static String parseLabel(final String codesArraySpec) {
        final String labelSpec = getLabelSpec(codesArraySpec);
        final StringBuilder sb = new StringBuilder();
        for (final String codeInHex : labelSpec.split(COMMA_STRING)) {
        for (final String codeInHex : labelSpec.split(COMMA_REGEX)) {
            final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
            sb.appendCodePoint(codePoint);
        }
@@ -63,17 +65,15 @@ public final class CodesArrayParser {
    }

    private static String getCodeSpec(final String codesArraySpec) {
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
        if (strs.length <= 1) {
            return codesArraySpec;
        }
        return TextUtils.isEmpty(strs[1]) ? strs[0] : strs[1];
    }

    // codesArraySpec consists of:
    // <label>|<code0>,<code1>,...|<minSupportSdkVersion>
    public static int getMinSupportSdkVersion(final String codesArraySpec) {
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_STRING, -1);
        final String[] strs = codesArraySpec.split(VERTICAL_BAR_REGEX, -1);
        if (strs.length <= 2) {
            return 0;
        }
@@ -98,7 +98,7 @@ public final class CodesArrayParser {
            return null;
        }
        final StringBuilder sb = new StringBuilder();
        for (final String codeInHex : codeSpec.split(COMMA_STRING)) {
        for (final String codeInHex : codeSpec.split(COMMA_REGEX)) {
            final int codePoint = Integer.parseInt(codeInHex, BASE_HEX);
            sb.appendCodePoint(codePoint);
        }
+64 −256
Original line number Diff line number Diff line
@@ -19,106 +19,46 @@ package com.android.inputmethod.keyboard.internal;
import static com.android.inputmethod.latin.Constants.CODE_OUTPUT_TEXT;
import static com.android.inputmethod.latin.Constants.CODE_UNSPECIFIED;

import android.text.TextUtils;

import com.android.inputmethod.latin.LatinImeLogger;
import com.android.inputmethod.latin.utils.CollectionUtils;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.StringUtils;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * The string parser of more keys specification.
 * The specification is comma separated texts each of which represents one "more key".
 * The specification might have label or string resource reference in it. These references are
 * expanded before parsing comma.
 * - Label reference should be a string representation of label (!text/label_name)
 * - String resource reference should be a string representation of resource (!text/resource_name)
 * Each "more key" specification is one of the following:
 * - Label optionally followed by keyOutputText or code (keyLabel|keyOutputText).
 * - Icon followed by keyOutputText or code (!icon/icon_name|!code/code_name)
 *   - Icon should be a string representation of icon (!icon/icon_name).
 *   - Code should be a code point presented by hexadecimal string prefixed with "0x", or a string
 *     representation of code (!code/code_name).
 * The string parser of the key specification.
 *
 * Each key specification is one of the following:
 * - Label optionally followed by keyOutputText (keyLabel|keyOutputText).
 * - Label optionally followed by code point (keyLabel|!code/code_name).
 * - Icon followed by keyOutputText (!icon/icon_name|keyOutputText).
 * - Icon followed by code point (!icon/icon_name|!code/code_name).
 * Label and keyOutputText are one of the following:
 * - Literal string.
 * - Label reference represented by (!text/label_name), see {@link KeyboardTextsSet}.
 * - String resource reference represented by (!text/resource_name), see {@link KeyboardTextsSet}.
 * Icon is represented by (!icon/icon_name), see {@link KeyboardIconsSet}.
 * Code is one of the following:
 * - Code point presented by hexadecimal string prefixed with "0x"
 * - Code reference represented by (!code/code_name), see {@link KeyboardCodesSet}.
 * Special character, comma ',' backslash '\', and bar '|' can be escaped by '\' character.
 * Note that the '\' is also parsed by XML parser and CSV parser as well.
 * See {@link KeyboardIconsSet} about icon_name.
 * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)}
 * as well.
 */
public final class KeySpecParser {
    private static final boolean DEBUG = LatinImeLogger.sDBG;

    // Constants for parsing.
    private static final char COMMA = ',';
    private static final char BACKSLASH = '\\';
    private static final char VERTICAL_BAR = '|';
    private static final char BACKSLASH = Constants.CODE_BACKSLASH;
    private static final char VERTICAL_BAR = Constants.CODE_VERTICAL_BAR;
    private static final String PREFIX_HEX = "0x";
    private static final String ADDITIONAL_MORE_KEY_MARKER = "%";

    private KeySpecParser() {
        // Intentional empty constructor for utility class.
    }

    /**
     * Split the text containing multiple key specifications separated by commas into an array of
     * key specifications.
     * A key specification can contain a character escaped by the backslash character, including a
     * comma character.
     * Note that an empty key specification will be eliminated from the result array.
     *
     * @param text the text containing multiple key specifications.
     * @return an array of key specification text. Null if the specified <code>text</code> is empty
     * or has no key specifications.
     */
    public static String[] splitKeySpecs(final String text) {
        if (TextUtils.isEmpty(text)) {
            return null;
        }
        final int size = text.length();
        // Optimization for one-letter key specification.
        if (size == 1) {
            return text.charAt(0) == COMMA ? null : new String[] { text };
    private static boolean hasIcon(final String keySpec) {
        return keySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
    }

        ArrayList<String> list = null;
        int start = 0;
        // The characters in question in this loop are COMMA and BACKSLASH. These characters never
        // match any high or low surrogate character. So it is OK to iterate through with char
        // index.
        for (int pos = 0; pos < size; pos++) {
            final char c = text.charAt(pos);
            if (c == COMMA) {
                // Skip empty entry.
                if (pos - start > 0) {
                    if (list == null) {
                        list = CollectionUtils.newArrayList();
                    }
                    list.add(text.substring(start, pos));
                }
                // Skip comma
                start = pos + 1;
            } else if (c == BACKSLASH) {
                // Skip escape character and escaped character.
                pos++;
            }
        }
        final String remain = (size - start > 0) ? text.substring(start) : null;
        if (list == null) {
            return remain != null ? new String[] { remain } : null;
        }
        if (remain != null) {
            list.add(remain);
        }
        return list.toArray(new String[list.size()]);
    }

    private static boolean hasIcon(final String moreKeySpec) {
        return moreKeySpec.startsWith(KeyboardIconsSet.PREFIX_ICON);
    }

    private static boolean hasCode(final String moreKeySpec) {
        final int end = indexOfLabelEnd(moreKeySpec, 0);
        if (end > 0 && end + 1 < moreKeySpec.length() && moreKeySpec.startsWith(
    private static boolean hasCode(final String keySpec) {
        final int end = indexOfLabelEnd(keySpec, 0);
        if (end > 0 && end + 1 < keySpec.length() && keySpec.startsWith(
                KeyboardCodesSet.PREFIX_CODE, end + 1)) {
            return true;
        }
@@ -144,17 +84,17 @@ public final class KeySpecParser {
        return sb.toString();
    }

    private static int indexOfLabelEnd(final String moreKeySpec, final int start) {
        if (moreKeySpec.indexOf(BACKSLASH, start) < 0) {
            final int end = moreKeySpec.indexOf(VERTICAL_BAR, start);
    private static int indexOfLabelEnd(final String keySpec, final int start) {
        if (keySpec.indexOf(BACKSLASH, start) < 0) {
            final int end = keySpec.indexOf(VERTICAL_BAR, start);
            if (end == 0) {
                throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + moreKeySpec);
                throw new KeySpecParserError(VERTICAL_BAR + " at " + start + ": " + keySpec);
            }
            return end;
        }
        final int length = moreKeySpec.length();
        final int length = keySpec.length();
        for (int pos = start; pos < length; pos++) {
            final char c = moreKeySpec.charAt(pos);
            final char c = keySpec.charAt(pos);
            if (c == BACKSLASH && pos + 1 < length) {
                // Skip escape char
                pos++;
@@ -165,63 +105,63 @@ public final class KeySpecParser {
        return -1;
    }

    public static String getLabel(final String moreKeySpec) {
        if (hasIcon(moreKeySpec)) {
    public static String getLabel(final String keySpec) {
        if (hasIcon(keySpec)) {
            return null;
        }
        final int end = indexOfLabelEnd(moreKeySpec, 0);
        final String label = (end > 0) ? parseEscape(moreKeySpec.substring(0, end))
                : parseEscape(moreKeySpec);
        if (TextUtils.isEmpty(label)) {
            throw new KeySpecParserError("Empty label: " + moreKeySpec);
        final int end = indexOfLabelEnd(keySpec, 0);
        final String label = (end > 0) ? parseEscape(keySpec.substring(0, end))
                : parseEscape(keySpec);
        if (label.isEmpty()) {
            throw new KeySpecParserError("Empty label: " + keySpec);
        }
        return label;
    }

    private static String getOutputTextInternal(final String moreKeySpec) {
        final int end = indexOfLabelEnd(moreKeySpec, 0);
    private static String getOutputTextInternal(final String keySpec) {
        final int end = indexOfLabelEnd(keySpec, 0);
        if (end <= 0) {
            return null;
        }
        if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
            throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
        if (indexOfLabelEnd(keySpec, end + 1) >= 0) {
            throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
        }
        return parseEscape(moreKeySpec.substring(end + /* VERTICAL_BAR */1));
        return parseEscape(keySpec.substring(end + /* VERTICAL_BAR */1));
    }

    static String getOutputText(final String moreKeySpec) {
        if (hasCode(moreKeySpec)) {
    public static String getOutputText(final String keySpec) {
        if (hasCode(keySpec)) {
            return null;
        }
        final String outputText = getOutputTextInternal(moreKeySpec);
        final String outputText = getOutputTextInternal(keySpec);
        if (outputText != null) {
            if (StringUtils.codePointCount(outputText) == 1) {
                // If output text is one code point, it should be treated as a code.
                // See {@link #getCode(Resources, String)}.
                return null;
            }
            if (!TextUtils.isEmpty(outputText)) {
                return outputText;
            if (outputText.isEmpty()) {
                throw new KeySpecParserError("Empty outputText: " + keySpec);
            }
            throw new KeySpecParserError("Empty outputText: " + moreKeySpec);
            return outputText;
        }
        final String label = getLabel(moreKeySpec);
        final String label = getLabel(keySpec);
        if (label == null) {
            throw new KeySpecParserError("Empty label: " + moreKeySpec);
            throw new KeySpecParserError("Empty label: " + keySpec);
        }
        // Code is automatically generated for one letter label. See {@link getCode()}.
        return (StringUtils.codePointCount(label) == 1) ? null : label;
    }

    static int getCode(final String moreKeySpec, final KeyboardCodesSet codesSet) {
        if (hasCode(moreKeySpec)) {
            final int end = indexOfLabelEnd(moreKeySpec, 0);
            if (indexOfLabelEnd(moreKeySpec, end + 1) >= 0) {
                throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + moreKeySpec);
    public static int getCode(final String keySpec, final KeyboardCodesSet codesSet) {
        if (hasCode(keySpec)) {
            final int end = indexOfLabelEnd(keySpec, 0);
            if (indexOfLabelEnd(keySpec, end + 1) >= 0) {
                throw new KeySpecParserError("Multiple " + VERTICAL_BAR + ": " + keySpec);
            }
            return parseCode(moreKeySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
            return parseCode(keySpec.substring(end + 1), codesSet, CODE_UNSPECIFIED);
        }
        final String outputText = getOutputTextInternal(moreKeySpec);
        final String outputText = getOutputTextInternal(keySpec);
        if (outputText != null) {
            // If output text is one code point, it should be treated as a code.
            // See {@link #getOutputText(String)}.
@@ -230,7 +170,7 @@ public final class KeySpecParser {
            }
            return CODE_OUTPUT_TEXT;
        }
        final String label = getLabel(moreKeySpec);
        final String label = getLabel(keySpec);
        // Code is automatically generated for one letter label.
        if (StringUtils.codePointCount(label) == 1) {
            return label.codePointAt(0);
@@ -250,154 +190,22 @@ public final class KeySpecParser {
        }
    }

    public static int getIconId(final String moreKeySpec) {
        if (moreKeySpec != null && hasIcon(moreKeySpec)) {
            final int end = moreKeySpec.indexOf(
    public static int getIconId(final String keySpec) {
        if (keySpec != null && hasIcon(keySpec)) {
            final int end = keySpec.indexOf(
                    VERTICAL_BAR, KeyboardIconsSet.PREFIX_ICON.length());
            final String name = (end < 0)
                    ? moreKeySpec.substring(KeyboardIconsSet.PREFIX_ICON.length())
                    : moreKeySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end);
                    ? keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length())
                    : keySpec.substring(KeyboardIconsSet.PREFIX_ICON.length(), end);
            return KeyboardIconsSet.getIconId(name);
        }
        return KeyboardIconsSet.ICON_UNDEFINED;
    }

    private static final String[] EMPTY_STRING_ARRAY = new String[0];

    private static String[] filterOutEmptyString(final String[] array) {
        if (array == null) {
            return EMPTY_STRING_ARRAY;
        }
        ArrayList<String> out = null;
        for (int i = 0; i < array.length; i++) {
            final String entry = array[i];
            if (TextUtils.isEmpty(entry)) {
                if (out == null) {
                    out = CollectionUtils.arrayAsList(array, 0, i);
                }
            } else if (out != null) {
                out.add(entry);
            }
        }
        if (out == null) {
            return array;
        }
        return out.toArray(new String[out.size()]);
    }

    public static String[] insertAdditionalMoreKeys(final String[] moreKeySpecs,
            final String[] additionalMoreKeySpecs) {
        final String[] moreKeys = filterOutEmptyString(moreKeySpecs);
        final String[] additionalMoreKeys = filterOutEmptyString(additionalMoreKeySpecs);
        final int moreKeysCount = moreKeys.length;
        final int additionalCount = additionalMoreKeys.length;
        ArrayList<String> out = null;
        int additionalIndex = 0;
        for (int moreKeyIndex = 0; moreKeyIndex < moreKeysCount; moreKeyIndex++) {
            final String moreKeySpec = moreKeys[moreKeyIndex];
            if (moreKeySpec.equals(ADDITIONAL_MORE_KEY_MARKER)) {
                if (additionalIndex < additionalCount) {
                    // Replace '%' marker with additional more key specification.
                    final String additionalMoreKey = additionalMoreKeys[additionalIndex];
                    if (out != null) {
                        out.add(additionalMoreKey);
                    } else {
                        moreKeys[moreKeyIndex] = additionalMoreKey;
                    }
                    additionalIndex++;
                } else {
                    // Filter out excessive '%' marker.
                    if (out == null) {
                        out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeyIndex);
                    }
                }
            } else {
                if (out != null) {
                    out.add(moreKeySpec);
                }
            }
        }
        if (additionalCount > 0 && additionalIndex == 0) {
            // No '%' marker is found in more keys.
            // Insert all additional more keys to the head of more keys.
            if (DEBUG && out != null) {
                throw new RuntimeException("Internal logic error:"
                        + " moreKeys=" + Arrays.toString(moreKeys)
                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
            }
            out = CollectionUtils.arrayAsList(additionalMoreKeys, additionalIndex, additionalCount);
            for (int i = 0; i < moreKeysCount; i++) {
                out.add(moreKeys[i]);
            }
        } else if (additionalIndex < additionalCount) {
            // The number of '%' markers are less than additional more keys.
            // Append remained additional more keys to the tail of more keys.
            if (DEBUG && out != null) {
                throw new RuntimeException("Internal logic error:"
                        + " moreKeys=" + Arrays.toString(moreKeys)
                        + " additionalMoreKeys=" + Arrays.toString(additionalMoreKeys));
            }
            out = CollectionUtils.arrayAsList(moreKeys, 0, moreKeysCount);
            for (int i = additionalIndex; i < additionalCount; i++) {
                out.add(additionalMoreKeys[additionalIndex]);
            }
        }
        if (out == null && moreKeysCount > 0) {
            return moreKeys;
        } else if (out != null && out.size() > 0) {
            return out.toArray(new String[out.size()]);
        } else {
            return null;
        }
    }

    @SuppressWarnings("serial")
    public static final class KeySpecParserError extends RuntimeException {
        public KeySpecParserError(final String message) {
            super(message);
        }
    }

    public static int getIntValue(final String[] moreKeys, final String key,
            final int defaultValue) {
        if (moreKeys == null) {
            return defaultValue;
        }
        final int keyLen = key.length();
        boolean foundValue = false;
        int value = defaultValue;
        for (int i = 0; i < moreKeys.length; i++) {
            final String moreKeySpec = moreKeys[i];
            if (moreKeySpec == null || !moreKeySpec.startsWith(key)) {
                continue;
            }
            moreKeys[i] = null;
            try {
                if (!foundValue) {
                    value = Integer.parseInt(moreKeySpec.substring(keyLen));
                    foundValue = true;
                }
            } catch (NumberFormatException e) {
                throw new RuntimeException(
                        "integer should follow after " + key + ": " + moreKeySpec);
            }
        }
        return value;
    }

    public static boolean getBooleanValue(final String[] moreKeys, final String key) {
        if (moreKeys == null) {
            return false;
        }
        boolean value = false;
        for (int i = 0; i < moreKeys.length; i++) {
            final String moreKeySpec = moreKeys[i];
            if (moreKeySpec == null || !moreKeySpec.equals(key)) {
                continue;
            }
            moreKeys[i] = null;
            value = true;
        }
        return value;
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -40,7 +40,7 @@ public abstract class KeyStyle {
    protected String[] parseStringArray(final TypedArray a, final int index) {
        if (a.hasValue(index)) {
            final String text = mTextsSet.resolveTextReference(a.getString(index));
            return KeySpecParser.splitKeySpecs(text);
            return MoreKeySpec.splitKeySpecs(text);
        }
        return null;
    }
+2 −1
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.res.Resources;
import android.text.TextUtils;

import com.android.inputmethod.annotations.UsedForTesting;
import com.android.inputmethod.latin.Constants;
import com.android.inputmethod.latin.utils.CollectionUtils;

import java.util.HashMap;
@@ -47,7 +48,7 @@ import java.util.HashMap;
 */
public final class KeyboardTextsSet {
    public static final String PREFIX_TEXT = "!text/";
    private static final char BACKSLASH = '\\';
    private static final char BACKSLASH = Constants.CODE_BACKSLASH;
    private static final int MAX_STRING_REFERENCE_INDIRECTION = 10;

    // Language to texts map.
Loading