• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 Google Inc.
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.i18n.addressinput;
18 
19 import java.util.HashMap;
20 import java.util.Locale;
21 import java.util.Map;
22 import java.util.regex.Matcher;
23 import java.util.regex.Pattern;
24 
25 /**
26  * Utility functions used by the address widget.
27  */
28 class Util {
29     /**
30      * This variable is in upper-case, since we convert the language code to upper case before doing
31      * string comparison.
32      */
33     private static final String LATIN_SCRIPT = "LATN";
34 
35     /**
36      * Map of countries that have non-latin local names, with the language that their local names
37      * are in. We only list a country here if we have the appropriate data. Only language sub-tags
38      * are listed.
39      */
40      private static final Map<String, String> nonLatinLocalLanguageCountries =
41              new HashMap<String, String>();
42      static {
43        nonLatinLocalLanguageCountries.put("AM", "hy");
44        nonLatinLocalLanguageCountries.put("CN", "zh");
45        nonLatinLocalLanguageCountries.put("HK", "zh");
46        nonLatinLocalLanguageCountries.put("JP", "ja");
47        nonLatinLocalLanguageCountries.put("KP", "ko");
48        nonLatinLocalLanguageCountries.put("KR", "ko");
49        nonLatinLocalLanguageCountries.put("MO", "zh");
50        nonLatinLocalLanguageCountries.put("TH", "th");
51        nonLatinLocalLanguageCountries.put("TW", "zh");
52        nonLatinLocalLanguageCountries.put("VN", "vi");
53      }
54 
55     /**
56      * Cannot instantiate this class - private constructor.
57      */
Util()58     private Util() {
59     }
60 
61     /**
62      * Returns true if the language code is explicitly marked to be in the latin script. For
63      * example, "zh-Latn" would return true, but "zh-TW", "en" and "zh" would all return false.
64      */
isExplicitLatinScript(String languageCode)65     static boolean isExplicitLatinScript(String languageCode) {
66         // Convert to upper-case for easier comparison.
67         languageCode = languageCode.toUpperCase();
68         // Check to see if the language code contains a script modifier.
69         final Pattern languageCodePattern = Pattern.compile("\\w{2,3}[-_](\\w{4})");
70         Matcher m = languageCodePattern.matcher(languageCode);
71         if (m.lookingAt()) {
72             String script = m.group(1);
73             if (script.equals(LATIN_SCRIPT)) {
74                 return true;
75             }
76         }
77         return false;
78     }
79 
80     /**
81      * Returns the language subtag of a language code. For example, returns "zh" if given "zh-Hans",
82      * "zh-CN" or other "zh" variants. If no language subtag can be found or the language tag is
83      * malformed, returns "und".
84      */
getLanguageSubtag(String languageCode)85     static String getLanguageSubtag(String languageCode) {
86         final Pattern languageCodePattern = Pattern
87                 .compile("(\\w{2,3})(?:[-_]\\w{4})?(?:[-_]\\w{2})?");
88         Matcher m = languageCodePattern.matcher(languageCode);
89         if (m.matches()) {
90             return m.group(1).toLowerCase();
91         }
92         return "und";
93     }
94 
95     /**
96      * Trims the string. If the field is empty after trimming, returns null instead. Note that this
97      * only trims ASCII white-space.
98      */
trimToNull(String originalStr)99     static String trimToNull(String originalStr) {
100         if (originalStr == null) {
101             return null;
102         }
103         String trimmedString = originalStr.trim();
104         return (trimmedString.length() == 0) ? null : trimmedString;
105     }
106 
107     /**
108      * Throws an exception if the object is null, with a generic error message.
109      */
checkNotNull(Object o)110     static void checkNotNull(Object o) throws NullPointerException {
111         checkNotNull(o, "This object should not be null.");
112     }
113 
114     /**
115      * Throws an exception if the object is null, with the error message supplied.
116      */
checkNotNull(Object o, String message)117     static void checkNotNull(Object o, String message) throws NullPointerException {
118         if (o == null) {
119             throw new NullPointerException(message);
120         }
121     }
122 
123     /**
124      * Joins input string with the given separator. If an input string is null, it will be skipped.
125      */
joinAndSkipNulls(String separator, String... strings)126     static String joinAndSkipNulls(String separator, String... strings) {
127         StringBuilder sb = null;
128         for (String s : strings) {
129             if (s != null) {
130                 s = s.trim();
131                 if (s.length() > 0) {
132                     if (sb == null) {
133                         sb = new StringBuilder(s);
134                     } else {
135                         sb.append(separator).append(s);
136                     }
137                 }
138             }
139         }
140         return sb == null ? null : sb.toString();
141     }
142 
143     /**
144      * Builds a map of the lower-cased values of the keys, names and local names provided. Each name
145      * and local name is mapped to its respective key in the map.
146      *
147      * @throws IllegalStateException if the names or lnames array is greater than the keys array.
148      */
buildNameToKeyMap(String[] keys, String[] names, String[] lnames)149     static Map<String, String> buildNameToKeyMap(String[] keys, String[] names, String[] lnames) {
150         if (keys == null) {
151             return null;
152         }
153 
154         Map<String, String> nameToKeyMap = new HashMap<String, String>();
155 
156         int keyLength = keys.length;
157         for (String k : keys) {
158             nameToKeyMap.put(k.toLowerCase(), k);
159         }
160         if (names != null) {
161             if (names.length > keyLength) {
162                 throw new IllegalStateException(
163                         "names length (" + names.length + ") is greater than keys length (" +
164                         keys.length + ")");
165             }
166             for (int i = 0; i < keyLength; i++) {
167                 // If we have less names than keys, we ignore all missing names. This happens
168                 // generally because reg-ex splitting methods on different platforms (java, js etc)
169                 // behave differently in the default case. Since missing names are fine, we opt to
170                 // be more robust here.
171                 if (i < names.length && names[i].length() > 0) {
172                     nameToKeyMap.put(names[i].toLowerCase(), keys[i]);
173                 }
174             }
175         }
176         if (lnames != null) {
177             if (lnames.length > keyLength) {
178                 throw new IllegalStateException(
179                         "lnames length (" + lnames.length + ") is greater than keys length (" +
180                         keys.length + ")");
181             }
182             for (int i = 0; i < keyLength; i++) {
183                 if (i < lnames.length && lnames[i].length() > 0) {
184                     nameToKeyMap.put(lnames[i].toLowerCase(), keys[i]);
185                 }
186             }
187         }
188         return nameToKeyMap;
189     }
190 
191     /**
192      * Returns a language code that the widget can use when fetching data, based on a {@link
193      * java.util.Locale} language and the current selected country in the address widget. This
194      * method is necessary since we have to determine later whether a language is "local" or "latin"
195      * for certain countries.
196      *
197      * @param language the current user language
198      * @param currentCountry the current selected country
199      * @return a language code string in BCP-47 format (e.g. "en", "zh-Latn", "zh-Hans" or
200      * "en-US").
201      */
getWidgetCompatibleLanguageCode(Locale language, String currentCountry)202     static String getWidgetCompatibleLanguageCode(Locale language, String currentCountry) {
203         String country = currentCountry.toUpperCase();
204         // Only do something if the country is one of those where we have names in the local
205         // language as well as in latin script.
206         if (nonLatinLocalLanguageCountries.containsKey(country)) {
207             String languageTag = language.getLanguage();
208             // Only do something if the language tag is _not_ the local language.
209             if (!languageTag.equals(nonLatinLocalLanguageCountries.get(country))) {
210                 // Build up the language tag with the country and language specified, and add in the
211                 // script-tag of "Latn" explicitly, since this is _not_ a local language. This means
212                 // that we might create a language tag of "th-Latn", which is not what the actual
213                 // language being used is, but it indicates that we prefer "Latn" names to whatever
214                 // the local alternative was.
215                 StringBuilder languageTagBuilder = new StringBuilder(languageTag);
216                 languageTagBuilder.append("_latn");
217                 if (language.getCountry().length() > 0) {
218                     languageTagBuilder.append("_");
219                     languageTagBuilder.append(language.getCountry());
220                 }
221                 return languageTagBuilder.toString();
222             }
223         }
224         return language.toString();
225     }
226 }
227