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