diff options
Diffstat (limited to 'java/src/com')
9 files changed, 299 insertions, 279 deletions
diff --git a/java/src/com/android/inputmethod/keyboard/Key.java b/java/src/com/android/inputmethod/keyboard/Key.java index 5fd70ea63..f7eec6b43 100644 --- a/java/src/com/android/inputmethod/keyboard/Key.java +++ b/java/src/com/android/inputmethod/keyboard/Key.java @@ -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]; diff --git a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java index 4ccecb2f0..dce7fc57e 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/CodesArrayParser.java @@ -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); } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java index 07c5cae8a..c51941d32 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeySpecParser.java @@ -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 }; - } - - 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 hasIcon(final String keySpec) { + return keySpec.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; - } } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java index a2242b841..7941ddd41 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyStyle.java @@ -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; } diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java index 96a5b089f..f8c098893 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java +++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsSet.java @@ -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. diff --git a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java index dd58e1ff4..d3bc0c2b2 100644 --- a/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java +++ b/java/src/com/android/inputmethod/keyboard/internal/MoreKeySpec.java @@ -19,10 +19,25 @@ package com.android.inputmethod.keyboard.internal; import android.text.TextUtils; import com.android.inputmethod.latin.Constants; +import com.android.inputmethod.latin.LatinImeLogger; +import com.android.inputmethod.latin.utils.CollectionUtils; import com.android.inputmethod.latin.utils.StringUtils; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Locale; +/** + * The more key specification object. The more keys are an array of {@link MoreKeySpec}. + * + * The more keys specification is comma separated "key specification" each of which represents one + * "more key". + * The key specification might have label or string resource reference in it. These references are + * expanded before parsing comma. + * Special character, comma ',' backslash '\' can be escaped by '\' character. + * Note that the '\' is also parsed by XML parser and {@link MoreKeySpec#splitKeySpecs(String)} + * as well. + */ public final class MoreKeySpec { public final int mCode; public final String mLabel; @@ -83,4 +98,196 @@ public final class MoreKeySpec { return label + "|" + output; } } + + private static final boolean DEBUG = LatinImeLogger.sDBG; + // Constants for parsing. + private static final char COMMA = Constants.CODE_COMMA; + private static final char BACKSLASH = Constants.CODE_BACKSLASH; + private static final String ADDITIONAL_MORE_KEY_MARKER = + StringUtils.newSingleCodePointString(Constants.CODE_PERCENT); + + /** + * 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 }; + } + + 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 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; + } + } + + 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; + } } diff --git a/java/src/com/android/inputmethod/latin/Constants.java b/java/src/com/android/inputmethod/latin/Constants.java index 0b396b1de..d6ac71fe2 100644 --- a/java/src/com/android/inputmethod/latin/Constants.java +++ b/java/src/com/android/inputmethod/latin/Constants.java @@ -191,6 +191,8 @@ public final class Constants { public static final int CODE_QUESTION_MARK = '?'; public static final int CODE_EXCLAMATION_MARK = '!'; public static final int CODE_SLASH = '/'; + public static final int CODE_BACKSLASH = '\\'; + public static final int CODE_VERTICAL_BAR = '|'; public static final int CODE_COMMERCIAL_AT = '@'; public static final int CODE_PLUS = '+'; public static final int CODE_PERCENT = '%'; diff --git a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java index 8ba32ff76..70cb2b285 100644 --- a/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java +++ b/java/src/com/android/inputmethod/latin/settings/SpacingAndPunctuations.java @@ -19,6 +19,7 @@ package com.android.inputmethod.latin.settings; import android.content.res.Resources; import com.android.inputmethod.keyboard.internal.KeySpecParser; +import com.android.inputmethod.keyboard.internal.MoreKeySpec; import com.android.inputmethod.latin.Constants; import com.android.inputmethod.latin.Dictionary; import com.android.inputmethod.latin.R; @@ -55,7 +56,7 @@ public final class SpacingAndPunctuations { res.getString(R.string.symbols_word_connectors)); mSortedWordSeparators = StringUtils.toSortedCodePointArray( res.getString(R.string.symbols_word_separators)); - final String[] suggestPuncsSpec = KeySpecParser.splitKeySpecs(res.getString( + final String[] suggestPuncsSpec = MoreKeySpec.splitKeySpecs(res.getString( R.string.suggested_punctuations)); mSuggestPuncList = createSuggestPuncList(suggestPuncsSpec); mSentenceSeparator = res.getInteger(R.integer.sentence_separator); diff --git a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java index 36b927eea..b18a1d83b 100644 --- a/java/src/com/android/inputmethod/latin/utils/CsvUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/CsvUtils.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin.utils; import com.android.inputmethod.annotations.UsedForTesting; +import com.android.inputmethod.latin.Constants; import java.util.ArrayList; @@ -57,9 +58,9 @@ public final class CsvUtils { // Note that none of these characters match high or low surrogate characters, so we need not // take care of matching by code point. - private static final char COMMA = ','; - private static final char SPACE = ' '; - private static final char QUOTE = '"'; + private static final char COMMA = Constants.CODE_COMMA; + private static final char SPACE = Constants.CODE_SPACE; + private static final char QUOTE = Constants.CODE_DOUBLE_QUOTE; @SuppressWarnings("serial") public static class CsvParseException extends RuntimeException { |