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