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