Home | History | Annotate | Download | only in tools
      1 /*
      2  * Copyright (C) 2012 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.io.File;
     20 import java.io.IOException;
     21 import java.io.InputStreamReader;
     22 import java.io.LineNumberReader;
     23 import java.io.PrintStream;
     24 import java.util.ArrayList;
     25 import java.util.Collections;
     26 import java.util.Comparator;
     27 import java.util.HashMap;
     28 import java.util.Locale;
     29 import java.util.TreeMap;
     30 import java.util.jar.JarFile;
     31 
     32 public class MoreKeysResources {
     33     private static final String TEXT_RESOURCE_NAME = "donottranslate-more-keys.xml";
     34 
     35     private static final String JAVA_TEMPLATE = "KeyboardTextsTable.tmpl";
     36     private static final String MARK_NAMES = "@NAMES@";
     37     private static final String MARK_DEFAULT_TEXTS = "@DEFAULT_TEXTS@";
     38     private static final String MARK_TEXTS = "@TEXTS@";
     39     private static final String TEXTS_ARRAY_NAME_PREFIX = "TEXTS_";
     40     private static final String MARK_LOCALES_AND_TEXTS = "@LOCALES_AND_TEXTS@";
     41     private static final String EMPTY_STRING_VAR = "EMPTY";
     42 
     43     private final JarFile mJar;
     44     // String resources maps sorted by its language. The language is determined from the jar entry
     45     // name by calling {@link JarUtils#getLocaleFromEntryName(String)}.
     46     private final TreeMap<String, StringResourceMap> mResourcesMap = new TreeMap<>();
     47     // Default string resources map.
     48     private final StringResourceMap mDefaultResourceMap;
     49     // Histogram of string resource names. This is used to sort {@link #mSortedResourceNames}.
     50     private final HashMap<String, Integer> mNameHistogram = new HashMap<>();
     51     // Sorted string resource names array; Descending order of histogram count.
     52     // The string resource name is specified as an attribute "name" in string resource files.
     53     // The string resource can be accessed by specifying name "!text/<name>"
     54     // via {@link KeyboardTextsSet#getText(String)}.
     55     private final String[] mSortedResourceNames;
     56 
     57     public MoreKeysResources(final JarFile jar) {
     58         mJar = jar;
     59         final ArrayList<String> resourceEntryNames = JarUtils.getEntryNameListing(
     60                 jar, TEXT_RESOURCE_NAME);
     61         for (final String entryName : resourceEntryNames) {
     62             final StringResourceMap resMap = new StringResourceMap(entryName);
     63             mResourcesMap.put(LocaleUtils.getLocaleCode(resMap.mLocale), resMap);
     64         }
     65         mDefaultResourceMap = mResourcesMap.get(
     66                 LocaleUtils.getLocaleCode(LocaleUtils.DEFAULT_LOCALE));
     67 
     68         // Initialize name histogram and names list.
     69         final HashMap<String, Integer> nameHistogram = mNameHistogram;
     70         final ArrayList<String> resourceNamesList = new ArrayList<>();
     71         for (final StringResource res : mDefaultResourceMap.getResources()) {
     72             nameHistogram.put(res.mName, 0); // Initialize histogram value.
     73             resourceNamesList.add(res.mName);
     74         }
     75         // Make name histogram.
     76         for (final String locale : mResourcesMap.keySet()) {
     77             final StringResourceMap resMap = mResourcesMap.get(locale);
     78             if (resMap == mDefaultResourceMap) continue;
     79             for (final StringResource res : resMap.getResources()) {
     80                 if (!mDefaultResourceMap.contains(res.mName)) {
     81                     throw new RuntimeException(res.mName + " in " + locale
     82                             + " doesn't have default resource");
     83                 }
     84                 final int histogramValue = nameHistogram.get(res.mName);
     85                 nameHistogram.put(res.mName, histogramValue + 1);
     86             }
     87         }
     88         // Sort names list.
     89         Collections.sort(resourceNamesList, new Comparator<String>() {
     90             @Override
     91             public int compare(final String leftName, final String rightName) {
     92                 final int leftCount = nameHistogram.get(leftName);
     93                 final int rightCount = nameHistogram.get(rightName);
     94                 // Descending order of histogram count.
     95                 if (leftCount > rightCount) return -1;
     96                 if (leftCount < rightCount) return 1;
     97                 // TODO: Add further criteria to order the same histogram value names to be able to
     98                 // minimize footprints of string resources arrays.
     99                 return 0;
    100             }
    101         });
    102         mSortedResourceNames = resourceNamesList.toArray(new String[resourceNamesList.size()]);
    103     }
    104 
    105     public void writeToJava(final String outDir) {
    106         final ArrayList<String> list = JarUtils.getEntryNameListing(mJar, JAVA_TEMPLATE);
    107         if (list.isEmpty()) {
    108             throw new RuntimeException("Can't find java template " + JAVA_TEMPLATE);
    109         }
    110         if (list.size() > 1) {
    111             throw new RuntimeException("Found multiple java template " + JAVA_TEMPLATE);
    112         }
    113         final String template = list.get(0);
    114         final String javaPackage = template.substring(0, template.lastIndexOf('/'));
    115         PrintStream ps = null;
    116         LineNumberReader lnr = null;
    117         try {
    118             if (outDir == null) {
    119                 ps = System.out;
    120             } else {
    121                 final File outPackage = new File(outDir, javaPackage);
    122                 final File outputFile = new File(outPackage,
    123                         JAVA_TEMPLATE.replace(".tmpl", ".java"));
    124                 outPackage.mkdirs();
    125                 ps = new PrintStream(outputFile, "UTF-8");
    126             }
    127             lnr = new LineNumberReader(new InputStreamReader(JarUtils.openResource(template)));
    128             inflateTemplate(lnr, ps);
    129         } catch (IOException e) {
    130             throw new RuntimeException(e);
    131         } finally {
    132             JarUtils.close(lnr);
    133             JarUtils.close(ps);
    134         }
    135     }
    136 
    137     private void inflateTemplate(final LineNumberReader in, final PrintStream out)
    138             throws IOException {
    139         String line;
    140         while ((line = in.readLine()) != null) {
    141             if (line.contains(MARK_NAMES)) {
    142                 dumpNames(out);
    143             } else if (line.contains(MARK_DEFAULT_TEXTS)) {
    144                 dumpDefaultTexts(out);
    145             } else if (line.contains(MARK_TEXTS)) {
    146                 dumpTexts(out);
    147             } else if (line.contains(MARK_LOCALES_AND_TEXTS)) {
    148                 dumpLocalesMap(out);
    149             } else {
    150                 out.println(line);
    151             }
    152         }
    153     }
    154 
    155     private void dumpNames(final PrintStream out) {
    156         final int namesCount = mSortedResourceNames.length;
    157         for (int index = 0; index < namesCount; index++) {
    158             final String name = mSortedResourceNames[index];
    159             final int histogramValue = mNameHistogram.get(name);
    160             out.format("        /* %3d:%2d */ \"%s\",\n", index, histogramValue, name);
    161         }
    162     }
    163 
    164     private void dumpDefaultTexts(final PrintStream out) {
    165         final int outputArraySize = dumpTextsInternal(out, mDefaultResourceMap);
    166         mDefaultResourceMap.setOutputArraySize(outputArraySize);
    167     }
    168 
    169     private static String getArrayNameForLocale(final Locale locale) {
    170         return TEXTS_ARRAY_NAME_PREFIX + LocaleUtils.getLocaleCode(locale);
    171     }
    172 
    173     private void dumpTexts(final PrintStream out) {
    174         for (final StringResourceMap resMap : mResourcesMap.values()) {
    175             final Locale locale = resMap.mLocale;
    176             if (resMap == mDefaultResourceMap) continue;
    177             out.format("    /* Locale %s: %s */\n",
    178                     locale, LocaleUtils.getLocaleDisplayName(locale));
    179             out.format("    private static final String[] " + getArrayNameForLocale(locale)
    180                     + " = {\n");
    181             final int outputArraySize = dumpTextsInternal(out, resMap);
    182             resMap.setOutputArraySize(outputArraySize);
    183             out.format("    };\n\n");
    184         }
    185     }
    186 
    187     private void dumpLocalesMap(final PrintStream out) {
    188         for (final StringResourceMap resMap : mResourcesMap.values()) {
    189             final Locale locale = resMap.mLocale;
    190             final String localeStr = LocaleUtils.getLocaleCode(locale);
    191             final String localeToDump = (locale == LocaleUtils.DEFAULT_LOCALE)
    192                     ? String.format("\"%s\"", localeStr)
    193                     : String.format("\"%s\"%s", localeStr, "       ".substring(localeStr.length()));
    194             out.format("        %s, %-12s /* %3d/%3d %s */\n",
    195                     localeToDump, getArrayNameForLocale(locale) + ",",
    196                     resMap.getResources().size(), resMap.getOutputArraySize(),
    197                     LocaleUtils.getLocaleDisplayName(locale));
    198         }
    199     }
    200 
    201     private int dumpTextsInternal(final PrintStream out, final StringResourceMap resMap) {
    202         final ArrayInitializerFormatter formatter =
    203                 new ArrayInitializerFormatter(out, 100, "        ", mSortedResourceNames);
    204         int outputArraySize = 0;
    205         boolean successiveNull = false;
    206         final int namesCount = mSortedResourceNames.length;
    207         for (int index = 0; index < namesCount; index++) {
    208             final String name = mSortedResourceNames[index];
    209             final StringResource res = resMap.get(name);
    210             if (res != null) {
    211                 // TODO: Check whether the resource value is equal to the default.
    212                 if (res.mComment != null) {
    213                     formatter.outCommentLines(addPrefix("        // ", res. mComment));
    214                 }
    215                 final String escaped = escapeNonAscii(res.mValue);
    216                 if (escaped.length() == 0) {
    217                     formatter.outElement(EMPTY_STRING_VAR + ",");
    218                 } else {
    219                     formatter.outElement(String.format("\"%s\",", escaped));
    220                 }
    221                 successiveNull = false;
    222                 outputArraySize = formatter.getCurrentIndex();
    223             } else {
    224                 formatter.outElement("null,");
    225                 successiveNull = true;
    226             }
    227         }
    228         if (!successiveNull) {
    229             formatter.flush();
    230         }
    231         return outputArraySize;
    232     }
    233 
    234     private static String addPrefix(final String prefix, final String lines) {
    235         final StringBuilder sb = new StringBuilder();
    236         for (final String line : lines.split("\n")) {
    237             sb.append(prefix + line.trim() + "\n");
    238         }
    239         return sb.toString();
    240     }
    241 
    242     private static String escapeNonAscii(final String text) {
    243         final StringBuilder sb = new StringBuilder();
    244         final int length = text.length();
    245         for (int i = 0; i < length; i++) {
    246             final char c = text.charAt(i);
    247             if (c >= ' ' && c < 0x7f) {
    248                 sb.append(c);
    249             } else {
    250                 sb.append(String.format("\\u%04X", (int)c));
    251             }
    252         }
    253         return sb.toString();
    254     }
    255 }
    256