BinaryDictionary.java revision d9b8602f4862c2c876e1499aad7ca7d77ea66595
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 addBigramWordsNative(long dict, int[] word0,
211            boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
212    private static native boolean removeBigramWordsNative(long dict, int[] word0,
213            boolean isBeginningOfSentence, int[] word1);
214    private static native int addMultipleDictionaryEntriesNative(long dict,
215            LanguageModelParam[] languageModelParams, int startIndex);
216    private static native String getPropertyNative(long dict, String query);
217    private static native boolean isCorruptedNative(long dict);
218    private static native boolean migrateNative(long dict, String dictFilePath,
219            long newFormatVersion);
220
221    // TODO: Move native dict into session
222    private final void loadDictionary(final String path, final long startOffset,
223            final long length, final boolean isUpdatable) {
224        mHasUpdated = false;
225        mNativeDict = openNative(path, startOffset, length, isUpdatable);
226    }
227
228    // TODO: Check isCorrupted() for main dictionaries.
229    public boolean isCorrupted() {
230        if (!isValidDictionary()) {
231            return false;
232        }
233        if (!isCorruptedNative(mNativeDict)) {
234            return false;
235        }
236        // TODO: Record the corruption.
237        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
238        Log.e(TAG, "locale: " + mLocale);
239        Log.e(TAG, "dict size: " + mDictSize);
240        Log.e(TAG, "updatable: " + mIsUpdatable);
241        return true;
242    }
243
244    public DictionaryHeader getHeader() throws UnsupportedFormatException {
245        if (mNativeDict == 0) {
246            return null;
247        }
248        final int[] outHeaderSize = new int[1];
249        final int[] outFormatVersion = new int[1];
250        final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
251        final ArrayList<int[]> outAttributeValues = new ArrayList<>();
252        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
253                outAttributeValues);
254        final HashMap<String, String> attributes = new HashMap<>();
255        for (int i = 0; i < outAttributeKeys.size(); i++) {
256            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
257                    outAttributeKeys.get(i));
258            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
259                    outAttributeValues.get(i));
260            attributes.put(attributeKey, attributeValue);
261        }
262        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
263                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
264        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
265                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
266    }
267
268    @Override
269    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
270            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
271            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
272            final int sessionId, final float[] inOutLanguageWeight) {
273        if (!isValidDictionary()) {
274            return null;
275        }
276
277        Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
278        // TODO: toLowerCase in the native code
279        final int[] prevWordCodePointArray = (null == prevWordsInfo.mPrevWord)
280                ? null : StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
281        final InputPointers inputPointers = composer.getInputPointers();
282        final boolean isGesture = composer.isBatchMode();
283        final int inputSize;
284        if (!isGesture) {
285            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
286                    mInputCodePoints);
287            if (inputSize < 0) {
288                return null;
289            }
290        } else {
291            inputSize = inputPointers.getPointerSize();
292        }
293
294        mNativeSuggestOptions.setIsGesture(isGesture);
295        mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
296        if (inOutLanguageWeight != null) {
297            mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
298        } else {
299            mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
300        }
301        // proximityInfo and/or prevWordForBigrams may not be null.
302        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
303                getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
304                inputPointers.getYCoordinates(), inputPointers.getTimes(),
305                inputPointers.getPointerIds(), mInputCodePoints, inputSize,
306                mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
307                prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount,
308                mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
309                mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
310        if (inOutLanguageWeight != null) {
311            inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
312        }
313        final int count = mOutputSuggestionCount[0];
314        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
315        for (int j = 0; j < count; ++j) {
316            final int start = j * MAX_WORD_LENGTH;
317            int len = 0;
318            while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
319                ++len;
320            }
321            if (len > 0) {
322                final SuggestedWordInfo suggestedWordInfo =
323                        new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
324                                mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
325                                mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
326                                mOutputAutoCommitFirstWordConfidence[0]);
327                if (blockOffensiveWords && suggestedWordInfo.isPossiblyOffensive()
328                        && !suggestedWordInfo.isExactMatch()) {
329                    // If we block potentially offensive words, and if the word is possibly
330                    // offensive, then we don't output it unless it's also an exact match.
331                    continue;
332                }
333                suggestions.add(suggestedWordInfo);
334            }
335        }
336        return suggestions;
337    }
338
339    public boolean isValidDictionary() {
340        return mNativeDict != 0;
341    }
342
343    public int getFormatVersion() {
344        return getFormatVersionNative(mNativeDict);
345    }
346
347    @Override
348    public boolean isValidWord(final String word) {
349        return getFrequency(word) != NOT_A_PROBABILITY;
350    }
351
352    @Override
353    public int getFrequency(final String word) {
354        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
355        int[] codePoints = StringUtils.toCodePointArray(word);
356        return getProbabilityNative(mNativeDict, codePoints);
357    }
358
359    @Override
360    public int getMaxFrequencyOfExactMatches(final String word) {
361        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
362        int[] codePoints = StringUtils.toCodePointArray(word);
363        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
364    }
365
366    @UsedForTesting
367    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
368        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
369    }
370
371    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
372        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
373            return NOT_A_PROBABILITY;
374        }
375        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
376        final int[] codePoints1 = StringUtils.toCodePointArray(word);
377        return getBigramProbabilityNative(mNativeDict, codePoints0,
378                prevWordsInfo.mIsBeginningOfSentence, codePoints1);
379    }
380
381    public WordProperty getWordProperty(final String word) {
382        if (TextUtils.isEmpty(word)) {
383            return null;
384        }
385        final int[] codePoints = StringUtils.toCodePointArray(word);
386        final int[] outCodePoints = new int[MAX_WORD_LENGTH];
387        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
388        final int[] outProbabilityInfo =
389                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
390        final ArrayList<int[]> outBigramTargets = new ArrayList<>();
391        final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
392        final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
393        final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
394        getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
395                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
396                outShortcutProbabilities);
397        return new WordProperty(codePoints,
398                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
399                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
400                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
401                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo,
402                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
403                outShortcutProbabilities);
404    }
405
406    public static class GetNextWordPropertyResult {
407        public WordProperty mWordProperty;
408        public int mNextToken;
409
410        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
411            mWordProperty = wordProperty;
412            mNextToken = nextToken;
413        }
414    }
415
416    /**
417     * Method to iterate all words in the dictionary for makedict.
418     * If token is 0, this method newly starts iterating the dictionary.
419     */
420    public GetNextWordPropertyResult getNextWordProperty(final int token) {
421        final int[] codePoints = new int[MAX_WORD_LENGTH];
422        final int nextToken = getNextWordNative(mNativeDict, token, codePoints);
423        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
424        return new GetNextWordPropertyResult(getWordProperty(word), nextToken);
425    }
426
427    // Add a unigram entry to binary dictionary with unigram attributes in native code.
428    public boolean addUnigramEntry(final String word, final int probability,
429            final String shortcutTarget, final int shortcutProbability,
430            final boolean isBeginningOfSentence, final boolean isNotAWord,
431            final boolean isBlacklisted, final int timestamp) {
432        if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
433            return false;
434        }
435        final int[] codePoints = StringUtils.toCodePointArray(word);
436        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
437                StringUtils.toCodePointArray(shortcutTarget) : null;
438        if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
439                shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
440            return false;
441        }
442        mHasUpdated = true;
443        return true;
444    }
445
446    // Add an n-gram entry to the binary dictionary with timestamp in native code.
447    public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
448            final int probability, final int timestamp) {
449        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
450            return false;
451        }
452        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
453        final int[] codePoints1 = StringUtils.toCodePointArray(word);
454        if (!addBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
455                codePoints1, probability, timestamp)) {
456            return false;
457        }
458        mHasUpdated = true;
459        return true;
460    }
461
462    // Remove an n-gram entry from the binary dictionary in native code.
463    public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
464        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
465            return false;
466        }
467        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
468        final int[] codePoints1 = StringUtils.toCodePointArray(word);
469        if (!removeBigramWordsNative(mNativeDict, codePoints0, prevWordsInfo.mIsBeginningOfSentence,
470                codePoints1)) {
471            return false;
472        }
473        mHasUpdated = true;
474        return true;
475    }
476
477    public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
478        if (!isValidDictionary()) return;
479        int processedParamCount = 0;
480        while (processedParamCount < languageModelParams.length) {
481            if (needsToRunGC(true /* mindsBlockByGC */)) {
482                flushWithGC();
483            }
484            processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
485                    languageModelParams, processedParamCount);
486            mHasUpdated = true;
487            if (processedParamCount <= 0) {
488                return;
489            }
490        }
491    }
492
493    private void reopen() {
494        close();
495        final File dictFile = new File(mDictFilePath);
496        // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
497        // only be called for actual files. Right now it's only called by the flush() family of
498        // functions, which require an updatable dictionary, so it's okay. But beware.
499        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
500                dictFile.length(), mIsUpdatable);
501    }
502
503    // Flush to dict file if the dictionary has been updated.
504    public boolean flush() {
505        if (!isValidDictionary()) return false;
506        if (mHasUpdated) {
507            if (!flushNative(mNativeDict, mDictFilePath)) {
508                return false;
509            }
510            reopen();
511        }
512        return true;
513    }
514
515    // Run GC and flush to dict file if the dictionary has been updated.
516    public boolean flushWithGCIfHasUpdated() {
517        if (mHasUpdated) {
518            return flushWithGC();
519        }
520        return true;
521    }
522
523    // Run GC and flush to dict file.
524    public boolean flushWithGC() {
525        if (!isValidDictionary()) return false;
526        if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
527            return false;
528        }
529        reopen();
530        return true;
531    }
532
533    /**
534     * Checks whether GC is needed to run or not.
535     * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
536     * the blocking in some situations such as in idle time or just before closing.
537     * @return whether GC is needed to run or not.
538     */
539    public boolean needsToRunGC(final boolean mindsBlockByGC) {
540        if (!isValidDictionary()) return false;
541        return needsToRunGCNative(mNativeDict, mindsBlockByGC);
542    }
543
544    public boolean migrateTo(final int newFormatVersion) {
545        if (!isValidDictionary()) {
546            return false;
547        }
548        final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
549        if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
550            return false;
551        }
552        close();
553        final File dictFile = new File(mDictFilePath);
554        final File tmpDictFile = new File(tmpDictFilePath);
555        if (!FileUtils.deleteRecursively(dictFile)) {
556            return false;
557        }
558        if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
559            return false;
560        }
561        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
562                dictFile.length(), mIsUpdatable);
563        return true;
564    }
565
566    @UsedForTesting
567    public String getPropertyForTest(final String query) {
568        if (!isValidDictionary()) return "";
569        return getPropertyNative(mNativeDict, query);
570    }
571
572    @Override
573    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
574        return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT;
575    }
576
577    @Override
578    public void close() {
579        synchronized (mDicTraverseSessions) {
580            final int sessionsSize = mDicTraverseSessions.size();
581            for (int index = 0; index < sessionsSize; ++index) {
582                final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
583                if (traverseSession != null) {
584                    traverseSession.close();
585                }
586            }
587            mDicTraverseSessions.clear();
588        }
589        closeInternalLocked();
590    }
591
592    private synchronized void closeInternalLocked() {
593        if (mNativeDict != 0) {
594            closeNative(mNativeDict);
595            mNativeDict = 0;
596        }
597    }
598
599    // TODO: Manage BinaryDictionary instances without using WeakReference or something.
600    @Override
601    protected void finalize() throws Throwable {
602        try {
603            closeInternalLocked();
604        } finally {
605            super.finalize();
606        }
607    }
608}
609