BinaryDictionary.java revision 1de95ceada64e7fd27ca4ee43243930b5d9c1df7
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;
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 final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
93    private final int[] mOutputSuggestionCount = new int[1];
94    private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
95    private final int[] mSpaceIndices = new int[MAX_RESULTS];
96    private final int[] mOutputScores = new int[MAX_RESULTS];
97    private final int[] mOutputTypes = new int[MAX_RESULTS];
98    // Only one result is ever used
99    private final int[] mOutputAutoCommitFirstWordConfidence = new int[1];
100    private final float[] mInputOutputLanguageWeight = new float[1];
101
102    private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
103
104    private final SparseArray<DicTraverseSession> mDicTraverseSessions =
105            CollectionUtils.newSparseArray();
106
107    // TODO: There should be a way to remove used DicTraverseSession objects from
108    // {@code mDicTraverseSessions}.
109    private DicTraverseSession getTraverseSession(final int traverseSessionId) {
110        synchronized(mDicTraverseSessions) {
111            DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
112            if (traverseSession == null) {
113                traverseSession = mDicTraverseSessions.get(traverseSessionId);
114                if (traverseSession == null) {
115                    traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
116                    mDicTraverseSessions.put(traverseSessionId, traverseSession);
117                }
118            }
119            return traverseSession;
120        }
121    }
122
123    /**
124     * Constructor for the binary dictionary. This is supposed to be called from the
125     * dictionary factory.
126     * @param filename the name of the file to read through native code.
127     * @param offset the offset of the dictionary data within the file.
128     * @param length the length of the binary data.
129     * @param useFullEditDistance whether to use the full edit distance in suggestions
130     * @param dictType the dictionary type, as a human-readable string
131     * @param isUpdatable whether to open the dictionary file in writable mode.
132     */
133    public BinaryDictionary(final String filename, final long offset, final long length,
134            final boolean useFullEditDistance, final Locale locale, final String dictType,
135            final boolean isUpdatable) {
136        super(dictType);
137        mLocale = locale;
138        mDictSize = length;
139        mDictFilePath = filename;
140        mIsUpdatable = isUpdatable;
141        mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
142        loadDictionary(filename, offset, length, isUpdatable);
143    }
144
145    static {
146        JniUtils.loadNativeLibrary();
147    }
148
149    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
150            boolean isUpdatable);
151    private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
152            int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
153            ArrayList<int[]> outAttributeValues);
154    private static native void flushNative(long dict, String filePath);
155    private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
156    private static native void flushWithGCNative(long dict, String filePath);
157    private static native void closeNative(long dict);
158    private static native int getFormatVersionNative(long dict);
159    private static native int getProbabilityNative(long dict, int[] word);
160    private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1);
161    private static native void getWordPropertyNative(long dict, int[] word,
162            int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo,
163            ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo,
164            ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities);
165    private static native int getNextWordNative(long dict, int token, int[] outCodePoints);
166    private static native void getSuggestionsNative(long dict, long proximityInfo,
167            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
168            int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
169            int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints,
170            int[] outputScores, int[] outputIndices, int[] outputTypes,
171            int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight);
172    private static native void addUnigramWordNative(long dict, int[] word, int probability,
173            int[] shortcutTarget, int shortcutProbability, boolean isNotAWord,
174            boolean isBlacklisted, int timestamp);
175    private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
176            int probability, int timestamp);
177    private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
178    private static native int addMultipleDictionaryEntriesNative(long dict,
179            LanguageModelParam[] languageModelParams, int startIndex);
180    private static native int calculateProbabilityNative(long dict, int unigramProbability,
181            int bigramProbability);
182    private static native String getPropertyNative(long dict, String query);
183    private static native boolean isCorruptedNative(long dict);
184
185    // TODO: Move native dict into session
186    private final void loadDictionary(final String path, final long startOffset,
187            final long length, final boolean isUpdatable) {
188        mNativeDict = openNative(path, startOffset, length, isUpdatable);
189    }
190
191    // TODO: Check isCorrupted() for main dictionaries.
192    public boolean isCorrupted() {
193        if (!isValidDictionary()) {
194            return false;
195        }
196        if (!isCorruptedNative(mNativeDict)) {
197            return false;
198        }
199        // TODO: Record the corruption.
200        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
201        Log.e(TAG, "locale: " + mLocale);
202        Log.e(TAG, "dict size: " + mDictSize);
203        Log.e(TAG, "updatable: " + mIsUpdatable);
204        return true;
205    }
206
207    public DictionaryHeader getHeader() throws UnsupportedFormatException {
208        if (mNativeDict == 0) {
209            return null;
210        }
211        final int[] outHeaderSize = new int[1];
212        final int[] outFormatVersion = new int[1];
213        final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList();
214        final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList();
215        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
216                outAttributeValues);
217        final HashMap<String, String> attributes = new HashMap<String, String>();
218        for (int i = 0; i < outAttributeKeys.size(); i++) {
219            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
220                    outAttributeKeys.get(i));
221            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
222                    outAttributeValues.get(i));
223            attributes.put(attributeKey, attributeValue);
224        }
225        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
226                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
227        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
228                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
229    }
230
231
232    @Override
233    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
234            final String prevWord, final ProximityInfo proximityInfo,
235            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
236            final float[] inOutLanguageWeight) {
237        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
238                additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight);
239    }
240
241    @Override
242    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
243            final String prevWord, final ProximityInfo proximityInfo,
244            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
245            final int sessionId, final float[] inOutLanguageWeight) {
246        if (!isValidDictionary()) return null;
247
248        Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
249        // TODO: toLowerCase in the native code
250        final int[] prevWordCodePointArray = (null == prevWord)
251                ? null : StringUtils.toCodePointArray(prevWord);
252        final int composerSize = composer.sizeWithoutTrailingSingleQuotes();
253
254        final boolean isGesture = composer.isBatchMode();
255        if (composerSize <= 1 || !isGesture) {
256            if (composerSize > MAX_WORD_LENGTH - 1) return null;
257            for (int i = 0; i < composerSize; i++) {
258                mInputCodePoints[i] = composer.getCodeAt(i);
259            }
260        }
261
262        final InputPointers ips = composer.getInputPointers();
263        final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
264        mNativeSuggestOptions.setIsGesture(isGesture);
265        mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
266        if (inOutLanguageWeight != null) {
267            mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
268        } else {
269            mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
270        }
271        // proximityInfo and/or prevWordForBigrams may not be null.
272        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
273                getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
274                ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
275                inputSize, mNativeSuggestOptions.getOptions(),
276                prevWordCodePointArray, mOutputSuggestionCount, mOutputCodePoints, mOutputScores,
277                mSpaceIndices, mOutputTypes, mOutputAutoCommitFirstWordConfidence,
278                mInputOutputLanguageWeight);
279        if (inOutLanguageWeight != null) {
280            inOutLanguageWeight[0] = mInputOutputLanguageWeight[0];
281        }
282        final int count = mOutputSuggestionCount[0];
283        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
284        for (int j = 0; j < count; ++j) {
285            final int start = j * MAX_WORD_LENGTH;
286            int len = 0;
287            while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
288                ++len;
289            }
290            if (len > 0) {
291                final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS;
292                if (blockOffensiveWords
293                        && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
294                        && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
295                    // If we block potentially offensive words, and if the word is possibly
296                    // offensive, then we don't output it unless it's also an exact match.
297                    continue;
298                }
299                final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND;
300                final int score = SuggestedWordInfo.KIND_WHITELIST == kind
301                        ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
302                // TODO: check that all users of the `kind' parameter are ready to accept
303                // flags too and pass mOutputTypes[j] instead of kind
304                suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
305                        score, kind, this /* sourceDict */,
306                        mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
307                        mOutputAutoCommitFirstWordConfidence[0]));
308            }
309        }
310        return suggestions;
311    }
312
313    public boolean isValidDictionary() {
314        return mNativeDict != 0;
315    }
316
317    public int getFormatVersion() {
318        return getFormatVersionNative(mNativeDict);
319    }
320
321    @Override
322    public boolean isValidWord(final String word) {
323        return getFrequency(word) != NOT_A_PROBABILITY;
324    }
325
326    @Override
327    public int getFrequency(final String word) {
328        if (word == null) return NOT_A_PROBABILITY;
329        int[] codePoints = StringUtils.toCodePointArray(word);
330        return getProbabilityNative(mNativeDict, codePoints);
331    }
332
333    // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
334    // calls when checking for changes in an entire dictionary.
335    public boolean isValidBigram(final String word0, final String word1) {
336        return getBigramProbability(word0, word1) != NOT_A_PROBABILITY;
337    }
338
339    public int getBigramProbability(final String word0, final String word1) {
340        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY;
341        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
342        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
343        return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1);
344    }
345
346    public WordProperty getWordProperty(final String word) {
347        if (TextUtils.isEmpty(word)) {
348            return null;
349        }
350        final int[] codePoints = StringUtils.toCodePointArray(word);
351        final int[] outCodePoints = new int[MAX_WORD_LENGTH];
352        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
353        final int[] outProbabilityInfo =
354                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
355        final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList();
356        final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList();
357        final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList();
358        final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList();
359        getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo,
360                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
361                outShortcutProbabilities);
362        return new WordProperty(codePoints,
363                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
364                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
365                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
366                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo,
367                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
368                outShortcutProbabilities);
369    }
370
371    public static class GetNextWordPropertyResult {
372        public WordProperty mWordProperty;
373        public int mNextToken;
374
375        public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) {
376            mWordProperty = wordPreperty;
377            mNextToken = nextToken;
378        }
379    }
380
381    /**
382     * Method to iterate all words in the dictionary for makedict.
383     * If token is 0, this method newly starts iterating the dictionary.
384     */
385    public GetNextWordPropertyResult getNextWordProperty(final int token) {
386        final int[] codePoints = new int[MAX_WORD_LENGTH];
387        final int nextToken = getNextWordNative(mNativeDict, token, codePoints);
388        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
389        return new GetNextWordPropertyResult(getWordProperty(word), nextToken);
390    }
391
392    // Add a unigram entry to binary dictionary with unigram attributes in native code.
393    public void addUnigramWord(final String word, final int probability,
394            final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord,
395            final boolean isBlacklisted, final int timestamp) {
396        if (TextUtils.isEmpty(word)) {
397            return;
398        }
399        final int[] codePoints = StringUtils.toCodePointArray(word);
400        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
401                StringUtils.toCodePointArray(shortcutTarget) : null;
402        addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
403                shortcutProbability, isNotAWord, isBlacklisted, timestamp);
404    }
405
406    // Add a bigram entry to binary dictionary with timestamp in native code.
407    public void addBigramWords(final String word0, final String word1, final int probability,
408            final int timestamp) {
409        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
410            return;
411        }
412        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
413        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
414        addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp);
415    }
416
417    // Remove a bigram entry form binary dictionary in native code.
418    public void removeBigramWords(final String word0, final String word1) {
419        if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) {
420            return;
421        }
422        final int[] codePoints0 = StringUtils.toCodePointArray(word0);
423        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
424        removeBigramWordsNative(mNativeDict, codePoints0, codePoints1);
425    }
426
427    public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
428        if (!isValidDictionary()) return;
429        int processedParamCount = 0;
430        while (processedParamCount < languageModelParams.length) {
431            if (needsToRunGC(true /* mindsBlockByGC */)) {
432                flushWithGC();
433            }
434            processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
435                    languageModelParams, processedParamCount);
436            if (processedParamCount <= 0) {
437                return;
438            }
439        }
440    }
441
442    private void reopen() {
443        close();
444        final File dictFile = new File(mDictFilePath);
445        // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
446        // only be called for actual files. Right now it's only called by the flush() family of
447        // functions, which require an updatable dictionary, so it's okay. But beware.
448        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
449                dictFile.length(), mIsUpdatable);
450    }
451
452    public void flush() {
453        if (!isValidDictionary()) return;
454        flushNative(mNativeDict, mDictFilePath);
455        reopen();
456    }
457
458    public void flushWithGC() {
459        if (!isValidDictionary()) return;
460        flushWithGCNative(mNativeDict, mDictFilePath);
461        reopen();
462    }
463
464    /**
465     * Checks whether GC is needed to run or not.
466     * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
467     * the blocking in some situations such as in idle time or just before closing.
468     * @return whether GC is needed to run or not.
469     */
470    public boolean needsToRunGC(final boolean mindsBlockByGC) {
471        if (!isValidDictionary()) return false;
472        return needsToRunGCNative(mNativeDict, mindsBlockByGC);
473    }
474
475    public boolean migrateTo(final int newFormatVersion) {
476        if (!isValidDictionary()) {
477            return false;
478        }
479        final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
480        // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion).
481        close();
482        final File dictFile = new File(mDictFilePath);
483        final File tmpDictFile = new File(tmpDictFilePath);
484        FileUtils.deleteRecursively(dictFile);
485        if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
486            return false;
487        }
488        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
489                dictFile.length(), mIsUpdatable);
490        return true;
491    }
492
493    @UsedForTesting
494    public int calculateProbability(final int unigramProbability, final int bigramProbability) {
495        if (!isValidDictionary()) return NOT_A_PROBABILITY;
496        return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability);
497    }
498
499    @UsedForTesting
500    public String getPropertyForTest(final String query) {
501        if (!isValidDictionary()) return "";
502        return getPropertyNative(mNativeDict, query);
503    }
504
505    @Override
506    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
507        return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT;
508    }
509
510    @Override
511    public void close() {
512        synchronized (mDicTraverseSessions) {
513            final int sessionsSize = mDicTraverseSessions.size();
514            for (int index = 0; index < sessionsSize; ++index) {
515                final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
516                if (traverseSession != null) {
517                    traverseSession.close();
518                }
519            }
520            mDicTraverseSessions.clear();
521        }
522        closeInternalLocked();
523    }
524
525    private synchronized void closeInternalLocked() {
526        if (mNativeDict != 0) {
527            closeNative(mNativeDict);
528            mNativeDict = 0;
529        }
530    }
531
532    // TODO: Manage BinaryDictionary instances without using WeakReference or something.
533    @Override
534    protected void finalize() throws Throwable {
535        try {
536            closeInternalLocked();
537        } finally {
538            super.finalize();
539        }
540    }
541}
542