• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.inputmethod.latin.utils;
18 
19 import android.text.TextUtils;
20 
21 import java.util.HashMap;
22 import java.util.Locale;
23 
24 /**
25  * A class to help with handling Locales in string form.
26  *
27  * This file has the same meaning and features (and shares all of its code) with
28  * the one in the dictionary pack. They need to be kept synchronized; for any
29  * update/bugfix to this file, consider also updating/fixing the version in the
30  * dictionary pack.
31  */
32 public final class LocaleUtils {
LocaleUtils()33     private LocaleUtils() {
34         // Intentional empty constructor for utility class.
35     }
36 
37     // Locale match level constants.
38     // A higher level of match is guaranteed to have a higher numerical value.
39     // Some room is left within constants to add match cases that may arise necessary
40     // in the future, for example differentiating between the case where the countries
41     // are both present and different, and the case where one of the locales does not
42     // specify the countries. This difference is not needed now.
43 
44     // Nothing matches.
45     public static final int LOCALE_NO_MATCH = 0;
46     // The languages matches, but the country are different. Or, the reference locale requires a
47     // country and the tested locale does not have one.
48     public static final int LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER = 3;
49     // The languages and country match, but the variants are different. Or, the reference locale
50     // requires a variant and the tested locale does not have one.
51     public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER = 6;
52     // The required locale is null or empty so it will accept anything, and the tested locale
53     // is non-null and non-empty.
54     public static final int LOCALE_ANY_MATCH = 10;
55     // The language matches, and the tested locale specifies a country but the reference locale
56     // does not require one.
57     public static final int LOCALE_LANGUAGE_MATCH = 15;
58     // The language and the country match, and the tested locale specifies a variant but the
59     // reference locale does not require one.
60     public static final int LOCALE_LANGUAGE_AND_COUNTRY_MATCH = 20;
61     // The compared locales are fully identical. This is the best match level.
62     public static final int LOCALE_FULL_MATCH = 30;
63 
64     // The level at which a match is "normally" considered a locale match with standard algorithms.
65     // Don't use this directly, use #isMatch to test.
66     private static final int LOCALE_MATCH = LOCALE_ANY_MATCH;
67 
68     // Make this match the maximum match level. If this evolves to have more than 2 digits
69     // when written in base 10, also adjust the getMatchLevelSortedString method.
70     private static final int MATCH_LEVEL_MAX = 30;
71 
72     /**
73      * Return how well a tested locale matches a reference locale.
74      *
75      * This will check the tested locale against the reference locale and return a measure of how
76      * a well it matches the reference. The general idea is that the tested locale has to match
77      * every specified part of the required locale. A full match occur when they are equal, a
78      * partial match when the tested locale agrees with the reference locale but is more specific,
79      * and a difference when the tested locale does not comply with all requirements from the
80      * reference locale.
81      * In more detail, if the reference locale specifies at least a language and the testedLocale
82      * does not specify one, or specifies a different one, LOCALE_NO_MATCH is returned. If the
83      * reference locale is empty or null, it will match anything - in the form of LOCALE_FULL_MATCH
84      * if the tested locale is empty or null, and LOCALE_ANY_MATCH otherwise. If the reference and
85      * tested locale agree on the language, but not on the country,
86      * LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER is returned if the reference locale specifies a country,
87      * and LOCALE_LANGUAGE_MATCH otherwise.
88      * If they agree on both the language and the country, but not on the variant,
89      * LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER is returned if the reference locale
90      * specifies a variant, and LOCALE_LANGUAGE_AND_COUNTRY_MATCH otherwise. If everything matches,
91      * LOCALE_FULL_MATCH is returned.
92      * Examples:
93      * en <=> en_US  => LOCALE_LANGUAGE_MATCH
94      * en_US <=> en => LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER
95      * en_US_POSIX <=> en_US_Android  =>  LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER
96      * en_US <=> en_US_Android => LOCALE_LANGUAGE_AND_COUNTRY_MATCH
97      * sp_US <=> en_US  =>  LOCALE_NO_MATCH
98      * de <=> de  => LOCALE_FULL_MATCH
99      * en_US <=> en_US => LOCALE_FULL_MATCH
100      * "" <=> en_US => LOCALE_ANY_MATCH
101      *
102      * @param referenceLocale the reference locale to test against.
103      * @param testedLocale the locale to test.
104      * @return a constant that measures how well the tested locale matches the reference locale.
105      */
getMatchLevel(String referenceLocale, String testedLocale)106     public static int getMatchLevel(String referenceLocale, String testedLocale) {
107         if (TextUtils.isEmpty(referenceLocale)) {
108             return TextUtils.isEmpty(testedLocale) ? LOCALE_FULL_MATCH : LOCALE_ANY_MATCH;
109         }
110         if (null == testedLocale) return LOCALE_NO_MATCH;
111         String[] referenceParams = referenceLocale.split("_", 3);
112         String[] testedParams = testedLocale.split("_", 3);
113         // By spec of String#split, [0] cannot be null and length cannot be 0.
114         if (!referenceParams[0].equals(testedParams[0])) return LOCALE_NO_MATCH;
115         switch (referenceParams.length) {
116         case 1:
117             return 1 == testedParams.length ? LOCALE_FULL_MATCH : LOCALE_LANGUAGE_MATCH;
118         case 2:
119             if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
120             if (!referenceParams[1].equals(testedParams[1]))
121                 return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
122             if (3 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH;
123             return LOCALE_FULL_MATCH;
124         case 3:
125             if (1 == testedParams.length) return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
126             if (!referenceParams[1].equals(testedParams[1]))
127                 return LOCALE_LANGUAGE_MATCH_COUNTRY_DIFFER;
128             if (2 == testedParams.length) return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
129             if (!referenceParams[2].equals(testedParams[2]))
130                 return LOCALE_LANGUAGE_AND_COUNTRY_MATCH_VARIANT_DIFFER;
131             return LOCALE_FULL_MATCH;
132         }
133         // It should be impossible to come here
134         return LOCALE_NO_MATCH;
135     }
136 
137     /**
138      * Return a string that represents this match level, with better matches first.
139      *
140      * The strings are sorted in lexicographic order: a better match will always be less than
141      * a worse match when compared together.
142      */
getMatchLevelSortedString(int matchLevel)143     public static String getMatchLevelSortedString(int matchLevel) {
144         // This works because the match levels are 0~99 (actually 0~30)
145         // Ideally this should use a number of digits equals to the 1og10 of the greater matchLevel
146         return String.format(Locale.ROOT, "%02d", MATCH_LEVEL_MAX - matchLevel);
147     }
148 
149     /**
150      * Find out whether a match level should be considered a match.
151      *
152      * This method takes a match level as returned by the #getMatchLevel method, and returns whether
153      * it should be considered a match in the usual sense with standard Locale functions.
154      *
155      * @param level the match level, as returned by getMatchLevel.
156      * @return whether this is a match or not.
157      */
isMatch(int level)158     public static boolean isMatch(int level) {
159         return LOCALE_MATCH <= level;
160     }
161 
162     private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
163 
164     /**
165      * Creates a locale from a string specification.
166      */
constructLocaleFromString(final String localeStr)167     public static Locale constructLocaleFromString(final String localeStr) {
168         if (localeStr == null) {
169             return null;
170         }
171         synchronized (sLocaleCache) {
172             Locale retval = sLocaleCache.get(localeStr);
173             if (retval != null) {
174                 return retval;
175             }
176             String[] localeParams = localeStr.split("_", 3);
177             if (localeParams.length == 1) {
178                 retval = new Locale(localeParams[0]);
179             } else if (localeParams.length == 2) {
180                 retval = new Locale(localeParams[0], localeParams[1]);
181             } else if (localeParams.length == 3) {
182                 retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
183             }
184             if (retval != null) {
185                 sLocaleCache.put(localeStr, retval);
186             }
187             return retval;
188         }
189     }
190 }
191