/* * 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 com.android.internal.util.XmlUtils; import android.view.View; 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; } }