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