BinaryDictionary.java revision 88fa47a27d45f6460971d0d223aa558e121b3478
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 = 5;
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    private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4;
73
74    // Format to get probability and historical info from native side via getWordPropertyNative().
75    public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4;
76    public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0;
77    public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1;
78    public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2;
79    public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3;
80
81    public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate";
82
83    private long mNativeDict;
84    private final Locale mLocale;
85    private final long mDictSize;
86    private final String mDictFilePath;
87    private final boolean mUseFullEditDistance;
88    private final boolean mIsUpdatable;
89    private boolean mHasUpdated;
90
91    private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>();
92
93    // TODO: There should be a way to remove used DicTraverseSession objects from
94    // {@code mDicTraverseSessions}.
95    private DicTraverseSession getTraverseSession(final int traverseSessionId) {
96        synchronized(mDicTraverseSessions) {
97            DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
98            if (traverseSession == null) {
99                traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
100                mDicTraverseSessions.put(traverseSessionId, traverseSession);
101            }
102            return traverseSession;
103        }
104    }
105
106    /**
107     * Constructs binary dictionary using existing dictionary file.
108     * @param filename the name of the file to read through native code.
109     * @param offset the offset of the dictionary data within the file.
110     * @param length the length of the binary data.
111     * @param useFullEditDistance whether to use the full edit distance in suggestions
112     * @param dictType the dictionary type, as a human-readable string
113     * @param isUpdatable whether to open the dictionary file in writable mode.
114     */
115    public BinaryDictionary(final String filename, final long offset, final long length,
116            final boolean useFullEditDistance, final Locale locale, final String dictType,
117            final boolean isUpdatable) {
118        super(dictType);
119        mLocale = locale;
120        mDictSize = length;
121        mDictFilePath = filename;
122        mIsUpdatable = isUpdatable;
123        mHasUpdated = false;
124        mUseFullEditDistance = useFullEditDistance;
125        loadDictionary(filename, offset, length, isUpdatable);
126    }
127
128    /**
129     * Constructs binary dictionary on memory.
130     * @param filename the name of the file used to flush.
131     * @param useFullEditDistance whether to use the full edit distance in suggestions
132     * @param dictType the dictionary type, as a human-readable string
133     * @param formatVersion the format version of the dictionary
134     * @param attributeMap the attributes of the dictionary
135     */
136    public BinaryDictionary(final String filename, final boolean useFullEditDistance,
137            final Locale locale, final String dictType, final long formatVersion,
138            final Map<String, String> attributeMap) {
139        super(dictType);
140        mLocale = locale;
141        mDictSize = 0;
142        mDictFilePath = filename;
143        // On memory dictionary is always updatable.
144        mIsUpdatable = true;
145        mHasUpdated = false;
146        mUseFullEditDistance = useFullEditDistance;
147        final String[] keyArray = new String[attributeMap.size()];
148        final String[] valueArray = new String[attributeMap.size()];
149        int index = 0;
150        for (final String key : attributeMap.keySet()) {
151            keyArray[index] = key;
152            valueArray[index] = attributeMap.get(key);
153            index++;
154        }
155        mNativeDict = createOnMemoryNative(formatVersion, locale.toString(), keyArray, valueArray);
156    }
157
158
159    static {
160        JniUtils.loadNativeLibrary();
161    }
162
163    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
164            boolean isUpdatable);
165    private static native long createOnMemoryNative(long formatVersion,
166            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
167    private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
168            int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
169            ArrayList<int[]> outAttributeValues);
170    private static native boolean flushNative(long dict, String filePath);
171    private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
172    private static native boolean flushWithGCNative(long dict, String filePath);
173    private static native void closeNative(long dict);
174    private static native int getFormatVersionNative(long dict);
175    private static native int getProbabilityNative(long dict, int[] word);
176    private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
177    private static native int getBigramProbabilityNative(long dict, int[] word0,
178            boolean isBeginningOfSentence, int[] word1);
179    private static native void getWordPropertyNative(long dict, int[] word,
180            boolean isBeginningOfSentence, int[] outCodePoints, boolean[] outFlags,
181            int[] outProbabilityInfo, ArrayList<int[]> outBigramTargets,
182            ArrayList<int[]> outBigramProbabilityInfo, ArrayList<int[]> outShortcutTargets,
183            ArrayList<Integer> outShortcutProbabilities);
184    private static native int getNextWordNative(long dict, int token, int[] outCodePoints,
185            boolean[] outIsBeginningOfSentence);
186    private static native void getSuggestionsNative(long dict, long proximityInfo,
187            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
188            int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
189            int[] prevWordCodePointArray, boolean isBeginningOfSentence,
190            int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
191            int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
192            float[] inOutLanguageWeight);
193    private static native boolean addUnigramWordNative(long dict, int[] word, int probability,
194            int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
195            boolean isNotAWord, boolean isBlacklisted, int timestamp);
196    private static native boolean removeUnigramWordNative(long dict, int[] word);
197    private static native boolean addBigramWordsNative(long dict, int[] word0,
198            boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
199    private static native boolean removeBigramWordsNative(long dict, int[] word0,
200            boolean isBeginningOfSentence, int[] word1);
201    private static native int addMultipleDictionaryEntriesNative(long dict,
202            LanguageModelParam[] languageModelParams, int startIndex);
203    private static native String getPropertyNative(long dict, String query);
204    private static native boolean isCorruptedNative(long dict);
205    private static native boolean migrateNative(long dict, String dictFilePath,
206            long newFormatVersion);
207
208    // TODO: Move native dict into session
209    private final void loadDictionary(final String path, final long startOffset,
210            final long length, final boolean isUpdatable) {
211        mHasUpdated = false;
212        mNativeDict = openNative(path, startOffset, length, isUpdatable);
213    }
214
215    // TODO: Check isCorrupted() for main dictionaries.
216    public boolean isCorrupted() {
217        if (!isValidDictionary()) {
218            return false;
219        }
220        if (!isCorruptedNative(mNativeDict)) {
221            return false;
222        }
223        // TODO: Record the corruption.
224        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
225        Log.e(TAG, "locale: " + mLocale);
226        Log.e(TAG, "dict size: " + mDictSize);
227        Log.e(TAG, "updatable: " + mIsUpdatable);
228        return true;
229    }
230
231    public DictionaryHeader getHeader() throws UnsupportedFormatException {
232        if (mNativeDict == 0) {
233            return null;
234        }
235        final int[] outHeaderSize = new int[1];
236        final int[] outFormatVersion = new int[1];
237        final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
238        final ArrayList<int[]> outAttributeValues = new ArrayList<>();
239        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
240                outAttributeValues);
241        final HashMap<String, String> attributes = new HashMap<>();
242        for (int i = 0; i < outAttributeKeys.size(); i++) {
243            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
244                    outAttributeKeys.get(i));
245            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
246                    outAttributeValues.get(i));
247            attributes.put(attributeKey, attributeValue);
248        }
249        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
250                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
251        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
252                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
253    }
254
255    @Override
256    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
257            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
258            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
259            final int sessionId, final float[] inOutLanguageWeight) {
260        if (!isValidDictionary()) {
261            return null;
262        }
263        final DicTraverseSession session = getTraverseSession(sessionId);
264        Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE);
265        // TODO: toLowerCase in the native code
266        final int[] prevWordCodePointArray = (null == prevWordsInfo.mPrevWord)
267                ? null : StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
268        final InputPointers inputPointers = composer.getInputPointers();
269        final boolean isGesture = composer.isBatchMode();
270        final int inputSize;
271        if (!isGesture) {
272            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
273                    session.mInputCodePoints);
274            if (inputSize < 0) {
275                return null;
276            }
277        } else {
278            inputSize = inputPointers.getPointerSize();
279        }
280        session.mNativeSuggestOptions.setUseFullEditDistance(mUseFullEditDistance);
281        session.mNativeSuggestOptions.setIsGesture(isGesture);
282        session.mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords);
283        session.mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
284        if (inOutLanguageWeight != null) {
285            session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
286        } else {
287            session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
288        }
289        // proximityInfo and/or prevWordForBigrams may not be null.
290        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
291                getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
292                inputPointers.getYCoordinates(), inputPointers.getTimes(),
293                inputPointers.getPointerIds(), session.mInputCodePoints, inputSize,
294                session.mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
295                prevWordsInfo.mIsBeginningOfSentence, session.mOutputSuggestionCount,
296                session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices,
297                session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence,
298                session.mInputOutputLanguageWeight);
299        if (inOutLanguageWeight != null) {
300            inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0];
301        }
302        final int count = session.mOutputSuggestionCount[0];
303        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
304        for (int j = 0; j < count; ++j) {
305            final int start = j * Constants.DICTIONARY_MAX_WORD_LENGTH;
306            int len = 0;
307            while (len < Constants.DICTIONARY_MAX_WORD_LENGTH
308                    && session.mOutputCodePoints[start + len] != 0) {
309                ++len;
310            }
311            if (len > 0) {
312                suggestions.add(new SuggestedWordInfo(
313                        new String(session.mOutputCodePoints, start, len),
314                        session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */,
315                        session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
316                        session.mOutputAutoCommitFirstWordConfidence[0]));
317            }
318        }
319        return suggestions;
320    }
321
322    public boolean isValidDictionary() {
323        return mNativeDict != 0;
324    }
325
326    public int getFormatVersion() {
327        return getFormatVersionNative(mNativeDict);
328    }
329
330    @Override
331    public boolean isInDictionary(final String word) {
332        return getFrequency(word) != NOT_A_PROBABILITY;
333    }
334
335    @Override
336    public int getFrequency(final String word) {
337        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
338        int[] codePoints = StringUtils.toCodePointArray(word);
339        return getProbabilityNative(mNativeDict, codePoints);
340    }
341
342    @Override
343    public int getMaxFrequencyOfExactMatches(final String word) {
344        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
345        int[] codePoints = StringUtils.toCodePointArray(word);
346        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
347    }
348
349    @UsedForTesting
350    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
351        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
352    }
353
354    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
355        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
356            return NOT_A_PROBABILITY;
357        }
358        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
359        final int[] codePoints1 = StringUtils.toCodePointArray(word);
360        return getBigramProbabilityNative(mNativeDict, codePoints0,
361                prevWordsInfo.mIsBeginningOfSentence, codePoints1);
362    }
363
364    public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) {
365        if (word == null) {
366            return null;
367        }
368        final int[] codePoints = StringUtils.toCodePointArray(word);
369        final int[] outCodePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
370        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
371        final int[] outProbabilityInfo =
372                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
373        final ArrayList<int[]> outBigramTargets = new ArrayList<>();
374        final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
375        final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
376        final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
377        getWordPropertyNative(mNativeDict, codePoints, isBeginningOfSentence, outCodePoints,
378                outFlags, outProbabilityInfo, outBigramTargets, outBigramProbabilityInfo,
379                outShortcutTargets, outShortcutProbabilities);
380        return new WordProperty(codePoints,
381                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
382                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
383                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
384                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX],
385                outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo,
386                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
387                outShortcutProbabilities);
388    }
389
390    public static class GetNextWordPropertyResult {
391        public WordProperty mWordProperty;
392        public int mNextToken;
393
394        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
395            mWordProperty = wordProperty;
396            mNextToken = nextToken;
397        }
398    }
399
400    /**
401     * Method to iterate all words in the dictionary for makedict.
402     * If token is 0, this method newly starts iterating the dictionary.
403     */
404    public GetNextWordPropertyResult getNextWordProperty(final int token) {
405        final int[] codePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
406        final boolean[] isBeginningOfSentence = new boolean[1];
407        final int nextToken = getNextWordNative(mNativeDict, token, codePoints,
408                isBeginningOfSentence);
409        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
410        return new GetNextWordPropertyResult(
411                getWordProperty(word, isBeginningOfSentence[0]), nextToken);
412    }
413
414    // Add a unigram entry to binary dictionary with unigram attributes in native code.
415    public boolean addUnigramEntry(final String word, final int probability,
416            final String shortcutTarget, final int shortcutProbability,
417            final boolean isBeginningOfSentence, final boolean isNotAWord,
418            final boolean isBlacklisted, final int timestamp) {
419        if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
420            return false;
421        }
422        final int[] codePoints = StringUtils.toCodePointArray(word);
423        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
424                StringUtils.toCodePointArray(shortcutTarget) : null;
425        if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
426                shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
427            return false;
428        }
429        mHasUpdated = true;
430        return true;
431    }
432
433    // Remove a unigram entry from the binary dictionary in native code.
434    public boolean removeUnigramEntry(final String word) {
435        if (TextUtils.isEmpty(word)) {
436            return false;
437        }
438        final int[] codePoints = StringUtils.toCodePointArray(word);
439        if (!removeUnigramWordNative(mNativeDict, codePoints)) {
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