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