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
17package com.android.inputmethod.keyboard.tools;
18
19import java.util.HashMap;
20import 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 */
28public 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
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     */
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
104    private static boolean isValidLanguage(final String text) {
105        return isAlphabetSequence(text, MIN_LENGTH_OF_LANGUAGE, MAX_LENGTH_OF_LANGUAGE);
106    }
107
108    private static boolean isValidScript(final String text) {
109        return isAlphabetSequence(text, LENGTH_OF_SCRIPT, LENGTH_OF_SCRIPT);
110    }
111
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
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
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
143    private static boolean isAsciiAlphabet(char c) {
144        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
145    }
146
147    private static boolean isAsciiDigit(char c) {
148        return c >= '0' && c <= '9';
149    }
150
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
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