BinaryDictionary.java revision d58a07666e9b38086c6486f95ac2d8e8e8eeb955
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import android.text.TextUtils;
20import android.util.Log;
21import android.util.SparseArray;
22
23import com.android.inputmethod.annotations.UsedForTesting;
24import com.android.inputmethod.keyboard.ProximityInfo;
25import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
26import com.android.inputmethod.latin.makedict.DictionaryHeader;
27import com.android.inputmethod.latin.makedict.FormatSpec;
28import com.android.inputmethod.latin.makedict.FormatSpec.DictionaryOptions;
29import com.android.inputmethod.latin.makedict.UnsupportedFormatException;
30import com.android.inputmethod.latin.makedict.WordProperty;
31import com.android.inputmethod.latin.utils.BinaryDictionaryUtils;
32import com.android.inputmethod.latin.utils.FileUtils;
33import com.android.inputmethod.latin.utils.JniUtils;
34import com.android.inputmethod.latin.utils.LanguageModelParam;
35import com.android.inputmethod.latin.utils.StringUtils;
36
37import java.io.File;
38import java.util.ArrayList;
39import java.util.Arrays;
40import java.util.HashMap;
41import java.util.Locale;
42import java.util.Map;
43
44/**
45 * Implements a static, compacted, binary dictionary of standard words.
46 */
47// TODO: All methods which should be locked need to have a suffix "Locked".
48public final class BinaryDictionary extends Dictionary {
49    private static final String TAG = BinaryDictionary.class.getSimpleName();
50
51    // The cutoff returned by native for auto-commit confidence.
52    // Must be equal to CONFIDENCE_TO_AUTO_COMMIT in native/jni/src/defines.h
53    private static final int CONFIDENCE_TO_AUTO_COMMIT = 1000000;
54
55    @UsedForTesting
56    public static final String UNIGRAM_COUNT_QUERY = "UNIGRAM_COUNT";
57    @UsedForTesting
58    public static final String BIGRAM_COUNT_QUERY = "BIGRAM_COUNT";
59    @UsedForTesting
60    public static final String MAX_UNIGRAM_COUNT_QUERY = "MAX_UNIGRAM_COUNT";
61    @UsedForTesting
62    public static final String MAX_BIGRAM_COUNT_QUERY = "MAX_BIGRAM_COUNT";
63
64    public static final int NOT_A_VALID_TIMESTAMP = -1;
65
66    // Format to get unigram flags from native side via getWordPropertyNative().
67    private static final int FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT = 5;
68    private static final int FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX = 0;
69    private static final int FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX = 1;
70    private static final int FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX = 2;
71    private static final int FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX = 3;
72    private static final int FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX = 4;
73
74    // Format to get probability and historical info from native side via getWordPropertyNative().
75    public static final int FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT = 4;
76    public static final int FORMAT_WORD_PROPERTY_PROBABILITY_INDEX = 0;
77    public static final int FORMAT_WORD_PROPERTY_TIMESTAMP_INDEX = 1;
78    public static final int FORMAT_WORD_PROPERTY_LEVEL_INDEX = 2;
79    public static final int FORMAT_WORD_PROPERTY_COUNT_INDEX = 3;
80
81    public static final String DICT_FILE_NAME_SUFFIX_FOR_MIGRATION = ".migrate";
82
83    private long mNativeDict;
84    private final Locale mLocale;
85    private final long mDictSize;
86    private final String mDictFilePath;
87    private final boolean mUseFullEditDistance;
88    private final boolean mIsUpdatable;
89    private boolean mHasUpdated;
90
91    private final SparseArray<DicTraverseSession> mDicTraverseSessions = new SparseArray<>();
92
93    // TODO: There should be a way to remove used DicTraverseSession objects from
94    // {@code mDicTraverseSessions}.
95    private DicTraverseSession getTraverseSession(final int traverseSessionId) {
96        synchronized(mDicTraverseSessions) {
97            DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
98            if (traverseSession == null) {
99                traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize);
100                mDicTraverseSessions.put(traverseSessionId, traverseSession);
101            }
102            return traverseSession;
103        }
104    }
105
106    /**
107     * Constructs binary dictionary using existing dictionary file.
108     * @param filename the name of the file to read through native code.
109     * @param offset the offset of the dictionary data within the file.
110     * @param length the length of the binary data.
111     * @param useFullEditDistance whether to use the full edit distance in suggestions
112     * @param dictType the dictionary type, as a human-readable string
113     * @param isUpdatable whether to open the dictionary file in writable mode.
114     */
115    public BinaryDictionary(final String filename, final long offset, final long length,
116            final boolean useFullEditDistance, final Locale locale, final String dictType,
117            final boolean isUpdatable) {
118        super(dictType);
119        mLocale = locale;
120        mDictSize = length;
121        mDictFilePath = filename;
122        mIsUpdatable = isUpdatable;
123        mHasUpdated = false;
124        mUseFullEditDistance = useFullEditDistance;
125        loadDictionary(filename, offset, length, isUpdatable);
126    }
127
128    /**
129     * Constructs binary dictionary on memory.
130     * @param filename the name of the file used to flush.
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 formatVersion the format version of the dictionary
134     * @param attributeMap the attributes of the dictionary
135     */
136    public BinaryDictionary(final String filename, final boolean useFullEditDistance,
137            final Locale locale, final String dictType, final long formatVersion,
138            final Map<String, String> attributeMap) {
139        super(dictType);
140        mLocale = locale;
141        mDictSize = 0;
142        mDictFilePath = filename;
143        // On memory dictionary is always updatable.
144        mIsUpdatable = true;
145        mHasUpdated = false;
146        mUseFullEditDistance = useFullEditDistance;
147        final String[] keyArray = new String[attributeMap.size()];
148        final String[] valueArray = new String[attributeMap.size()];
149        int index = 0;
150        for (final String key : attributeMap.keySet()) {
151            keyArray[index] = key;
152            valueArray[index] = attributeMap.get(key);
153            index++;
154        }
155        mNativeDict = createOnMemoryNative(formatVersion, locale.toString(), keyArray, valueArray);
156    }
157
158
159    static {
160        JniUtils.loadNativeLibrary();
161    }
162
163    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
164            boolean isUpdatable);
165    private static native long createOnMemoryNative(long formatVersion,
166            String locale, String[] attributeKeyStringArray, String[] attributeValueStringArray);
167    private static native void getHeaderInfoNative(long dict, int[] outHeaderSize,
168            int[] outFormatVersion, ArrayList<int[]> outAttributeKeys,
169            ArrayList<int[]> outAttributeValues);
170    private static native boolean flushNative(long dict, String filePath);
171    private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC);
172    private static native boolean flushWithGCNative(long dict, String filePath);
173    private static native void closeNative(long dict);
174    private static native int getFormatVersionNative(long dict);
175    private static native int getProbabilityNative(long dict, int[] word);
176    private static native int getMaxProbabilityOfExactMatchesNative(long dict, int[] word);
177    private static native int getNgramProbabilityNative(long dict, int[] word0,
178            boolean isBeginningOfSentence, int[] word1);
179    private static native void getWordPropertyNative(long dict, int[] word,
180            boolean isBeginningOfSentence, int[] outCodePoints, boolean[] outFlags,
181            int[] outProbabilityInfo, ArrayList<int[]> outBigramTargets,
182            ArrayList<int[]> outBigramProbabilityInfo, ArrayList<int[]> outShortcutTargets,
183            ArrayList<Integer> outShortcutProbabilities);
184    private static native int getNextWordNative(long dict, int token, int[] outCodePoints,
185            boolean[] outIsBeginningOfSentence);
186    private static native void getSuggestionsNative(long dict, long proximityInfo,
187            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
188            int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions,
189            int[] prevWordCodePointArray, boolean isBeginningOfSentence,
190            int[] outputSuggestionCount, int[] outputCodePoints, int[] outputScores,
191            int[] outputIndices, int[] outputTypes, int[] outputAutoCommitFirstWordConfidence,
192            float[] inOutLanguageWeight);
193    private static native boolean addUnigramEntryNative(long dict, int[] word, int probability,
194            int[] shortcutTarget, int shortcutProbability, boolean isBeginningOfSentence,
195            boolean isNotAWord, boolean isBlacklisted, int timestamp);
196    private static native boolean removeUnigramEntryNative(long dict, int[] word);
197    private static native boolean addNgramEntryNative(long dict, int[] word0,
198            boolean isBeginningOfSentence, int[] word1, int probability, int timestamp);
199    private static native boolean removeNgramEntryNative(long dict, int[] word0,
200            boolean isBeginningOfSentence, int[] word1);
201    private static native int addMultipleDictionaryEntriesNative(long dict,
202            LanguageModelParam[] languageModelParams, int startIndex);
203    private static native String getPropertyNative(long dict, String query);
204    private static native boolean isCorruptedNative(long dict);
205    private static native boolean migrateNative(long dict, String dictFilePath,
206            long newFormatVersion);
207
208    // TODO: Move native dict into session
209    private final void loadDictionary(final String path, final long startOffset,
210            final long length, final boolean isUpdatable) {
211        mHasUpdated = false;
212        mNativeDict = openNative(path, startOffset, length, isUpdatable);
213    }
214
215    // TODO: Check isCorrupted() for main dictionaries.
216    public boolean isCorrupted() {
217        if (!isValidDictionary()) {
218            return false;
219        }
220        if (!isCorruptedNative(mNativeDict)) {
221            return false;
222        }
223        // TODO: Record the corruption.
224        Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted.");
225        Log.e(TAG, "locale: " + mLocale);
226        Log.e(TAG, "dict size: " + mDictSize);
227        Log.e(TAG, "updatable: " + mIsUpdatable);
228        return true;
229    }
230
231    public DictionaryHeader getHeader() throws UnsupportedFormatException {
232        if (mNativeDict == 0) {
233            return null;
234        }
235        final int[] outHeaderSize = new int[1];
236        final int[] outFormatVersion = new int[1];
237        final ArrayList<int[]> outAttributeKeys = new ArrayList<>();
238        final ArrayList<int[]> outAttributeValues = new ArrayList<>();
239        getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys,
240                outAttributeValues);
241        final HashMap<String, String> attributes = new HashMap<>();
242        for (int i = 0; i < outAttributeKeys.size(); i++) {
243            final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray(
244                    outAttributeKeys.get(i));
245            final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray(
246                    outAttributeValues.get(i));
247            attributes.put(attributeKey, attributeValue);
248        }
249        final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals(
250                attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY));
251        return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes),
252                new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo));
253    }
254
255    @Override
256    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
257            final PrevWordsInfo prevWordsInfo, final ProximityInfo proximityInfo,
258            final boolean blockOffensiveWords, final int[] additionalFeaturesOptions,
259            final int sessionId, final float[] inOutLanguageWeight) {
260        if (!isValidDictionary()) {
261            return null;
262        }
263        final DicTraverseSession session = getTraverseSession(sessionId);
264        Arrays.fill(session.mInputCodePoints, Constants.NOT_A_CODE);
265        prevWordsInfo.outputToArray(session.mPrevWordCodePointArrays,
266                session.mIsBeginningOfSentenceArray);
267        final InputPointers inputPointers = composer.getInputPointers();
268        final boolean isGesture = composer.isBatchMode();
269        final int inputSize;
270        if (!isGesture) {
271            inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount(
272                    session.mInputCodePoints);
273            if (inputSize < 0) {
274                return null;
275            }
276        } else {
277            inputSize = inputPointers.getPointerSize();
278        }
279        session.mNativeSuggestOptions.setUseFullEditDistance(mUseFullEditDistance);
280        session.mNativeSuggestOptions.setIsGesture(isGesture);
281        session.mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords);
282        session.mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions);
283        if (inOutLanguageWeight != null) {
284            session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0];
285        } else {
286            session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT;
287        }
288        // TOOD: Pass multiple previous words information for n-gram.
289        getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
290                getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(),
291                inputPointers.getYCoordinates(), inputPointers.getTimes(),
292                inputPointers.getPointerIds(), session.mInputCodePoints, inputSize,
293                session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays[0],
294                session.mIsBeginningOfSentenceArray[0], session.mOutputSuggestionCount,
295                session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices,
296                session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence,
297                session.mInputOutputLanguageWeight);
298        if (inOutLanguageWeight != null) {
299            inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0];
300        }
301        final int count = session.mOutputSuggestionCount[0];
302        final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>();
303        for (int j = 0; j < count; ++j) {
304            final int start = j * Constants.DICTIONARY_MAX_WORD_LENGTH;
305            int len = 0;
306            while (len < Constants.DICTIONARY_MAX_WORD_LENGTH
307                    && session.mOutputCodePoints[start + len] != 0) {
308                ++len;
309            }
310            if (len > 0) {
311                suggestions.add(new SuggestedWordInfo(
312                        new String(session.mOutputCodePoints, start, len),
313                        session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */,
314                        session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */,
315                        session.mOutputAutoCommitFirstWordConfidence[0]));
316            }
317        }
318        return suggestions;
319    }
320
321    public boolean isValidDictionary() {
322        return mNativeDict != 0;
323    }
324
325    public int getFormatVersion() {
326        return getFormatVersionNative(mNativeDict);
327    }
328
329    @Override
330    public boolean isInDictionary(final String word) {
331        return getFrequency(word) != NOT_A_PROBABILITY;
332    }
333
334    @Override
335    public int getFrequency(final String word) {
336        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
337        int[] codePoints = StringUtils.toCodePointArray(word);
338        return getProbabilityNative(mNativeDict, codePoints);
339    }
340
341    @Override
342    public int getMaxFrequencyOfExactMatches(final String word) {
343        if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY;
344        int[] codePoints = StringUtils.toCodePointArray(word);
345        return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints);
346    }
347
348    @UsedForTesting
349    public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) {
350        return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY;
351    }
352
353    public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) {
354        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
355            return NOT_A_PROBABILITY;
356        }
357        final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
358        final boolean[] isBeginningOfSentenceArray =
359                new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
360        prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
361        final int[] wordCodePoints = StringUtils.toCodePointArray(word);
362        return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays[0],
363                isBeginningOfSentenceArray[0], wordCodePoints);
364    }
365
366    public WordProperty getWordProperty(final String word, final boolean isBeginningOfSentence) {
367        if (word == null) {
368            return null;
369        }
370        final int[] codePoints = StringUtils.toCodePointArray(word);
371        final int[] outCodePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
372        final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT];
373        final int[] outProbabilityInfo =
374                new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT];
375        final ArrayList<int[]> outBigramTargets = new ArrayList<>();
376        final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>();
377        final ArrayList<int[]> outShortcutTargets = new ArrayList<>();
378        final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>();
379        getWordPropertyNative(mNativeDict, codePoints, isBeginningOfSentence, outCodePoints,
380                outFlags, outProbabilityInfo, outBigramTargets, outBigramProbabilityInfo,
381                outShortcutTargets, outShortcutProbabilities);
382        return new WordProperty(codePoints,
383                outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX],
384                outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX],
385                outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX],
386                outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX],
387                outFlags[FORMAT_WORD_PROPERTY_IS_BEGINNING_OF_SENTENCE_INDEX], outProbabilityInfo,
388                outBigramTargets, outBigramProbabilityInfo, outShortcutTargets,
389                outShortcutProbabilities);
390    }
391
392    public static class GetNextWordPropertyResult {
393        public WordProperty mWordProperty;
394        public int mNextToken;
395
396        public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) {
397            mWordProperty = wordProperty;
398            mNextToken = nextToken;
399        }
400    }
401
402    /**
403     * Method to iterate all words in the dictionary for makedict.
404     * If token is 0, this method newly starts iterating the dictionary.
405     */
406    public GetNextWordPropertyResult getNextWordProperty(final int token) {
407        final int[] codePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH];
408        final boolean[] isBeginningOfSentence = new boolean[1];
409        final int nextToken = getNextWordNative(mNativeDict, token, codePoints,
410                isBeginningOfSentence);
411        final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints);
412        return new GetNextWordPropertyResult(
413                getWordProperty(word, isBeginningOfSentence[0]), nextToken);
414    }
415
416    // Add a unigram entry to binary dictionary with unigram attributes in native code.
417    public boolean addUnigramEntry(final String word, final int probability,
418            final String shortcutTarget, final int shortcutProbability,
419            final boolean isBeginningOfSentence, final boolean isNotAWord,
420            final boolean isBlacklisted, final int timestamp) {
421        if (word == null || (word.isEmpty() && !isBeginningOfSentence)) {
422            return false;
423        }
424        final int[] codePoints = StringUtils.toCodePointArray(word);
425        final int[] shortcutTargetCodePoints = (shortcutTarget != null) ?
426                StringUtils.toCodePointArray(shortcutTarget) : null;
427        if (!addUnigramEntryNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints,
428                shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) {
429            return false;
430        }
431        mHasUpdated = true;
432        return true;
433    }
434
435    // Remove a unigram entry from the binary dictionary in native code.
436    public boolean removeUnigramEntry(final String word) {
437        if (TextUtils.isEmpty(word)) {
438            return false;
439        }
440        final int[] codePoints = StringUtils.toCodePointArray(word);
441        if (!removeUnigramEntryNative(mNativeDict, codePoints)) {
442            return false;
443        }
444        mHasUpdated = true;
445        return true;
446    }
447
448    // Add an n-gram entry to the binary dictionary with timestamp in native code.
449    public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word,
450            final int probability, final int timestamp) {
451        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
452            return false;
453        }
454        final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
455        final boolean[] isBeginningOfSentenceArray =
456                new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
457        prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
458        final int[] wordCodePoints = StringUtils.toCodePointArray(word);
459        if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays[0],
460                isBeginningOfSentenceArray[0], wordCodePoints, probability, timestamp)) {
461            return false;
462        }
463        mHasUpdated = true;
464        return true;
465    }
466
467    // Remove an n-gram entry from the binary dictionary in native code.
468    public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) {
469        if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) {
470            return false;
471        }
472        final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][];
473        final boolean[] isBeginningOfSentenceArray =
474                new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM];
475        prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray);
476        final int[] wordCodePoints = StringUtils.toCodePointArray(word);
477        if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays[0],
478                isBeginningOfSentenceArray[0], wordCodePoints)) {
479            return false;
480        }
481        mHasUpdated = true;
482        return true;
483    }
484
485    public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) {
486        if (!isValidDictionary()) return;
487        int processedParamCount = 0;
488        while (processedParamCount < languageModelParams.length) {
489            if (needsToRunGC(true /* mindsBlockByGC */)) {
490                flushWithGC();
491            }
492            processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict,
493                    languageModelParams, processedParamCount);
494            mHasUpdated = true;
495            if (processedParamCount <= 0) {
496                return;
497            }
498        }
499    }
500
501    private void reopen() {
502        close();
503        final File dictFile = new File(mDictFilePath);
504        // WARNING: Because we pass 0 as the offset and file.length() as the length, this can
505        // only be called for actual files. Right now it's only called by the flush() family of
506        // functions, which require an updatable dictionary, so it's okay. But beware.
507        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
508                dictFile.length(), mIsUpdatable);
509    }
510
511    // Flush to dict file if the dictionary has been updated.
512    public boolean flush() {
513        if (!isValidDictionary()) return false;
514        if (mHasUpdated) {
515            if (!flushNative(mNativeDict, mDictFilePath)) {
516                return false;
517            }
518            reopen();
519        }
520        return true;
521    }
522
523    // Run GC and flush to dict file if the dictionary has been updated.
524    public boolean flushWithGCIfHasUpdated() {
525        if (mHasUpdated) {
526            return flushWithGC();
527        }
528        return true;
529    }
530
531    // Run GC and flush to dict file.
532    public boolean flushWithGC() {
533        if (!isValidDictionary()) return false;
534        if (!flushWithGCNative(mNativeDict, mDictFilePath)) {
535            return false;
536        }
537        reopen();
538        return true;
539    }
540
541    /**
542     * Checks whether GC is needed to run or not.
543     * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about
544     * the blocking in some situations such as in idle time or just before closing.
545     * @return whether GC is needed to run or not.
546     */
547    public boolean needsToRunGC(final boolean mindsBlockByGC) {
548        if (!isValidDictionary()) return false;
549        return needsToRunGCNative(mNativeDict, mindsBlockByGC);
550    }
551
552    public boolean migrateTo(final int newFormatVersion) {
553        if (!isValidDictionary()) {
554            return false;
555        }
556        final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION;
557        if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) {
558            return false;
559        }
560        close();
561        final File dictFile = new File(mDictFilePath);
562        final File tmpDictFile = new File(tmpDictFilePath);
563        if (!FileUtils.deleteRecursively(dictFile)) {
564            return false;
565        }
566        if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) {
567            return false;
568        }
569        loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */,
570                dictFile.length(), mIsUpdatable);
571        return true;
572    }
573
574    @UsedForTesting
575    public String getPropertyForTest(final String query) {
576        if (!isValidDictionary()) return "";
577        return getPropertyNative(mNativeDict, query);
578    }
579
580    @Override
581    public boolean shouldAutoCommit(final SuggestedWordInfo candidate) {
582        return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT;
583    }
584
585    @Override
586    public void close() {
587        synchronized (mDicTraverseSessions) {
588            final int sessionsSize = mDicTraverseSessions.size();
589            for (int index = 0; index < sessionsSize; ++index) {
590                final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
591                if (traverseSession != null) {
592                    traverseSession.close();
593                }
594            }
595            mDicTraverseSessions.clear();
596        }
597        closeInternalLocked();
598    }
599
600    private synchronized void closeInternalLocked() {
601        if (mNativeDict != 0) {
602            closeNative(mNativeDict);
603            mNativeDict = 0;
604        }
605    }
606
607    // TODO: Manage BinaryDictionary instances without using WeakReference or something.
608    @Override
609    protected void finalize() throws Throwable {
610        try {
611            closeInternalLocked();
612        } finally {
613            super.finalize();
614        }
615    }
616}
617