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