1 /*
2  * Copyright (C) 2013 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.keyboard.tools;
18 
19 import java.util.HashMap;
20 import java.util.Locale;
21 
22 /**
23  * A class to help with handling Locales in string form.
24  *
25  * This is a subset of com/android/inputmethod/latin/utils/LocaleUtils.java in order to use
26  * for the make-keyboard-text tool.
27  */
28 public final class LocaleUtils {
29     public static final Locale DEFAULT_LOCALE = Locale.ROOT;
30     private static final String DEFAULT_LOCALE_CODE = "DEFAULT";
31     public static final String NO_LANGUAGE_LOCALE_CODE = "zz";
32     public static final String NO_LANGUAGE_LOCALE_DISPLAY_NAME = "Alphabet";
33 
LocaleUtils()34     private LocaleUtils() {
35         // Intentional empty constructor for utility class.
36     }
37 
38     private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
39 
40     private static final int INDEX_LANGUAGE = 0;
41     private static final int INDEX_SCRIPT = 1;
42     private static final int INDEX_REGION = 2;
43     private static final int ELEMENT_LIMIT = INDEX_REGION + 1;
44 
45     /**
46      * Creates a locale from a string specification.
47      *
48      * Locale string is: language(_script)?(_region)?
49      * where: language := [a-zA-Z]{2,3}
50      *        script := [a-zA-Z]{4}
51      *        region := [a-zA-Z]{2,3}|[0-9]{3}
52      */
constructLocaleFromString(final String localeStr)53     public static Locale constructLocaleFromString(final String localeStr) {
54         if (localeStr == null) {
55             return null;
56         }
57         synchronized (sLocaleCache) {
58             if (sLocaleCache.containsKey(localeStr)) {
59                 return sLocaleCache.get(localeStr);
60             }
61             boolean hasRegion = false;
62             final Locale.Builder builder = new Locale.Builder();
63             final String[] localeElements = localeStr.split("_", ELEMENT_LIMIT);
64             if (localeElements.length > INDEX_LANGUAGE) {
65                 final String text = localeElements[INDEX_LANGUAGE];
66                 if (isValidLanguage(text)) {
67                     builder.setLanguage(text);
68                 } else {
69                     throw new RuntimeException("Unknown locale format: " + localeStr);
70                 }
71             }
72             if (localeElements.length > INDEX_SCRIPT) {
73                 final String text = localeElements[INDEX_SCRIPT];
74                 if (isValidScript(text)) {
75                     builder.setScript(text);
76                 } else if (isValidRegion(text)) {
77                     builder.setRegion(text);
78                     hasRegion = true;
79                 } else {
80                     throw new RuntimeException("Unknown locale format: " + localeStr);
81                 }
82             }
83             if (localeElements.length > INDEX_REGION) {
84                 final String text = localeElements[INDEX_REGION];
85                 if (!hasRegion && isValidRegion(text)) {
86                     builder.setRegion(text);
87                 } else {
88                     throw new RuntimeException("Unknown locale format: " + localeStr);
89                 }
90             }
91             final Locale locale = builder.build();
92             sLocaleCache.put(localeStr, locale);
93             return locale;
94         }
95     }
96 
97     private static final int MIN_LENGTH_OF_LANGUAGE = 2;
98     private static final int MAX_LENGTH_OF_LANGUAGE = 2;
99     private static final int LENGTH_OF_SCRIPT = 4;
100     private static final int MIN_LENGTH_OF_REGION = 2;
101     private static final int MAX_LENGTH_OF_REGION = 2;
102     private static final int LENGTH_OF_AREA_CODE = 3;
103 
isValidLanguage(final String text)104     private static boolean isValidLanguage(final String text) {
105         return isAlphabetSequence(text, MIN_LENGTH_OF_LANGUAGE, MAX_LENGTH_OF_LANGUAGE);
106     }
107 
isValidScript(final String text)108     private static boolean isValidScript(final String text) {
109         return isAlphabetSequence(text, LENGTH_OF_SCRIPT, LENGTH_OF_SCRIPT);
110     }
111 
isValidRegion(final String text)112     private static boolean isValidRegion(final String text) {
113         return isAlphabetSequence(text, MIN_LENGTH_OF_REGION, MAX_LENGTH_OF_REGION)
114                 || isDigitSequence(text, LENGTH_OF_AREA_CODE, LENGTH_OF_AREA_CODE);
115     }
116 
isAlphabetSequence(final String text, final int lower, final int upper)117     private static boolean isAlphabetSequence(final String text, final int lower, final int upper) {
118         final int length = text.length();
119         if (length < lower || length > upper) {
120             return false;
121         }
122         for (int index = 0; index < length; index++) {
123             if (!isAsciiAlphabet(text.charAt(index))) {
124                 return false;
125             }
126         }
127         return true;
128     }
129 
isDigitSequence(final String text, final int lower, final int upper)130     private static boolean isDigitSequence(final String text, final int lower, final int upper) {
131         final int length = text.length();
132         if (length < lower || length > upper) {
133             return false;
134         }
135         for (int index = 0; index < length; ++index) {
136             if (!isAsciiDigit(text.charAt(index))) {
137                 return false;
138             }
139         }
140         return true;
141     }
142 
isAsciiAlphabet(char c)143     private static boolean isAsciiAlphabet(char c) {
144         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
145     }
146 
isAsciiDigit(char c)147     private static boolean isAsciiDigit(char c) {
148         return c >= '0' && c <= '9';
149     }
150 
getLocaleCode(final Locale locale)151     public static String getLocaleCode(final Locale locale) {
152         if (locale == DEFAULT_LOCALE) {
153             return DEFAULT_LOCALE_CODE;
154         }
155         return locale.toString();
156     }
157 
getLocaleDisplayName(final Locale locale)158     public static String getLocaleDisplayName(final Locale locale) {
159         if (locale == DEFAULT_LOCALE) {
160             return DEFAULT_LOCALE_CODE;
161         }
162         if (locale.getLanguage().equals(NO_LANGUAGE_LOCALE_CODE)) {
163             return NO_LANGUAGE_LOCALE_DISPLAY_NAME;
164         }
165         return locale.getDisplayName(Locale.ENGLISH);
166     }
167 }
168