From d317796207d9c9443669ff94aac63c4193ec0e6f Mon Sep 17 00:00:00 2001
From: "Tadashi G. Takaoka" <takaoka@google.com>
Date: Tue, 8 Apr 2014 19:20:23 +0900
Subject: [PATCH] Use Locale to process text resources

Change-Id: Ic1c4e1776071332e02c368055157124bb539d14e
---
 .../keyboard/internal/KeyboardTextsTable.java |   2 +-
 .../inputmethod/keyboard/tools/JarUtils.java  |  15 +-
 .../keyboard/tools/LocaleUtils.java           | 129 +++++++++++++++---
 .../keyboard/tools/MoreKeysResources.java     |  21 +--
 .../keyboard/tools/StringResourceMap.java     |   5 +-
 5 files changed, 135 insertions(+), 37 deletions(-)

diff --git a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
index 1bd98332f8..f0356df794 100644
--- a/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
+++ b/java/src/com/android/inputmethod/keyboard/internal/KeyboardTextsTable.java
@@ -3656,7 +3656,7 @@ public final class KeyboardTextsTable {
 
     private static final Object[] LOCALES_AND_TEXTS = {
     // "locale", TEXT_ARRAY,  /* numberOfNonNullText/lengthOf_TEXT_ARRAY localeName */
-        "DEFAULT", TEXTS_DEFAULT, /* 168/168 default */
+        "DEFAULT", TEXTS_DEFAULT, /* 168/168 DEFAULT */
         "af"     , TEXTS_af,    /*   7/ 12 Afrikaans */
         "ar"     , TEXTS_ar,    /*  55/107 Arabic */
         "az_AZ"  , TEXTS_az_AZ, /*   8/ 17 Azerbaijani (Azerbaijan) */
diff --git a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
index b892df236b..c947a63bf1 100644
--- a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/JarUtils.java
@@ -24,6 +24,7 @@ import java.net.URL;
 import java.net.URLDecoder;
 import java.util.ArrayList;
 import java.util.Enumeration;
+import java.util.Locale;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 
@@ -86,23 +87,23 @@ public final class JarUtils {
     }
 
     // The locale is taken from string resource jar entry name (values-<locale>/)
-    // or {@link LocaleUtils#DEFAULT_LOCALE_KEY} for the default string resource
+    // or {@link LocaleUtils#DEFAULT_LOCALE} for the default string resource
     // directory (values/).
-    public static String getLocaleFromEntryName(final String jarEntryName) {
+    public static Locale getLocaleFromEntryName(final String jarEntryName) {
         final String dirName = jarEntryName.substring(0, jarEntryName.lastIndexOf('/'));
         final int pos = dirName.lastIndexOf('/');
         final String parentName = (pos >= 0) ? dirName.substring(pos + 1) : dirName;
         final int localePos = parentName.indexOf('-');
         if (localePos < 0) {
             // Default resource name.
-            return LocaleUtils.DEFAULT_LOCALE_KEY;
+            return LocaleUtils.DEFAULT_LOCALE;
         }
-        final String locale = parentName.substring(localePos + 1);
-        final int regionPos = locale.indexOf("-r");
+        final String localeStr = parentName.substring(localePos + 1);
+        final int regionPos = localeStr.indexOf("-r");
         if (regionPos < 0) {
-            return locale;
+            return LocaleUtils.constructLocaleFromString(localeStr);
         }
-        return locale.replace("-r", "_");
+        return LocaleUtils.constructLocaleFromString(localeStr.replace("-r", "_"));
     }
 
     public static void close(final Closeable stream) {
diff --git a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/LocaleUtils.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/LocaleUtils.java
index d0f8b42922..0dfa37667f 100644
--- a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/LocaleUtils.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/LocaleUtils.java
@@ -26,7 +26,8 @@ import java.util.Locale;
  * for the make-keyboard-text tool.
  */
 public final class LocaleUtils {
-    public static final String DEFAULT_LOCALE_KEY = "DEFAULT";
+    public static final Locale DEFAULT_LOCALE = Locale.ROOT;
+    private static final String DEFAULT_LOCALE_CODE = "DEFAULT";
     public static final String NO_LANGUAGE_LOCALE_CODE = "zz";
     public static final String NO_LANGUAGE_LOCALE_DISPLAY_NAME = "Alphabet";
 
@@ -36,39 +37,131 @@ public final class LocaleUtils {
 
     private static final HashMap<String, Locale> sLocaleCache = new HashMap<String, Locale>();
 
+    private static final int INDEX_LANGUAGE = 0;
+    private static final int INDEX_SCRIPT = 1;
+    private static final int INDEX_REGION = 2;
+    private static final int ELEMENT_LIMIT = INDEX_REGION + 1;
+
     /**
      * Creates a locale from a string specification.
+     *
+     * Locale string is: language(_script)?(_region)?
+     * where: language := [a-zA-Z]{2,3}
+     *        script := [a-zA-Z]{4}
+     *        region := [a-zA-Z]{2,3}|[0-9]{3}
      */
     public static Locale constructLocaleFromString(final String localeStr) {
         if (localeStr == null) {
             return null;
         }
         synchronized (sLocaleCache) {
-            Locale retval = sLocaleCache.get(localeStr);
-            if (retval != null) {
-                return retval;
+            if (sLocaleCache.containsKey(localeStr)) {
+                return sLocaleCache.get(localeStr);
+            }
+            boolean hasRegion = false;
+            final Locale.Builder builder = new Locale.Builder();
+            final String[] localeElements = localeStr.split("_", ELEMENT_LIMIT);
+            if (localeElements.length > INDEX_LANGUAGE) {
+                final String text = localeElements[INDEX_LANGUAGE];
+                if (isValidLanguage(text)) {
+                    builder.setLanguage(text);
+                } else {
+                    throw new RuntimeException("Unknown locale format: " + localeStr);
+                }
             }
-            final String[] localeParams = localeStr.split("_", 3);
-            // TODO: Use JDK 7 Locale.Builder to handle a script name.
-            if (localeParams.length == 1) {
-                retval = new Locale(localeParams[0]);
-            } else if (localeParams.length == 2) {
-                retval = new Locale(localeParams[0], localeParams[1]);
-            } else if (localeParams.length == 3) {
-                retval = new Locale(localeParams[0], localeParams[1], localeParams[2]);
+            if (localeElements.length > INDEX_SCRIPT) {
+                final String text = localeElements[INDEX_SCRIPT];
+                if (isValidScript(text)) {
+                    builder.setScript(text);
+                } else if (isValidRegion(text)) {
+                    builder.setRegion(text);
+                    hasRegion = true;
+                } else {
+                    throw new RuntimeException("Unknown locale format: " + localeStr);
+                }
             }
-            if (retval != null) {
-                sLocaleCache.put(localeStr, retval);
+            if (localeElements.length > INDEX_REGION) {
+                final String text = localeElements[INDEX_REGION];
+                if (!hasRegion && isValidRegion(text)) {
+                    builder.setRegion(text);
+                } else {
+                    throw new RuntimeException("Unknown locale format: " + localeStr);
+                }
+            }
+            final Locale locale = builder.build();
+            sLocaleCache.put(localeStr, locale);
+            return locale;
+        }
+    }
+
+    private static final int MIN_LENGTH_OF_LANGUAGE = 2;
+    private static final int MAX_LENGTH_OF_LANGUAGE = 2;
+    private static final int LENGTH_OF_SCRIPT = 4;
+    private static final int MIN_LENGTH_OF_REGION = 2;
+    private static final int MAX_LENGTH_OF_REGION = 2;
+    private static final int LENGTH_OF_AREA_CODE = 3;
+
+    private static boolean isValidLanguage(final String text) {
+        return isAlphabetSequence(text, MIN_LENGTH_OF_LANGUAGE, MAX_LENGTH_OF_LANGUAGE);
+    }
+
+    private static boolean isValidScript(final String text) {
+        return isAlphabetSequence(text, LENGTH_OF_SCRIPT, LENGTH_OF_SCRIPT);
+    }
+
+    private static boolean isValidRegion(final String text) {
+        return isAlphabetSequence(text, MIN_LENGTH_OF_REGION, MAX_LENGTH_OF_REGION)
+                || isDigitSequence(text, LENGTH_OF_AREA_CODE, LENGTH_OF_AREA_CODE);
+    }
+
+    private static boolean isAlphabetSequence(final String text, final int lower, final int upper) {
+        final int length = text.length();
+        if (length < lower || length > upper) {
+            return false;
+        }
+        for (int index = 0; index < length; index++) {
+            if (!isAsciiAlphabet(text.charAt(index))) {
+                return false;
             }
-            return retval;
         }
+        return true;
     }
 
-    public static String getLocaleDisplayName(final String localeString) {
-        if (localeString.equals(NO_LANGUAGE_LOCALE_CODE)) {
+    private static boolean isDigitSequence(final String text, final int lower, final int upper) {
+        final int length = text.length();
+        if (length < lower || length > upper) {
+            return false;
+        }
+        for (int index = 0; index < length; ++index) {
+            if (!isAsciiDigit(text.charAt(index))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean isAsciiAlphabet(char c) {
+        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+    }
+
+    private static boolean isAsciiDigit(char c) {
+        return c >= '0' && c <= '9';
+    }
+
+    public static String getLocaleCode(final Locale locale) {
+        if (locale == DEFAULT_LOCALE) {
+            return DEFAULT_LOCALE_CODE;
+        }
+        return locale.toString();
+    }
+
+    public static String getLocaleDisplayName(final Locale locale) {
+        if (locale == DEFAULT_LOCALE) {
+            return DEFAULT_LOCALE_CODE;
+        }
+        if (locale.getLanguage().equals(NO_LANGUAGE_LOCALE_CODE)) {
             return NO_LANGUAGE_LOCALE_DISPLAY_NAME;
         }
-        final Locale locale = constructLocaleFromString(localeString);
         return locale.getDisplayName(Locale.ENGLISH);
     }
 }
diff --git a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
index c1a9753cc7..c8cb4acec4 100644
--- a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/MoreKeysResources.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.TreeMap;
 import java.util.jar.JarFile;
 
@@ -60,9 +61,10 @@ public class MoreKeysResources {
                 jar, TEXT_RESOURCE_NAME);
         for (final String entryName : resourceEntryNames) {
             final StringResourceMap resMap = new StringResourceMap(entryName);
-            mResourcesMap.put(resMap.mLocale, resMap);
+            mResourcesMap.put(LocaleUtils.getLocaleCode(resMap.mLocale), resMap);
         }
-        mDefaultResourceMap = mResourcesMap.get(LocaleUtils.DEFAULT_LOCALE_KEY);
+        mDefaultResourceMap = mResourcesMap.get(
+                LocaleUtils.getLocaleCode(LocaleUtils.DEFAULT_LOCALE));
 
         // Initialize name histogram and names list.
         final HashMap<String, Integer> nameHistogram = mNameHistogram;
@@ -165,13 +167,13 @@ public class MoreKeysResources {
         mDefaultResourceMap.setOutputArraySize(outputArraySize);
     }
 
-    private static String getArrayNameForLocale(final String locale) {
-        return TEXTS_ARRAY_NAME_PREFIX + locale;
+    private static String getArrayNameForLocale(final Locale locale) {
+        return TEXTS_ARRAY_NAME_PREFIX + LocaleUtils.getLocaleCode(locale);
     }
 
     private void dumpTexts(final PrintStream out) {
         for (final StringResourceMap resMap : mResourcesMap.values()) {
-            final String locale = resMap.mLocale;
+            final Locale locale = resMap.mLocale;
             if (resMap == mDefaultResourceMap) continue;
             out.format("    /* Locale %s: %s */\n",
                     locale, LocaleUtils.getLocaleDisplayName(locale));
@@ -185,10 +187,11 @@ public class MoreKeysResources {
 
     private void dumpLocalesMap(final PrintStream out) {
         for (final StringResourceMap resMap : mResourcesMap.values()) {
-            final String locale = resMap.mLocale;
-            final String localeToDump = locale.equals(LocaleUtils.DEFAULT_LOCALE_KEY)
-                    ? String.format("\"%s\"", locale)
-                    : String.format("\"%s\"%s", locale, "       ".substring(locale.length()));
+            final Locale locale = resMap.mLocale;
+            final String localeStr = LocaleUtils.getLocaleCode(locale);
+            final String localeToDump = (locale == LocaleUtils.DEFAULT_LOCALE)
+                    ? String.format("\"%s\"", localeStr)
+                    : String.format("\"%s\"%s", localeStr, "       ".substring(localeStr.length()));
             out.format("        %s, %-12s /* %3d/%3d %s */\n",
                     localeToDump, getArrayNameForLocale(locale) + ",",
                     resMap.getResources().size(), resMap.getOutputArraySize(),
diff --git a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
index d7e76ad776..6a79268e5c 100644
--- a/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
+++ b/tools/make-keyboard-text/src/com/android/inputmethod/keyboard/tools/StringResourceMap.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 
 import javax.xml.parsers.ParserConfigurationException;
@@ -34,8 +35,8 @@ import javax.xml.parsers.SAXParser;
 import javax.xml.parsers.SAXParserFactory;
 
 public class StringResourceMap {
-    // Locale name.
-    public final String mLocale;
+    // Locale of this string resource map.
+    public final Locale mLocale;
     // String resource list.
     private final List<StringResource> mResources;
     // Name to string resource map.
-- 
GitLab