aboutsummaryrefslogtreecommitdiffstats
path: root/java/src/com/android/inputmethod/latin/Suggest.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/src/com/android/inputmethod/latin/Suggest.java')
-rw-r--r--java/src/com/android/inputmethod/latin/Suggest.java307
1 files changed, 118 insertions, 189 deletions
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java
index 0938bd127..4a1c897ca 100644
--- a/java/src/com/android/inputmethod/latin/Suggest.java
+++ b/java/src/com/android/inputmethod/latin/Suggest.java
@@ -26,6 +26,7 @@ import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import java.io.File;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
@@ -34,7 +35,7 @@ import java.util.concurrent.ConcurrentHashMap;
* This class loads a dictionary and provides a list of suggestions for a given sequence of
* characters. This includes corrections and completions.
*/
-public class Suggest implements Dictionary.WordCallback {
+public class Suggest {
public static final String TAG = Suggest.class.getSimpleName();
public static final int APPROX_MAX_WORD_LENGTH = 32;
@@ -58,56 +59,61 @@ public class Suggest implements Dictionary.WordCallback {
public static final String DICT_KEY_CONTACTS = "contacts";
// User dictionary, the system-managed one.
public static final String DICT_KEY_USER = "user";
- // User history dictionary for the unigram map, internal to LatinIME
- public static final String DICT_KEY_USER_HISTORY_UNIGRAM = "history_unigram";
- // User history dictionary for the bigram map, internal to LatinIME
- public static final String DICT_KEY_USER_HISTORY_BIGRAM = "history_bigram";
+ // User history dictionary internal to LatinIME
+ public static final String DICT_KEY_USER_HISTORY = "history";
public static final String DICT_KEY_WHITELIST ="whitelist";
+ // TODO: remove this map. This only serves as backward compatibility with a feature
+ // that has never been used and has been broken for a while.
+ private static final HashMap<String, Integer> sDictKeyToDictIndex
+ = new HashMap<String, Integer>();
+ static {
+ sDictKeyToDictIndex.put(DICT_KEY_MAIN, DIC_MAIN);
+ sDictKeyToDictIndex.put(DICT_KEY_USER, DIC_USER);
+ sDictKeyToDictIndex.put(DICT_KEY_USER_HISTORY, DIC_USER_HISTORY);
+ sDictKeyToDictIndex.put(DICT_KEY_CONTACTS, DIC_CONTACTS);
+ sDictKeyToDictIndex.put(DICT_KEY_WHITELIST, DIC_WHITELIST);
+ }
private static final boolean DBG = LatinImeLogger.sDBG;
private Dictionary mMainDictionary;
private ContactsBinaryDictionary mContactsDict;
private WhitelistDictionary mWhiteListDictionary;
- private final ConcurrentHashMap<String, Dictionary> mUnigramDictionaries =
- new ConcurrentHashMap<String, Dictionary>();
- private final ConcurrentHashMap<String, Dictionary> mBigramDictionaries =
+ private final ConcurrentHashMap<String, Dictionary> mDictionaries =
new ConcurrentHashMap<String, Dictionary>();
public static final int MAX_SUGGESTIONS = 18;
- private static final int PREF_MAX_BIGRAMS = 60;
-
private float mAutoCorrectionThreshold;
- private ArrayList<SuggestedWordInfo> mSuggestions = new ArrayList<SuggestedWordInfo>();
- private ArrayList<SuggestedWordInfo> mBigramSuggestions = new ArrayList<SuggestedWordInfo>();
- private CharSequence mConsideredWord;
-
// TODO: Remove these member variables by passing more context to addWord() callback method
private boolean mIsFirstCharCapitalized;
private boolean mIsAllUpperCase;
private int mTrailingSingleQuotesCount;
+ // Locale used for upper- and title-casing words
+ final private Locale mLocale;
+
private static final int MINIMUM_SAFETY_NET_CHAR_LENGTH = 4;
public Suggest(final Context context, final Locale locale) {
initAsynchronously(context, locale);
+ mLocale = locale;
}
/* package for test */ Suggest(final Context context, final File dictionary,
final long startOffset, final long length, final Locale locale) {
final Dictionary mainDict = DictionaryFactory.createDictionaryForTest(context, dictionary,
startOffset, length /* useFullEditDistance */, false, locale);
+ mLocale = locale;
mMainDictionary = mainDict;
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, mainDict);
- addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, mainDict);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, mainDict);
initWhitelistAndAutocorrectAndPool(context, locale);
}
private void initWhitelistAndAutocorrectAndPool(final Context context, final Locale locale) {
mWhiteListDictionary = new WhitelistDictionary(context, locale);
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_WHITELIST, mWhiteListDictionary);
}
private void initAsynchronously(final Context context, final Locale locale) {
@@ -136,8 +142,7 @@ public class Suggest implements Dictionary.WordCallback {
public void run() {
final DictionaryCollection newMainDict =
DictionaryFactory.createMainDictionaryFromManager(context, locale);
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_MAIN, newMainDict);
- addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_MAIN, newMainDict);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_MAIN, newMainDict);
mMainDictionary = newMainDict;
}
}.start();
@@ -158,7 +163,7 @@ public class Suggest implements Dictionary.WordCallback {
}
public ConcurrentHashMap<String, Dictionary> getUnigramDictionaries() {
- return mUnigramDictionaries;
+ return mDictionaries;
}
public static int getApproxMaxWordLength() {
@@ -170,7 +175,7 @@ public class Suggest implements Dictionary.WordCallback {
* before the main dictionary, if set. This refers to the system-managed user dictionary.
*/
public void setUserDictionary(UserBinaryDictionary userDictionary) {
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER, userDictionary);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_USER, userDictionary);
}
/**
@@ -180,15 +185,11 @@ public class Suggest implements Dictionary.WordCallback {
*/
public void setContactsDictionary(ContactsBinaryDictionary contactsDictionary) {
mContactsDict = contactsDictionary;
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
- addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_CONTACTS, contactsDictionary);
}
public void setUserHistoryDictionary(UserHistoryDictionary userHistoryDictionary) {
- addOrReplaceDictionary(mUnigramDictionaries, DICT_KEY_USER_HISTORY_UNIGRAM,
- userHistoryDictionary);
- addOrReplaceDictionary(mBigramDictionaries, DICT_KEY_USER_HISTORY_BIGRAM,
- userHistoryDictionary);
+ addOrReplaceDictionary(mDictionaries, DICT_KEY_USER_HISTORY, userHistoryDictionary);
}
public void setAutoCorrectionThreshold(float threshold) {
@@ -212,52 +213,17 @@ public class Suggest implements Dictionary.WordCallback {
return sb;
}
- protected void addBigramToSuggestions(SuggestedWordInfo bigram) {
- mSuggestions.add(bigram);
- }
-
- private static final WordComposer sEmptyWordComposer = new WordComposer();
- public SuggestedWords getBigramPredictions(CharSequence prevWordForBigram) {
- LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mIsFirstCharCapitalized = false;
- mIsAllUpperCase = false;
- mTrailingSingleQuotesCount = 0;
- mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
-
- // Treating USER_TYPED as UNIGRAM suggestion for logging now.
- LatinImeLogger.onAddSuggestedWord("", Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
- mConsideredWord = "";
-
- mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
- getAllBigrams(prevWordForBigram, sEmptyWordComposer);
-
- // Nothing entered: return all bigrams for the previous word
- int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS);
- for (int i = 0; i < insertCount; ++i) {
- addBigramToSuggestions(mBigramSuggestions.get(i));
- }
-
- SuggestedWordInfo.removeDups(mSuggestions);
-
- return new SuggestedWords(mSuggestions,
- false /* typedWordValid */,
- false /* hasAutoCorrectionCandidate */,
- false /* allowsToBeAutoCorrected */,
- false /* isPunctuationSuggestions */,
- false /* isObsoleteSuggestions */,
- true /* isPrediction */);
- }
-
// TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder
public SuggestedWords getSuggestedWords(
final WordComposer wordComposer, CharSequence prevWordForBigram,
- final ProximityInfo proximityInfo, final boolean isCorrectionEnabled) {
+ final ProximityInfo proximityInfo, final boolean isCorrectionEnabled,
+ final boolean isPrediction) {
LatinImeLogger.onStartSuggestion(prevWordForBigram);
- mIsFirstCharCapitalized = wordComposer.isFirstCharCapitalized();
- mIsAllUpperCase = wordComposer.isAllUpperCase();
+ mIsFirstCharCapitalized = !isPrediction && wordComposer.isFirstCharCapitalized();
+ mIsAllUpperCase = !isPrediction && wordComposer.isAllUpperCase();
mTrailingSingleQuotesCount = wordComposer.trailingSingleQuotesCount();
- mSuggestions = new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
+ final ArrayList<SuggestedWordInfo> suggestionsContainer =
+ new ArrayList<SuggestedWordInfo>(MAX_SUGGESTIONS);
final String typedWord = wordComposer.getTypedWord();
final String consideredWord = mTrailingSingleQuotesCount > 0
@@ -265,41 +231,31 @@ public class Suggest implements Dictionary.WordCallback {
: typedWord;
// Treating USER_TYPED as UNIGRAM suggestion for logging now.
LatinImeLogger.onAddSuggestedWord(typedWord, Suggest.DIC_USER_TYPED, Dictionary.UNIGRAM);
- mConsideredWord = consideredWord;
if (wordComposer.size() <= 1 && isCorrectionEnabled) {
// At first character typed, search only the bigrams
- mBigramSuggestions = new ArrayList<SuggestedWordInfo>(PREF_MAX_BIGRAMS);
-
if (!TextUtils.isEmpty(prevWordForBigram)) {
- getAllBigrams(prevWordForBigram, wordComposer);
- if (TextUtils.isEmpty(consideredWord)) {
- // Nothing entered: return all bigrams for the previous word
- int insertCount = Math.min(mBigramSuggestions.size(), MAX_SUGGESTIONS);
- for (int i = 0; i < insertCount; ++i) {
- addBigramToSuggestions(mBigramSuggestions.get(i));
- }
- } else {
- // Word entered: return only bigrams that match the first char of the typed word
- final char currentChar = consideredWord.charAt(0);
+ final CharSequence lowerPrevWord;
+ if (StringUtils.hasUpperCase(prevWordForBigram)) {
// TODO: Must pay attention to locale when changing case.
- // TODO: Use codepoint instead of char
- final char currentCharUpper = Character.toUpperCase(currentChar);
- int count = 0;
- final int bigramSuggestionSize = mBigramSuggestions.size();
- for (int i = 0; i < bigramSuggestionSize; i++) {
- final SuggestedWordInfo bigramSuggestion = mBigramSuggestions.get(i);
- final char bigramSuggestionFirstChar =
- (char)bigramSuggestion.codePointAt(0);
- if (bigramSuggestionFirstChar == currentChar
- || bigramSuggestionFirstChar == currentCharUpper) {
- addBigramToSuggestions(bigramSuggestion);
- if (++count > MAX_SUGGESTIONS) break;
- }
+ lowerPrevWord = prevWordForBigram.toString().toLowerCase();
+ } else {
+ lowerPrevWord = null;
+ }
+ for (final String key : mDictionaries.keySet()) {
+ final int dicTypeId = sDictKeyToDictIndex.get(key);
+ final Dictionary dictionary = mDictionaries.get(key);
+ final ArrayList<SuggestedWordInfo> localSuggestions =
+ dictionary.getBigrams(wordComposer, prevWordForBigram);
+ if (null != lowerPrevWord) {
+ localSuggestions.addAll(dictionary.getBigrams(wordComposer, lowerPrevWord));
+ }
+ for (final SuggestedWordInfo localSuggestion : localSuggestions) {
+ addWord(localSuggestion, dicTypeId, Dictionary.BIGRAM,
+ suggestionsContainer, consideredWord);
}
}
}
-
} else if (wordComposer.size() > 1) {
final WordComposer wordComposerForLookup;
if (mTrailingSingleQuotesCount > 0) {
@@ -311,12 +267,18 @@ public class Suggest implements Dictionary.WordCallback {
wordComposerForLookup = wordComposer;
}
// At second character typed, search the unigrams (scores being affected by bigrams)
- for (final String key : mUnigramDictionaries.keySet()) {
+ for (final String key : mDictionaries.keySet()) {
// Skip UserUnigramDictionary and WhitelistDictionary to lookup
- if (key.equals(DICT_KEY_USER_HISTORY_UNIGRAM) || key.equals(DICT_KEY_WHITELIST))
+ if (key.equals(DICT_KEY_USER_HISTORY) || key.equals(DICT_KEY_WHITELIST))
continue;
- final Dictionary dictionary = mUnigramDictionaries.get(key);
- dictionary.getWords(wordComposerForLookup, prevWordForBigram, this, proximityInfo);
+ final int dicTypeId = sDictKeyToDictIndex.get(key);
+ final Dictionary dictionary = mDictionaries.get(key);
+ final ArrayList<SuggestedWordInfo> localSuggestions = dictionary.getWords(
+ wordComposerForLookup, prevWordForBigram, proximityInfo);
+ for (final SuggestedWordInfo suggestion : localSuggestions) {
+ addWord(suggestion, dicTypeId, Dictionary.UNIGRAM,
+ suggestionsContainer, consideredWord);
+ }
}
}
@@ -326,8 +288,8 @@ public class Suggest implements Dictionary.WordCallback {
final boolean hasAutoCorrection;
if (isCorrectionEnabled) {
final CharSequence autoCorrection =
- AutoCorrection.computeAutoCorrectionWord(mUnigramDictionaries, wordComposer,
- mSuggestions, consideredWord, mAutoCorrectionThreshold,
+ AutoCorrection.computeAutoCorrectionWord(mDictionaries, wordComposer,
+ suggestionsContainer, consideredWord, mAutoCorrectionThreshold,
whitelistedWord);
hasAutoCorrection = (null != autoCorrection);
} else {
@@ -340,23 +302,25 @@ public class Suggest implements Dictionary.WordCallback {
for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
}
- mSuggestions.add(0, new SuggestedWordInfo(sb.toString(),
+ suggestionsContainer.add(0, new SuggestedWordInfo(sb.toString(),
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST));
} else {
- mSuggestions.add(0, new SuggestedWordInfo(whitelistedWord,
+ suggestionsContainer.add(0, new SuggestedWordInfo(whitelistedWord,
SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_WHITELIST));
}
}
- mSuggestions.add(0, new SuggestedWordInfo(typedWord, SuggestedWordInfo.MAX_SCORE,
- SuggestedWordInfo.KIND_TYPED));
- SuggestedWordInfo.removeDups(mSuggestions);
+ if (!isPrediction) {
+ suggestionsContainer.add(0, new SuggestedWordInfo(typedWord,
+ SuggestedWordInfo.MAX_SCORE, SuggestedWordInfo.KIND_TYPED));
+ }
+ SuggestedWordInfo.removeDups(suggestionsContainer);
final ArrayList<SuggestedWordInfo> suggestionsList;
- if (DBG) {
- suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, mSuggestions);
+ if (DBG && !suggestionsContainer.isEmpty()) {
+ suggestionsList = getSuggestionsInfoListWithDebugInfo(typedWord, suggestionsContainer);
} else {
- suggestionsList = mSuggestions;
+ suggestionsList = suggestionsContainer;
}
// TODO: Change this scheme - a boolean is not enough. A whitelisted word may be "valid"
@@ -386,29 +350,12 @@ public class Suggest implements Dictionary.WordCallback {
autoCorrectionAvailable = false;
}
return new SuggestedWords(suggestionsList,
- !allowsToBeAutoCorrected /* typedWordValid */,
- autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
- allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
+ !isPrediction && !allowsToBeAutoCorrected /* typedWordValid */,
+ !isPrediction && autoCorrectionAvailable /* hasAutoCorrectionCandidate */,
+ !isPrediction && allowsToBeAutoCorrected /* allowsToBeAutoCorrected */,
false /* isPunctuationSuggestions */,
false /* isObsoleteSuggestions */,
- false /* isPrediction */);
- }
-
- /**
- * Adds all bigram predictions for prevWord. Also checks the lower case version of prevWord if
- * it contains any upper case characters.
- */
- private void getAllBigrams(final CharSequence prevWord, final WordComposer wordComposer) {
- if (StringUtils.hasUpperCase(prevWord)) {
- // TODO: Must pay attention to locale when changing case.
- final CharSequence lowerPrevWord = prevWord.toString().toLowerCase();
- for (final Dictionary dictionary : mBigramDictionaries.values()) {
- dictionary.getBigrams(wordComposer, lowerPrevWord, this);
- }
- }
- for (final Dictionary dictionary : mBigramDictionaries.values()) {
- dictionary.getBigrams(wordComposer, prevWord, this);
- }
+ isPrediction);
}
private static ArrayList<SuggestedWordInfo> getSuggestionsInfoListWithDebugInfo(
@@ -437,84 +384,66 @@ public class Suggest implements Dictionary.WordCallback {
return suggestionsList;
}
- // TODO: Use codepoint instead of char
- @Override
- public boolean addWord(final char[] word, int[] indices, final int offset, final int length,
- int score, final int dicTypeId, final int dataType) {
+ public boolean addWord(final SuggestedWordInfo wordInfo,
+ final int dicTypeId, final int dataType,
+ final ArrayList<SuggestedWordInfo> suggestions, final String consideredWord) {
int dataTypeForLog = dataType;
- final ArrayList<SuggestedWordInfo> suggestions;
- final int prefMaxSuggestions;
- if (dataType == Dictionary.BIGRAM) {
- suggestions = mBigramSuggestions;
- prefMaxSuggestions = PREF_MAX_BIGRAMS;
- } else {
- suggestions = mSuggestions;
- prefMaxSuggestions = MAX_SUGGESTIONS;
- }
+ final int prefMaxSuggestions = MAX_SUGGESTIONS;
+ final CharSequence word = wordInfo.mWord;
+ final int score = wordInfo.mScore;
int pos = 0;
- // Check if it's the same word, only caps are different
- if (StringUtils.equalsIgnoreCase(mConsideredWord, word, offset, length)) {
- // TODO: remove this surrounding if clause and move this logic to
- // getSuggestedWordBuilder.
- if (suggestions.size() > 0) {
- final SuggestedWordInfo currentHighestWord = suggestions.get(0);
- // If the current highest word is also equal to typed word, we need to compare
- // frequency to determine the insertion position. This does not ensure strictly
- // correct ordering, but ensures the top score is on top which is enough for
- // removing duplicates correctly.
- if (StringUtils.equalsIgnoreCase(currentHighestWord.mWord, word, offset, length)
- && score <= currentHighestWord.mScore) {
- pos = 1;
- }
- }
- } else {
- // Check the last one's score and bail
- if (suggestions.size() >= prefMaxSuggestions
- && suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true;
- while (pos < suggestions.size()) {
- final int curScore = suggestions.get(pos).mScore;
- if (curScore < score
- || (curScore == score && length < suggestions.get(pos).codePointCount())) {
- break;
- }
- pos++;
+ // Check the last one's score and bail
+ if (suggestions.size() >= prefMaxSuggestions
+ && suggestions.get(prefMaxSuggestions - 1).mScore >= score) return true;
+ final int length = wordInfo.mCodePointCount;
+ while (pos < suggestions.size()) {
+ final int curScore = suggestions.get(pos).mScore;
+ if (curScore < score
+ || (curScore == score && length < suggestions.get(pos).mCodePointCount)) {
+ break;
}
+ pos++;
}
if (pos >= prefMaxSuggestions) {
return true;
}
+ final SuggestedWordInfo transformedWordInfo = getTransformedSuggestedWordInfo(wordInfo,
+ mLocale, mIsAllUpperCase, mIsFirstCharCapitalized, mTrailingSingleQuotesCount);
+ suggestions.add(pos, transformedWordInfo);
+ if (suggestions.size() > prefMaxSuggestions) {
+ suggestions.remove(prefMaxSuggestions);
+ }
+ LatinImeLogger.onAddSuggestedWord(transformedWordInfo.mWord.toString(), dicTypeId,
+ dataTypeForLog);
+ return true;
+ }
+
+ private static SuggestedWordInfo getTransformedSuggestedWordInfo(
+ final SuggestedWordInfo wordInfo, final Locale locale, final boolean isAllUpperCase,
+ final boolean isFirstCharCapitalized, final int trailingSingleQuotesCount) {
+ if (!isFirstCharCapitalized && !isAllUpperCase && 0 == trailingSingleQuotesCount) {
+ return wordInfo;
+ }
final StringBuilder sb = new StringBuilder(getApproxMaxWordLength());
- // TODO: Must pay attention to locale when changing case.
- if (mIsAllUpperCase) {
- sb.append(new String(word, offset, length).toUpperCase());
- } else if (mIsFirstCharCapitalized) {
- sb.append(Character.toUpperCase(word[offset]));
- if (length > 1) {
- sb.append(word, offset + 1, length - 1);
- }
+ if (isAllUpperCase) {
+ sb.append(wordInfo.mWord.toString().toUpperCase(locale));
+ } else if (isFirstCharCapitalized) {
+ sb.append(StringUtils.toTitleCase(wordInfo.mWord.toString(), locale));
} else {
- sb.append(word, offset, length);
+ sb.append(wordInfo.mWord);
}
- for (int i = mTrailingSingleQuotesCount - 1; i >= 0; --i) {
+ for (int i = trailingSingleQuotesCount - 1; i >= 0; --i) {
sb.appendCodePoint(Keyboard.CODE_SINGLE_QUOTE);
}
- // TODO: figure out what type of suggestion this is
- suggestions.add(pos, new SuggestedWordInfo(sb, score, SuggestedWordInfo.KIND_CORRECTION));
- if (suggestions.size() > prefMaxSuggestions) {
- suggestions.remove(prefMaxSuggestions);
- } else {
- LatinImeLogger.onAddSuggestedWord(sb.toString(), dicTypeId, dataTypeForLog);
- }
- return true;
+ return new SuggestedWordInfo(sb, wordInfo.mScore, wordInfo.mKind);
}
public void close() {
final HashSet<Dictionary> dictionaries = new HashSet<Dictionary>();
- dictionaries.addAll(mUnigramDictionaries.values());
- dictionaries.addAll(mBigramDictionaries.values());
+ dictionaries.addAll(mDictionaries.values());
for (final Dictionary dictionary : dictionaries) {
dictionary.close();
}