12fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa/*
22fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Copyright (C) 2013 The Android Open Source Project
32fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa *
42fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Licensed under the Apache License, Version 2.0 (the "License");
52fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * you may not use this file except in compliance with the License.
62fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * You may obtain a copy of the License at
72fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa *
82fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa *      http://www.apache.org/licenses/LICENSE-2.0
92fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa *
102fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * Unless required by applicable law or agreed to in writing, software
112fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * distributed under the License is distributed on an "AS IS" BASIS,
122fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
132fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * See the License for the specific language governing permissions and
142fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * limitations under the License.
152fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa */
162fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
172fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasapackage com.android.inputmethod.keyboard.tools;
182fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
192fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasaimport java.util.HashMap;
202fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasaimport java.util.Locale;
212fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
222fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa/**
232fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * A class to help with handling Locales in string form.
242fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa *
252fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * This is a subset of com/android/inputmethod/latin/utils/LocaleUtils.java in order to use
262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa * for the make-keyboard-text tool.
272fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa */
282fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasapublic final class LocaleUtils {
29d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    public static final Locale DEFAULT_LOCALE = Locale.ROOT;
30d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final String DEFAULT_LOCALE_CODE = "DEFAULT";
316bb3556ff7b24a5a38d7cc4276017bda3a9a4bbaTadashi G. Takaoka    public static final String NO_LANGUAGE_LOCALE_CODE = "zz";
326bb3556ff7b24a5a38d7cc4276017bda3a9a4bbaTadashi G. Takaoka    public static final String NO_LANGUAGE_LOCALE_DISPLAY_NAME = "Alphabet";
33465a2ac534bf5c0dea38608dbe713ed3a9dc84ebTadashi G. Takaoka
342fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    private LocaleUtils() {
352fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        // Intentional empty constructor for utility class.
362fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    }
372fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
38a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka    private static final HashMap<String, Locale> sLocaleCache = new HashMap<>();
392fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
40d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int INDEX_LANGUAGE = 0;
41d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int INDEX_SCRIPT = 1;
42d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int INDEX_REGION = 2;
43d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int ELEMENT_LIMIT = INDEX_REGION + 1;
44d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
452fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    /**
462fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa     * Creates a locale from a string specification.
47d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka     *
48d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka     * Locale string is: language(_script)?(_region)?
49d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka     * where: language := [a-zA-Z]{2,3}
50d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka     *        script := [a-zA-Z]{4}
51d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka     *        region := [a-zA-Z]{2,3}|[0-9]{3}
522fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa     */
532fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    public static Locale constructLocaleFromString(final String localeStr) {
542fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        if (localeStr == null) {
552fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            return null;
562fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        }
572fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        synchronized (sLocaleCache) {
58d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (sLocaleCache.containsKey(localeStr)) {
59d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                return sLocaleCache.get(localeStr);
60d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            }
61d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            boolean hasRegion = false;
62d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            final Locale.Builder builder = new Locale.Builder();
63d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            final String[] localeElements = localeStr.split("_", ELEMENT_LIMIT);
64d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (localeElements.length > INDEX_LANGUAGE) {
65d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                final String text = localeElements[INDEX_LANGUAGE];
66d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                if (isValidLanguage(text)) {
67d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    builder.setLanguage(text);
68d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                } else {
69d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    throw new RuntimeException("Unknown locale format: " + localeStr);
70d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                }
712fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            }
72d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (localeElements.length > INDEX_SCRIPT) {
73d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                final String text = localeElements[INDEX_SCRIPT];
74d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                if (isValidScript(text)) {
75d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    builder.setScript(text);
76d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                } else if (isValidRegion(text)) {
77d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    builder.setRegion(text);
78d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    hasRegion = true;
79d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                } else {
80d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    throw new RuntimeException("Unknown locale format: " + localeStr);
81d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                }
822fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            }
83d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (localeElements.length > INDEX_REGION) {
84d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                final String text = localeElements[INDEX_REGION];
85d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                if (!hasRegion && isValidRegion(text)) {
86d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    builder.setRegion(text);
87d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                } else {
88d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                    throw new RuntimeException("Unknown locale format: " + localeStr);
89d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                }
90d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            }
91d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            final Locale locale = builder.build();
92d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            sLocaleCache.put(localeStr, locale);
93d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            return locale;
94d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
95d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
96d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
97d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int MIN_LENGTH_OF_LANGUAGE = 2;
98d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int MAX_LENGTH_OF_LANGUAGE = 2;
99d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int LENGTH_OF_SCRIPT = 4;
100d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int MIN_LENGTH_OF_REGION = 2;
101d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int MAX_LENGTH_OF_REGION = 2;
102d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static final int LENGTH_OF_AREA_CODE = 3;
103d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
104d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isValidLanguage(final String text) {
105d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return isAlphabetSequence(text, MIN_LENGTH_OF_LANGUAGE, MAX_LENGTH_OF_LANGUAGE);
106d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
107d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
108d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isValidScript(final String text) {
109d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return isAlphabetSequence(text, LENGTH_OF_SCRIPT, LENGTH_OF_SCRIPT);
110d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
111d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
112d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isValidRegion(final String text) {
113d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return isAlphabetSequence(text, MIN_LENGTH_OF_REGION, MAX_LENGTH_OF_REGION)
114d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                || isDigitSequence(text, LENGTH_OF_AREA_CODE, LENGTH_OF_AREA_CODE);
115d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
116d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
117d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isAlphabetSequence(final String text, final int lower, final int upper) {
118d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        final int length = text.length();
119d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        if (length < lower || length > upper) {
120d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            return false;
121d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
122d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        for (int index = 0; index < length; index++) {
123d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (!isAsciiAlphabet(text.charAt(index))) {
124d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                return false;
1252fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            }
1262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        }
127d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return true;
1282fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    }
129465a2ac534bf5c0dea38608dbe713ed3a9dc84ebTadashi G. Takaoka
130d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isDigitSequence(final String text, final int lower, final int upper) {
131d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        final int length = text.length();
132d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        if (length < lower || length > upper) {
133d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            return false;
134d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
135d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        for (int index = 0; index < length; ++index) {
136d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            if (!isAsciiDigit(text.charAt(index))) {
137d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka                return false;
138d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            }
139d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
140d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return true;
141d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
142d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
143d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isAsciiAlphabet(char c) {
144d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
145d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
146d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
147d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    private static boolean isAsciiDigit(char c) {
148d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return c >= '0' && c <= '9';
149d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
150d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
151d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    public static String getLocaleCode(final Locale locale) {
152d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        if (locale == DEFAULT_LOCALE) {
153d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            return DEFAULT_LOCALE_CODE;
154d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
155d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        return locale.toString();
156d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    }
157d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka
158d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka    public static String getLocaleDisplayName(final Locale locale) {
159d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        if (locale == DEFAULT_LOCALE) {
160d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka            return DEFAULT_LOCALE_CODE;
161d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        }
162d317796207d9c9443669ff94aac63c4193ec0e6fTadashi G. Takaoka        if (locale.getLanguage().equals(NO_LANGUAGE_LOCALE_CODE)) {
1636bb3556ff7b24a5a38d7cc4276017bda3a9a4bbaTadashi G. Takaoka            return NO_LANGUAGE_LOCALE_DISPLAY_NAME;
164465a2ac534bf5c0dea38608dbe713ed3a9dc84ebTadashi G. Takaoka        }
165465a2ac534bf5c0dea38608dbe713ed3a9dc84ebTadashi G. Takaoka        return locale.getDisplayName(Locale.ENGLISH);
166465a2ac534bf5c0dea38608dbe713ed3a9dc84ebTadashi G. Takaoka    }
1672fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa}
168