diff options
Diffstat (limited to 'java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java')
-rw-r--r-- | java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java | 310 |
1 files changed, 249 insertions, 61 deletions
diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 3f11391ba..b92283c5b 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -20,13 +20,16 @@ import android.content.Context; import android.os.SystemClock; import android.util.Log; +import com.android.inputmethod.annotations.UsedForTesting; import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo; +import com.android.inputmethod.latin.personalization.DynamicPersonalizationDictionaryWriter; import com.android.inputmethod.latin.utils.CollectionUtils; import java.io.File; import java.util.ArrayList; import java.util.HashMap; +import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantReadWriteLock; /** @@ -78,12 +81,21 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ private final String mFilename; + /** Whether to support dynamically updating the dictionary */ + private final boolean mIsUpdatable; + /** Controls access to the shared binary dictionary file across multiple instances. */ private final DictionaryController mSharedDictionaryController; /** Controls access to the local binary dictionary for this instance. */ private final DictionaryController mLocalDictionaryController = new DictionaryController(); + /* A extension for a binary dictionary file. */ + public static final String DICT_FILE_EXTENSION = ".dict"; + + private final AtomicReference<AsyncWriteBinaryDictionaryTask> mWaitingTask = + new AtomicReference<AsyncWriteBinaryDictionaryTask>(); + /** * Abstract method for loading the unigrams and bigrams of a given dictionary in a background * thread. @@ -110,6 +122,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return controller; } + private static AbstractDictionaryWriter getDictionaryWriter(final Context context, + final String dictType, final boolean isDynamicPersonalizationDictionary) { + if (isDynamicPersonalizationDictionary) { + return new DynamicPersonalizationDictionaryWriter(context, dictType); + } else { + return new DictionaryWriter(context, dictType); + } + } + /** * Creates a new expandable binary dictionary. * @@ -117,19 +138,23 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * @param filename The filename for this binary dictionary. Multiple dictionaries with the same * filename is supported. * @param dictType the dictionary type, as a human-readable string + * @param isUpdatable whether to support dynamically updating the dictionary. Please note that + * dynamic dictionary has negative effects on memory space and computation time. */ - public ExpandableBinaryDictionary( - final Context context, final String filename, final String dictType) { + public ExpandableBinaryDictionary(final Context context, final String filename, + final String dictType, final boolean isUpdatable) { super(dictType); mFilename = filename; mContext = context; + mIsUpdatable = isUpdatable; mBinaryDictionary = null; mSharedDictionaryController = getSharedDictionaryController(filename); - mDictionaryWriter = new DictionaryWriter(context, dictType); + // Currently, only dynamic personalization dictionary is updatable. + mDictionaryWriter = getDictionaryWriter(context, dictType, isUpdatable); } protected static String getFilenameWithLocale(final String name, final String localeStr) { - return name + "." + localeStr + ".dict"; + return name + "." + localeStr + DICT_FILE_EXTENSION; } /** @@ -137,6 +162,16 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { */ @Override public void close() { + closeBinaryDictionary(); + mLocalDictionaryController.writeLock().lock(); + try { + mDictionaryWriter.close(); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + + protected void closeBinaryDictionary() { // Ensure that no other threads are accessing the local binary dictionary. mLocalDictionaryController.writeLock().lock(); try { @@ -144,7 +179,15 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { mBinaryDictionary.close(); mBinaryDictionary = null; } - mDictionaryWriter.close(); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + + protected void clear() { + mLocalDictionaryController.writeLock().lock(); + try { + mDictionaryWriter.clear(); } finally { mLocalDictionaryController.writeLock().unlock(); } @@ -159,59 +202,95 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Sets a word bigram in the dictionary. Used for loading a dictionary. + * Adds a word bigram in the dictionary. Used for loading a dictionary. */ - protected void setBigram(final String prevWord, final String word, final int frequency) { - mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */); + protected void addBigram(final String prevWord, final String word, final int frequency, + final long lastModifiedTime) { + mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */, + lastModifiedTime); } /** - * Dynamically adds a word unigram to the dictionary. + * Dynamically adds a word unigram to the dictionary. May overwrite an existing entry. */ protected void addWordDynamically(final String word, final String shortcutTarget, final int frequency, final boolean isNotAWord) { - mLocalDictionaryController.writeLock().lock(); - try { - mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord); - } finally { - mLocalDictionaryController.writeLock().unlock(); + if (!mIsUpdatable) { + Log.w(TAG, "addWordDynamically is called for non-updatable dictionary: " + mFilename); + return; + } + // TODO: Use a queue to reflect what needs to be reflected. + if (mLocalDictionaryController.writeLock().tryLock()) { + try { + mDictionaryWriter.addUnigramWord(word, shortcutTarget, frequency, isNotAWord); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } } } /** - * Dynamically sets a word bigram in the dictionary. + * Dynamically adds a word bigram in the dictionary. May overwrite an existing entry. */ - protected void setBigramDynamically(final String prevWord, final String word, - final int frequency) { - mLocalDictionaryController.writeLock().lock(); - try { - mDictionaryWriter.addBigramWords(prevWord, word, frequency, true /* isValid */); - } finally { - mLocalDictionaryController.writeLock().unlock(); + protected void addBigramDynamically(final String word0, final String word1, + final int frequency, final boolean isValid) { + if (!mIsUpdatable) { + Log.w(TAG, "addBigramDynamically is called for non-updatable dictionary: " + + mFilename); + return; + } + // TODO: Use a queue to reflect what needs to be reflected. + if (mLocalDictionaryController.writeLock().tryLock()) { + try { + mDictionaryWriter.addBigramWords(word0, word1, frequency, isValid, + 0 /* lastTouchedTime */); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + } + + /** + * Dynamically remove a word bigram in the dictionary. + */ + protected void removeBigramDynamically(final String word0, final String word1) { + if (!mIsUpdatable) { + Log.w(TAG, "removeBigramDynamically is called for non-updatable dictionary: " + + mFilename); + return; + } + // TODO: Use a queue to reflect what needs to be reflected. + if (mLocalDictionaryController.writeLock().tryLock()) { + try { + mDictionaryWriter.removeBigramWords(word0, word1); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } } } @Override public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, final String prevWord, final ProximityInfo proximityInfo, - final boolean blockOffensiveWords) { + final boolean blockOffensiveWords, final int[] additionalFeaturesOptions) { asyncReloadDictionaryIfRequired(); // Write lock because getSuggestions in native updates session status. if (mLocalDictionaryController.writeLock().tryLock()) { try { final ArrayList<SuggestedWordInfo> inMemDictSuggestion = mDictionaryWriter.getSuggestions(composer, prevWord, proximityInfo, - blockOffensiveWords); - if (mBinaryDictionary != null) { + blockOffensiveWords, additionalFeaturesOptions); + // TODO: Remove checking mIsUpdatable and use native suggestion. + if (mBinaryDictionary != null && !mIsUpdatable) { final ArrayList<SuggestedWordInfo> binarySuggestion = mBinaryDictionary.getSuggestions(composer, prevWord, proximityInfo, - blockOffensiveWords); + blockOffensiveWords, additionalFeaturesOptions); if (inMemDictSuggestion == null) { return binarySuggestion; } else if (binarySuggestion == null) { return inMemDictSuggestion; } else { - binarySuggestion.addAll(binarySuggestion); + binarySuggestion.addAll(inMemDictSuggestion); return binarySuggestion; } } else { @@ -277,7 +356,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { // Build the new binary dictionary final BinaryDictionary newBinaryDictionary = new BinaryDictionary(filename, 0, length, - true /* useFullEditDistance */, null, mDictType, false /* isUpdatable */); + true /* useFullEditDistance */, null, mDictType, mIsUpdatable); if (mBinaryDictionary != null) { // Ensure all threads accessing the current dictionary have finished before swapping in @@ -302,9 +381,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { abstract protected boolean needsToReloadBeforeWriting(); /** - * Generates and writes a new binary dictionary based on the contents of the fusion dictionary. + * Writes a new binary dictionary based on the contents of the fusion dictionary. */ - private void generateBinaryDictionary() { + private void writeBinaryDictionary() { if (DEBUG) { Log.d(TAG, "Generating binary dictionary: " + mFilename + " request=" + mSharedDictionaryController.mLastUpdateRequestTime + " update=" @@ -337,7 +416,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** * Reloads the dictionary if required. Reload will occur asynchronously in a separate thread. */ - void asyncReloadDictionaryIfRequired() { + public void asyncReloadDictionaryIfRequired() { if (!isReloadRequired()) return; if (DEBUG) { Log.d(TAG, "Starting AsyncReloadDictionaryTask: " + mFilename); @@ -348,7 +427,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { /** * Reloads the dictionary if required. */ - protected final void syncReloadDictionaryIfRequired() { + public final void syncReloadDictionaryIfRequired() { if (!isReloadRequired()) return; syncReloadDictionaryInternal(); } @@ -367,41 +446,47 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { private final void syncReloadDictionaryInternal() { // Ensure that only one thread attempts to read or write to the shared binary dictionary // file at the same time. - mSharedDictionaryController.writeLock().lock(); + mLocalDictionaryController.writeLock().lock(); try { - final long time = SystemClock.uptimeMillis(); - final boolean dictionaryFileExists = dictionaryFileExists(); - if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists) { - // If the shared dictionary file does not exist or is out of date, the first - // instance that acquires the lock will generate a new one. - if (hasContentChanged() || !dictionaryFileExists) { - // If the source content has changed or the dictionary does not exist, rebuild - // the binary dictionary. Empty dictionaries are supported (in the case where - // loadDictionaryAsync() adds nothing) in order to provide a uniform framework. + mSharedDictionaryController.writeLock().lock(); + try { + final long time = SystemClock.uptimeMillis(); + final boolean dictionaryFileExists = dictionaryFileExists(); + if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists) { + // If the shared dictionary file does not exist or is out of date, the first + // instance that acquires the lock will generate a new one. + if (hasContentChanged() || !dictionaryFileExists) { + // If the source content has changed or the dictionary does not exist, + // rebuild the binary dictionary. Empty dictionaries are supported (in the + // case where loadDictionaryAsync() adds nothing) in order to provide a + // uniform framework. + mSharedDictionaryController.mLastUpdateTime = time; + writeBinaryDictionary(); + loadBinaryDictionary(); + } else { + // If not, the reload request was unnecessary so revert + // LastUpdateRequestTime to LastUpdateTime. + mSharedDictionaryController.mLastUpdateRequestTime = + mSharedDictionaryController.mLastUpdateTime; + } + } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime + < mSharedDictionaryController.mLastUpdateTime) { + // Otherwise, if the local dictionary is older than the shared dictionary, load + // the shared dictionary. + loadBinaryDictionary(); + } + if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { + // Binary dictionary is not valid. Regenerate the dictionary file. mSharedDictionaryController.mLastUpdateTime = time; - generateBinaryDictionary(); + writeBinaryDictionary(); loadBinaryDictionary(); - } else { - // If not, the reload request was unnecessary so revert LastUpdateRequestTime - // to LastUpdateTime. - mSharedDictionaryController.mLastUpdateRequestTime = - mSharedDictionaryController.mLastUpdateTime; } - } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime - < mSharedDictionaryController.mLastUpdateTime) { - // Otherwise, if the local dictionary is older than the shared dictionary, load the - // shared dictionary. - loadBinaryDictionary(); - } - if (mBinaryDictionary != null && !mBinaryDictionary.isValidDictionary()) { - // Binary dictionary is not valid. Regenerate the dictionary file. - mSharedDictionaryController.mLastUpdateTime = time; - generateBinaryDictionary(); - loadBinaryDictionary(); + mLocalDictionaryController.mLastUpdateTime = time; + } finally { + mSharedDictionaryController.writeLock().unlock(); } - mLocalDictionaryController.mLastUpdateTime = time; } finally { - mSharedDictionaryController.writeLock().unlock(); + mLocalDictionaryController.writeLock().unlock(); } } @@ -422,6 +507,68 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** + * Load the dictionary to memory. + */ + protected void asyncLoadDictionaryToMemory() { + new AsyncLoadDictionaryToMemoryTask().start(); + } + + /** + * Thread class for asynchronously loading dictionary to memory. + */ + private class AsyncLoadDictionaryToMemoryTask extends Thread { + @Override + public void run() { + mLocalDictionaryController.writeLock().lock(); + try { + mSharedDictionaryController.readLock().lock(); + try { + loadDictionaryAsync(); + } finally { + mSharedDictionaryController.readLock().unlock(); + } + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + } + + /** + * Generate binary dictionary using DictionaryWriter. + */ + protected void asyncWriteBinaryDictionary() { + final AsyncWriteBinaryDictionaryTask newTask = new AsyncWriteBinaryDictionaryTask(); + newTask.start(); + final AsyncWriteBinaryDictionaryTask oldTask = mWaitingTask.getAndSet(newTask); + if (oldTask != null) { + oldTask.interrupt(); + } + } + + /** + * Thread class for asynchronously writing the binary dictionary. + */ + private class AsyncWriteBinaryDictionaryTask extends Thread { + @Override + public void run() { + mSharedDictionaryController.writeLock().lock(); + try { + mLocalDictionaryController.writeLock().lock(); + try { + if (isInterrupted()) { + return; + } + writeBinaryDictionary(); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } finally { + mSharedDictionaryController.writeLock().unlock(); + } + } + } + + /** * Lock for controlling access to a given binary dictionary and for tracking whether the * dictionary is out of date. Can be shared across multiple dictionary instances that access the * same filename. @@ -434,4 +581,45 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return (mLastUpdateRequestTime > mLastUpdateTime); } } + + /** + * Dynamically adds a word unigram to the dictionary for testing with blocking-lock. + */ + @UsedForTesting + protected void addWordDynamicallyForTests(final String word, final String shortcutTarget, + final int frequency, final boolean isNotAWord) { + mLocalDictionaryController.writeLock().lock(); + try { + addWordDynamically(word, shortcutTarget, frequency, isNotAWord); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + + /** + * Dynamically adds a word bigram in the dictionary for testing with blocking-lock. + */ + @UsedForTesting + protected void addBigramDynamicallyForTests(final String word0, final String word1, + final int frequency, final boolean isValid) { + mLocalDictionaryController.writeLock().lock(); + try { + addBigramDynamically(word0, word1, frequency, isValid); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } + + /** + * Dynamically remove a word bigram in the dictionary for testing with blocking-lock. + */ + @UsedForTesting + protected void removeBigramDynamicallyForTests(final String word0, final String word1) { + mLocalDictionaryController.writeLock().lock(); + try { + removeBigramDynamically(word0, word1); + } finally { + mLocalDictionaryController.writeLock().unlock(); + } + } } |