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