diff options
author | 2013-06-24 01:11:32 +0900 | |
---|---|---|
committer | 2013-06-24 17:04:40 +0900 | |
commit | e28eba5074664d5716b8e58b8d0a235746b261eb (patch) | |
tree | 7f055d1617a9d621fb5b51eb4d52a9a93d9bad44 /java/src/com/android/inputmethod/latin/utils/LocaleUtils.java | |
parent | 80a4b7c92e96d359e0360f85b2ed3ed128ad0f3f (diff) | |
download | latinime-e28eba5074664d5716b8e58b8d0a235746b261eb.tar.gz latinime-e28eba5074664d5716b8e58b8d0a235746b261eb.tar.xz latinime-e28eba5074664d5716b8e58b8d0a235746b261eb.zip |
Move util classes to the latin/utils directory
Change-Id: I1c5b27c8edf231680edb8d96f63b9d04cfc6a6fa
Diffstat (limited to 'java/src/com/android/inputmethod/latin/utils/LocaleUtils.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/utils/LocaleUtils.java | 261 |
1 files changed, 261 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java new file mode 100644 index 000000000..58d062bbd --- /dev/null +++ b/java/src/com/android/inputmethod/latin/utils/LocaleUtils.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.inputmethod.latin.utils; + +import android.content.res.Configuration; +import android.content.res.Resources; +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.Locale; + +/** + * A class to help with handling Locales in string form. + * + * This file has the same meaning and features (and shares all of its code) with + * the one in the dictionary pack. They need to be kept synchronized; for any + * update/bugfix to this file, consider also updating/fixing the version in the + * dictionary pack. + */ +public final class LocaleUtils { + private static final HashMap<String, Long> EMPTY_LT_HASH_MAP = CollectionUtils.newHashMap(); + private static final String LOCALE_AND_TIME_STR_SEPARATER = ","; + + private LocaleUtils() { + // Intentional empty constructor for utility class. + } + + // Locale match level constants. + // A higher level of match is guaranteed to have a higher numerical value. + // Some room is left within constants to add match cases that may arise necessary + // in the future, for example differentiating between the case where the countries + // are both present and different, and the case where one of the locales does not + // specify the countries. This difference is not needed now. + + // Nothing matches. + public static final int LOCALE_NO_MATCH = 0; + // The languages matches, but the country are different. Or, the reference locale requires a + // country and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3; + // The languages and country match, but the variants are different. Or, the reference locale + // requires a variant and the tested locale does not have one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6; + // The required locale is null or empty so it will accept anything, and the tested locale + // is non-null and non-empty. + public static final int LOCALE_ANY_MATCH = 10; + // The language matches, and the tested locale specifies a country but the reference locale + // does not require one. + public static final int LOCALE_LANGUAGE_MATCH = 15; + // The language and the country match, and the tested locale specifies a variant but the + // reference locale does not require one. + public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20; + // The compared locales are fully identical. This is the best match level. + public static final int LOCALE_FULL_MATCH = 30; + + // The level at which a match is "normally" considered a locale match with standard algorithms. + // Don't use this directly, use #isMatch to test. + private static final int LOCALE_MATCH = LOCALE_ANY_MATCH; + + // Make this match the maximum match level. If this evolves to have more than 2 digits + // when written in base 10, also adjust the getMatchLevelSortedString method. + private static final int MATCH_LEVEL_MAX = 30; + + /** + * Return how well a tested locale matches a reference locale. + * + * This will check the tested locale against the reference locale and return a measure of how + * a well it matches the reference. The general idea is that the tested locale has to match + * every specified part of the required locale. A full match occur when they are equal, a + * partial match when the tested locale agrees with the reference locale but is more specific, + * and a difference when the tested locale does not comply with all requirements from the + * reference locale. + * In more detail, if the reference locale specifies at least a language and the testedLocale + * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the + * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH + * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and + * tested locale agree on the language, but not on the country, + * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country, + * and LOCALE_LANGUAGE_MATCH otherwise. + * If they agree on both the language and the country, but not on the variant, + * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale + * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches, + * LOCALE_FULL_MATCH is returned. + * Examples: + * en <=> en_US => LOCALE_LANGUAGE_MATCH + * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER + * en_US_POSIX <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER + * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH + * sp_US <=> en_US => LOCALE_NO_MATCH + * de <=> de => LOCALE_FULL_MATCH + * en_US <=> en_US => LOCALE_FULL_MATCH + * "" <=> en_US => LOCALE_ANY_MATCH + * + * @param referenceLocale the reference locale to test against. + * @param testedLocale the locale to test. + * @return a constant that measures how well the tested locale matches the reference locale. + */ + public static int getMatchLevel(String referenceLocale, String testedLocale) { + if (TextUtils.isEmpty(referenceLocale)) { + return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH; + } + if (null == testedLocale) return LOCALE_NO_MATCH; + String[] referenceParams = referenceLocale.split("_", 3); + String[] testedParams = testedLocale.split("_", 3); + // By spec of String#split, [0] cannot be null and length cannot be 0. + if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH; + switch (referenceParams.length) { + case 1: + return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH; + case 2: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH; + return LOCALE_FULL_MATCH; + case 3: + if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (!referenceParams[1].equals(testedParams[1])) + return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER; + if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + if (!referenceParams[2].equals(testedParams[2])) + return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER; + return LOCALE_FULL_MATCH; + } + // It should be impossible to come here + return LOCALE_NO_MATCH; + } + + /** + * Return a string that represents this match level, with better matches first. + * + * The strings are sorted in lexicographic order: a better match will always be less than + * a worse match when compared together. + */ + public static String getMatchLevelSortedString(int matchLevel) { + // This works because the match levels are 0~99 (actually 0~30) + // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel + return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel); + } + + /** + * Find out whether a match level should be considered a match. + * + * This method takes a match level as returned by the #getMatchLevel method, and returns whether + * it should be considered a match in the usual sense with standard Locale functions. + * + * @param level the match level, as returned by getMatchLevel. + * @return whether this is a match or not. + */ + public static boolean isMatch(int level) { + return LOCALE_MATCH <= level; + } + + static final Object sLockForRunInLocale = new Object(); + + // TODO: Make this an external class + public abstract static class RunInLocale<T> { + protected abstract T job(Resources res); + + /** + * Execute {@link #job(Resources)} method in specified system locale exclusively. + * + * @param res the resources to use. Pass current resources. + * @param newLocale the locale to change to + * @return the value returned from {@link #job(Resources)}. + */ + public T runInLocale(final Resources res, final Locale newLocale) { + synchronized (sLockForRunInLocale) { + final Configuration conf = res.getConfiguration(); + final Locale oldLocale = conf.locale; + final boolean needsChange = (newLocale != null && !newLocale.equals(oldLocale)); + try { + if (needsChange) { + conf.locale = newLocale; + res.updateConfiguration(conf, null); + } + return job(res); + } finally { + if (needsChange) { + conf.locale = oldLocale; + res.updateConfiguration(conf, null); + } + } + } + } + } + + private static final HashMap<String, Locale> sLocaleCache = CollectionUtils.newHashMap(); + + /** + * Creates a locale from a string specification. + */ + public static Locale constructLocaleFromString(final String localeStr) { + if (localeStr == null) + return null; + synchronized (sLocaleCache) { + if (sLocaleCache.containsKey(localeStr)) + return sLocaleCache.get(localeStr); + Locale retval = null; + String[] localeParams = localeStr.split("_", 3); + if (localeParams.length == 1) { + retval = new Locale(localeParams[0]); + } else if (localeParams.length == 2) { + retval = new Locale(localeParams[0], localeParams[1]); + } else if (localeParams.length == 3) { + retval = new Locale(localeParams[0], localeParams[1], localeParams[2]); + } + if (retval != null) { + sLocaleCache.put(localeStr, retval); + } + return retval; + } + } + + public static HashMap<String, Long> localeAndTimeStrToHashMap(String str) { + if (TextUtils.isEmpty(str)) { + return EMPTY_LT_HASH_MAP; + } + final String[] ss = str.split(LOCALE_AND_TIME_STR_SEPARATER); + final int N = ss.length; + if (N < 2 || N % 2 != 0) { + return EMPTY_LT_HASH_MAP; + } + final HashMap<String, Long> retval = CollectionUtils.newHashMap(); + for (int i = 0; i < N / 2; ++i) { + final String localeStr = ss[i * 2]; + final long time = Long.valueOf(ss[i * 2 + 1]); + retval.put(localeStr, time); + } + return retval; + } + + public static String localeAndTimeHashMapToStr(HashMap<String, Long> map) { + if (map == null || map.isEmpty()) { + return ""; + } + final StringBuilder builder = new StringBuilder(); + for (String localeStr : map.keySet()) { + if (builder.length() > 0) { + builder.append(LOCALE_AND_TIME_STR_SEPARATER); + } + final Long time = map.get(localeStr); + builder.append(localeStr).append(LOCALE_AND_TIME_STR_SEPARATER); + builder.append(String.valueOf(time)); + } + return builder.toString(); + } +} |