BinaryDictionary.java revision 1de95ceada64e7fd27ca4ee43243930b5d9c1df7
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.CollectionUtils; 34import com.android.inputmethod.latin.utils.FileUtils; 35import com.android.inputmethod.latin.utils.JniUtils; 36import com.android.inputmethod.latin.utils.LanguageModelParam; 37import com.android.inputmethod.latin.utils.StringUtils; 38 39import java.io.File; 40import java.util.ArrayList; 41import java.util.Arrays; 42import java.util.HashMap; 43import java.util.Locale; 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 final int[] mInputCodePoints = new int[MAX_WORD_LENGTH]; 93 private final int[] mOutputSuggestionCount = new int[1]; 94 private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS]; 95 private final int[] mSpaceIndices = new int[MAX_RESULTS]; 96 private final int[] mOutputScores = new int[MAX_RESULTS]; 97 private final int[] mOutputTypes = new int[MAX_RESULTS]; 98 // Only one result is ever used 99 private final int[] mOutputAutoCommitFirstWordConfidence = new int[1]; 100 private final float[] mInputOutputLanguageWeight = new float[1]; 101 102 private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions(); 103 104 private final SparseArray<DicTraverseSession> mDicTraverseSessions = 105 CollectionUtils.newSparseArray(); 106 107 // TODO: There should be a way to remove used DicTraverseSession objects from 108 // {@code mDicTraverseSessions}. 109 private DicTraverseSession getTraverseSession(final int traverseSessionId) { 110 synchronized(mDicTraverseSessions) { 111 DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId); 112 if (traverseSession == null) { 113 traverseSession = mDicTraverseSessions.get(traverseSessionId); 114 if (traverseSession == null) { 115 traverseSession = new DicTraverseSession(mLocale, mNativeDict, mDictSize); 116 mDicTraverseSessions.put(traverseSessionId, traverseSession); 117 } 118 } 119 return traverseSession; 120 } 121 } 122 123 /** 124 * Constructor for the binary dictionary. This is supposed to be called from the 125 * dictionary factory. 126 * @param filename the name of the file to read through native code. 127 * @param offset the offset of the dictionary data within the file. 128 * @param length the length of the binary data. 129 * @param useFullEditDistance whether to use the full edit distance in suggestions 130 * @param dictType the dictionary type, as a human-readable string 131 * @param isUpdatable whether to open the dictionary file in writable mode. 132 */ 133 public BinaryDictionary(final String filename, final long offset, final long length, 134 final boolean useFullEditDistance, final Locale locale, final String dictType, 135 final boolean isUpdatable) { 136 super(dictType); 137 mLocale = locale; 138 mDictSize = length; 139 mDictFilePath = filename; 140 mIsUpdatable = isUpdatable; 141 mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance); 142 loadDictionary(filename, offset, length, isUpdatable); 143 } 144 145 static { 146 JniUtils.loadNativeLibrary(); 147 } 148 149 private static native long openNative(String sourceDir, long dictOffset, long dictSize, 150 boolean isUpdatable); 151 private static native void getHeaderInfoNative(long dict, int[] outHeaderSize, 152 int[] outFormatVersion, ArrayList<int[]> outAttributeKeys, 153 ArrayList<int[]> outAttributeValues); 154 private static native void flushNative(long dict, String filePath); 155 private static native boolean needsToRunGCNative(long dict, boolean mindsBlockByGC); 156 private static native void flushWithGCNative(long dict, String filePath); 157 private static native void closeNative(long dict); 158 private static native int getFormatVersionNative(long dict); 159 private static native int getProbabilityNative(long dict, int[] word); 160 private static native int getBigramProbabilityNative(long dict, int[] word0, int[] word1); 161 private static native void getWordPropertyNative(long dict, int[] word, 162 int[] outCodePoints, boolean[] outFlags, int[] outProbabilityInfo, 163 ArrayList<int[]> outBigramTargets, ArrayList<int[]> outBigramProbabilityInfo, 164 ArrayList<int[]> outShortcutTargets, ArrayList<Integer> outShortcutProbabilities); 165 private static native int getNextWordNative(long dict, int token, int[] outCodePoints); 166 private static native void getSuggestionsNative(long dict, long proximityInfo, 167 long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times, 168 int[] pointerIds, int[] inputCodePoints, int inputSize, int[] suggestOptions, 169 int[] prevWordCodePointArray, int[] outputSuggestionCount, int[] outputCodePoints, 170 int[] outputScores, int[] outputIndices, int[] outputTypes, 171 int[] outputAutoCommitFirstWordConfidence, float[] inOutLanguageWeight); 172 private static native void addUnigramWordNative(long dict, int[] word, int probability, 173 int[] shortcutTarget, int shortcutProbability, boolean isNotAWord, 174 boolean isBlacklisted, int timestamp); 175 private static native void addBigramWordsNative(long dict, int[] word0, int[] word1, 176 int probability, int timestamp); 177 private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1); 178 private static native int addMultipleDictionaryEntriesNative(long dict, 179 LanguageModelParam[] languageModelParams, int startIndex); 180 private static native int calculateProbabilityNative(long dict, int unigramProbability, 181 int bigramProbability); 182 private static native String getPropertyNative(long dict, String query); 183 private static native boolean isCorruptedNative(long dict); 184 185 // TODO: Move native dict into session 186 private final void loadDictionary(final String path, final long startOffset, 187 final long length, final boolean isUpdatable) { 188 mNativeDict = openNative(path, startOffset, length, isUpdatable); 189 } 190 191 // TODO: Check isCorrupted() for main dictionaries. 192 public boolean isCorrupted() { 193 if (!isValidDictionary()) { 194 return false; 195 } 196 if (!isCorruptedNative(mNativeDict)) { 197 return false; 198 } 199 // TODO: Record the corruption. 200 Log.e(TAG, "BinaryDictionary (" + mDictFilePath + ") is corrupted."); 201 Log.e(TAG, "locale: " + mLocale); 202 Log.e(TAG, "dict size: " + mDictSize); 203 Log.e(TAG, "updatable: " + mIsUpdatable); 204 return true; 205 } 206 207 public DictionaryHeader getHeader() throws UnsupportedFormatException { 208 if (mNativeDict == 0) { 209 return null; 210 } 211 final int[] outHeaderSize = new int[1]; 212 final int[] outFormatVersion = new int[1]; 213 final ArrayList<int[]> outAttributeKeys = CollectionUtils.newArrayList(); 214 final ArrayList<int[]> outAttributeValues = CollectionUtils.newArrayList(); 215 getHeaderInfoNative(mNativeDict, outHeaderSize, outFormatVersion, outAttributeKeys, 216 outAttributeValues); 217 final HashMap<String, String> attributes = new HashMap<String, String>(); 218 for (int i = 0; i < outAttributeKeys.size(); i++) { 219 final String attributeKey = StringUtils.getStringFromNullTerminatedCodePointArray( 220 outAttributeKeys.get(i)); 221 final String attributeValue = StringUtils.getStringFromNullTerminatedCodePointArray( 222 outAttributeValues.get(i)); 223 attributes.put(attributeKey, attributeValue); 224 } 225 final boolean hasHistoricalInfo = DictionaryHeader.ATTRIBUTE_VALUE_TRUE.equals( 226 attributes.get(DictionaryHeader.HAS_HISTORICAL_INFO_KEY)); 227 return new DictionaryHeader(outHeaderSize[0], new DictionaryOptions(attributes), 228 new FormatSpec.FormatOptions(outFormatVersion[0], hasHistoricalInfo)); 229 } 230 231 232 @Override 233 public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer, 234 final String prevWord, final ProximityInfo proximityInfo, 235 final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, 236 final float[] inOutLanguageWeight) { 237 return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords, 238 additionalFeaturesOptions, 0 /* sessionId */, inOutLanguageWeight); 239 } 240 241 @Override 242 public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer, 243 final String prevWord, final ProximityInfo proximityInfo, 244 final boolean blockOffensiveWords, final int[] additionalFeaturesOptions, 245 final int sessionId, final float[] inOutLanguageWeight) { 246 if (!isValidDictionary()) return null; 247 248 Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE); 249 // TODO: toLowerCase in the native code 250 final int[] prevWordCodePointArray = (null == prevWord) 251 ? null : StringUtils.toCodePointArray(prevWord); 252 final int composerSize = composer.sizeWithoutTrailingSingleQuotes(); 253 254 final boolean isGesture = composer.isBatchMode(); 255 if (composerSize <= 1 || !isGesture) { 256 if (composerSize > MAX_WORD_LENGTH - 1) return null; 257 for (int i = 0; i < composerSize; i++) { 258 mInputCodePoints[i] = composer.getCodeAt(i); 259 } 260 } 261 262 final InputPointers ips = composer.getInputPointers(); 263 final int inputSize = isGesture ? ips.getPointerSize() : composerSize; 264 mNativeSuggestOptions.setIsGesture(isGesture); 265 mNativeSuggestOptions.setAdditionalFeaturesOptions(additionalFeaturesOptions); 266 if (inOutLanguageWeight != null) { 267 mInputOutputLanguageWeight[0] = inOutLanguageWeight[0]; 268 } else { 269 mInputOutputLanguageWeight[0] = Dictionary.NOT_A_LANGUAGE_WEIGHT; 270 } 271 // proximityInfo and/or prevWordForBigrams may not be null. 272 getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(), 273 getTraverseSession(sessionId).getSession(), ips.getXCoordinates(), 274 ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints, 275 inputSize, mNativeSuggestOptions.getOptions(), 276 prevWordCodePointArray, mOutputSuggestionCount, mOutputCodePoints, mOutputScores, 277 mSpaceIndices, mOutputTypes, mOutputAutoCommitFirstWordConfidence, 278 mInputOutputLanguageWeight); 279 if (inOutLanguageWeight != null) { 280 inOutLanguageWeight[0] = mInputOutputLanguageWeight[0]; 281 } 282 final int count = mOutputSuggestionCount[0]; 283 final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList(); 284 for (int j = 0; j < count; ++j) { 285 final int start = j * MAX_WORD_LENGTH; 286 int len = 0; 287 while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) { 288 ++len; 289 } 290 if (len > 0) { 291 final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS; 292 if (blockOffensiveWords 293 && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE) 294 && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) { 295 // If we block potentially offensive words, and if the word is possibly 296 // offensive, then we don't output it unless it's also an exact match. 297 continue; 298 } 299 final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND; 300 final int score = SuggestedWordInfo.KIND_WHITELIST == kind 301 ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j]; 302 // TODO: check that all users of the `kind' parameter are ready to accept 303 // flags too and pass mOutputTypes[j] instead of kind 304 suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len), 305 score, kind, this /* sourceDict */, 306 mSpaceIndices[j] /* indexOfTouchPointOfSecondWord */, 307 mOutputAutoCommitFirstWordConfidence[0])); 308 } 309 } 310 return suggestions; 311 } 312 313 public boolean isValidDictionary() { 314 return mNativeDict != 0; 315 } 316 317 public int getFormatVersion() { 318 return getFormatVersionNative(mNativeDict); 319 } 320 321 @Override 322 public boolean isValidWord(final String word) { 323 return getFrequency(word) != NOT_A_PROBABILITY; 324 } 325 326 @Override 327 public int getFrequency(final String word) { 328 if (word == null) return NOT_A_PROBABILITY; 329 int[] codePoints = StringUtils.toCodePointArray(word); 330 return getProbabilityNative(mNativeDict, codePoints); 331 } 332 333 // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni 334 // calls when checking for changes in an entire dictionary. 335 public boolean isValidBigram(final String word0, final String word1) { 336 return getBigramProbability(word0, word1) != NOT_A_PROBABILITY; 337 } 338 339 public int getBigramProbability(final String word0, final String word1) { 340 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) return NOT_A_PROBABILITY; 341 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 342 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 343 return getBigramProbabilityNative(mNativeDict, codePoints0, codePoints1); 344 } 345 346 public WordProperty getWordProperty(final String word) { 347 if (TextUtils.isEmpty(word)) { 348 return null; 349 } 350 final int[] codePoints = StringUtils.toCodePointArray(word); 351 final int[] outCodePoints = new int[MAX_WORD_LENGTH]; 352 final boolean[] outFlags = new boolean[FORMAT_WORD_PROPERTY_OUTPUT_FLAG_COUNT]; 353 final int[] outProbabilityInfo = 354 new int[FORMAT_WORD_PROPERTY_OUTPUT_PROBABILITY_INFO_COUNT]; 355 final ArrayList<int[]> outBigramTargets = CollectionUtils.newArrayList(); 356 final ArrayList<int[]> outBigramProbabilityInfo = CollectionUtils.newArrayList(); 357 final ArrayList<int[]> outShortcutTargets = CollectionUtils.newArrayList(); 358 final ArrayList<Integer> outShortcutProbabilities = CollectionUtils.newArrayList(); 359 getWordPropertyNative(mNativeDict, codePoints, outCodePoints, outFlags, outProbabilityInfo, 360 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 361 outShortcutProbabilities); 362 return new WordProperty(codePoints, 363 outFlags[FORMAT_WORD_PROPERTY_IS_NOT_A_WORD_INDEX], 364 outFlags[FORMAT_WORD_PROPERTY_IS_BLACKLISTED_INDEX], 365 outFlags[FORMAT_WORD_PROPERTY_HAS_BIGRAMS_INDEX], 366 outFlags[FORMAT_WORD_PROPERTY_HAS_SHORTCUTS_INDEX], outProbabilityInfo, 367 outBigramTargets, outBigramProbabilityInfo, outShortcutTargets, 368 outShortcutProbabilities); 369 } 370 371 public static class GetNextWordPropertyResult { 372 public WordProperty mWordProperty; 373 public int mNextToken; 374 375 public GetNextWordPropertyResult(final WordProperty wordPreperty, final int nextToken) { 376 mWordProperty = wordPreperty; 377 mNextToken = nextToken; 378 } 379 } 380 381 /** 382 * Method to iterate all words in the dictionary for makedict. 383 * If token is 0, this method newly starts iterating the dictionary. 384 */ 385 public GetNextWordPropertyResult getNextWordProperty(final int token) { 386 final int[] codePoints = new int[MAX_WORD_LENGTH]; 387 final int nextToken = getNextWordNative(mNativeDict, token, codePoints); 388 final String word = StringUtils.getStringFromNullTerminatedCodePointArray(codePoints); 389 return new GetNextWordPropertyResult(getWordProperty(word), nextToken); 390 } 391 392 // Add a unigram entry to binary dictionary with unigram attributes in native code. 393 public void addUnigramWord(final String word, final int probability, 394 final String shortcutTarget, final int shortcutProbability, final boolean isNotAWord, 395 final boolean isBlacklisted, final int timestamp) { 396 if (TextUtils.isEmpty(word)) { 397 return; 398 } 399 final int[] codePoints = StringUtils.toCodePointArray(word); 400 final int[] shortcutTargetCodePoints = (shortcutTarget != null) ? 401 StringUtils.toCodePointArray(shortcutTarget) : null; 402 addUnigramWordNative(mNativeDict, codePoints, probability, shortcutTargetCodePoints, 403 shortcutProbability, isNotAWord, isBlacklisted, timestamp); 404 } 405 406 // Add a bigram entry to binary dictionary with timestamp in native code. 407 public void addBigramWords(final String word0, final String word1, final int probability, 408 final int timestamp) { 409 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) { 410 return; 411 } 412 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 413 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 414 addBigramWordsNative(mNativeDict, codePoints0, codePoints1, probability, timestamp); 415 } 416 417 // Remove a bigram entry form binary dictionary in native code. 418 public void removeBigramWords(final String word0, final String word1) { 419 if (TextUtils.isEmpty(word0) || TextUtils.isEmpty(word1)) { 420 return; 421 } 422 final int[] codePoints0 = StringUtils.toCodePointArray(word0); 423 final int[] codePoints1 = StringUtils.toCodePointArray(word1); 424 removeBigramWordsNative(mNativeDict, codePoints0, codePoints1); 425 } 426 427 public void addMultipleDictionaryEntries(final LanguageModelParam[] languageModelParams) { 428 if (!isValidDictionary()) return; 429 int processedParamCount = 0; 430 while (processedParamCount < languageModelParams.length) { 431 if (needsToRunGC(true /* mindsBlockByGC */)) { 432 flushWithGC(); 433 } 434 processedParamCount = addMultipleDictionaryEntriesNative(mNativeDict, 435 languageModelParams, processedParamCount); 436 if (processedParamCount <= 0) { 437 return; 438 } 439 } 440 } 441 442 private void reopen() { 443 close(); 444 final File dictFile = new File(mDictFilePath); 445 // WARNING: Because we pass 0 as the offset and file.length() as the length, this can 446 // only be called for actual files. Right now it's only called by the flush() family of 447 // functions, which require an updatable dictionary, so it's okay. But beware. 448 loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, 449 dictFile.length(), mIsUpdatable); 450 } 451 452 public void flush() { 453 if (!isValidDictionary()) return; 454 flushNative(mNativeDict, mDictFilePath); 455 reopen(); 456 } 457 458 public void flushWithGC() { 459 if (!isValidDictionary()) return; 460 flushWithGCNative(mNativeDict, mDictFilePath); 461 reopen(); 462 } 463 464 /** 465 * Checks whether GC is needed to run or not. 466 * @param mindsBlockByGC Whether to mind operations blocked by GC. We don't need to care about 467 * the blocking in some situations such as in idle time or just before closing. 468 * @return whether GC is needed to run or not. 469 */ 470 public boolean needsToRunGC(final boolean mindsBlockByGC) { 471 if (!isValidDictionary()) return false; 472 return needsToRunGCNative(mNativeDict, mindsBlockByGC); 473 } 474 475 public boolean migrateTo(final int newFormatVersion) { 476 if (!isValidDictionary()) { 477 return false; 478 } 479 final String tmpDictFilePath = mDictFilePath + DICT_FILE_NAME_SUFFIX_FOR_MIGRATION; 480 // TODO: Implement migrateNative(tmpDictFilePath, newFormatVersion). 481 close(); 482 final File dictFile = new File(mDictFilePath); 483 final File tmpDictFile = new File(tmpDictFilePath); 484 FileUtils.deleteRecursively(dictFile); 485 if (!BinaryDictionaryUtils.renameDict(tmpDictFile, dictFile)) { 486 return false; 487 } 488 loadDictionary(dictFile.getAbsolutePath(), 0 /* startOffset */, 489 dictFile.length(), mIsUpdatable); 490 return true; 491 } 492 493 @UsedForTesting 494 public int calculateProbability(final int unigramProbability, final int bigramProbability) { 495 if (!isValidDictionary()) return NOT_A_PROBABILITY; 496 return calculateProbabilityNative(mNativeDict, unigramProbability, bigramProbability); 497 } 498 499 @UsedForTesting 500 public String getPropertyForTest(final String query) { 501 if (!isValidDictionary()) return ""; 502 return getPropertyNative(mNativeDict, query); 503 } 504 505 @Override 506 public boolean shouldAutoCommit(final SuggestedWordInfo candidate) { 507 return candidate.mAutoCommitFirstWordConfidence > CONFIDENCE_TO_AUTO_COMMIT; 508 } 509 510 @Override 511 public void close() { 512 synchronized (mDicTraverseSessions) { 513 final int sessionsSize = mDicTraverseSessions.size(); 514 for (int index = 0; index < sessionsSize; ++index) { 515 final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index); 516 if (traverseSession != null) { 517 traverseSession.close(); 518 } 519 } 520 mDicTraverseSessions.clear(); 521 } 522 closeInternalLocked(); 523 } 524 525 private synchronized void closeInternalLocked() { 526 if (mNativeDict != 0) { 527 closeNative(mNativeDict); 528 mNativeDict = 0; 529 } 530 } 531 532 // TODO: Manage BinaryDictionary instances without using WeakReference or something. 533 @Override 534 protected void finalize() throws Throwable { 535 try { 536 closeInternalLocked(); 537 } finally { 538 super.finalize(); 539 } 540 } 541} 542