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