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