From a785fa8edd7f7a1f91d45c5e66562d92cf5698af Mon Sep 17 00:00:00 2001 From: Keisuke Kuroyanagi Date: Thu, 27 Mar 2014 17:46:35 +0900 Subject: [PATCH] Dictionary migration in Java side. Bug: 13406708 Change-Id: If83938e4b4810d2e8353c70cdd8ef3ea97a29571 --- .../inputmethod/latin/BinaryDictionary.java | 23 ++++- .../latin/ExpandableBinaryDictionary.java | 9 ++ .../latin/utils/BinaryDictionaryUtils.java | 27 ++++++ .../utils/BinaryDictionaryUtilsTests.java | 92 +++++++++++++++++++ 4 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index 0aa34e82e..3efbdb78a 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -28,9 +28,10 @@ import com.android.inputmethod.latin.makedict.FormatSpec; import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions; import com.android.inputmethod.latin.makedict.UnsupportedFormatException; import com.android.inputmethod.latin.makedict.WordProperty; -import com.android.inputmethod.latin.personalization.PersonalizationHelper; import com.android.inputmethod.latin.settings.NativeSuggestOptions; +import com.android.inputmethod.latin.utils.BinaryDictionaryUtils; import com.android.inputmethod.latin.utils.CollectionUtils; +import com.android.inputmethod.latin.utils.FileUtils; import com.android.inputmethod.latin.utils.JniUtils; import com.android.inputmethod.latin.utils.LanguageModelParam; import com.android.inputmethod.latin.utils.StringUtils; @@ -81,6 +82,8 @@ public final class BinaryDictionary extends Dictionary { public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2; public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3; + public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate"; + private long mNativeDict; private final Locale mLocale; private final long mDictSize; @@ -458,6 +461,24 @@ public final class BinaryDictionary extends Dictionary { return needsToRunGCNative(mNativeDict, mindsBlockByGC); } + public boolean migrateTo(final int newFormatVersion) { + if (!isValidDictionary()) { + return false; + } + final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION; + // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion). + close(); + final File dictFile = new File(mDictFilePath); + final File tmpDictFile = new File(tmpDictFilePath); + FileUtils.deleteRecursively(dictFile); + if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) { + return false; + } + loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, + dictFile.length(), mIsUpdatable); + return true; + } + @UsedForTesting public int calculateProbability(final int unigramProbability, final int bigramProbability) { if (!isValidDictionary()) return NOT_A_PROBABILITY; diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 7847738e0..aa320e362 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -137,6 +137,11 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return formatVersion == FormatSpec.VERSION4; } + private boolean needsToMigrateDictionary(final int formatVersion) { + // TODO: Check version. + return false; + } + public boolean isValidDictionaryLocked() { return mBinaryDictionary.isValidDictionary(); } @@ -471,6 +476,10 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (oldBinaryDictionary != null) { oldBinaryDictionary.close(); } + if (mBinaryDictionary.isValidDictionary() + && needsToMigrateDictionary(mBinaryDictionary.getFormatVersion())) { + mBinaryDictionary.migrateTo(DICTIONARY_FORMAT_VERSION); + } } /** diff --git a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java index 638830046..b4658b531 100644 --- a/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java +++ b/java/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtils.java @@ -26,6 +26,8 @@ import java.io.File; import java.io.IOException; import java.util.Locale; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class BinaryDictionaryUtils { private static final String TAG = BinaryDictionaryUtils.class.getSimpleName(); @@ -64,6 +66,31 @@ public final class BinaryDictionaryUtils { return header; } + public static boolean renameDict(final File dictFile, final File newDictFile) { + if (dictFile.isFile()) { + return dictFile.renameTo(newDictFile); + } else if (dictFile.isDirectory()) { + final String dictName = dictFile.getName(); + final String newDictName = newDictFile.getName(); + if (newDictFile.exists()) { + return false; + } + for (final File file : dictFile.listFiles()) { + if (!file.isFile()) { + continue; + } + final String fileName = file.getName(); + final String newFileName = fileName.replaceFirst( + Pattern.quote(dictName), Matcher.quoteReplacement(newDictName)); + if (!file.renameTo(new File(dictFile, newFileName))) { + return false; + } + } + return dictFile.renameTo(newDictFile); + } + return false; + } + public static boolean createEmptyDictFile(final String filePath, final long dictVersion, final Locale locale, final Map attributeMap) { final String[] keyArray = new String[attributeMap.size()]; diff --git a/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java new file mode 100644 index 000000000..d86639101 --- /dev/null +++ b/tests/src/com/android/inputmethod/latin/utils/BinaryDictionaryUtilsTests.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 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.latin.utils; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; + +import com.android.inputmethod.latin.BinaryDictionary; +import com.android.inputmethod.latin.makedict.DictionaryHeader; +import com.android.inputmethod.latin.makedict.FormatSpec; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@LargeTest +public class BinaryDictionaryUtilsTests extends AndroidTestCase { + private static final String TEST_DICT_FILE_EXTENSION = ".testDict"; + private static final String TEST_LOCALE = "test"; + + private File createEmptyDictionaryAndGetFile(final String dictId, + final int formatVersion) throws IOException { + if (formatVersion == FormatSpec.VERSION4) { + return createEmptyVer4DictionaryAndGetFile(dictId); + } else { + throw new IOException("Dictionary format version " + formatVersion + + " is not supported."); + } + } + + private File createEmptyVer4DictionaryAndGetFile(final String dictId) throws IOException { + final File file = getDictFile(dictId); + FileUtils.deleteRecursively(file); + Map attributeMap = new HashMap(); + attributeMap.put(DictionaryHeader.DICTIONARY_ID_KEY, dictId); + attributeMap.put(DictionaryHeader.DICTIONARY_VERSION_KEY, + String.valueOf(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()))); + attributeMap.put(DictionaryHeader.USES_FORGETTING_CURVE_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + attributeMap.put(DictionaryHeader.HAS_HISTORICAL_INFO_KEY, + DictionaryHeader.ATTRIBUTE_VALUE_TRUE); + if (BinaryDictionaryUtils.createEmptyDictFile(file.getAbsolutePath(), FormatSpec.VERSION4, + LocaleUtils.constructLocaleFromString(TEST_LOCALE), attributeMap)) { + return file; + } else { + throw new IOException("Empty dictionary " + file.getAbsolutePath() + + " cannot be created."); + } + } + + private File getDictFile(final String dictId) { + return new File(getContext().getCacheDir(), dictId + TEST_DICT_FILE_EXTENSION); + } + + public void testRenameDictionary() { + final int formatVersion = FormatSpec.VERSION4; + File dictFile0 = null; + try { + dictFile0 = createEmptyDictionaryAndGetFile("MoveFromDictionary", formatVersion); + } catch (IOException e) { + fail("IOException while writing an initial dictionary : " + e); + } + final File dictFile1 = getDictFile("MoveToDictionary"); + FileUtils.deleteRecursively(dictFile1); + assertTrue(BinaryDictionaryUtils.renameDict(dictFile0, dictFile1)); + assertFalse(dictFile0.exists()); + assertTrue(dictFile1.exists()); + BinaryDictionary binaryDictionary = new BinaryDictionary(dictFile1.getAbsolutePath(), + 0 /* offset */, dictFile1.length(), true /* useFullEditDistance */, + Locale.getDefault(), TEST_LOCALE, true /* isUpdatable */); + assertTrue(binaryDictionary.isValidDictionary()); + assertTrue(binaryDictionary.getFormatVersion() == formatVersion); + binaryDictionary.close(); + } +}