blob: 563acc57e293470db3f3532420d3df4ae96bb4ec [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.inputmethod.keyboard.tools;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PrintStream;
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;
public class MoreKeysResources {
private static final String TEXT_RESOURCE_NAME = "donottranslate-more-keys.xml";
private static final String JAVA_TEMPLATE = "KeyboardTextsTable.tmpl";
private static final String MARK_NAMES = "@NAMES@";
private static final String MARK_DEFAULT_TEXTS = "@DEFAULT_TEXTS@";
private static final String MARK_TEXTS = "@TEXTS@";
private static final String TEXTS_ARRAY_NAME_PREFIX = "TEXTS_";
private static final String MARK_LOCALES_AND_TEXTS = "@LOCALES_AND_TEXTS@";
private static final String EMPTY_STRING_VAR = "EMPTY";
private final JarFile mJar;
// String resources maps sorted by its language. The language is determined from the jar entry
// name by calling {@link JarUtils#getLocaleFromEntryName(String)}.
private final TreeMap<String, StringResourceMap> mResourcesMap = new TreeMap<>();
// Default string resources map.
private final StringResourceMap mDefaultResourceMap;
// Histogram of string resource names. This is used to sort {@link #mSortedResourceNames}.
private final HashMap<String, Integer> mNameHistogram = new HashMap<>();
// Sorted string resource names array; Descending order of histogram count.
// The string resource name is specified as an attribute "name" in string resource files.
// The string resource can be accessed by specifying name "!text/<name>"
// via {@link KeyboardTextsSet#getText(String)}.
private final String[] mSortedResourceNames;
public MoreKeysResources(final JarFile jar) {
mJar = jar;
final ArrayList<String> resourceEntryNames = JarUtils.getEntryNameListing(
jar, TEXT_RESOURCE_NAME);
for (final String entryName : resourceEntryNames) {
final StringResourceMap resMap = new StringResourceMap(entryName);
mResourcesMap.put(LocaleUtils.getLocaleCode(resMap.mLocale), resMap);
}
mDefaultResourceMap = mResourcesMap.get(
LocaleUtils.getLocaleCode(LocaleUtils.DEFAULT_LOCALE));
// Initialize name histogram and names list.
final HashMap<String, Integer> nameHistogram = mNameHistogram;
final ArrayList<String> resourceNamesList = new ArrayList<>();
for (final StringResource res : mDefaultResourceMap.getResources()) {
nameHistogram.put(res.mName, 0); // Initialize histogram value.
resourceNamesList.add(res.mName);
}
// Make name histogram.
for (final String locale : mResourcesMap.keySet()) {
final StringResourceMap resMap = mResourcesMap.get(locale);
if (resMap == mDefaultResourceMap) continue;
for (final StringResource res : resMap.getResources()) {
if (!mDefaultResourceMap.contains(res.mName)) {
throw new RuntimeException(res.mName + " in " + locale
+ " doesn't have default resource");
}
final int histogramValue = nameHistogram.get(res.mName);
nameHistogram.put(res.mName, histogramValue + 1);
}
}
// Sort names list.
Collections.sort(resourceNamesList, new Comparator<String>() {
@Override
public int compare(final String leftName, final String rightName) {
final int leftCount = nameHistogram.get(leftName);
final int rightCount = nameHistogram.get(rightName);
// Descending order of histogram count.
if (leftCount > rightCount) return -1;
if (leftCount < rightCount) return 1;
// TODO: Add further criteria to order the same histogram value names to be able to
// minimize footprints of string resources arrays.
return 0;
}
});
mSortedResourceNames = resourceNamesList.toArray(new String[resourceNamesList.size()]);
}
public void writeToJava(final String outDir) {
final ArrayList<String> list = JarUtils.getEntryNameListing(mJar, JAVA_TEMPLATE);
if (list.isEmpty()) {
throw new RuntimeException("Can't find java template " + JAVA_TEMPLATE);
}
if (list.size() > 1) {
throw new RuntimeException("Found multiple java template " + JAVA_TEMPLATE);
}
final String template = list.get(0);
final String javaPackage = template.substring(0, template.lastIndexOf('/'));
PrintStream ps = null;
LineNumberReader lnr = null;
try {
if (outDir == null) {
ps = System.out;
} else {
final File outPackage = new File(outDir, javaPackage);
final File outputFile = new File(outPackage,
JAVA_TEMPLATE.replace(".tmpl", ".java"));
outPackage.mkdirs();
ps = new PrintStream(outputFile, "UTF-8");
}
lnr = new LineNumberReader(new InputStreamReader(JarUtils.openResource(template)));
inflateTemplate(lnr, ps);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
JarUtils.close(lnr);
JarUtils.close(ps);
}
}
private void inflateTemplate(final LineNumberReader in, final PrintStream out)
throws IOException {
String line;
while ((line = in.readLine()) != null) {
if (line.contains(MARK_NAMES)) {
dumpNames(out);
} else if (line.contains(MARK_DEFAULT_TEXTS)) {
dumpDefaultTexts(out);
} else if (line.contains(MARK_TEXTS)) {
dumpTexts(out);
} else if (line.contains(MARK_LOCALES_AND_TEXTS)) {
dumpLocalesMap(out);
} else {
out.println(line);
}
}
}
private void dumpNames(final PrintStream out) {
final int namesCount = mSortedResourceNames.length;
for (int index = 0; index < namesCount; index++) {
final String name = mSortedResourceNames[index];
final int histogramValue = mNameHistogram.get(name);
out.format(" /* %3d:%2d */ \"%s\",\n", index, histogramValue, name);
}
}
private void dumpDefaultTexts(final PrintStream out) {
final int outputArraySize = dumpTextsInternal(out, mDefaultResourceMap);
mDefaultResourceMap.setOutputArraySize(outputArraySize);
}
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 Locale locale = resMap.mLocale;
if (resMap == mDefaultResourceMap) continue;
out.format(" /* Locale %s: %s */\n",
locale, LocaleUtils.getLocaleDisplayName(locale));
out.format(" private static final String[] " + getArrayNameForLocale(locale)
+ " = {\n");
final int outputArraySize = dumpTextsInternal(out, resMap);
resMap.setOutputArraySize(outputArraySize);
out.format(" };\n\n");
}
}
private void dumpLocalesMap(final PrintStream out) {
for (final StringResourceMap resMap : mResourcesMap.values()) {
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(),
LocaleUtils.getLocaleDisplayName(locale));
}
}
private int dumpTextsInternal(final PrintStream out, final StringResourceMap resMap) {
final ArrayInitializerFormatter formatter =
new ArrayInitializerFormatter(out, 100, " ", mSortedResourceNames);
int outputArraySize = 0;
boolean successiveNull = false;
final int namesCount = mSortedResourceNames.length;
for (int index = 0; index < namesCount; index++) {
final String name = mSortedResourceNames[index];
final StringResource res = resMap.get(name);
if (res != null) {
// TODO: Check whether the resource value is equal to the default.
if (res.mComment != null) {
formatter.outCommentLines(addPrefix(" // ", res. mComment));
}
final String escaped = escapeNonAscii(res.mValue);
if (escaped.length() == 0) {
formatter.outElement(EMPTY_STRING_VAR + ",");
} else {
formatter.outElement(String.format("\"%s\",", escaped));
}
successiveNull = false;
outputArraySize = formatter.getCurrentIndex();
} else {
formatter.outElement("null,");
successiveNull = true;
}
}
if (!successiveNull) {
formatter.flush();
}
return outputArraySize;
}
private static String addPrefix(final String prefix, final String lines) {
final StringBuilder sb = new StringBuilder();
for (final String line : lines.split("\n")) {
sb.append(prefix + line.trim() + "\n");
}
return sb.toString();
}
private static String escapeNonAscii(final String text) {
final StringBuilder sb = new StringBuilder();
final int length = text.length();
for (int i = 0; i < length; i++) {
final char c = text.charAt(i);
if (c >= ' ' && c < 0x7f) {
sb.append(c);
} else {
sb.append(String.format("\\u%04X", (int)c));
}
}
return sb.toString();
}
}