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:
parent
aa9de26732
commit
4250eb27f5
5 changed files with 254 additions and 116 deletions
|
@ -17,12 +17,11 @@
|
|||
package com.android.inputmethod.deprecated.languageswitcher;
|
||||
|
||||
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.Settings;
|
||||
import com.android.inputmethod.latin.SharedPreferencesCompat;
|
||||
import com.android.inputmethod.latin.SubtypeSwitcher;
|
||||
import com.android.inputmethod.latin.Suggest;
|
||||
import com.android.inputmethod.latin.Utils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
@ -123,20 +122,10 @@ public class InputLanguageSelection extends PreferenceActivity {
|
|||
if (locale == null) return new Pair<Boolean, Boolean>(false, false);
|
||||
final Resources res = getResources();
|
||||
final Locale saveLocale = Utils.setSystemLocale(res, locale);
|
||||
boolean hasDictionary = false;
|
||||
final boolean hasDictionary = DictionaryFactory.isDictionaryAvailable(this, locale);
|
||||
boolean hasLayout = false;
|
||||
|
||||
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[] layoutCountryCodes = KeyboardParser.parseKeyboardLocale(
|
||||
this, R.xml.kbd_qwerty).split(",", -1);
|
||||
|
|
|
@ -21,12 +21,8 @@ import com.android.inputmethod.keyboard.KeyboardSwitcher;
|
|||
import com.android.inputmethod.keyboard.ProximityInfo;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* 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_WORDS = 18;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = "BinaryDictionary";
|
||||
private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
|
||||
private static final int MAX_BIGRAMS = 60;
|
||||
|
||||
private static final int TYPED_LETTER_MULTIPLIER = 2;
|
||||
|
||||
private static final BinaryDictionary sInstance = new BinaryDictionary();
|
||||
private int mDicTypeId;
|
||||
private int mNativeDict;
|
||||
private long mDictLength;
|
||||
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_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
|
||||
|
@ -79,95 +74,32 @@ public class BinaryDictionary extends Dictionary {
|
|||
|
||||
private int mFlags = 0;
|
||||
|
||||
private BinaryDictionary() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a dictionary from a raw resource file
|
||||
* @param context application context for reading resources
|
||||
* @param resId the resource containing the raw binary dictionary
|
||||
* @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
|
||||
* @return an initialized instance of BinaryDictionary
|
||||
* Constructor for the binary dictionary. This is supposed to be called from the
|
||||
* dictionary factory.
|
||||
* All implementations should pass null into flagArray, except for testing purposes.
|
||||
* @param context the context to access the environment from.
|
||||
* @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) {
|
||||
synchronized (sInstance) {
|
||||
sInstance.closeInternal();
|
||||
try {
|
||||
final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
|
||||
if (afd == null) {
|
||||
Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
|
||||
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;
|
||||
}
|
||||
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;
|
||||
public BinaryDictionary(final Context context,
|
||||
final String filename, final long offset, final long length, Flag[] flagArray) {
|
||||
// Note: at the moment a binary dictionary is always of the "main" type.
|
||||
// Initializing this here will help transitioning out of the scheme where
|
||||
// the Suggest class knows everything about every single dictionary.
|
||||
mDicTypeId = Suggest.DIC_MAIN;
|
||||
// TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter
|
||||
mFlags = Flag.initFlags(null == flagArray ? ALL_FLAGS : flagArray, context,
|
||||
SubtypeSwitcher.getInstance());
|
||||
loadDictionary(filename, offset, length);
|
||||
}
|
||||
|
||||
static {
|
||||
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,
|
||||
int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
|
||||
int maxWords, int maxAlternatives);
|
||||
|
@ -184,7 +116,6 @@ public class BinaryDictionary extends Dictionary {
|
|||
mNativeDict = openNative(path, startOffset, length,
|
||||
TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
|
||||
MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
|
||||
mDictLength = length;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -278,10 +209,6 @@ public class BinaryDictionary extends Dictionary {
|
|||
return isValidWordNative(mNativeDict, chars, chars.length);
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return mDictLength; // This value is initialized in loadDictionary()
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
closeInternal();
|
||||
|
@ -291,7 +218,6 @@ public class BinaryDictionary extends Dictionary {
|
|||
if (mNativeDict != 0) {
|
||||
closeNative(mNativeDict);
|
||||
mNativeDict = 0;
|
||||
mDictLength = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
159
java/src/com/android/inputmethod/latin/DictionaryFactory.java
Normal file
159
java/src/com/android/inputmethod/latin/DictionaryFactory.java
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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_WHITELIST ="whitelist";
|
||||
|
||||
public static final int LARGE_DICTIONARY_THRESHOLD = 200 * 1000;
|
||||
|
||||
private static final boolean DBG = LatinImeLogger.sDBG;
|
||||
|
||||
private AutoCorrection mAutoCorrection;
|
||||
|
||||
private BinaryDictionary mMainDict;
|
||||
private Dictionary mMainDict;
|
||||
private WhitelistDictionary mWhiteListDictionary;
|
||||
private final Map<String, Dictionary> mUnigramDictionaries = 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;
|
||||
|
||||
public Suggest(Context context, int dictionaryResId, Locale locale) {
|
||||
init(context, BinaryDictionary.initDictionaryFromManager(context, DIC_MAIN, locale,
|
||||
init(context, DictionaryFactory.createDictionaryFromManager(context, locale,
|
||||
dictionaryResId));
|
||||
}
|
||||
|
||||
/* package for test */ Suggest(Context context, File dictionary, long startOffset, long length,
|
||||
Flag[] flagArray) {
|
||||
init(null, BinaryDictionary.initDictionary(context, dictionary, startOffset, length,
|
||||
DIC_MAIN, flagArray));
|
||||
init(null, DictionaryFactory.createDictionaryForTest(context, dictionary, startOffset,
|
||||
length, flagArray));
|
||||
}
|
||||
|
||||
private void init(Context context, BinaryDictionary mainDict) {
|
||||
private void init(Context context, Dictionary mainDict) {
|
||||
if (mainDict != null) {
|
||||
mMainDict = 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) {
|
||||
final BinaryDictionary newMainDict = BinaryDictionary.initDictionaryFromManager(context,
|
||||
DIC_MAIN, locale, dictionaryResId);
|
||||
final Dictionary newMainDict = DictionaryFactory.createDictionaryFromManager(
|
||||
context, locale, dictionaryResId);
|
||||
mMainDict = newMainDict;
|
||||
if (null == newMainDict) {
|
||||
mUnigramDictionaries.remove(DICT_KEY_MAIN);
|
||||
|
@ -165,7 +163,7 @@ public class Suggest implements Dictionary.WordCallback {
|
|||
}
|
||||
|
||||
public boolean hasMainDictionary() {
|
||||
return mMainDict != null && mMainDict.getSize() > LARGE_DICTIONARY_THRESHOLD;
|
||||
return mMainDict != null;
|
||||
}
|
||||
|
||||
public Map<String, Dictionary> getUnigramDictionaries() {
|
||||
|
|
Loading…
Reference in a new issue