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