280 lines
8.5 KiB
Java
280 lines
8.5 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|