BinaryDictionary.java revision 83b6ee25385e1c4fd76ca5842ff025acf945596b
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 getNgramProbabilityNative(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 addUnigramEntryNative(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 removeUnigramEntryNative(long dict, int[] word); 194 private static native boolean addNgramEntryNative(long dict, int[] word0, 195 boolean isBeginningOfSentence, int[] word1, int probability, int timestamp); 196 private static native boolean removeNgramEntryNative(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 prevWordsInfo.outputToArray(session.mPrevWordCodePointArrays, 263 session.mIsBeginningOfSentenceArray); 264 final InputPointers inputPointers = composer.getInputPointers(); 265 final boolean isGesture = composer.isBatchMode(); 266 final int inputSize; 267 if (!isGesture) { 268 inputSize = composer.copyCodePointsExceptTrailingSingleQuotesAndReturnCodePointCount( 269 session.mInputCodePoints); 270 if (inputSize < 0) { 271 return null; 272 } 273 } else { 274 inputSize = inputPointers.getPointerSize(); 275 } 276 session.mNativeSuggestOptions.setUseFullEditDistance(mUseFullEditDistance); 277 session.mNativeSuggestOptions.setIsGesture(isGesture); 278 session.mNativeSuggestOptions.setBlockOffensiveWords(blockOffensiveWords); 279 session.mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions); 280 if (inOutLanguageWeight != null) { 281 session.mInputOutputLanguageWeight[0] = inOutLanguageWeight[0]; 282 } else { 283 session.mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT; 284 } 285 // TOOD: Pass multiple previous words information for n-gram. 286 getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), 287 getTraverseSession(sessionId).getSession(), inputPointers.getXCoordinates(), 288 inputPointers.getYCoordinates(), inputPointers.getTimes(), 289 inputPointers.getPointerIds(), session.mInputCodePoints, inputSize, 290 session.mNativeSuggestOptions.getOptions(), session.mPrevWordCodePointArrays[0], 291 session.mIsBeginningOfSentenceArray[0], session.mOutputSuggestionCount, 292 session.mOutputCodePoints, session.mOutputScores, session.mSpaceIndices, 293 session.mOutputTypes, session.mOutputAutoCommitFirstWordConfidence, 294 session.mInputOutputLanguageWeight); 295 if (inOutLanguageWeight != null) { 296 inOutLanguageWeight[0] = session.mInputOutputLanguageWeight[0]; 297 } 298 final int count = session.mOutputSuggestionCount[0]; 299 final ArrayList<SuggestedWordInfo> suggestions = new ArrayList<>(); 300 for (int j = 0; j < count; ++j) { 301 final int start = j * Constants.DICTIONARY_MAX_WORD_LENGTH; 302 int len = 0; 303 while (len < Constants.DICTIONARY_MAX_WORD_LENGTH 304 && session.mOutputCodePoints[start + len] != 0) { 305 ++len; 306 } 307 if (len > 0) { 308 suggestions.add(new SuggestedWordInfo( 309 new String(session.mOutputCodePoints, start, len), 310 session.mOutputScores[j], session.mOutputTypes[j], this /* sourceDict */, 311 session.mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, 312 session.mOutputAutoCommitFirstWordConfidence[0])); 313 } 314 } 315 return suggestions; 316 } 317 318 public boolean isValidDictionary() { 319 return mNativeDict != 0; 320 } 321 322 public int getFormatVersion() { 323 return getFormatVersionNative(mNativeDict); 324 } 325 326 @Override 327 public boolean isInDictionary(final String word) { 328 return getFrequency(word) != NOT_A_PROBABILITY; 329 } 330 331 @Override 332 public int getFrequency(final String word) { 333 if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY; 334 int[] codePoints = StringUtils.toCodePointArray(word); 335 return getProbabilityNative(mNativeDict, codePoints); 336 } 337 338 @Override 339 public int getMaxFrequencyOfExactMatches(final String word) { 340 if (TextUtils.isEmpty(word)) return NOT_A_PROBABILITY; 341 int[] codePoints = StringUtils.toCodePointArray(word); 342 return getMaxProbabilityOfExactMatchesNative(mNativeDict, codePoints); 343 } 344 345 @UsedForTesting 346 public boolean isValidNgram(final PrevWordsInfo prevWordsInfo, final String word) { 347 return getNgramProbability(prevWordsInfo, word) != NOT_A_PROBABILITY; 348 } 349 350 public int getNgramProbability(final PrevWordsInfo prevWordsInfo, final String word) { 351 if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { 352 return NOT_A_PROBABILITY; 353 } 354 final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; 355 final boolean[] isBeginningOfSentenceArray = 356 new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; 357 prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); 358 final int[] wordCodePoints = StringUtils.toCodePointArray(word); 359 return getNgramProbabilityNative(mNativeDict, prevWordCodePointArrays[0], 360 isBeginningOfSentenceArray[0], wordCodePoints); 361 } 362 363 public WordProperty getWordProperty(final String word) { 364 if (TextUtils.isEmpty(word)) { 365 return null; 366 } 367 final int[] codePoints = StringUtils.toCodePointArray(word); 368 final int[] outCodePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH]; 369 final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; 370 final int[] outProbabilityInfo = 371 new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; 372 final ArrayList<int[]> outBigramTargets = new ArrayList<>(); 373 final ArrayList<int[]> outBigramProbabilityInfo = new ArrayList<>(); 374 final ArrayList<int[]> outShortcutTargets = new ArrayList<>(); 375 final ArrayList<Integer> outShortcutProbabilities = new ArrayList<>(); 376 getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo, 377 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 378 outShortcutProbabilities); 379 return new WordProperty(codePoints, 380 outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], 381 outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX], 382 outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX], 383 outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo, 384 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 385 outShortcutProbabilities); 386 } 387 388 public static class GetNextWordPropertyResult { 389 public WordProperty mWordProperty; 390 public int mNextToken; 391 392 public GetNextWordPropertyResult(final WordProperty wordProperty, final int nextToken) { 393 mWordProperty = wordProperty; 394 mNextToken = nextToken; 395 } 396 } 397 398 /** 399 * Method to iterate all words in the dictionary for makedict. 400 * If token is 0, this method newly starts iterating the dictionary. 401 */ 402 public GetNextWordPropertyResult getNextWordProperty(final int token) { 403 final int[] codePoints = new int[Constants.DICTIONARY_MAX_WORD_LENGTH]; 404 final int nextToken = getNextWordNative(mNativeDict, token, codePoints); 405 final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); 406 return new GetNextWordPropertyResult(getWordProperty(word), nextToken); 407 } 408 409 // Add a unigram entry to binary dictionary with unigram attributes in native code. 410 public boolean addUnigramEntry(final String word, final int probability, 411 final String shortcutTarget, final int shortcutProbability, 412 final boolean isBeginningOfSentence, final boolean isNotAWord, 413 final boolean isBlacklisted, final int timestamp) { 414 if (word == null || (word.isEmpty() && !isBeginningOfSentence)) { 415 return false; 416 } 417 final int[] codePoints = StringUtils.toCodePointArray(word); 418 final int[] shortcutTargetCodePoints = (shortcutTarget != null) ? 419 StringUtils.toCodePointArray(shortcutTarget) : null; 420 if (!addUnigramEntryNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, 421 shortcutProbability, isBeginningOfSentence, isNotAWord, isBlacklisted, timestamp)) { 422 return false; 423 } 424 mHasUpdated = true; 425 return true; 426 } 427 428 // Remove a unigram entry from the binary dictionary in native code. 429 public boolean removeUnigramEntry(final String word) { 430 if (TextUtils.isEmpty(word)) { 431 return false; 432 } 433 final int[] codePoints = StringUtils.toCodePointArray(word); 434 if (!removeUnigramEntryNative(mNativeDict, codePoints)) { 435 return false; 436 } 437 mHasUpdated = true; 438 return true; 439 } 440 441 // Add an n-gram entry to the binary dictionary with timestamp in native code. 442 public boolean addNgramEntry(final PrevWordsInfo prevWordsInfo, final String word, 443 final int probability, final int timestamp) { 444 if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { 445 return false; 446 } 447 final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; 448 final boolean[] isBeginningOfSentenceArray = 449 new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; 450 prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); 451 final int[] wordCodePoints = StringUtils.toCodePointArray(word); 452 if (!addNgramEntryNative(mNativeDict, prevWordCodePointArrays[0], 453 isBeginningOfSentenceArray[0], wordCodePoints, probability, timestamp)) { 454 return false; 455 } 456 mHasUpdated = true; 457 return true; 458 } 459 460 // Remove an n-gram entry from the binary dictionary in native code. 461 public boolean removeNgramEntry(final PrevWordsInfo prevWordsInfo, final String word) { 462 if (!prevWordsInfo.isValid() || TextUtils.isEmpty(word)) { 463 return false; 464 } 465 final int[][] prevWordCodePointArrays = new int[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM][]; 466 final boolean[] isBeginningOfSentenceArray = 467 new boolean[Constants.MAX_PREV_WORD_COUNT_FOR_N_GRAM]; 468 prevWordsInfo.outputToArray(prevWordCodePointArrays, isBeginningOfSentenceArray); 469 final int[] wordCodePoints = StringUtils.toCodePointArray(word); 470 if (!removeNgramEntryNative(mNativeDict, prevWordCodePointArrays[0], 471 isBeginningOfSentenceArray[0], wordCodePoints)) { 472 return false; 473 } 474 mHasUpdated = true; 475 return true; 476 } 477 478 public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { 479 if (!isValidDictionary()) return; 480 int processedParamCount = 0; 481 while (processedParamCount < languageModelParams.length) { 482 if (needsToRunGC(true /* mindsBlockByGC */)) { 483 flushWithGC(); 484 } 485 processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict, 486 languageModelParams, processedParamCount); 487 mHasUpdated = true; 488 if (processedParamCount <= 0) { 489 return; 490 } 491 } 492 } 493 494 private void reopen() { 495 close(); 496 final File dictFile = new File(mDictFilePath); 497 // WARNING: Because we pass 0 as the offset and file.length() as the length, this can 498 // only be called for actual files. Right now it's only called by the flush() family of 499 // functions, which require an updatable dictionary, so it's okay. But beware. 500 loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, 501 dictFile.length(), mIsUpdatable); 502 } 503 504 // Flush to dict file if the dictionary has been updated. 505 public boolean flush() { 506 if (!isValidDictionary()) return false; 507 if (mHasUpdated) { 508 if (!flushNative(mNativeDict, mDictFilePath)) { 509 return false; 510 } 511 reopen(); 512 } 513 return true; 514 } 515 516 // Run GC and flush to dict file if the dictionary has been updated. 517 public boolean flushWithGCIfHasUpdated() { 518 if (mHasUpdated) { 519 return flushWithGC(); 520 } 521 return true; 522 } 523 524 // Run GC and flush to dict file. 525 public boolean flushWithGC() { 526 if (!isValidDictionary()) return false; 527 if (!flushWithGCNative(mNativeDict, mDictFilePath)) { 528 return false; 529 } 530 reopen(); 531 return true; 532 } 533 534 /** 535 * Checks whether GC is needed to run or not. 536 * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about 537 * the blocking in some situations such as in idle time or just before closing. 538 * @return whether GC is needed to run or not. 539 */ 540 public boolean needsToRunGC(final boolean mindsBlockByGC) { 541 if (!isValidDictionary()) return false; 542 return needsToRunGCNative(mNativeDict, mindsBlockByGC); 543 } 544 545 public boolean migrateTo(final int newFormatVersion) { 546 if (!isValidDictionary()) { 547 return false; 548 } 549 final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION; 550 if (!migrateNative(mNativeDict, tmpDictFilePath, newFormatVersion)) { 551 return false; 552 } 553 close(); 554 final File dictFile = new File(mDictFilePath); 555 final File tmpDictFile = new File(tmpDictFilePath); 556 if (!FileUtils.deleteRecursively(dictFile)) { 557 return false; 558 } 559 if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) { 560 return false; 561 } 562 loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, 563 dictFile.length(), mIsUpdatable); 564 return true; 565 } 566 567 @UsedForTesting 568 public String getPropertyForTest(final String query) { 569 if (!isValidDictionary()) return ""; 570 return getPropertyNative(mNativeDict, query); 571 } 572 573 @Override 574 public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { 575 return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT; 576 } 577 578 @Override 579 public void close() { 580 synchronized (mDicTraverseSessions) { 581 final int sessionsSize = mDicTraverseSessions.size(); 582 for (int index = 0; index < sessionsSize; ++index) { 583 final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index); 584 if (traverseSession != null) { 585 traverseSession.close(); 586 } 587 } 588 mDicTraverseSessions.clear(); 589 } 590 closeInternalLocked(); 591 } 592 593 private synchronized void closeInternalLocked() { 594 if (mNativeDict != 0) { 595 closeNative(mNativeDict); 596 mNativeDict = 0; 597 } 598 } 599 600 // TODO: Manage BinaryDictionary instances without using WeakReference or something. 601 @Override 602 protected void finalize() throws Throwable { 603 try { 604 closeInternalLocked(); 605 } finally { 606 super.finalize(); 607 } 608 } 609} 610