Create a dictionary collection and a dictionary factory.

The dictionary collection is a class complying to the Dictionary
interface that acts as a front end to a collection of arbitrarily many
dictionaries of any type.
The dictionary factory is a helper class for creating various
dictionaries and get some meta information about them.

At the same time, this change makes the BinaryDictionary class
not a singleton any more.

This also needs I9afe61a9 to not break the build.

Change-Id: I61fdcc4867fcda18342807bf1865e6e46979e5d5
This commit is contained in:
Jean Chalard 2011-04-26 21:49:09 +09:00
parent aa9de26732
commit 4250eb27f5
5 changed files with 254 additions and 116 deletions

View file

@ -17,12 +17,11 @@
package com.android.inputmethod.deprecated.languageswitcher; package com.android.inputmethod.deprecated.languageswitcher;
import com.android.inputmethod.keyboard.KeyboardParser; import com.android.inputmethod.keyboard.KeyboardParser;
import com.android.inputmethod.latin.BinaryDictionary; import com.android.inputmethod.latin.DictionaryFactory;
import com.android.inputmethod.latin.R; import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.Settings; import com.android.inputmethod.latin.Settings;
import com.android.inputmethod.latin.SharedPreferencesCompat; import com.android.inputmethod.latin.SharedPreferencesCompat;
import com.android.inputmethod.latin.SubtypeSwitcher; import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.Suggest;
import com.android.inputmethod.latin.Utils; import com.android.inputmethod.latin.Utils;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@ -123,20 +122,10 @@ public class InputLanguageSelection extends PreferenceActivity {
if (locale == null) return new Pair<Boolean, Boolean>(false, false); if (locale == null) return new Pair<Boolean, Boolean>(false, false);
final Resources res = getResources(); final Resources res = getResources();
final Locale saveLocale = Utils.setSystemLocale(res, locale); final Locale saveLocale = Utils.setSystemLocale(res, locale);
boolean hasDictionary = false; final boolean hasDictionary = DictionaryFactory.isDictionaryAvailable(this, locale);
boolean hasLayout = false; boolean hasLayout = false;
try { try {
BinaryDictionary bd = BinaryDictionary.initDictionaryFromManager(this, Suggest.DIC_MAIN,
locale, Utils.getMainDictionaryResourceId(res));
// Is the dictionary larger than a placeholder? Arbitrarily chose a lower limit of
// 4000-5000 words, whereas the LARGE_DICTIONARY is about 20000+ words.
if (bd.getSize() > Suggest.LARGE_DICTIONARY_THRESHOLD / 4) {
hasDictionary = true;
}
bd.close();
final String localeStr = locale.toString(); final String localeStr = locale.toString();
final String[] layoutCountryCodes = KeyboardParser.parseKeyboardLocale( final String[] layoutCountryCodes = KeyboardParser.parseKeyboardLocale(
this, R.xml.kbd_qwerty).split(",", -1); this, R.xml.kbd_qwerty).split(",", -1);

View file

@ -21,12 +21,8 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher;
import com.android.inputmethod.keyboard.ProximityInfo; import com.android.inputmethod.keyboard.ProximityInfo;
import android.content.Context; import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.util.Log;
import java.io.File;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale;
/** /**
* Implements a static, compacted, binary dictionary of standard words. * Implements a static, compacted, binary dictionary of standard words.
@ -45,16 +41,15 @@ public class BinaryDictionary extends Dictionary {
public static final int MAX_WORD_LENGTH = 48; public static final int MAX_WORD_LENGTH = 48;
public static final int MAX_WORDS = 18; public static final int MAX_WORDS = 18;
@SuppressWarnings("unused")
private static final String TAG = "BinaryDictionary"; private static final String TAG = "BinaryDictionary";
private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
private static final int MAX_BIGRAMS = 60; private static final int MAX_BIGRAMS = 60;
private static final int TYPED_LETTER_MULTIPLIER = 2; private static final int TYPED_LETTER_MULTIPLIER = 2;
private static final BinaryDictionary sInstance = new BinaryDictionary();
private int mDicTypeId; private int mDicTypeId;
private int mNativeDict; private int mNativeDict;
private long mDictLength;
private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
@ -79,95 +74,32 @@ public class BinaryDictionary extends Dictionary {
private int mFlags = 0; private int mFlags = 0;
private BinaryDictionary() {
}
/** /**
* Initializes a dictionary from a raw resource file * Constructor for the binary dictionary. This is supposed to be called from the
* @param context application context for reading resources * dictionary factory.
* @param resId the resource containing the raw binary dictionary * All implementations should pass null into flagArray, except for testing purposes.
* @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_* * @param context the context to access the environment from.
* @return an initialized instance of BinaryDictionary * @param filename the name of the file to read through native code.
* @param offset the offset of the dictionary data within the file.
* @param length the length of the binary data.
* @param flagArray the flags to limit the dictionary to, or null for default.
*/ */
public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { public BinaryDictionary(final Context context,
synchronized (sInstance) { final String filename, final long offset, final long length, Flag[] flagArray) {
sInstance.closeInternal(); // Note: at the moment a binary dictionary is always of the "main" type.
try { // Initializing this here will help transitioning out of the scheme where
final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); // the Suggest class knows everything about every single dictionary.
if (afd == null) { mDicTypeId = Suggest.DIC_MAIN;
Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); // TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter
return null; mFlags = Flag.initFlags(null == flagArray ? ALL_FLAGS : flagArray, context,
} SubtypeSwitcher.getInstance());
final String sourceDir = context.getApplicationInfo().sourceDir; loadDictionary(filename, offset, length);
final File packagePath = new File(sourceDir);
// TODO: Come up with a way to handle a directory.
if (!packagePath.isFile()) {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
sInstance.mDicTypeId = dicTypeId;
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource. resId=" + resId);
return null;
}
}
sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance());
return sInstance;
}
/* package for test */ static BinaryDictionary initDictionary(Context context, File dictionary,
long startOffset, long length, int dicTypeId, Flag[] flagArray) {
synchronized (sInstance) {
sInstance.closeInternal();
if (dictionary.isFile()) {
sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
sInstance.mDicTypeId = dicTypeId;
} else {
Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
return null;
}
}
sInstance.mFlags = Flag.initFlags(flagArray, context, null);
return sInstance;
} }
static { static {
Utils.loadNativeLibrary(); Utils.loadNativeLibrary();
} }
/**
* Initializes a dictionary from a dictionary pack.
*
* This searches for a content provider providing a dictionary pack for the specified
* locale. If none is found, it falls back to using the resource passed as fallBackResId
* as a dictionary.
* @param context application context for reading resources
* @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
* @param locale the locale for which to create the dictionary
* @param fallbackResId the id of the resource to use as a fallback if no pack is found
* @return an initialized instance of BinaryDictionary
*/
public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId,
Locale locale, int fallbackResId) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
return initDictionary(context, fallbackResId, dicTypeId);
}
synchronized (sInstance) {
sInstance.closeInternal();
final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
context, fallbackResId);
if (null != dictFile) {
sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength);
sInstance.mDicTypeId = dicTypeId;
}
}
sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance());
return sInstance;
}
private native int openNative(String sourceDir, long dictOffset, long dictSize, private native int openNative(String sourceDir, long dictOffset, long dictSize,
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
int maxWords, int maxAlternatives); int maxWords, int maxAlternatives);
@ -184,7 +116,6 @@ public class BinaryDictionary extends Dictionary {
mNativeDict = openNative(path, startOffset, length, mNativeDict = openNative(path, startOffset, length,
TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
mDictLength = length;
} }
@Override @Override
@ -278,10 +209,6 @@ public class BinaryDictionary extends Dictionary {
return isValidWordNative(mNativeDict, chars, chars.length); return isValidWordNative(mNativeDict, chars, chars.length);
} }
public long getSize() {
return mDictLength; // This value is initialized in loadDictionary()
}
@Override @Override
public synchronized void close() { public synchronized void close() {
closeInternal(); closeInternal();
@ -291,7 +218,6 @@ public class BinaryDictionary extends Dictionary {
if (mNativeDict != 0) { if (mNativeDict != 0) {
closeNative(mNativeDict); closeNative(mNativeDict);
mNativeDict = 0; mNativeDict = 0;
mDictLength = 0;
} }
} }

View file

@ -0,0 +1,66 @@
/*
* Copyright (C) 2011 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 com.android.inputmethod.latin;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Class for a collection of dictionaries that behave like one dictionary.
*/
public class DictionaryCollection extends Dictionary {
protected final List<Dictionary> mDictionaries;
public DictionaryCollection() {
mDictionaries = new CopyOnWriteArrayList<Dictionary>();
}
public DictionaryCollection(Dictionary... dictionaries) {
mDictionaries = new CopyOnWriteArrayList<Dictionary>(dictionaries);
}
@Override
public void getWords(final WordComposer composer, final WordCallback callback) {
for (final Dictionary dict : mDictionaries)
dict.getWords(composer, callback);
}
@Override
public void getBigrams(final WordComposer composer, final CharSequence previousWord,
final WordCallback callback) {
for (final Dictionary dict : mDictionaries)
dict.getBigrams(composer, previousWord, callback);
}
@Override
public boolean isValidWord(CharSequence word) {
for (final Dictionary dict : mDictionaries)
if (dict.isValidWord(word)) return true;
return false;
}
@Override
public void close() {
for (final Dictionary dict : mDictionaries)
dict.close();
}
public void addDictionary(Dictionary newDict) {
mDictionaries.add(newDict);
}
}

View file

@ -0,0 +1,159 @@
/*
* Copyright (C) 2011 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 com.android.inputmethod.latin;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.Log;
import java.io.File;
import java.util.Locale;
/**
* Factory for dictionary instances.
*/
public class DictionaryFactory {
private static String TAG = DictionaryFactory.class.getSimpleName();
/**
* Initializes a dictionary from a dictionary pack.
*
* This searches for a content provider providing a dictionary pack for the specified
* locale. If none is found, it falls back to using the resource passed as fallBackResId
* as a dictionary.
* @param context application context for reading resources
* @param locale the locale for which to create the dictionary
* @param fallbackResId the id of the resource to use as a fallback if no pack is found
* @return an initialized instance of Dictionary
*/
public static Dictionary createDictionaryFromManager(Context context, Locale locale,
int fallbackResId) {
if (null == locale) {
Log.e(TAG, "No locale defined for dictionary");
return new DictionaryCollection(createBinaryDictionary(context, fallbackResId));
}
final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
context, fallbackResId);
if (null == dictFile) return null;
return new DictionaryCollection(new BinaryDictionary(context,
dictFile.mFilename, dictFile.mOffset, dictFile.mLength, null));
}
/**
* Initializes a dictionary from a raw resource file
* @param context application context for reading resources
* @param resId the resource containing the raw binary dictionary
* @return an initialized instance of BinaryDictionary
*/
protected static BinaryDictionary createBinaryDictionary(Context context, int resId) {
AssetFileDescriptor afd = null;
try {
// TODO: IMPORTANT: Do not create a dictionary from a placeholder.
afd = context.getResources().openRawResourceFd(resId);
if (afd == null) {
Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
return null;
}
if (!isFullDictionary(afd)) return null;
final String sourceDir = context.getApplicationInfo().sourceDir;
final File packagePath = new File(sourceDir);
// TODO: Come up with a way to handle a directory.
if (!packagePath.isFile()) {
Log.e(TAG, "sourceDir is not a file: " + sourceDir);
return null;
}
return new BinaryDictionary(context,
sourceDir, afd.getStartOffset(), afd.getLength(), null);
} catch (android.content.res.Resources.NotFoundException e) {
Log.e(TAG, "Could not find the resource. resId=" + resId);
return null;
} finally {
if (null != afd) {
try {
afd.close();
} catch (java.io.IOException e) {
/* IOException on close ? What am I supposed to do ? */
}
}
}
}
/**
* Create a dictionary from passed data. This is intended for unit tests only.
* @param context the test context to create this data from.
* @param dictionary the file to read
* @param startOffset the offset in the file where the data starts
* @param length the length of the data
* @param flagArray the flags to use with this data for testing
* @return the created dictionary, or null.
*/
public static Dictionary createDictionaryForTest(Context context, File dictionary,
long startOffset, long length, Flag[] flagArray) {
if (dictionary.isFile()) {
return new BinaryDictionary(context, dictionary.getAbsolutePath(), startOffset, length,
flagArray);
} else {
Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
return null;
}
}
/**
* Find out whether a dictionary is available for this locale.
* @param context the context on which to check resources.
* @param locale the locale to check for.
* @return whether a (non-placeholder) dictionary is available or not.
*/
public static boolean isDictionaryAvailable(Context context, Locale locale) {
final Resources res = context.getResources();
final Configuration conf = res.getConfiguration();
final Locale saveLocale = conf.locale;
conf.locale = locale;
res.updateConfiguration(conf, res.getDisplayMetrics());
final int resourceId = Utils.getMainDictionaryResourceId(res);
final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
final boolean hasDictionary = isFullDictionary(afd);
try {
if (null != afd) afd.close();
} catch (java.io.IOException e) {
/* Um, what can we do here exactly? */
}
conf.locale = saveLocale;
res.updateConfiguration(conf, res.getDisplayMetrics());
return hasDictionary;
}
// TODO: Find the Right Way to find out whether the resource is a placeholder or not.
// Suggestion : strip the locale, open the placeholder file and store its offset.
// Upon opening the file, if it's the same offset, then it's the placeholder.
private static final long PLACEHOLDER_LENGTH = 34;
/**
* Finds out whether the data pointed out by an AssetFileDescriptor is a full
* dictionary (as opposed to null, or to a place holder).
* @param afd the file descriptor to test, or null
* @return true if the dictionary is a real full dictionary, false if it's null or a placeholder
*/
protected static boolean isFullDictionary(final AssetFileDescriptor afd) {
return (afd != null && afd.getLength() > PLACEHOLDER_LENGTH);
}
}

View file

@ -75,13 +75,11 @@ public class Suggest implements Dictionary.WordCallback {
public static final String DICT_KEY_USER_BIGRAM = "user_bigram"; public static final String DICT_KEY_USER_BIGRAM = "user_bigram";
public static final String DICT_KEY_WHITELIST ="whitelist"; public static final String DICT_KEY_WHITELIST ="whitelist";
public static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
private static final boolean DBG = LatinImeLogger.sDBG; private static final boolean DBG = LatinImeLogger.sDBG;
private AutoCorrection mAutoCorrection; private AutoCorrection mAutoCorrection;
private BinaryDictionary mMainDict; private Dictionary mMainDict;
private WhitelistDictionary mWhiteListDictionary; private WhitelistDictionary mWhiteListDictionary;
private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mUnigramDictionaries = new HashMap<String, Dictionary>();
private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>(); private final Map<String, Dictionary> mBigramDictionaries = new HashMap<String, Dictionary>();
@ -108,17 +106,17 @@ public class Suggest implements Dictionary.WordCallback {
private int mCorrectionMode = CORRECTION_BASIC; private int mCorrectionMode = CORRECTION_BASIC;
public Suggest(Context context, int dictionaryResId, Locale locale) { public Suggest(Context context, int dictionaryResId, Locale locale) {
init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale, init(context, DictionaryFactory.createDictionaryFromManager(context, locale,
dictionaryResId)); dictionaryResId));
} }
/* package for test */ Suggest(Context context, File dictionary, long startOffset, long length, /* package for test */ Suggest(Context context, File dictionary, long startOffset, long length,
Flag[] flagArray) { Flag[] flagArray) {
init(null, BinaryDictionary.initDictionary(context, dictionary, startOffset, length, init(null, DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset,
DIC_MAIN, flagArray)); length, flagArray));
} }
private void init(Context context, BinaryDictionary mainDict) { private void init(Context context, Dictionary mainDict) {
if (mainDict != null) { if (mainDict != null) {
mMainDict = mainDict; mMainDict = mainDict;
mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict); mUnigramDictionaries.put(DICT_KEY_MAIN, mainDict);
@ -133,8 +131,8 @@ public class Suggest implements Dictionary.WordCallback {
} }
public void resetMainDict(Context context, int dictionaryResId, Locale locale) { public void resetMainDict(Context context, int dictionaryResId, Locale locale) {
final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context, final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
DIC_MAIN, locale, dictionaryResId); context, locale, dictionaryResId);
mMainDict = newMainDict; mMainDict = newMainDict;
if (null == newMainDict) { if (null == newMainDict) {
mUnigramDictionaries.remove(DICT_KEY_MAIN); mUnigramDictionaries.remove(DICT_KEY_MAIN);
@ -165,7 +163,7 @@ public class Suggest implements Dictionary.WordCallback {
} }
public boolean hasMainDictionary() { public boolean hasMainDictionary() {
return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD; return mMainDict != null;
} }
public Map<String, Dictionary> getUnigramDictionaries() { public Map<String, Dictionary> getUnigramDictionaries() {