BinaryDictionary.java revision b4d77eca55fa48eaf29ab036ac3b098ebac5e691
1/*
2 * Copyright (C) 2008 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
17package com.android.inputmethod.latin;
18
19import android.text.TextUtils;
20import android.util.Log;
21import android.util.SparseArray;
22
23import com.android.inputmethod.annotations.UsedForTesting;
24import com.android.inputmethod.keyboard.ProximityInfo;
25import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
26import com.android.inputmethod.latin.makedict.DictionaryHeader;
27import com.android.inputmethod.latin.makedict.FormatSpec;
28import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
29import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
30import com.android.inputmethod.latin.makedict.WordProperty;
31import com.android.inputmethod.latin.settings.NativeSuggestOptions;
32import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
33import com.android.inputmethod.latin.utils.FileUtils;
34import com.android.inputmethod.latin.utils.JniUtils;
35import com.android.inputmethod.latin.utils.LanguageModelParam;
36import com.android.inputmethod.latin.utils.StringUtils;
37
38import java.io.File;
39import java.util.ArrayList;
40import java.util.Arrays;
41import java.util.HashMap;
42import java.util.Locale;
43import java.util.Map;
44
45/**
46 * Implements a static, compacted, binary dictionary of standard words.
47 */
48// TODO: All methods which should be locked need to have a suffix "Locked".
49public final class BinaryDictionary extends Dictionary {
50    private static final String TAG = BinaryDictionary.class.getSimpleName();
51
52    // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
53    private static final int MAX_WORD_LENGTH = Constants.DICTIONARY_MAX_WORD_LENGTH;
54    // Must be equal to MAX_RESULTS in native/jni/src/defines.h
55    private static final int MAX_RESULTS = 18;
56    // The cutoff returned by native for auto-commit confidence.
57    // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h
58    private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000;
59
60    @UsedForTesting
61    public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
62    @UsedForTesting
63    public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
64    @UsedForTesting
65    public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
66    @UsedForTesting
67    public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
68
69    public static final int NOT_A_VALID_TIMESTAMP = -1;
70
71    // Format to get unigram flags from native side via getWordPropertyNative().
72    private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 4;
73    private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
74    private static final int FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX = 1;
75    private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2;
76    private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
77
78    // Format to get probability and historical info from native side via getWordPropertyNative().
79    public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4;
80    public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0;
81    public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1;
82    public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2;
83    public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3;
84
85    public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate";
86
87    private long mNativeDict;
88    private final Locale mLocale;
89    private final long mDictSize;
90    private final String mDictFilePath;
91    private final boolean mIsUpdatable;
92    private boolean mHasUpdated;
93
94    private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
95    private final int[] mOutputSuggestionCount = new int[1];
96    private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
97    private final int[] mSpaceIndices = new int[MAX_RESULTS];
98    private final int[] mOutputScores = new int[MAX_RESULTS];
99    private final int[] mOutputTypes = new int[MAX_RESULTS];
100    // Only one result is ever used
101    private final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
102    private final float[] mInputOutputLanguageWeight = new float[1];
103
104    private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
105
106    private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>();
107
108    // TODO: There should be a way to remove used DicTraverseSession objects from
109    // {@code mDicTraverseSessions}.
110    private DicTraverseSession getTraverseSession(final int traverseSessionId) {
111        synchronized(mDicTraverseSessions) {
112            DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
113            if (traverseSession == null) {
114                traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
115                mDicTraverseSessions.put(traverseSessionId, traverseSession);
116            }
117            return traverseSession;
118        }
119    }
120
121    /**
122     * Constructs binary dictionary using existing dictionary file.
123     * @param filename the name of the file to read through native code.
124     * @param offset the offset of the dictionary data within the file.
125     * @param length the length of the binary data.
126     * @param useFullEditDistance whether to use the full edit distance in suggestions
127     * @param dictType the dictionary type, as a human-readable string
128     * @param isUpdatable whether to open the dictionary file in writable mode.
129     */
130    public BinaryDictionary(final String filename, final long offset, final long length,
131            final boolean useFullEditDistance, final Locale locale, final String dictType,
132            final boolean isUpdatable) {
133        super(dictType);
134        mLocale = locale;
135        mDictSize = length;
136        mDictFilePath = filename;
137        mIsUpdatable = isUpdatable;
138        mHasUpdated = false;
139        mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
140        loadDictionary(filename, offset, length, isUpdatable);
141    }
142
143    /**
144     * Constructs binary dictionary on memory.
145     * @param filename the name of the file used to flush.
146     * @param useFullEditDistance whether to use the full edit distance in suggestions
147     * @param dictType the dictionary type, as a human-readable string
148     * @param formatVersion the format version of the dictionary
149     * @param attributeMap the attributes of the dictionary
150     */
151    @UsedForTesting
152    public BinaryDictionary(final String filename, final boolean useFullEditDistance,
153            final Locale locale, final String dictType, final long formatVersion,
154            final Map<String, String> attributeMap) {
155        super(dictType);
156        mLocale = locale;
157        mDictSize = 0;
158        mDictFilePath = filename;
159        // On memory dictionary is always updatable.
160        mIsUpdatable = true;
161        mHasUpdated = false;
162        mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
163        final String[] keyArray = new String[attributeMap.size()];
164        final String[] valueArray = new String[attributeMap.size()];
165        int index = 0;
166        for (final String key : attributeMap.keySet()) {
167            keyArray[index] = key;
168            valueArray[index] = attributeMap.get(key);
169            index++;
170        }
171        mNativeDict = createOnMemoryNative(formatVersion, locale.toString(), keyArray, valueArray);
172    }
173
174
175    static {
176        JniUtils.loadNativeLibrary();
177    }
178
179    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
180            boolean isUpdatable);
181    private static native long createOnMemoryNative(long formatVersion,
182            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
183    private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
184            int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
185            ArrayList<int[]> outAttributeValues);
186    private static native boolean flushNative(long dict, String filePath);
187    private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
188    private static native boolean flushWithGCNative(long dict, String filePath);
189    private static native void closeNative(long dict);
190    private static native int getFormatVersionNative(long dict);
191    private static native int getProbabilityNative(long dict, int[] word);
192    private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
193    private static native int getBigramProbabilityNative(long dict, int[] word0,
194            boolean isBeginningOfSentence, int[] word1);
195    private static native void getWordPropertyNative(long dict, int[] word,
196            int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
197            ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
198            ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities);
199    private static native int getNextWordNative(long dict, int token, int[] outCodePoints);
200    private static native void getSuggestionsNative(long dict, long proximityInfo,
201            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
202            int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
203            int[] prevWordCodePointArray, boolean isBeginningOfSentence,
204            int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
205            int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
206            float[] inOutLanguageWeight);
207    private static native boolean addUnigramWordNative(long dict, int[] word, int probability,
208            int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
209            boolean isNotAWord, boolean isBlacklisted, int timestamp);
210    private static native boolean removeUnigramWordNative(long dict, int[] word);
211    private static native boolean addBigramWordsNative(long dict, int[] word0,
212            boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
213    private static native boolean removeBigramWordsNative(long dict, int[] word0,
214            boolean isBeginningOfSentence, int[] word1);
215    private static native int addMultipleDictionaryEntriesNative(long dict,
216            LanguageModelParam[] languageModelParams, int startIndex);
217    private static native String getPropertyNative(long dict, String query);
218    private static native boolean isCorruptedNative(long dict);
219    private static native boolean migrateNative(long dict, String dictFilePath,
220            long newFormatVersion);
221
222    // TODO: Move native dict into session
223    private final void loadDictionary(final String path, final long startOffset,
224            final long length, final boolean isUpdatable) {
225        mHasUpdated = false;
226        mNativeDict = openNative(path, startOffset, length, isUpdatable);
227    }
228
229    // TODO: Check isCorrupted() for main dictionaries.
230    public boolean isCorrupted() {
231        if (!isValidDictionary()) {
232            return false;
233        }
234        if (!isCorruptedNative(mNativeDict)) {
235            return false;
236        }
237        // TODO: Record the corruption.
238        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
239        Log.e(TAG, "locale: " + mLocale);
240        Log.e(TAG, "dict size: " + mDictSize);
241        Log.e(TAG, "updatable: " + mIsUpdatable);
242        return true;
243    }
244
245    public DictionaryHeader getHeader() throws UnsupportedFormatException {
246        if (mNativeDict == 0) {
247            return null;
248        }
249        final int[] outHeaderSize = new int[1];
250        final int[] outFormatVersion = new int[1];
251        final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
252        final ArrayList<int[]> outAttributeValues = new ArrayList<>();
253        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
254                outAttributeValues);
255        final HashMap<String, String> attributes = new HashMap<>();
256        for (int i = 0; i < outAttributeKeys.size(); i++) {
257            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
258                    outAttributeKeys.get(i));
259            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
260                    outAttributeValues.get(i));
261            attributes.put(attributeKey, attributeValue);
262        }
263        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
264                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
265        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
266                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
267    }
268
269    @Override
270    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
271            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
272            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
273            final int sessionId, final float[] inOutLanguageWeight) {
274        if (!isValidDictionary()) {
275            return null;
276        }
277
278        Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
279        // TODO: toLowerCase in the native code
280        final int[] prevWordCodePointArray = (null == prevWordsInfo.mPrevWord)
281                ? null : StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
282        final InputPointers inputPointers = composer.getInputPointers();
283        final boolean isGesture = composer.isBatchMode();
284        final int inputSize;
285        if (!isGesture) {
286            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
287                    mInputCodePoints);
288            if (inputSize < 0) {
289                return null;
290            }
291        } else {
292            inputSize = inputPointers.getPointerSize();
293        }
294
295        mNativeSuggestOptions.setIsGesture(isGesture);
296        mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
297        if (inOutLanguageWeight != null) {
298            mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
299        } else {
300            mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
301        }
302        // proximityInfo and/or prevWordForBigrams may not be null.
303        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
304                getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
305                inputPointers.getYCoordinates(), inputPointers.getTimes(),
306                inputPointers.getPointerIds(), mInputCodePoints, inputSize,
307                mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
308                prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount,
309                mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
310                mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
311        if (inOutLanguageWeight != null) {
312            inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
313        }
314        final int count = mOutputSuggestionCount[0];
315        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
316        for (int j = 0; j < count; ++j) {
317            final int start = j * MAX_WORD_LENGTH;
318            int len = 0;
319            while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
320                ++len;
321            }
322            if (len > 0) {
323                final SuggestedWordInfo suggestedWordInfo =
324                        new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
325                                mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
326                                mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
327                                mOutputAutoCommitFirstWordConfidence[0]);
328                if (blockOffensiveWords && suggestedWordInfo.isPossiblyOffensive()
329                        && !suggestedWordInfo.isExactMatch()) {
330                    // If we block potentially offensive words, and if the word is possibly
331                    // offensive, then we don't output it unless it's also an exact match.
332                    continue;
333                }
334                suggestions.add(suggestedWordInfo);
335            }
336        }
337        return suggestions;
338    }
339
340    public boolean isValidDictionary() {
341        return mNativeDict != 0;
342    }
343
344    public int getFormatVersion() {
345        return getFormatVersionNative(mNativeDict);
346    }
347
348    @Override
349    public boolean isInDictionary(final String word) {
350        return getFrequency(word) != NOT_A_PROBABILITY;
351    }
352
353    @Override
354    public int getFrequency(final String word) {
355        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
356        int[] codePoints = StringUtils.toCodePointArray(word);
357        return getProbabilityNative(mNativeDict, codePoints);
358    }
359
360    @Override
361    public int getMaxFrequencyOfExactMatches(final String word) {
362        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
363        int[] codePoints = StringUtils.toCodePointArray(word);
364        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
365    }
366
367    @UsedForTesting
368    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
369        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
370    }
371
372    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
373        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
374            return NOT_A_PROBABILITY;
375        }
376        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
377        final int[] codePoints1 = StringUtils.toCodePointArray(word);
378        return getBigramProbabilityNative(mNativeDict, codePoints0,
379                prevWordsInfo.mIsBeginningOfSentence, codePoints1);
380    }
381
382    public WordProperty getWordProperty(final String word) {
383        if (TextUtils.isEmpty(word)) {
384            return null;
385        }
386        final int[] codePoints = StringUtils.toCodePointArray(word);
387        final int[] outCodePoints = new int[MAX_WORD_LENGTH];
388        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
389        final int[] outProbabilityInfo =
390                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
391        final ArrayList<int[]> outBigramTargets = new ArrayList<>();
392        final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
393        final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
394        final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
395        getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
396                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
397                outShortcutProbabilities);
398        return new WordProperty(codePoints,
399                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
400                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
401                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
402                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo,
403                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
404                outShortcutProbabilities);
405    }
406
407    public static class GetNextWordPropertyResult {
408        public WordProperty mWordProperty;
409        public int mNextToken;
410
411        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
412            mWordProperty = wordProperty;
413            mNextToken = nextToken;
414        }
415    }
416
417    /**
418     * Method to iterate all words in the dictionary for makedict.
419     * If token is 0, this method newly starts iterating the dictionary.
420     */
421    public GetNextWordPropertyResult getNextWordProperty(final int token) {
422        final int[] codePoints = new int[MAX_WORD_LENGTH];
423        final int nextToken = getNextWordNative(mNativeDict, token, codePoints);
424        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
425        return new GetNextWordPropertyResult(getWordProperty(word), nextToken);
426    }
427
428    // Add a unigram entry to binary dictionary with unigram attributes in native code.
429    public boolean addUnigramEntry(final String word, final int probability,
430            final String shortcutTarget, final int shortcutProbability,
431            final boolean isBeginningOfSentence, final boolean isNotAWord,
432            final boolean isBlacklisted, final int timestamp) {
433        if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
434            return false;
435        }
436        final int[] codePoints = StringUtils.toCodePointArray(word);
437        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
438                StringUtils.toCodePointArray(shortcutTarget) : null;
439        if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
440                shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
441            return false;
442        }
443        mHasUpdated = true;
444        return true;
445    }
446
447    // Remove a unigram entry from the binary dictionary in native code.
448    public boolean removeUnigramEntry(final String word) {
449        if (TextUtils.isEmpty(word)) {
450            return false;
451        }
452        final int[] codePoints = StringUtils.toCodePointArray(word);
453        if (!removeUnigramWordNative(mNativeDict, codePoints)) {
454            return false;
455        }
456        mHasUpdated = true;
457        return true;
458    }
459
460    // Add an n-gram entry to the binary dictionary with timestamp in native code.
461    public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
462            final int probability, final int timestamp) {
463        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
464            return false;
465        }
466        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
467        final int[] codePoints1 = StringUtils.toCodePointArray(word);
468        if (!addBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
469                codePoints1, probability, timestamp)) {
470            return false;
471        }
472        mHasUpdated = true;
473        return true;
474    }
475
476    // Remove an n-gram entry from the binary dictionary in native code.
477    public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
478        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
479            return false;
480        }
481        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
482        final int[] codePoints1 = StringUtils.toCodePointArray(word);
483        if (!removeBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
484                codePoints1)) {
485            return false;
486        }
487        mHasUpdated = true;
488        return true;
489    }
490
491    public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
492        if (!isValidDictionary()) return;
493        int processedParamCount = 0;
494        while (processedParamCount < languageModelParams.length) {
495            if (needsToRunGC(true /* mindsBlockByGC */)) {
496                flushWithGC();
497            }
498            processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
499                    languageModelParams, processedParamCount);
500            mHasUpdated = true;
501            if (processedParamCount <= 0) {
502                return;
503            }
504        }
505    }
506
507    private void reopen() {
508        close();
509        final File dictFile = new File(mDictFilePath);
510        // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
511        // only be called for actual files. Right now it's only called by the flush() family of
512        // functions, which require an updatable dictionary, so it's okay. But beware.
513        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
514                dictFile.length(), mIsUpdatable);
515    }
516
517    // Flush to dict file if the dictionary has been updated.
518    public boolean flush() {
519        if (!isValidDictionary()) return false;
520        if (mHasUpdated) {
521            if (!flushNative(mNativeDict, mDictFilePath)) {
522                return false;
523            }
524            reopen();
525        }
526        return true;
527    }
528
529    // Run GC and flush to dict file if the dictionary has been updated.
530    public boolean flushWithGCIfHasUpdated() {
531        if (mHasUpdated) {
532            return flushWithGC();
533        }
534        return true;
535    }
536
537    // Run GC and flush to dict file.
538    public boolean flushWithGC() {
539        if (!isValidDictionary()) return false;
540        if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
541            return false;
542        }
543        reopen();
544        return true;
545    }
546
547    /**
548     * Checks whether GC is needed to run or not.
549     * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
550     * the blocking in some situations such as in idle time or just before closing.
551     * @return whether GC is needed to run or not.
552     */
553    public boolean needsToRunGC(final boolean mindsBlockByGC) {
554        if (!isValidDictionary()) return false;
555        return needsToRunGCNative(mNativeDict, mindsBlockByGC);
556    }
557
558    public boolean migrateTo(final int newFormatVersion) {
559        if (!isValidDictionary()) {
560            return false;
561        }
562        final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
563        if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
564            return false;
565        }
566        close();
567        final File dictFile = new File(mDictFilePath);
568        final File tmpDictFile = new File(tmpDictFilePath);
569        if (!FileUtils.deleteRecursively(dictFile)) {
570            return false;
571        }
572        if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
573            return false;
574        }
575        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
576                dictFile.length(), mIsUpdatable);
577        return true;
578    }
579
580    @UsedForTesting
581    public String getPropertyForTest(final String query) {
582        if (!isValidDictionary()) return "";
583        return getPropertyNative(mNativeDict, query);
584    }
585
586    @Override
587    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
588        return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT;
589    }
590
591    @Override
592    public void close() {
593        synchronized (mDicTraverseSessions) {
594            final int sessionsSize = mDicTraverseSessions.size();
595            for (int index = 0; index < sessionsSize; ++index) {
596                final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
597                if (traverseSession != null) {
598                    traverseSession.close();
599                }
600            }
601            mDicTraverseSessions.clear();
602        }
603        closeInternalLocked();
604    }
605
606    private synchronized void closeInternalLocked() {
607        if (mNativeDict != 0) {
608            closeNative(mNativeDict);
609            mNativeDict = 0;
610        }
611    }
612
613    // TODO: Manage BinaryDictionary instances without using WeakReference or something.
614    @Override
615    protected void finalize() throws Throwable {
616        try {
617            closeInternalLocked();
618        } finally {
619            super.finalize();
620        }
621    }
622}
623