diff options
Diffstat (limited to 'java/src')
11 files changed, 258 insertions, 80 deletions
diff --git a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java index 25afef1e6..a0f48d24c 100644 --- a/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java +++ b/java/src/com/android/inputmethod/compat/SuggestionSpanUtils.java @@ -108,6 +108,7 @@ public class SuggestionSpanUtils { if (!dictionaryAvailable || TextUtils.isEmpty(pickedWord) || CONSTRUCTOR_SuggestionSpan == null || suggestedWords == null || suggestedWords.size() == 0 + || suggestedWords.mIsPrediction || suggestedWords.mIsPunctuationSuggestions || OBJ_SUGGESTIONS_MAX_SIZE == null) { return pickedWord; } diff --git a/java/src/com/android/inputmethod/latin/BinaryDictionary.java b/java/src/com/android/inputmethod/latin/BinaryDictionary.java index a644ec0d9..cc20f4294 100644 --- a/java/src/com/android/inputmethod/latin/BinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/BinaryDictionary.java @@ -17,6 +17,7 @@ package com.android.inputmethod.latin; import android.content.Context; +import android.text.TextUtils; import com.android.inputmethod.keyboard.ProximityInfo; @@ -84,6 +85,7 @@ public class BinaryDictionary extends Dictionary { int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int maxWords); private native void closeNative(long dict); private native boolean isValidWordNative(long dict, int[] word, int wordLength); + private native boolean isValidBigramNative(long dict, int[] word1, int[] word2); private native int getSuggestionsNative(long dict, long proximityInfo, int[] xCoordinates, int[] yCoordinates, int[] inputCodes, int codesSize, int[] prevWordForBigrams, boolean useFullEditDistance, char[] outputChars, int[] scores); @@ -204,6 +206,15 @@ public class BinaryDictionary extends Dictionary { return isValidWordNative(mNativeDict, chars, chars.length); } + // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni + // calls when checking for changes in an entire dictionary. + public boolean isValidBigram(CharSequence word1, CharSequence word2) { + if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false; + int[] chars1 = StringUtils.toCodePointArray(word1.toString()); + int[] chars2 = StringUtils.toCodePointArray(word2.toString()); + return isValidBigramNative(mNativeDict, chars1, chars2); + } + @Override public synchronized void close() { closeInternal(); diff --git a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java index 65f97e987..22787c218 100644 --- a/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ContactsBinaryDictionary.java @@ -18,6 +18,7 @@ import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.database.Cursor; +import android.os.SystemClock; import android.provider.BaseColumns; import android.provider.ContactsContract.Contacts; import android.text.TextUtils; @@ -30,18 +31,27 @@ import java.util.Locale; public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { private static final String[] PROJECTION = {BaseColumns._ID, Contacts.DISPLAY_NAME,}; + private static final String[] PROJECTION_ID_ONLY = {BaseColumns._ID}; private static final String TAG = ContactsBinaryDictionary.class.getSimpleName(); private static final String NAME = "contacts"; + private static boolean DEBUG = false; + /** * Frequency for contacts information into the dictionary */ private static final int FREQUENCY_FOR_CONTACTS = 40; private static final int FREQUENCY_FOR_CONTACTS_BIGRAM = 90; + /** The maximum number of contacts that this dictionary supports. */ + private static final int MAX_CONTACT_COUNT = 10000; + private static final int INDEX_NAME = 1; + /** The number of contacts in the most recent dictionary rebuild. */ + static private int sContactCountAtLastRebuild = 0; + private ContentObserver mObserver; /** @@ -98,6 +108,7 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { if (cursor != null) { try { if (cursor.moveToFirst()) { + sContactCountAtLastRebuild = getContactCount(); addWords(cursor); } } finally { @@ -125,15 +136,28 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { private void addWords(Cursor cursor) { clearFusionDictionary(); - while (!cursor.isAfterLast()) { + int count = 0; + while (!cursor.isAfterLast() && count < MAX_CONTACT_COUNT) { String name = cursor.getString(INDEX_NAME); - if (name != null && -1 == name.indexOf('@')) { + if (isValidName(name)) { addName(name); + ++count; } cursor.moveToNext(); } } + private int getContactCount() { + // TODO: consider switching to a rawQuery("select count(*)...") on the database if + // performance is a bottleneck. + final Cursor cursor = mContext.getContentResolver().query( + Contacts.CONTENT_URI, PROJECTION_ID_ONLY, null, null, null); + if (cursor != null) { + return cursor.getCount(); + } + return 0; + } + /** * Adds the words in a name (e.g., firstname/lastname) to the binary dictionary along with their * bigrams depending on locale. @@ -144,16 +168,9 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { // TODO: Better tokenization for non-Latin writing systems for (int i = 0; i < len; i++) { if (Character.isLetter(name.codePointAt(i))) { - int j; - for (j = i + 1; j < len; j++) { - final int codePoint = name.codePointAt(j); - if (!(codePoint == Keyboard.CODE_DASH || codePoint == Keyboard.CODE_SINGLE_QUOTE - || Character.isLetter(codePoint))) { - break; - } - } - String word = name.substring(i, j); - i = j - 1; + int end = getWordEndPosition(name, len, i); + String word = name.substring(i, end); + i = end - 1; // Don't add single letter words, possibly confuses // capitalization of i. final int wordLen = word.codePointCount(0, word.length()); @@ -169,4 +186,100 @@ public class ContactsBinaryDictionary extends ExpandableBinaryDictionary { } } } + + /** + * Returns the index of the last letter in the word, starting from position startIndex. + */ + private static int getWordEndPosition(String string, int len, int startIndex) { + int end; + int cp = 0; + for (end = startIndex + 1; end < len; end += Character.charCount(cp)) { + cp = string.codePointAt(end); + if (!(cp == Keyboard.CODE_DASH || cp == Keyboard.CODE_SINGLE_QUOTE + || Character.isLetter(cp))) { + break; + } + } + return end; + } + + @Override + protected boolean hasContentChanged() { + final long startTime = SystemClock.uptimeMillis(); + final int contactCount = getContactCount(); + if (contactCount > MAX_CONTACT_COUNT) { + // If there are too many contacts then return false. In this rare case it is impossible + // to include all of them anyways and the cost of rebuilding the dictionary is too high. + // TODO: Sort and check only the MAX_CONTACT_COUNT most recent contacts? + return false; + } + if (contactCount != sContactCountAtLastRebuild) { + return true; + } + // Check all contacts since it's not possible to find out which names have changed. + // This is needed because it's possible to receive extraneous onChange events even when no + // name has changed. + Cursor cursor = mContext.getContentResolver().query( + Contacts.CONTENT_URI, PROJECTION, null, null, null); + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + while (!cursor.isAfterLast()) { + String name = cursor.getString(INDEX_NAME); + if (isValidName(name) && !isNameInDictionary(name)) { + if (DEBUG) { + Log.d(TAG, "Contact name missing: " + name + " (runtime = " + + (SystemClock.uptimeMillis() - startTime) + " ms)"); + } + return true; + } + cursor.moveToNext(); + } + } + } finally { + cursor.close(); + } + } + if (DEBUG) { + Log.d(TAG, "No contacts changed. (runtime = " + (SystemClock.uptimeMillis() - startTime) + + " ms)"); + } + return false; + } + + private static boolean isValidName(String name) { + if (name != null && -1 == name.indexOf('@')) { + return true; + } + return false; + } + + /** + * Checks if the words in a name are in the current binary dictionary. + */ + private boolean isNameInDictionary(String name) { + int len = name.codePointCount(0, name.length()); + String prevWord = null; + for (int i = 0; i < len; i++) { + if (Character.isLetter(name.codePointAt(i))) { + int end = getWordEndPosition(name, len, i); + String word = name.substring(i, end); + i = end - 1; + final int wordLen = word.codePointCount(0, word.length()); + if (wordLen < MAX_WORD_LENGTH && wordLen > 1) { + if (!TextUtils.isEmpty(prevWord) && mUseFirstLastBigrams) { + if (!super.isValidBigramLocked(prevWord, word)) { + return false; + } + } else { + if (!super.isValidWordLocked(word)) { + return false; + } + } + prevWord = word; + } + } + } + return true; + } } diff --git a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java index 3d89226c0..22d8f24f1 100644 --- a/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java +++ b/java/src/com/android/inputmethod/latin/ExpandableBinaryDictionary.java @@ -96,6 +96,13 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected abstract void loadDictionaryAsync(); /** + * Indicates that the source dictionary content has changed and a rebuild of the binary file is + * required. If it returns false, the next reload will only read the current binary dictionary + * from file. Note that the shared binary dictionary is locked when this is called. + */ + protected abstract boolean hasContentChanged(); + + /** * Gets the shared dictionary controller for the given filename. */ private static synchronized DictionaryController getSharedDictionaryController( @@ -148,8 +155,9 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { * the native side. */ public void clearFusionDictionary() { - mFusionDictionary = new FusionDictionary(new Node(), new FusionDictionary.DictionaryOptions( - new HashMap<String, String>(), false, false)); + mFusionDictionary = new FusionDictionary(new Node(), + new FusionDictionary.DictionaryOptions(new HashMap<String, String>(), false, + false)); } /** @@ -224,9 +232,7 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { protected boolean isValidWordInner(final CharSequence word) { if (mLocalDictionaryController.tryLock()) { try { - if (mBinaryDictionary != null) { - return mBinaryDictionary.isValidWord(word); - } + return isValidWordLocked(word); } finally { mLocalDictionaryController.unlock(); } @@ -234,6 +240,32 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { return false; } + protected boolean isValidWordLocked(final CharSequence word) { + if (mBinaryDictionary == null) return false; + return mBinaryDictionary.isValidWord(word); + } + + protected boolean isValidBigram(final CharSequence word1, final CharSequence word2) { + if (mBinaryDictionary == null) return false; + return mBinaryDictionary.isValidBigram(word1, word2); + } + + protected boolean isValidBigramInner(final CharSequence word1, final CharSequence word2) { + if (mLocalDictionaryController.tryLock()) { + try { + return isValidBigramLocked(word1, word2); + } finally { + mLocalDictionaryController.unlock(); + } + } + return false; + } + + protected boolean isValidBigramLocked(final CharSequence word1, final CharSequence word2) { + if (mBinaryDictionary == null) return false; + return mBinaryDictionary.isValidBigram(word1, word2); + } + /** * Load the current binary dictionary from internal storage in a background thread. If no binary * dictionary exists, this method will generate one. @@ -315,12 +347,16 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { } /** - * Sets whether or not the dictionary is out of date and requires a reload. + * Marks that the dictionary is out of date and requires a reload. + * + * @param requiresRebuild Indicates that the source dictionary content has changed and a rebuild + * of the binary file is required. If not true, the next reload process will only read + * the current binary dictionary from file. */ - protected void setRequiresReload(final boolean reload) { - final long time = reload ? SystemClock.uptimeMillis() : 0; - mSharedDictionaryController.mLastUpdateRequestTime = time; + protected void setRequiresReload(final boolean requiresRebuild) { + final long time = SystemClock.uptimeMillis(); mLocalDictionaryController.mLastUpdateRequestTime = time; + mSharedDictionaryController.mLastUpdateRequestTime = time; if (DEBUG) { Log.d(TAG, "Reload request: request=" + time + " update=" + mSharedDictionaryController.mLastUpdateTime); @@ -351,21 +387,30 @@ abstract public class ExpandableBinaryDictionary extends Dictionary { if (mSharedDictionaryController.isOutOfDate() || !dictionaryFileExists()) { // If the shared dictionary file does not exist or is out of date, the first // instance that acquires the lock will generate a new one. - mSharedDictionaryController.mLastUpdateTime = time; - mLocalDictionaryController.mLastUpdateTime = time; - generateBinaryDictionary(); - loadBinaryDictionary(); - } else if (mLocalDictionaryController.isOutOfDate()) { - // Otherwise, if only the local dictionary for this instance is out of date, load - // the shared dictionary from file. - mLocalDictionaryController.mLastUpdateTime = time; + if (hasContentChanged()) { + // If the source content has changed, rebuild the binary dictionary. + mSharedDictionaryController.mLastUpdateTime = time; + generateBinaryDictionary(); + loadBinaryDictionary(); + } else { + // If not, the reload request was unnecessary so revert LastUpdateRequestTime + // to LastUpdateTime. + mSharedDictionaryController.mLastUpdateRequestTime = + mSharedDictionaryController.mLastUpdateTime; + } + } else if (mBinaryDictionary == null || mLocalDictionaryController.mLastUpdateTime + < mSharedDictionaryController.mLastUpdateTime) { + // Otherwise, if the local dictionary is older than the shared dictionary, load the + // shared dictionary. loadBinaryDictionary(); } + mLocalDictionaryController.mLastUpdateTime = time; } finally { mSharedDictionaryController.unlock(); } } + // TODO: cache the file's existence so that we avoid doing a disk access each time. private boolean dictionaryFileExists() { final File file = new File(mContext.getFilesDir(), mFilename); return file.exists(); diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java index 011b512e8..fb119da02 100644 --- a/java/src/com/android/inputmethod/latin/LatinIME.java +++ b/java/src/com/android/inputmethod/latin/LatinIME.java @@ -877,7 +877,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* hasAutoCorrectionCandidate */, false /* allowsToBeAutoCorrected */, false /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */); + false /* isObsoleteSuggestions */, + false /* isPrediction */); // When in fullscreen mode, show completions generated by the application final boolean isAutoCorrection = false; setSuggestions(suggestedWords, isAutoCorrection); @@ -1772,7 +1773,8 @@ public class LatinIME extends InputMethodService implements KeyboardActionListen false /* hasAutoCorrectionCandidate */, false /* allowsToBeAutoCorrected */, false /* isPunctuationSuggestions */, - true /* isObsoleteSuggestions */); + true /* isObsoleteSuggestions */, + false /* isPrediction */); showSuggestions(obsoleteSuggestedWords, typedWord); } } diff --git a/java/src/com/android/inputmethod/latin/SettingsValues.java b/java/src/com/android/inputmethod/latin/SettingsValues.java index 5f9e1bc76..55b896f5a 100644 --- a/java/src/com/android/inputmethod/latin/SettingsValues.java +++ b/java/src/com/android/inputmethod/latin/SettingsValues.java @@ -166,7 +166,8 @@ public class SettingsValues { false /* hasAutoCorrectionCandidate */, false /* allowsToBeAutoCorrected */, true /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */); + false /* isObsoleteSuggestions */, + false /* isPrediction */); } private static String createWordSeparators(final String weakSpaceStrippers, diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java index 112bde6a3..845df81f6 100644 --- a/java/src/com/android/inputmethod/latin/Suggest.java +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -253,13 +253,12 @@ public class Suggest implements Dictionary.WordCallback { SuggestedWordInfo.removeDups(mSuggestions); return new SuggestedWords(mSuggestions, - // TODO: Just assuming the suggestions that came from the bigram prediction are - // valid now. Need to assign a correct value for typedWordValid. - true /* typedWordValid */, + false /* typedWordValid */, false /* hasAutoCorrectionCandidate */, false /* allowsToBeAutoCorrected */, false /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */); + false /* isObsoleteSuggestions */, + true /* isPrediction */); } // TODO: cleanup dictionaries looking up and suggestions building with SuggestedWords.Builder @@ -396,7 +395,8 @@ public class Suggest implements Dictionary.WordCallback { autoCorrectionAvailable /* hasAutoCorrectionCandidate */, allowsToBeAutoCorrected /* allowsToBeAutoCorrected */, false /* isPunctuationSuggestions */, - false /* isObsoleteSuggestions */); + false /* isObsoleteSuggestions */, + false /* isPrediction */); } /** diff --git a/java/src/com/android/inputmethod/latin/SuggestedWords.java b/java/src/com/android/inputmethod/latin/SuggestedWords.java index 91110d888..497fd3bfa 100644 --- a/java/src/com/android/inputmethod/latin/SuggestedWords.java +++ b/java/src/com/android/inputmethod/latin/SuggestedWords.java @@ -25,13 +25,14 @@ import java.util.HashSet; public class SuggestedWords { public static final SuggestedWords EMPTY = new SuggestedWords( - new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false); + new ArrayList<SuggestedWordInfo>(0), false, false, false, false, false, false); public final boolean mTypedWordValid; public final boolean mHasAutoCorrectionCandidate; public final boolean mIsPunctuationSuggestions; public final boolean mAllowsToBeAutoCorrected; public final boolean mIsObsoleteSuggestions; + public final boolean mIsPrediction; private final ArrayList<SuggestedWordInfo> mSuggestedWordInfoList; public SuggestedWords(final ArrayList<SuggestedWordInfo> suggestedWordInfoList, @@ -39,13 +40,15 @@ public class SuggestedWords { final boolean hasAutoCorrectionCandidate, final boolean allowsToBeAutoCorrected, final boolean isPunctuationSuggestions, - final boolean isObsoleteSuggestions) { + final boolean isObsoleteSuggestions, + final boolean isPrediction) { mSuggestedWordInfoList = suggestedWordInfoList; mTypedWordValid = typedWordValid; mHasAutoCorrectionCandidate = hasAutoCorrectionCandidate; mAllowsToBeAutoCorrected = allowsToBeAutoCorrected; mIsPunctuationSuggestions = isPunctuationSuggestions; mIsObsoleteSuggestions = isObsoleteSuggestions; + mIsPrediction = isPrediction; } public int size() { diff --git a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java index 3c818cc56..830fbf07e 100644 --- a/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java +++ b/java/src/com/android/inputmethod/latin/makedict/BinaryDictInputOutput.java @@ -40,6 +40,8 @@ import java.util.TreeMap; */ public class BinaryDictInputOutput { + final static boolean DBG = MakedictLog.DBG; + /* Node layout is as follows: * | addressType xx : mask with MASK_GROUP_ADDRESS_TYPE * 2 bits, 00 = no children : FLAG_GROUP_ADDRESS_TYPE_NOADDRESS @@ -489,10 +491,17 @@ public class BinaryDictInputOutput { // Merging tails can only be done if there are no attributes. Searching for attributes // in LatinIME code depends on a total breadth-first ordering, which merging tails // breaks. If there are no attributes, it should be fine (and reduce the file size) - // to merge tails, and the following step would be necessary. - // If eventually the code runs on Android, searching through the whole array each time - // may be a performance concern. - list.remove(node); + // to merge tails, and removing the node from the list would be necessary. However, + // we don't merge tails because breaking the breadth-first ordering would result in + // extreme overhead at bigram lookup time (it would make the search function O(n) instead + // of the current O(log(n)), where n=number of nodes in the dictionary which is pretty + // high). + // If no nodes are ever merged, we can't have the same node twice in the list, hence + // searching for duplicates in unnecessary. It is also very performance consuming, + // since `list' is an ArrayList so it's an O(n) operation that runs on all nodes, making + // this simple list.remove operation O(n*n) overall. On Android this overhead is very + // high. + // For future reference, the code to remove duplicate is a simple : list.remove(node); list.add(node); final ArrayList<CharGroup> branches = node.mData; final int nodeSize = branches.size(); @@ -708,13 +717,13 @@ public class BinaryDictInputOutput { } } if (null != group.mShortcutTargets) { - if (0 == group.mShortcutTargets.size()) { + if (DBG && 0 == group.mShortcutTargets.size()) { throw new RuntimeException("0-sized shortcut list must be null"); } flags |= FLAG_HAS_SHORTCUT_TARGETS; } if (null != group.mBigrams) { - if (0 == group.mBigrams.size()) { + if (DBG && 0 == group.mBigrams.size()) { throw new RuntimeException("0-sized bigram list must be null"); } flags |= FLAG_HAS_BIGRAMS; @@ -823,7 +832,7 @@ public class BinaryDictInputOutput { + index + " <> " + group.mCachedAddress); groupAddress += GROUP_FLAGS_SIZE + getGroupCharactersSize(group); // Sanity checks. - if (group.mFrequency > MAX_TERMINAL_FREQUENCY) { + if (DBG && group.mFrequency > MAX_TERMINAL_FREQUENCY) { throw new RuntimeException("A node has a frequency > " + MAX_TERMINAL_FREQUENCY + " : " + group.mFrequency); } @@ -1030,7 +1039,7 @@ public class BinaryDictInputOutput { MakedictLog.i("Computing addresses..."); computeAddresses(dict, flatNodes); MakedictLog.i("Checking array..."); - checkFlatNodeArray(flatNodes); + if (DBG) checkFlatNodeArray(flatNodes); // Create a buffer that matches the final dictionary size. final Node lastNode = flatNodes.get(flatNodes.size() - 1); @@ -1044,7 +1053,7 @@ public class BinaryDictInputOutput { dataEndOffset = writePlacedNode(dict, buffer, n); } - showStatistics(flatNodes); + if (DBG) showStatistics(flatNodes); destination.write(buffer, 0, dataEndOffset); diff --git a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java index b08702e47..c467ef7d4 100644 --- a/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java +++ b/java/src/com/android/inputmethod/latin/makedict/FusionDictionary.java @@ -28,6 +28,8 @@ import java.util.LinkedList; */ public class FusionDictionary implements Iterable<Word> { + private static final boolean DBG = MakedictLog.DBG; + /** * A node of the dictionary, containing several CharGroups. * @@ -159,6 +161,7 @@ public class FusionDictionary implements Iterable<Word> { * shortcut list. */ public WeightedString getShortcut(final String word) { + // TODO: Don't do a linear search if (mShortcutTargets != null) { final int size = mShortcutTargets.size(); for (int i = 0; i < size; ++i) { @@ -176,6 +179,7 @@ public class FusionDictionary implements Iterable<Word> { * Returns null if the word is not in the bigrams list. */ public WeightedString getBigram(final String word) { + // TODO: Don't do a linear search if (mBigrams != null) { final int size = mBigrams.size(); for (int i = 0; i < size; ++i) { @@ -265,31 +269,21 @@ public class FusionDictionary implements Iterable<Word> { /** * Helper method to convert a String to an int array. */ - static private int[] getCodePoints(String word) { - final int wordLength = word.length(); - int[] array = new int[word.codePointCount(0, wordLength)]; - for (int i = 0; i < wordLength; i = word.offsetByCodePoints(i, 1)) { - array[i] = word.codePointAt(i); - } - return array; - } - - /** - * Helper method to add all words in a list as 0-frequency entries - * - * These words are added when shortcuts targets or bigrams are not found in the dictionary - * yet. The same words may be added later with an actual frequency - this is handled by - * the private version of add(). - */ - private void addNeutralWords(final ArrayList<WeightedString> words) { - if (null != words) { - for (WeightedString word : words) { - final CharGroup t = findWordInTree(mRoot, word.mWord); - if (null == t) { - add(getCodePoints(word.mWord), 0, null); - } - } - } + static private int[] getCodePoints(final String word) { + // TODO: this is a copy-paste of the contents of StringUtils.toCodePointArray, + // which is not visible from the makedict package. Factor this code. + final char[] characters = word.toCharArray(); + final int length = characters.length; + final int[] codePoints = new int[Character.codePointCount(characters, 0, length)]; + int codePoint = Character.codePointAt(characters, 0); + int dsti = 0; + for (int srci = Character.charCount(codePoint); + srci < length; srci += Character.charCount(codePoint), ++dsti) { + codePoints[dsti] = codePoint; + codePoint = Character.codePointAt(characters, srci); + } + codePoints[dsti] = codePoint; + return codePoints; } /** @@ -339,7 +333,6 @@ public class FusionDictionary implements Iterable<Word> { if (charGroup != null) { final CharGroup charGroup2 = findWordInTree(mRoot, word2); if (charGroup2 == null) { - // TODO: refactor with the identical code in addNeutralWords add(getCodePoints(word2), 0, null); } charGroup.addBigram(word2, frequency); @@ -386,7 +379,7 @@ public class FusionDictionary implements Iterable<Word> { Arrays.copyOfRange(word, charIndex, word.length), shortcutTargets, null /* bigrams */, frequency); currentNode.mData.add(insertionIndex, newGroup); - checkStack(currentNode); + if (DBG) checkStack(currentNode); } else { // There is a word with a common prefix. if (differentCharIndex == currentGroup.mChars.length) { @@ -437,7 +430,7 @@ public class FusionDictionary implements Iterable<Word> { } currentNode.mData.set(nodeIndex, newParent); } - checkStack(currentNode); + if (DBG) checkStack(currentNode); } } } @@ -514,21 +507,21 @@ public class FusionDictionary implements Iterable<Word> { */ public static CharGroup findWordInTree(Node node, final String s) { int index = 0; - final StringBuilder checker = new StringBuilder(); + final StringBuilder checker = DBG ? new StringBuilder() : null; CharGroup currentGroup; do { int indexOfGroup = findIndexOfChar(node, s.codePointAt(index)); if (CHARACTER_NOT_FOUND == indexOfGroup) return null; currentGroup = node.mData.get(indexOfGroup); - checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length)); + if (DBG) checker.append(new String(currentGroup.mChars, 0, currentGroup.mChars.length)); index += currentGroup.mChars.length; if (index < s.length()) { node = currentGroup.mChildren; } } while (null != node && index < s.length()); - if (!s.equals(checker.toString())) return null; + if (DBG && !s.equals(checker.toString())) return null; return currentGroup; } diff --git a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java index 1281c7e3a..3f0cd0796 100644 --- a/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java +++ b/java/src/com/android/inputmethod/latin/makedict/MakedictLog.java @@ -22,7 +22,7 @@ import android.util.Log; * Wrapper to redirect log events to the right output medium. */ public class MakedictLog { - private static final boolean DBG = false; + public static final boolean DBG = false; private static final String TAG = MakedictLog.class.getSimpleName(); public static void d(String message) { |