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.latin.maketext; 18 19 import java.io.Closeable; 20 import java.io.File; 21 import java.io.IOException; 22 import java.io.InputStream; 23 import java.io.InputStreamReader; 24 import java.io.LineNumberReader; 25 import java.io.PrintStream; 26 import java.util.ArrayList; 27 import java.util.Collections; 28 import java.util.HashMap; 29 import java.util.Locale; 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 = "KeyboardTextsSet.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 MARK_LANGUAGES_AND_TEXTS = "@LANGUAGES_AND_TEXTS@"; 40 private static final String DEFAUT_LANGUAGE_NAME = "DEFAULT"; 41 private static final String ARRAY_NAME_FOR_LANGUAGE = "LANGUAGE_%s"; 42 private static final String EMPTY_STRING_VAR = "EMPTY"; 43 44 private static final String NO_LANGUAGE_CODE = "zz"; 45 private static final String NO_LANGUAGE_DISPLAY_NAME = "No language"; 46 47 private final JarFile mJar; 48 // Language to string resources map. 49 private final HashMap<String, StringResourceMap> mResourcesMap = 50 new HashMap<String, StringResourceMap>(); 51 // Name to id map. 52 private final HashMap<String, Integer> mNameToIdMap = new HashMap<String,Integer>(); 53 54 public MoreKeysResources(final JarFile jar) { 55 mJar = jar; 56 final ArrayList<String> resources = JarUtils.getNameListing(jar, TEXT_RESOURCE_NAME); 57 for (final String name : resources) { 58 final String dirName = name.substring(0, name.lastIndexOf('/')); 59 final int pos = dirName.lastIndexOf('/'); 60 final String parentName = (pos >= 0) ? dirName.substring(pos + 1) : dirName; 61 final String language = getLanguageFromResDir(parentName); 62 final InputStream stream = JarUtils.openResource(name); 63 try { 64 mResourcesMap.put(language, new StringResourceMap(stream)); 65 } finally { 66 close(stream); 67 } 68 } 69 } 70 71 private static String getLanguageFromResDir(final String dirName) { 72 final int languagePos = dirName.indexOf('-'); 73 if (languagePos < 0) { 74 // Default resource. 75 return DEFAUT_LANGUAGE_NAME; 76 } 77 final String language = dirName.substring(languagePos + 1); 78 final int countryPos = language.indexOf("-r"); 79 if (countryPos < 0) { 80 return language; 81 } 82 return language.replace("-r", "_"); 83 } 84 85 public void writeToJava(final String outDir) { 86 final ArrayList<String> list = JarUtils.getNameListing(mJar, JAVA_TEMPLATE); 87 if (list.isEmpty()) 88 throw new RuntimeException("Can't find java template " + JAVA_TEMPLATE); 89 if (list.size() > 1) 90 throw new RuntimeException("Found multiple java template " + JAVA_TEMPLATE); 91 final String template = list.get(0); 92 final String javaPackage = template.substring(0, template.lastIndexOf('/')); 93 PrintStream ps = null; 94 LineNumberReader lnr = null; 95 try { 96 if (outDir == null) { 97 ps = System.out; 98 } else { 99 final File outPackage = new File(outDir, javaPackage); 100 final File outputFile = new File(outPackage, 101 JAVA_TEMPLATE.replace(".tmpl", ".java")); 102 outPackage.mkdirs(); 103 ps = new PrintStream(outputFile); 104 } 105 lnr = new LineNumberReader(new InputStreamReader(JarUtils.openResource(template))); 106 inflateTemplate(lnr, ps); 107 } catch (IOException e) { 108 throw new RuntimeException(e); 109 } finally { 110 close(lnr); 111 close(ps); 112 } 113 } 114 115 private void inflateTemplate(final LineNumberReader in, final PrintStream out) 116 throws IOException { 117 String line; 118 while ((line = in.readLine()) != null) { 119 if (line.contains(MARK_NAMES)) { 120 dumpNames(out); 121 } else if (line.contains(MARK_DEFAULT_TEXTS)) { 122 dumpDefaultTexts(out); 123 } else if (line.contains(MARK_TEXTS)) { 124 dumpTexts(out); 125 } else if (line.contains(MARK_LANGUAGES_AND_TEXTS)) { 126 dumpLanguageMap(out); 127 } else { 128 out.println(line); 129 } 130 } 131 } 132 133 private void dumpNames(final PrintStream out) { 134 final StringResourceMap defaultResMap = mResourcesMap.get(DEFAUT_LANGUAGE_NAME); 135 int id = 0; 136 for (final StringResource res : defaultResMap.getResources()) { 137 out.format(" /* %2d */ \"%s\",\n", id, res.mName); 138 mNameToIdMap.put(res.mName, id); 139 id++; 140 } 141 } 142 143 private void dumpDefaultTexts(final PrintStream out) { 144 final StringResourceMap defaultResMap = mResourcesMap.get(DEFAUT_LANGUAGE_NAME); 145 dumpTextsInternal(out, defaultResMap, defaultResMap); 146 } 147 148 private void dumpTexts(final PrintStream out) { 149 final StringResourceMap defaultResMap = mResourcesMap.get(DEFAUT_LANGUAGE_NAME); 150 final ArrayList<String> allLanguages = new ArrayList<String>(); 151 allLanguages.addAll(mResourcesMap.keySet()); 152 Collections.sort(allLanguages); 153 for (final String language : allLanguages) { 154 if (language.equals(DEFAUT_LANGUAGE_NAME)) { 155 continue; 156 } 157 out.format(" /* Language %s: %s */\n", language, getLanguageDisplayName(language)); 158 out.format(" private static final String[] " + ARRAY_NAME_FOR_LANGUAGE + " = {\n", 159 language); 160 final StringResourceMap resMap = mResourcesMap.get(language); 161 dumpTextsInternal(out, resMap, defaultResMap); 162 out.format(" };\n\n"); 163 } 164 } 165 166 private void dumpLanguageMap(final PrintStream out) { 167 final ArrayList<String> allLanguages = new ArrayList<String>(); 168 allLanguages.addAll(mResourcesMap.keySet()); 169 Collections.sort(allLanguages); 170 for (final String language : allLanguages) { 171 out.format(" \"%s\", " + ARRAY_NAME_FOR_LANGUAGE + ", /* %s */\n", 172 language, language, getLanguageDisplayName(language)); 173 } 174 } 175 176 private static String getLanguageDisplayName(final String language) { 177 if (language.equals(NO_LANGUAGE_CODE)) { 178 return NO_LANGUAGE_DISPLAY_NAME; 179 } else { 180 return new Locale(language).getDisplayLanguage(); 181 } 182 } 183 184 private static void dumpTextsInternal(final PrintStream out, final StringResourceMap resMap, 185 final StringResourceMap defaultResMap) { 186 final ArrayInitializerFormatter formatter = 187 new ArrayInitializerFormatter(out, 100, " "); 188 boolean successiveNull = false; 189 for (final StringResource defaultRes : defaultResMap.getResources()) { 190 if (resMap.contains(defaultRes.mName)) { 191 final StringResource res = resMap.get(defaultRes.mName); 192 if (res.mComment != null) { 193 formatter.outCommentLines(addPrefix(" // ", res. mComment)); 194 } 195 final String escaped = escapeNonAscii(res.mValue); 196 if (escaped.length() == 0) { 197 formatter.outElement(EMPTY_STRING_VAR + ","); 198 } else { 199 formatter.outElement(String.format("\"%s\",", escaped)); 200 } 201 successiveNull = false; 202 } else { 203 formatter.outElement("null,"); 204 successiveNull = true; 205 } 206 } 207 if (!successiveNull) { 208 formatter.flush(); 209 } 210 } 211 212 private static String addPrefix(final String prefix, final String lines) { 213 final StringBuilder sb = new StringBuilder(); 214 for (final String line : lines.split("\n")) { 215 sb.append(prefix + line.trim() + "\n"); 216 } 217 return sb.toString(); 218 } 219 220 private static String escapeNonAscii(final String text) { 221 final StringBuilder sb = new StringBuilder(); 222 final int length = text.length(); 223 for (int i = 0; i < length; i++) { 224 final char c = text.charAt(i); 225 if (c >= ' ' && c < 0x7f) { 226 sb.append(c); 227 } else { 228 sb.append(String.format("\\u%04X", (int)c)); 229 } 230 } 231 return replaceIncompatibleEscape(sb.toString()); 232 } 233 234 private static String replaceIncompatibleEscape(final String text) { 235 String t = text; 236 t = replaceAll(t, "\\?", "?"); 237 t = replaceAll(t, "\\@", "@"); 238 t = replaceAll(t, "@string/", "!text/"); 239 return t; 240 } 241 242 private static String replaceAll(final String text, final String target, final String replace) { 243 String t = text; 244 while (t.indexOf(target) >= 0) { 245 t = t.replace(target, replace); 246 } 247 return t; 248 } 249 250 private static void close(Closeable stream) { 251 try { 252 if (stream != null) { 253 stream.close(); 254 } 255 } catch (IOException e) { 256 } 257 } 258 } 259