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