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