| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * 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 android.text; |
| |
| import android.content.res.Resources; |
| import android.content.res.XmlResourceParser; |
| import android.view.View; |
| |
| import com.android.internal.util.XmlUtils; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.io.IOException; |
| import java.util.Locale; |
| |
| /** |
| * This class accesses a dictionary of corrections to frequent misspellings. |
| */ |
| public class AutoText { |
| // struct trie { |
| // char c; |
| // int off; |
| // struct trie *child; |
| // struct trie *next; |
| // }; |
| |
| private static final int TRIE_C = 0; |
| private static final int TRIE_OFF = 1; |
| private static final int TRIE_CHILD = 2; |
| private static final int TRIE_NEXT = 3; |
| |
| private static final int TRIE_SIZEOF = 4; |
| private static final char TRIE_NULL = (char) -1; |
| private static final int TRIE_ROOT = 0; |
| |
| private static final int INCREMENT = 1024; |
| |
| private static final int DEFAULT = 14337; // Size of the Trie 13 Aug 2007 |
| |
| private static final int RIGHT = 9300; // Size of 'right' 13 Aug 2007 |
| |
| private static AutoText sInstance = new AutoText(Resources.getSystem()); |
| private static Object sLock = new Object(); |
| |
| // TODO: |
| // |
| // Note the assumption that the destination strings total less than |
| // 64K characters and that the trie for the source side totals less |
| // than 64K chars/offsets/child pointers/next pointers. |
| // |
| // This seems very safe for English (currently 7K of destination, |
| // 14K of trie) but may need to be revisited. |
| |
| private char[] mTrie; |
| private char mTrieUsed; |
| private String mText; |
| private Locale mLocale; |
| private int mSize; |
| |
| private AutoText(Resources resources) { |
| mLocale = resources.getConfiguration().locale; |
| init(resources); |
| } |
| |
| /** |
| * Returns the instance of AutoText. If the locale has changed, it will create a new |
| * instance of AutoText for the locale. |
| * @param view to get the resources from |
| * @return the single instance of AutoText |
| */ |
| private static AutoText getInstance(View view) { |
| Resources res = view.getContext().getResources(); |
| Locale locale = res.getConfiguration().locale; |
| AutoText instance; |
| |
| synchronized (sLock) { |
| instance = sInstance; |
| |
| if (!locale.equals(instance.mLocale)) { |
| instance = new AutoText(res); |
| sInstance = instance; |
| } |
| } |
| |
| return instance; |
| } |
| |
| /** |
| * Retrieves a possible spelling correction for the specified range |
| * of text. Returns null if no correction can be found. |
| * The View is used to get the current Locale and Resources. |
| */ |
| public static String get(CharSequence src, final int start, final int end, |
| View view) { |
| return getInstance(view).lookup(src, start, end); |
| } |
| |
| /** |
| * Returns the size of the auto text dictionary. The return value can be zero if there is |
| * no auto correction data available for the current locale. |
| * @param view used to retrieve the current Locale and Resources. |
| * @return the number of entries in the auto text dictionary |
| */ |
| public static int getSize(View view) { |
| |
| return getInstance(view).getSize(); |
| } |
| |
| /** |
| * Returns the size of the dictionary. |
| */ |
| private int getSize() { |
| return mSize; |
| } |
| |
| private String lookup(CharSequence src, final int start, final int end) { |
| int here = mTrie[TRIE_ROOT]; |
| |
| for (int i = start; i < end; i++) { |
| char c = src.charAt(i); |
| |
| for (; here != TRIE_NULL; here = mTrie[here + TRIE_NEXT]) { |
| if (c == mTrie[here + TRIE_C]) { |
| if ((i == end - 1) |
| && (mTrie[here + TRIE_OFF] != TRIE_NULL)) { |
| int off = mTrie[here + TRIE_OFF]; |
| int len = mText.charAt(off); |
| |
| return mText.substring(off + 1, off + 1 + len); |
| } |
| |
| here = mTrie[here + TRIE_CHILD]; |
| break; |
| } |
| } |
| |
| if (here == TRIE_NULL) { |
| return null; |
| } |
| } |
| |
| return null; |
| } |
| |
| private void init(Resources r) { |
| XmlResourceParser parser = r.getXml(com.android.internal.R.xml.autotext); |
| |
| StringBuilder right = new StringBuilder(RIGHT); |
| mTrie = new char[DEFAULT]; |
| mTrie[TRIE_ROOT] = TRIE_NULL; |
| mTrieUsed = TRIE_ROOT + 1; |
| |
| try { |
| XmlUtils.beginDocument(parser, "words"); |
| String odest = ""; |
| char ooff = 0; |
| |
| while (true) { |
| XmlUtils.nextElement(parser); |
| |
| String element = parser.getName(); |
| if (element == null || !(element.equals("word"))) { |
| break; |
| } |
| |
| String src = parser.getAttributeValue(null, "src"); |
| if (parser.next() == XmlPullParser.TEXT) { |
| String dest = parser.getText(); |
| char off; |
| |
| if (dest.equals(odest)) { |
| off = ooff; |
| } else { |
| off = (char) right.length(); |
| right.append((char) dest.length()); |
| right.append(dest); |
| } |
| |
| add(src, off); |
| } |
| } |
| |
| // Don't let Resources cache a copy of all these strings. |
| r.flushLayoutCache(); |
| } catch (XmlPullParserException e) { |
| throw new RuntimeException(e); |
| } catch (IOException e) { |
| throw new RuntimeException(e); |
| } finally { |
| parser.close(); |
| } |
| |
| mText = right.toString(); |
| } |
| |
| private void add(String src, char off) { |
| int slen = src.length(); |
| int herep = TRIE_ROOT; |
| // Keep track of the size of the dictionary |
| mSize++; |
| |
| for (int i = 0; i < slen; i++) { |
| char c = src.charAt(i); |
| boolean found = false; |
| |
| for (; mTrie[herep] != TRIE_NULL; |
| herep = mTrie[herep] + TRIE_NEXT) { |
| if (c == mTrie[mTrie[herep] + TRIE_C]) { |
| // There is a node for this letter, and this is the |
| // end, so fill in the right hand side fields. |
| |
| if (i == slen - 1) { |
| mTrie[mTrie[herep] + TRIE_OFF] = off; |
| return; |
| } |
| |
| // There is a node for this letter, and we need |
| // to go deeper into it to fill in the rest. |
| |
| herep = mTrie[herep] + TRIE_CHILD; |
| found = true; |
| break; |
| } |
| } |
| |
| if (!found) { |
| // No node for this letter yet. Make one. |
| |
| char node = newTrieNode(); |
| mTrie[herep] = node; |
| |
| mTrie[mTrie[herep] + TRIE_C] = c; |
| mTrie[mTrie[herep] + TRIE_OFF] = TRIE_NULL; |
| mTrie[mTrie[herep] + TRIE_NEXT] = TRIE_NULL; |
| mTrie[mTrie[herep] + TRIE_CHILD] = TRIE_NULL; |
| |
| // If this is the end of the word, fill in the offset. |
| |
| if (i == slen - 1) { |
| mTrie[mTrie[herep] + TRIE_OFF] = off; |
| return; |
| } |
| |
| // Otherwise, step in deeper and go to the next letter. |
| |
| herep = mTrie[herep] + TRIE_CHILD; |
| } |
| } |
| } |
| |
| private char newTrieNode() { |
| if (mTrieUsed + TRIE_SIZEOF > mTrie.length) { |
| char[] copy = new char[mTrie.length + INCREMENT]; |
| System.arraycopy(mTrie, 0, copy, 0, mTrie.length); |
| mTrie = copy; |
| } |
| |
| char ret = mTrieUsed; |
| mTrieUsed += TRIE_SIZEOF; |
| |
| return ret; |
| } |
| } |