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