AutoText.java revision d4a4729c0cac582a2dcec7c8cfb316b81885a0f0
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.text; 18 19import android.content.res.Resources; 20import android.content.res.XmlResourceParser; 21import com.android.common.XmlUtils; 22import android.view.View; 23 24import org.xmlpull.v1.XmlPullParser; 25import org.xmlpull.v1.XmlPullParserException; 26 27import java.io.IOException; 28import java.util.Locale; 29 30/** 31 * This class accesses a dictionary of corrections to frequent misspellings. 32 */ 33public class AutoText { 34 // struct trie { 35 // char c; 36 // int off; 37 // struct trie *child; 38 // struct trie *next; 39 // }; 40 41 private static final int TRIE_C = 0; 42 private static final int TRIE_OFF = 1; 43 private static final int TRIE_CHILD = 2; 44 private static final int TRIE_NEXT = 3; 45 46 private static final int TRIE_SIZEOF = 4; 47 private static final char TRIE_NULL = (char) -1; 48 private static final int TRIE_ROOT = 0; 49 50 private static final int INCREMENT = 1024; 51 52 private static final int DEFAULT = 14337; // Size of the Trie 13 Aug 2007 53 54 private static final int RIGHT = 9300; // Size of 'right' 13 Aug 2007 55 56 private static AutoText sInstance = new AutoText(Resources.getSystem()); 57 private static Object sLock = new Object(); 58 59 // TODO: 60 // 61 // Note the assumption that the destination strings total less than 62 // 64K characters and that the trie for the source side totals less 63 // than 64K chars/offsets/child pointers/next pointers. 64 // 65 // This seems very safe for English (currently 7K of destination, 66 // 14K of trie) but may need to be revisited. 67 68 private char[] mTrie; 69 private char mTrieUsed; 70 private String mText; 71 private Locale mLocale; 72 private int mSize; 73 74 private AutoText(Resources resources) { 75 mLocale = resources.getConfiguration().locale; 76 init(resources); 77 } 78 79 /** 80 * Returns the instance of AutoText. If the locale has changed, it will create a new 81 * instance of AutoText for the locale. 82 * @param view to get the resources from 83 * @return the single instance of AutoText 84 */ 85 private static AutoText getInstance(View view) { 86 Resources res = view.getContext().getResources(); 87 Locale locale = res.getConfiguration().locale; 88 AutoText instance; 89 90 synchronized (sLock) { 91 instance = sInstance; 92 93 if (!locale.equals(instance.mLocale)) { 94 instance = new AutoText(res); 95 sInstance = instance; 96 } 97 } 98 99 return instance; 100 } 101 102 /** 103 * Retrieves a possible spelling correction for the specified range 104 * of text. Returns null if no correction can be found. 105 * The View is used to get the current Locale and Resources. 106 */ 107 public static String get(CharSequence src, final int start, final int end, 108 View view) { 109 return getInstance(view).lookup(src, start, end); 110 } 111 112 /** 113 * Returns the size of the auto text dictionary. The return value can be zero if there is 114 * no auto correction data available for the current locale. 115 * @param view used to retrieve the current Locale and Resources. 116 * @return the number of entries in the auto text dictionary 117 */ 118 public static int getSize(View view) { 119 120 return getInstance(view).getSize(); 121 } 122 123 /** 124 * Returns the size of the dictionary. 125 */ 126 private int getSize() { 127 return mSize; 128 } 129 130 private String lookup(CharSequence src, final int start, final int end) { 131 int here = mTrie[TRIE_ROOT]; 132 133 for (int i = start; i < end; i++) { 134 char c = src.charAt(i); 135 136 for (; here != TRIE_NULL; here = mTrie[here + TRIE_NEXT]) { 137 if (c == mTrie[here + TRIE_C]) { 138 if ((i == end - 1) 139 && (mTrie[here + TRIE_OFF] != TRIE_NULL)) { 140 int off = mTrie[here + TRIE_OFF]; 141 int len = mText.charAt(off); 142 143 return mText.substring(off + 1, off + 1 + len); 144 } 145 146 here = mTrie[here + TRIE_CHILD]; 147 break; 148 } 149 } 150 151 if (here == TRIE_NULL) { 152 return null; 153 } 154 } 155 156 return null; 157 } 158 159 private void init(Resources r) { 160 XmlResourceParser parser = r.getXml(com.android.internal.R.xml.autotext); 161 162 StringBuilder right = new StringBuilder(RIGHT); 163 mTrie = new char[DEFAULT]; 164 mTrie[TRIE_ROOT] = TRIE_NULL; 165 mTrieUsed = TRIE_ROOT + 1; 166 167 try { 168 XmlUtils.beginDocument(parser, "words"); 169 String odest = ""; 170 char ooff = 0; 171 172 while (true) { 173 XmlUtils.nextElement(parser); 174 175 String element = parser.getName(); 176 if (element == null || !(element.equals("word"))) { 177 break; 178 } 179 180 String src = parser.getAttributeValue(null, "src"); 181 if (parser.next() == XmlPullParser.TEXT) { 182 String dest = parser.getText(); 183 char off; 184 185 if (dest.equals(odest)) { 186 off = ooff; 187 } else { 188 off = (char) right.length(); 189 right.append((char) dest.length()); 190 right.append(dest); 191 } 192 193 add(src, off); 194 } 195 } 196 197 // Don't let Resources cache a copy of all these strings. 198 r.flushLayoutCache(); 199 } catch (XmlPullParserException e) { 200 throw new RuntimeException(e); 201 } catch (IOException e) { 202 throw new RuntimeException(e); 203 } finally { 204 parser.close(); 205 } 206 207 mText = right.toString(); 208 } 209 210 private void add(String src, char off) { 211 int slen = src.length(); 212 int herep = TRIE_ROOT; 213 // Keep track of the size of the dictionary 214 mSize++; 215 216 for (int i = 0; i < slen; i++) { 217 char c = src.charAt(i); 218 boolean found = false; 219 220 for (; mTrie[herep] != TRIE_NULL; 221 herep = mTrie[herep] + TRIE_NEXT) { 222 if (c == mTrie[mTrie[herep] + TRIE_C]) { 223 // There is a node for this letter, and this is the 224 // end, so fill in the right hand side fields. 225 226 if (i == slen - 1) { 227 mTrie[mTrie[herep] + TRIE_OFF] = off; 228 return; 229 } 230 231 // There is a node for this letter, and we need 232 // to go deeper into it to fill in the rest. 233 234 herep = mTrie[herep] + TRIE_CHILD; 235 found = true; 236 break; 237 } 238 } 239 240 if (!found) { 241 // No node for this letter yet. Make one. 242 243 char node = newTrieNode(); 244 mTrie[herep] = node; 245 246 mTrie[mTrie[herep] + TRIE_C] = c; 247 mTrie[mTrie[herep] + TRIE_OFF] = TRIE_NULL; 248 mTrie[mTrie[herep] + TRIE_NEXT] = TRIE_NULL; 249 mTrie[mTrie[herep] + TRIE_CHILD] = TRIE_NULL; 250 251 // If this is the end of the word, fill in the offset. 252 253 if (i == slen - 1) { 254 mTrie[mTrie[herep] + TRIE_OFF] = off; 255 return; 256 } 257 258 // Otherwise, step in deeper and go to the next letter. 259 260 herep = mTrie[herep] + TRIE_CHILD; 261 } 262 } 263 } 264 265 private char newTrieNode() { 266 if (mTrieUsed + TRIE_SIZEOF > mTrie.length) { 267 char[] copy = new char[mTrie.length + INCREMENT]; 268 System.arraycopy(mTrie, 0, copy, 0, mTrie.length); 269 mTrie = copy; 270 } 271 272 char ret = mTrieUsed; 273 mTrieUsed += TRIE_SIZEOF; 274 275 return ret; 276 } 277} 278