BinaryDictionary.java revision 59ed0c2db2714f36c18d0882c7845455b5b3dd43
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 boolean flushNative(long dict, String filePath);
187    private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
188    private static native boolean 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 getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
193    private static native int getBigramProbabilityNative(long dict, int[] word0,
194            boolean isBeginningOfSentence, int[] word1);
195    private static native void getWordPropertyNative(long dict, int[] word,
196            int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
197            ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
198            ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities);
199    private static native int getNextWordNative(long dict, int token, int[] outCodePoints);
200    private static native void getSuggestionsNative(long dict, long proximityInfo,
201            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
202            int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
203            int[] prevWordCodePointArray, boolean isBeginningOfSentence,
204            int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
205            int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
206            float[] inOutLanguageWeight);
207    private static native boolean addUnigramWordNative(long dict, int[] word, int probability,
208            int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
209            boolean isNotAWord, boolean isBlacklisted, int timestamp);
210    private static native boolean addBigramWordsNative(long dict, int[] word0,
211            boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
212    private static native boolean removeBigramWordsNative(long dict, int[] word0,
213            boolean isBeginningOfSentence, int[] word1);
214    private static native int addMultipleDictionaryEntriesNative(long dict,
215            LanguageModelParam[] languageModelParams, int startIndex);
216    private static native String getPropertyNative(long dict, String query);
217    private static native boolean isCorruptedNative(long dict);
218    private static native boolean migrateNative(long dict, String dictFilePath,
219            long newFormatVersion);
220
221    // TODO: Move native dict into session
222    private final void loadDictionary(final String path, final long startOffset,
223            final long length, final boolean isUpdatable) {
224        mHasUpdated = false;
225        mNativeDict = openNative(path, startOffset, length, isUpdatable);
226    }
227
228    // TODO: Check isCorrupted() for main dictionaries.
229    public boolean isCorrupted() {
230        if (!isValidDictionary()) {
231            return false;
232        }
233        if (!isCorruptedNative(mNativeDict)) {
234            return false;
235        }
236        // TODO: Record the corruption.
237        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
238        Log.e(TAG, "locale: " + mLocale);
239        Log.e(TAG, "dict size: " + mDictSize);
240        Log.e(TAG, "updatable: " + mIsUpdatable);
241        return true;
242    }
243
244    public DictionaryHeader getHeader() throws UnsupportedFormatException {
245        if (mNativeDict == 0) {
246            return null;
247        }
248        final int[] outHeaderSize = new int[1];
249        final int[] outFormatVersion = new int[1];
250        final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
251        final ArrayList<int[]> outAttributeValues = new ArrayList<>();
252        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
253                outAttributeValues);
254        final HashMap<String, String> attributes = new HashMap<>();
255        for (int i = 0; i < outAttributeKeys.size(); i++) {
256            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
257                    outAttributeKeys.get(i));
258            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
259                    outAttributeValues.get(i));
260            attributes.put(attributeKey, attributeValue);
261        }
262        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
263                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
264        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
265                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
266    }
267
268    @Override
269    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
270            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
271            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
272            final int sessionId, final float[] inOutLanguageWeight) {
273        if (!isValidDictionary()) {
274            return null;
275        }
276
277        Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
278        // TODO: toLowerCase in the native code
279        final int[] prevWordCodePointArray = (null == prevWordsInfo.mPrevWord)
280                ? null : StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
281        final InputPointers inputPointers = composer.getInputPointers();
282        final boolean isGesture = composer.isBatchMode();
283        final int inputSize;
284        if (!isGesture) {
285            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
286                    mInputCodePoints);
287            if (inputSize < 0) {
288                return null;
289            }
290        } else {
291            inputSize = inputPointers.getPointerSize();
292        }
293
294        mNativeSuggestOptions.setIsGesture(isGesture);
295        mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords);
296        mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
297        if (inOutLanguageWeight != null) {
298            mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
299        } else {
300            mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
301        }
302        // proximityInfo and/or prevWordForBigrams may not be null.
303        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
304                getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
305                inputPointers.getYCoordinates(), inputPointers.getTimes(),
306                inputPointers.getPointerIds(), mInputCodePoints, inputSize,
307                mNativeSuggestOptions.getOptions(), prevWordCodePointArray,
308                prevWordsInfo.mIsBeginningOfSentence, mOutputSuggestionCount,
309                mOutputCodePoints, mOutputScores, mSpaceIndices, mOutputTypes,
310                mOutputAutoCommitFirstWordConfidence, mInputOutputLanguageWeight);
311        if (inOutLanguageWeight != null) {
312            inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
313        }
314        final int count = mOutputSuggestionCount[0];
315        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
316        for (int j = 0; j < count; ++j) {
317            final int start = j * MAX_WORD_LENGTH;
318            int len = 0;
319            while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
320                ++len;
321            }
322            if (len > 0) {
323                suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
324                        mOutputScores[j], mOutputTypes[j], this /* sourceDict */,
325                        mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
326                        mOutputAutoCommitFirstWordConfidence[0]));
327            }
328        }
329        return suggestions;
330    }
331
332    public boolean isValidDictionary() {
333        return mNativeDict != 0;
334    }
335
336    public int getFormatVersion() {
337        return getFormatVersionNative(mNativeDict);
338    }
339
340    @Override
341    public boolean isInDictionary(final String word) {
342        return getFrequency(word) != NOT_A_PROBABILITY;
343    }
344
345    @Override
346    public int getFrequency(final String word) {
347        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
348        int[] codePoints = StringUtils.toCodePointArray(word);
349        return getProbabilityNative(mNativeDict, codePoints);
350    }
351
352    @Override
353    public int getMaxFrequencyOfExactMatches(final String word) {
354        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
355        int[] codePoints = StringUtils.toCodePointArray(word);
356        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
357    }
358
359    @UsedForTesting
360    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
361        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
362    }
363
364    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
365        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
366            return NOT_A_PROBABILITY;
367        }
368        final int[] codePoints0 = StringUtils.toCodePointArray(prevWordsInfo.mPrevWord);
369        final int[] codePoints1 = StringUtils.toCodePointArray(word);
370        return getBigramProbabilityNative(mNativeDict, codePoints0,
371                prevWordsInfo.mIsBeginningOfSentence, codePoints1);
372    }
373
374    public WordProperty getWordProperty(final String word) {
375        if (TextUtils.isEmpty(word)) {
376            return null;
377        }
378        final int[] codePoints = StringUtils.toCodePointArray(word);
379        final int[] outCodePoints = new int[MAX_WORD_LENGTH];
380        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
381        final int[] outProbabilityInfo =
382                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
383        final ArrayList<int[]> outBigramTargets = new ArrayList<>();
384        final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
385        final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
386        final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
387        getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
388                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
389                outShortcutProbabilities);
390        return new WordProperty(codePoints,
391                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
392                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
393                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
394                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo,
395                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
396                outShortcutProbabilities);
397    }
398
399    public static class GetNextWordPropertyResult {
400        public WordProperty mWordProperty;
401        public int mNextToken;
402
403        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
404            mWordProperty = wordProperty;
405            mNextToken = nextToken;
406        }
407    }
408
409    /**
410     * Method to iterate all words in the dictionary for makedict.
411     * If token is 0, this method newly starts iterating the dictionary.
412     */
413    public GetNextWordPropertyResult getNextWordProperty(final int token) {
414        final int[] codePoints = new int[MAX_WORD_LENGTH];
415        final int nextToken = getNextWordNative(mNativeDict, token, codePoints);
416        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
417        return new GetNextWordPropertyResult(getWordProperty(word), nextToken);
418    }
419
420    // Add a unigram entry to binary dictionary with unigram attributes in native code.
421    public boolean addUnigramEntry(final String word, final int probability,
422            final String shortcutTarget, final int shortcutProbability,
423            final boolean isBeginningOfSentence, final boolean isNotAWord,
424            final boolean isBlacklisted, final int timestamp) {
425        if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
426            return false;
427        }
428        final int[] codePoints = StringUtils.toCodePointArray(word);
429        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
430                StringUtils.toCodePointArray(shortcutTarget) : null;
431        if (!addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
432                shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
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