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