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