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