diff options
author | 2010-03-09 12:46:57 -0800 | |
---|---|---|
committer | 2010-03-09 15:01:09 -0800 | |
commit | 07b1603a3f9611f6d15dd7fcedf883d6ef8e5817 (patch) | |
tree | 3a88daaee27b886909a5af8a646b41dfb794a9f8 /java/src/com/android/inputmethod/latin/Suggest.java | |
parent | 81c52293f84ce475ac6b1661f4a4b92703405247 (diff) | |
download | latinime-07b1603a3f9611f6d15dd7fcedf883d6ef8e5817.tar.gz latinime-07b1603a3f9611f6d15dd7fcedf883d6ef8e5817.tar.xz latinime-07b1603a3f9611f6d15dd7fcedf883d6ef8e5817.zip |
Don't let the native code target be included twice when unbundling.
Move java code to a different directory so that the unbundled
version doesn't try to compile the native code again.
Change-Id: I05cf9e643824ddc448821f69805ccb0240c5b986
Diffstat (limited to 'java/src/com/android/inputmethod/latin/Suggest.java')
-rwxr-xr-x | java/src/com/android/inputmethod/latin/Suggest.java | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/java/src/com/android/inputmethod/latin/Suggest.java b/java/src/com/android/inputmethod/latin/Suggest.java new file mode 100755 index 000000000..712b9cf37 --- /dev/null +++ b/java/src/com/android/inputmethod/latin/Suggest.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2008-2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.android.inputmethod.latin; + +import android.content.Context; +import android.text.AutoText; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.android.inputmethod.latin.WordComposer; + +/** + * This class loads a dictionary and provides a list of suggestions for a given sequence of + * characters. This includes corrections and completions. + * @hide pending API Council Approval + */ +public class Suggest implements Dictionary.WordCallback { + + public static final int CORRECTION_NONE = 0; + public static final int CORRECTION_BASIC = 1; + public static final int CORRECTION_FULL = 2; + + private static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000; + + private BinaryDictionary mMainDict; + + private Dictionary mUserDictionary; + + private Dictionary mAutoDictionary; + + private Dictionary mContactsDictionary; + + private int mPrefMaxSuggestions = 12; + + private boolean mAutoTextEnabled; + + private int[] mPriorities = new int[mPrefMaxSuggestions]; + // Handle predictive correction for only the first 1280 characters for performance reasons + // If we support scripts that need latin characters beyond that, we should probably use some + // kind of a sparse array or language specific list with a mapping lookup table. + // 1280 is the size of the BASE_CHARS array in ExpandableDictionary, which is a basic set of + // latin characters. + private int[] mNextLettersFrequencies = new int[1280]; + private ArrayList<CharSequence> mSuggestions = new ArrayList<CharSequence>(); + private ArrayList<CharSequence> mStringPool = new ArrayList<CharSequence>(); + private boolean mHaveCorrection; + private CharSequence mOriginalWord; + private String mLowerOriginalWord; + private boolean mCapitalize; + + private int mCorrectionMode = CORRECTION_BASIC; + + + public Suggest(Context context, int dictionaryResId) { + mMainDict = new BinaryDictionary(context, dictionaryResId); + for (int i = 0; i < mPrefMaxSuggestions; i++) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + public void setAutoTextEnabled(boolean enabled) { + mAutoTextEnabled = enabled; + } + + public int getCorrectionMode() { + return mCorrectionMode; + } + + public void setCorrectionMode(int mode) { + mCorrectionMode = mode; + } + + public boolean hasMainDictionary() { + return mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD; + } + + /** + * Sets an optional user dictionary resource to be loaded. The user dictionary is consulted + * before the main dictionary, if set. + */ + public void setUserDictionary(Dictionary userDictionary) { + mUserDictionary = userDictionary; + } + + /** + * Sets an optional contacts dictionary resource to be loaded. + */ + public void setContactsDictionary(Dictionary userDictionary) { + mContactsDictionary = userDictionary; + } + + public void setAutoDictionary(Dictionary autoDictionary) { + mAutoDictionary = autoDictionary; + } + + /** + * Number of suggestions to generate from the input key sequence. This has + * to be a number between 1 and 100 (inclusive). + * @param maxSuggestions + * @throws IllegalArgumentException if the number is out of range + */ + public void setMaxSuggestions(int maxSuggestions) { + if (maxSuggestions < 1 || maxSuggestions > 100) { + throw new IllegalArgumentException("maxSuggestions must be between 1 and 100"); + } + mPrefMaxSuggestions = maxSuggestions; + mPriorities = new int[mPrefMaxSuggestions]; + collectGarbage(); + while (mStringPool.size() < mPrefMaxSuggestions) { + StringBuilder sb = new StringBuilder(32); + mStringPool.add(sb); + } + } + + private boolean haveSufficientCommonality(String original, CharSequence suggestion) { + final int originalLength = original.length(); + final int suggestionLength = suggestion.length(); + final int minLength = Math.min(originalLength, suggestionLength); + if (minLength <= 2) return true; + int matching = 0; + int lessMatching = 0; // Count matches if we skip one character + int i; + for (i = 0; i < minLength; i++) { + final char origChar = ExpandableDictionary.toLowerCase(original.charAt(i)); + if (origChar == ExpandableDictionary.toLowerCase(suggestion.charAt(i))) { + matching++; + lessMatching++; + } else if (i + 1 < suggestionLength + && origChar == ExpandableDictionary.toLowerCase(suggestion.charAt(i + 1))) { + lessMatching++; + } + } + matching = Math.max(matching, lessMatching); + + if (minLength <= 4) { + return matching >= 2; + } else { + return matching > minLength / 2; + } + } + + /** + * Returns a list of words that match the list of character codes passed in. + * This list will be overwritten the next time this function is called. + * @param a view for retrieving the context for AutoText + * @param codes the list of codes. Each list item contains an array of character codes + * in order of probability where the character at index 0 in the array has the highest + * probability. + * @return list of suggestions. + */ + public List<CharSequence> getSuggestions(View view, WordComposer wordComposer, + boolean includeTypedWordIfValid) { + mHaveCorrection = false; + mCapitalize = wordComposer.isCapitalized(); + collectGarbage(); + Arrays.fill(mPriorities, 0); + Arrays.fill(mNextLettersFrequencies, 0); + + // Save a lowercase version of the original word + mOriginalWord = wordComposer.getTypedWord(); + if (mOriginalWord != null) { + mOriginalWord = mOriginalWord.toString(); + mLowerOriginalWord = mOriginalWord.toString().toLowerCase(); + } else { + mLowerOriginalWord = ""; + } + // Search the dictionary only if there are at least 2 characters + if (wordComposer.size() > 1) { + if (mUserDictionary != null || mContactsDictionary != null) { + if (mUserDictionary != null) { + mUserDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + } + if (mContactsDictionary != null) { + mContactsDictionary.getWords(wordComposer, this, mNextLettersFrequencies); + } + + if (mSuggestions.size() > 0 && isValidWord(mOriginalWord) + && mCorrectionMode == CORRECTION_FULL) { + mHaveCorrection = true; + } + } + mMainDict.getWords(wordComposer, this, mNextLettersFrequencies); + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 0) { + mHaveCorrection = true; + } + } + if (mOriginalWord != null) { + mSuggestions.add(0, mOriginalWord.toString()); + } + + // Check if the first suggestion has a minimum number of characters in common + if (mCorrectionMode == CORRECTION_FULL && mSuggestions.size() > 1) { + if (!haveSufficientCommonality(mLowerOriginalWord, mSuggestions.get(1))) { + mHaveCorrection = false; + } + } + + if (mAutoTextEnabled) { + int i = 0; + int max = 6; + // Don't autotext the suggestions from the dictionaries + if (mCorrectionMode == CORRECTION_BASIC) max = 1; + while (i < mSuggestions.size() && i < max) { + String suggestedWord = mSuggestions.get(i).toString().toLowerCase(); + CharSequence autoText = + AutoText.get(suggestedWord, 0, suggestedWord.length(), view); + // Is there an AutoText correction? + boolean canAdd = autoText != null; + // Is that correction already the current prediction (or original word)? + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i)); + // Is that correction already the next predicted word? + if (canAdd && i + 1 < mSuggestions.size() && mCorrectionMode != CORRECTION_BASIC) { + canAdd &= !TextUtils.equals(autoText, mSuggestions.get(i + 1)); + } + if (canAdd) { + mHaveCorrection = true; + mSuggestions.add(i + 1, autoText); + i++; + } + i++; + } + } + + removeDupes(); + return mSuggestions; + } + + public int[] getNextLettersFrequencies() { + return mNextLettersFrequencies; + } + + private void removeDupes() { + final ArrayList<CharSequence> suggestions = mSuggestions; + if (suggestions.size() < 2) return; + int i = 1; + // Don't cache suggestions.size(), since we may be removing items + while (i < suggestions.size()) { + final CharSequence cur = suggestions.get(i); + // Compare each candidate with each previous candidate + for (int j = 0; j < i; j++) { + CharSequence previous = suggestions.get(j); + if (TextUtils.equals(cur, previous)) { + removeFromSuggestions(i); + i--; + break; + } + } + i++; + } + } + + private void removeFromSuggestions(int index) { + CharSequence garbage = mSuggestions.remove(index); + if (garbage != null && garbage instanceof StringBuilder) { + mStringPool.add(garbage); + } + } + + public boolean hasMinimalCorrection() { + return mHaveCorrection; + } + + private boolean compareCaseInsensitive(final String mLowerOriginalWord, + final char[] word, final int offset, final int length) { + final int originalLength = mLowerOriginalWord.length(); + if (originalLength == length && Character.isUpperCase(word[offset])) { + for (int i = 0; i < originalLength; i++) { + if (mLowerOriginalWord.charAt(i) != Character.toLowerCase(word[offset+i])) { + return false; + } + } + return true; + } + return false; + } + + public boolean addWord(final char[] word, final int offset, final int length, final int freq) { + int pos = 0; + final int[] priorities = mPriorities; + final int prefMaxSuggestions = mPrefMaxSuggestions; + // Check if it's the same word, only caps are different + if (compareCaseInsensitive(mLowerOriginalWord, word, offset, length)) { + pos = 0; + } else { + // Check the last one's priority and bail + if (priorities[prefMaxSuggestions - 1] >= freq) return true; + while (pos < prefMaxSuggestions) { + if (priorities[pos] < freq + || (priorities[pos] == freq && length < mSuggestions + .get(pos).length())) { + break; + } + pos++; + } + } + + if (pos >= prefMaxSuggestions) { + return true; + } + System.arraycopy(priorities, pos, priorities, pos + 1, + prefMaxSuggestions - pos - 1); + priorities[pos] = freq; + int poolSize = mStringPool.size(); + StringBuilder sb = poolSize > 0 ? (StringBuilder) mStringPool.remove(poolSize - 1) + : new StringBuilder(32); + sb.setLength(0); + if (mCapitalize) { + sb.append(Character.toUpperCase(word[offset])); + if (length > 1) { + sb.append(word, offset + 1, length - 1); + } + } else { + sb.append(word, offset, length); + } + mSuggestions.add(pos, sb); + if (mSuggestions.size() > prefMaxSuggestions) { + CharSequence garbage = mSuggestions.remove(prefMaxSuggestions); + if (garbage instanceof StringBuilder) { + mStringPool.add(garbage); + } + } + return true; + } + + public boolean isValidWord(final CharSequence word) { + if (word == null || word.length() == 0) { + return false; + } + return (mCorrectionMode == CORRECTION_FULL && mMainDict.isValidWord(word)) + || (mCorrectionMode > CORRECTION_NONE && + ((mUserDictionary != null && mUserDictionary.isValidWord(word))) + || (mAutoDictionary != null && mAutoDictionary.isValidWord(word)) + || (mContactsDictionary != null && mContactsDictionary.isValidWord(word))); + } + + private void collectGarbage() { + int poolSize = mStringPool.size(); + int garbageSize = mSuggestions.size(); + while (poolSize < mPrefMaxSuggestions && garbageSize > 0) { + CharSequence garbage = mSuggestions.get(garbageSize - 1); + if (garbage != null && garbage instanceof StringBuilder) { + mStringPool.add(garbage); + poolSize++; + } + garbageSize--; + } + if (poolSize == mPrefMaxSuggestions + 1) { + Log.w("Suggest", "String pool got too big: " + poolSize); + } + mSuggestions.clear(); + } + + public void close() { + if (mMainDict != null) { + mMainDict.close(); + } + } +} |