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