Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d515f134 authored by Jean Chalard's avatar Jean Chalard
Browse files

Refactoring

This creates a new DictionaryInfoUtils class and moves a bunch
of static methods there for later usage.

Change-Id: Iecb0643e6029a7be36bd6cb36aa918c40e6d8c6a
parent 19e13df9
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -35,11 +35,14 @@ final class AssetFileAddress {
        mLength = length;
    }

    public static AssetFileAddress makeFromFile(final File file) {
        if (!file.isFile()) return null;
        return new AssetFileAddress(file.getAbsolutePath(), 0L, file.length());
    }

    public static AssetFileAddress makeFromFileName(final String filename) {
        if (null == filename) return null;
        final File f = new File(filename);
        if (!f.isFile()) return null;
        return new AssetFileAddress(filename, 0l, f.length());
        return makeFromFile(new File(filename));
    }

    public static AssetFileAddress makeFromFileNameAndOffset(final String filename,
+1 −1
Original line number Diff line number Diff line
@@ -149,7 +149,7 @@ public final class BinaryDictionaryFileDumper {
        final int MODE_MAX = NONE;

        final Uri.Builder wordListUriBuilder = getProviderUriBuilder(id);
        final String finalFileName = BinaryDictionaryGetter.getCacheFileName(id, locale, context);
        final String finalFileName = DictionaryInfoUtils.getCacheFileName(id, locale, context);
        String tempFileName;
        try {
            tempFileName = BinaryDictionaryGetter.getTempFileName(id, context);
+15 −154
Original line number Diff line number Diff line
@@ -65,110 +65,12 @@ final class BinaryDictionaryGetter {
    // Prevents this from being instantiated
    private BinaryDictionaryGetter() {}

    /**
     * Returns whether we may want to use this character as part of a file name.
     *
     * This basically only accepts ascii letters and numbers, and rejects everything else.
     */
    private static boolean isFileNameCharacter(int codePoint) {
        if (codePoint >= 0x30 && codePoint <= 0x39) return true; // Digit
        if (codePoint >= 0x41 && codePoint <= 0x5A) return true; // Uppercase
        if (codePoint >= 0x61 && codePoint <= 0x7A) return true; // Lowercase
        return codePoint == '_'; // Underscore
    }

    /**
     * Escapes a string for any characters that may be suspicious for a file or directory name.
     *
     * Concretely this does a sort of URL-encoding except it will encode everything that's not
     * alphanumeric or underscore. (true URL-encoding leaves alone characters like '*', which
     * we cannot allow here)
     */
    // TODO: create a unit test for this method
    private static String replaceFileNameDangerousCharacters(final String name) {
        // This assumes '%' is fully available as a non-separator, normal
        // character in a file name. This is probably true for all file systems.
        final StringBuilder sb = new StringBuilder();
        final int nameLength = name.length();
        for (int i = 0; i < nameLength; i = name.offsetByCodePoints(i, 1)) {
            final int codePoint = name.codePointAt(i);
            if (isFileNameCharacter(codePoint)) {
                sb.appendCodePoint(codePoint);
            } else {
                // 6 digits - unicode is limited to 21 bits
                sb.append(String.format((Locale)null, "%%%1$06x", codePoint));
            }
        }
        return sb.toString();
    }

    /**
     * Reverse escaping done by replaceFileNameDangerousCharacters.
     */
    private static String getWordListIdFromFileName(final String fname) {
        final StringBuilder sb = new StringBuilder();
        final int fnameLength = fname.length();
        for (int i = 0; i < fnameLength; i = fname.offsetByCodePoints(i, 1)) {
            final int codePoint = fname.codePointAt(i);
            if ('%' != codePoint) {
                sb.appendCodePoint(codePoint);
            } else {
                final int encodedCodePoint = Integer.parseInt(fname.substring(i + 1, i + 7), 16);
                i += 6;
                sb.appendCodePoint(encodedCodePoint);
            }
        }
        return sb.toString();
    }

    /**
     * Helper method to get the top level cache directory.
     */
    private static String getWordListCacheDirectory(final Context context) {
        return context.getFilesDir() + File.separator + "dicts";
    }

    /**
     * Find out the cache directory associated with a specific locale.
     */
    private static String getCacheDirectoryForLocale(final String locale, final Context context) {
        final String relativeDirectoryName = replaceFileNameDangerousCharacters(locale);
        final String absoluteDirectoryName = getWordListCacheDirectory(context) + File.separator
                + relativeDirectoryName;
        final File directory = new File(absoluteDirectoryName);
        if (!directory.exists()) {
            if (!directory.mkdirs()) {
                Log.e(TAG, "Could not create the directory for locale" + locale);
            }
        }
        return absoluteDirectoryName;
    }

    /**
     * Generates a file name for the id and locale passed as an argument.
     *
     * In the current implementation the file name returned will always be unique for
     * any id/locale pair, but please do not expect that the id can be the same for
     * different dictionaries with different locales. An id should be unique for any
     * dictionary.
     * The file name is pretty much an URL-encoded version of the id inside a directory
     * named like the locale, except it will also escape characters that look dangerous
     * to some file systems.
     * @param id the id of the dictionary for which to get a file name
     * @param locale the locale for which to get the file name as a string
     * @param context the context to use for getting the directory
     * @return the name of the file to be created
     */
    public static String getCacheFileName(String id, String locale, Context context) {
        final String fileName = replaceFileNameDangerousCharacters(id);
        return getCacheDirectoryForLocale(locale, context) + File.separator + fileName;
    }

    /**
     * Generates a unique temporary file name in the app cache directory.
     */
    public static String getTempFileName(String id, Context context) throws IOException {
        return File.createTempFile(replaceFileNameDangerousCharacters(id), null).getAbsolutePath();
        return File.createTempFile(DictionaryInfoUtils.replaceFileNameDangerousCharacters(id),
                null).getAbsolutePath();
    }

    /**
@@ -221,27 +123,6 @@ final class BinaryDictionaryGetter {
        }
    }

    /**
     * Helper method to the list of cache directories, one for each distinct locale.
     */
    private static File[] getCachedDirectoryList(final Context context) {
        return new File(getWordListCacheDirectory(context)).listFiles();
    }

    /**
     * Returns the category for a given file name.
     *
     * This parses the file name, extracts the category, and returns it. See
     * {@link #getMainDictId(Locale)} and {@link #isMainWordListId(String)}.
     * @return The category as a string or null if it can't be found in the file name.
     */
    private static String getCategoryFromFileName(final String fileName) {
        final String id = getWordListIdFromFileName(fileName);
        final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
        if (2 != idArray.length) return null;
        return idArray[0];
    }

    /**
     * Utility class for the {@link #getCachedWordLists} method
     */
@@ -268,20 +149,21 @@ final class BinaryDictionaryGetter {
     * @param context the context on which to open the files upon.
     * @return an array of binary dictionary files, which may be empty but may not be null.
     */
    private static File[] getCachedWordLists(final String locale,
            final Context context) {
        final File[] directoryList = getCachedDirectoryList(context);
    private static File[] getCachedWordLists(final String locale, final Context context) {
        final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
        if (null == directoryList) return EMPTY_FILE_ARRAY;
        final HashMap<String, FileAndMatchLevel> cacheFiles = CollectionUtils.newHashMap();
        for (File directory : directoryList) {
            if (!directory.isDirectory()) continue;
            final String dirLocale = getWordListIdFromFileName(directory.getName());
            final String dirLocale =
                    DictionaryInfoUtils.getWordListIdFromFileName(directory.getName());
            final int matchLevel = LocaleUtils.getMatchLevel(dirLocale, locale);
            if (LocaleUtils.isMatch(matchLevel)) {
                final File[] wordLists = directory.listFiles();
                if (null != wordLists) {
                    for (File wordList : wordLists) {
                        final String category = getCategoryFromFileName(wordList.getName());
                        final String category =
                                DictionaryInfoUtils.getCategoryFromFileName(wordList.getName());
                        final FileAndMatchLevel currentBestMatch = cacheFiles.get(category);
                        if (null == currentBestMatch || currentBestMatch.mMatchLevel < matchLevel) {
                            cacheFiles.put(category, new FileAndMatchLevel(wordList, matchLevel));
@@ -310,7 +192,7 @@ final class BinaryDictionaryGetter {
            final File fileToKeep) {
        try {
            final File canonicalFileToKeep = fileToKeep.getCanonicalFile();
            final File[] directoryList = getCachedDirectoryList(context);
            final File[] directoryList = DictionaryInfoUtils.getCachedDirectoryList(context);
            if (null == directoryList) return;
            for (File directory : directoryList) {
                // There is one directory per locale. See #getCachedDirectoryList
@@ -318,7 +200,8 @@ final class BinaryDictionaryGetter {
                final File[] wordLists = directory.listFiles();
                if (null == wordLists) continue;
                for (File wordList : wordLists) {
                    final String fileId = getWordListIdFromFileName(wordList.getName());
                    final String fileId =
                            DictionaryInfoUtils.getWordListIdFromFileName(wordList.getName());
                    if (fileId.equals(id)) {
                        if (!canonicalFileToKeep.equals(wordList.getCanonicalFile())) {
                            wordList.delete();
@@ -331,28 +214,6 @@ final class BinaryDictionaryGetter {
        }
    }


    /**
     * Returns the id associated with the main word list for a specified locale.
     *
     * Word lists stored in Android Keyboard's resources are referred to as the "main"
     * word lists. Since they can be updated like any other list, we need to assign a
     * unique ID to them. This ID is just the name of the language (locale-wise) they
     * are for, and this method returns this ID.
     */
    private static String getMainDictId(final Locale locale) {
        // This works because we don't include by default different dictionaries for
        // different countries. This actually needs to return the id that we would
        // like to use for word lists included in resources, and the following is okay.
        return MAIN_DICTIONARY_CATEGORY + ID_CATEGORY_SEPARATOR + locale.getLanguage().toString();
    }

    private static boolean isMainWordListId(final String id) {
        final String[] idArray = id.split(ID_CATEGORY_SEPARATOR);
        if (2 != idArray.length) return false;
        return MAIN_DICTIONARY_CATEGORY.equals(idArray[0]);
    }

    // ## HACK ## we prevent usage of a dictionary before version 18 for English only. The reason
    // for this is, since those do not include whitelist entries, the new code with an old version
    // of the dictionary would lose whitelist functionality.
@@ -429,16 +290,16 @@ final class BinaryDictionaryGetter {
                    hasDefaultWordList);
        }
        final File[] cachedWordLists = getCachedWordLists(locale.toString(), context);
        final String mainDictId = getMainDictId(locale);
        final String mainDictId = DictionaryInfoUtils.getMainDictId(locale);
        final DictPackSettings dictPackSettings = new DictPackSettings(context);

        boolean foundMainDict = false;
        final ArrayList<AssetFileAddress> fileList = CollectionUtils.newArrayList();
        // cachedWordLists may not be null, see doc for getCachedDictionaryList
        for (final File f : cachedWordLists) {
            final String wordListId = getWordListIdFromFileName(f.getName());
            final String wordListId = DictionaryInfoUtils.getWordListIdFromFileName(f.getName());
            final boolean canUse = f.canRead() && hackCanUseDictionaryFile(locale, f);
            if (canUse && isMainWordListId(wordListId)) {
            if (canUse && DictionaryInfoUtils.isMainWordListId(wordListId)) {
                foundMainDict = true;
            }
            if (!dictPackSettings.isWordListActive(wordListId)) continue;
@@ -451,7 +312,7 @@ final class BinaryDictionaryGetter {

        if (!foundMainDict && dictPackSettings.isWordListActive(mainDictId)) {
            final int fallbackResId =
                    DictionaryFactory.getMainDictionaryResourceId(context.getResources(), locale);
                    DictionaryInfoUtils.getMainDictionaryResourceId(context.getResources(), locale);
            final AssetFileAddress fallbackAsset = loadFallbackResource(context, fallbackResId);
            if (null != fallbackAsset) {
                fileList.add(fallbackAsset);
+4 −47
Original line number Diff line number Diff line
@@ -31,9 +31,6 @@ import java.util.Locale;
 */
public final class DictionaryFactory {
    private static final String TAG = DictionaryFactory.class.getSimpleName();
    // This class must be located in the same package as LatinIME.java.
    private static final String RESOURCE_PACKAGE_NAME =
            DictionaryFactory.class.getPackage().getName();

    /**
     * Initializes a main dictionary collection from a dictionary pack, with explicit flags.
@@ -96,8 +93,8 @@ public final class DictionaryFactory {
            final Locale locale) {
        AssetFileDescriptor afd = null;
        try {
            final int resId =
                    getMainDictionaryResourceIdIfAvailableForLocale(context.getResources(), locale);
            final int resId = DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
                    context.getResources(), locale);
            if (0 == resId) return null;
            afd = context.getResources().openRawResourceFd(resId);
            if (afd == null) {
@@ -154,47 +151,7 @@ public final class DictionaryFactory {
     */
    public static boolean isDictionaryAvailable(Context context, Locale locale) {
        final Resources res = context.getResources();
        return 0 != getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
    }

    private static final String DEFAULT_MAIN_DICT = "main";
    private static final String MAIN_DICT_PREFIX = "main_";

    /**
     * Helper method to return a dictionary res id for a locale, or 0 if none.
     * @param locale dictionary locale
     * @return main dictionary resource id
     */
    private static int getMainDictionaryResourceIdIfAvailableForLocale(final Resources res,
            final Locale locale) {
        int resId;
        // Try to find main_language_country dictionary.
        if (!locale.getCountry().isEmpty()) {
            final String dictLanguageCountry = MAIN_DICT_PREFIX + locale.toString().toLowerCase();
            if ((resId = res.getIdentifier(
                    dictLanguageCountry, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
                return resId;
            }
        }

        // Try to find main_language dictionary.
        final String dictLanguage = MAIN_DICT_PREFIX + locale.getLanguage();
        if ((resId = res.getIdentifier(dictLanguage, "raw", RESOURCE_PACKAGE_NAME)) != 0) {
            return resId;
        }

        // Not found, return 0
        return 0;
    }

    /**
     * Returns a main dictionary resource id
     * @param locale dictionary locale
     * @return main dictionary resource id
     */
    public static int getMainDictionaryResourceId(final Resources res, final Locale locale) {
        int resourceId = getMainDictionaryResourceIdIfAvailableForLocale(res, locale);
        if (0 != resourceId) return resourceId;
        return res.getIdentifier(DEFAULT_MAIN_DICT, "raw", RESOURCE_PACKAGE_NAME);
        return 0 != DictionaryInfoUtils.getMainDictionaryResourceIdIfAvailableForLocale(
                res, locale);
    }
}
+245 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading