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.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 = "Alphabet"; 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, "UTF-8"); 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 for (final StringResource res : resMap.getResources()) { 162 if (!defaultResMap.contains(res.mName)) { 163 throw new RuntimeException(res.mName + " in " + language 164 + " doesn't have default resource"); 165 } 166 } 167 dumpTextsInternal(out, resMap, defaultResMap); 168 out.format(" };\n\n"); 169 } 170 } 171 172 private void dumpLanguageMap(final PrintStream out) { 173 final ArrayList<String> allLanguages = new ArrayList<String>(); 174 allLanguages.addAll(mResourcesMap.keySet()); 175 Collections.sort(allLanguages); 176 for (final String language : allLanguages) { 177 out.format(" \"%s\", " + ARRAY_NAME_FOR_LANGUAGE + ", /* %s */\n", 178 language, language, getLanguageDisplayName(language)); 179 } 180 } 181 182 private static String getLanguageDisplayName(final String language) { 183 if (language.equals(NO_LANGUAGE_CODE)) { 184 return NO_LANGUAGE_DISPLAY_NAME; 185 } else { 186 return new Locale(language).getDisplayLanguage(); 187 } 188 } 189 190 private static void dumpTextsInternal(final PrintStream out, final StringResourceMap resMap, 191 final StringResourceMap defaultResMap) { 192 final ArrayInitializerFormatter formatter = 193 new ArrayInitializerFormatter(out, 100, " "); 194 boolean successiveNull = false; 195 for (final StringResource defaultRes : defaultResMap.getResources()) { 196 if (resMap.contains(defaultRes.mName)) { 197 final StringResource res = resMap.get(defaultRes.mName); 198 if (res.mComment != null) { 199 formatter.outCommentLines(addPrefix(" // ", res. mComment)); 200 } 201 final String escaped = escapeNonAscii(res.mValue); 202 if (escaped.length() == 0) { 203 formatter.outElement(EMPTY_STRING_VAR + ","); 204 } else { 205 formatter.outElement(String.format("\"%s\",", escaped)); 206 } 207 successiveNull = false; 208 } else { 209 formatter.outElement("null,"); 210 successiveNull = true; 211 } 212 } 213 if (!successiveNull) { 214 formatter.flush(); 215 } 216 } 217 218 private static String addPrefix(final String prefix, final String lines) { 219 final StringBuilder sb = new StringBuilder(); 220 for (final String line : lines.split("\n")) { 221 sb.append(prefix + line.trim() + "\n"); 222 } 223 return sb.toString(); 224 } 225 226 private static String escapeNonAscii(final String text) { 227 final StringBuilder sb = new StringBuilder(); 228 final int length = text.length(); 229 for (int i = 0; i < length; i++) { 230 final char c = text.charAt(i); 231 if (c >= ' ' && c < 0x7f) { 232 sb.append(c); 233 } else { 234 sb.append(String.format("\\u%04X", (int)c)); 235 } 236 } 237 return replaceIncompatibleEscape(sb.toString()); 238 } 239 240 private static String replaceIncompatibleEscape(final String text) { 241 String t = text; 242 t = replaceAll(t, "\\?", "?"); 243 t = replaceAll(t, "\\@", "@"); 244 t = replaceAll(t, "@string/", "!text/"); 245 return t; 246 } 247 248 private static String replaceAll(final String text, final String target, final String replace) { 249 String t = text; 250 while (t.indexOf(target) >= 0) { 251 t = t.replace(target, replace); 252 } 253 return t; 254 } 255 256 private static void close(Closeable stream) { 257 try { 258 if (stream != null) { 259 stream.close(); 260 } 261 } catch (IOException e) { 262 } 263 } 264 } 265