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