BinaryDictionary.java revision e7a2512aa3666e1b891dc7dfc5a0cb28fd66bea9
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * 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, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.inputmethod.latin; 18 19import com.android.inputmethod.keyboard.Keyboard; 20import com.android.inputmethod.keyboard.KeyboardSwitcher; 21import com.android.inputmethod.keyboard.ProximityInfo; 22 23import android.content.Context; 24import android.content.res.AssetFileDescriptor; 25import android.util.Log; 26 27import java.io.File; 28import java.util.Arrays; 29 30/** 31 * Implements a static, compacted, binary dictionary of standard words. 32 */ 33public class BinaryDictionary extends Dictionary { 34 35 /** 36 * There is difference between what java and native code can handle. 37 * This value should only be used in BinaryDictionary.java 38 * It is necessary to keep it at this value because some languages e.g. German have 39 * really long words. 40 */ 41 public static final int MAX_WORD_LENGTH = 48; 42 public static final int MAX_WORDS = 18; 43 44 private static final String TAG = "BinaryDictionary"; 45 private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; 46 private static final int MAX_BIGRAMS = 60; 47 48 private static final int TYPED_LETTER_MULTIPLIER = 2; 49 50 private static final BinaryDictionary sInstance = new BinaryDictionary(); 51 private int mDicTypeId; 52 private int mNativeDict; 53 private long mDictLength; 54 private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; 55 private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; 56 private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; 57 private final int[] mScores = new int[MAX_WORDS]; 58 private final int[] mBigramScores = new int[MAX_BIGRAMS]; 59 60 private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 61 private final SubtypeSwitcher mSubtypeSwitcher = SubtypeSwitcher.getInstance(); 62 63 private static class Flags { 64 private static class FlagEntry { 65 public final String mName; 66 public final int mValue; 67 public FlagEntry(String name, int value) { 68 mName = name; 69 mValue = value; 70 } 71 } 72 public static final FlagEntry[] ALL_FLAGS = { 73 // Here should reside all flags that trigger some special processing 74 // These *must* match the definition in UnigramDictionary enum in 75 // unigram_dictionary.h so please update both at the same time. 76 new FlagEntry("requiresGermanUmlautProcessing", 0x1) 77 }; 78 } 79 private int mFlags = 0; 80 81 private BinaryDictionary() { 82 } 83 84 /** 85 * Initialize a dictionary from a raw resource file 86 * @param context application context for reading resources 87 * @param resId the resource containing the raw binary dictionary 88 * @return initialized instance of BinaryDictionary 89 */ 90 public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) { 91 synchronized (sInstance) { 92 sInstance.closeInternal(); 93 try { 94 final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId); 95 if (afd == null) { 96 Log.e(TAG, "Found the resource but it is compressed. resId=" + resId); 97 return null; 98 } 99 final String sourceDir = context.getApplicationInfo().sourceDir; 100 final File packagePath = new File(sourceDir); 101 // TODO: Come up with a way to handle a directory. 102 if (!packagePath.isFile()) { 103 Log.e(TAG, "sourceDir is not a file: " + sourceDir); 104 return null; 105 } 106 sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength()); 107 sInstance.mDicTypeId = dicTypeId; 108 } catch (android.content.res.Resources.NotFoundException e) { 109 Log.e(TAG, "Could not find the resource. resId=" + resId); 110 return null; 111 } 112 } 113 sInstance.initFlags(); 114 return sInstance; 115 } 116 117 /* package for test */ static BinaryDictionary initDictionary(File dictionary, long startOffset, 118 long length, int dicTypeId) { 119 synchronized (sInstance) { 120 sInstance.closeInternal(); 121 if (dictionary.isFile()) { 122 sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length); 123 sInstance.mDicTypeId = dicTypeId; 124 } else { 125 Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath()); 126 return null; 127 } 128 } 129 return sInstance; 130 } 131 132 private void initFlags() { 133 int flags = 0; 134 for (Flags.FlagEntry entry : Flags.ALL_FLAGS) { 135 if (mSubtypeSwitcher.currentSubtypeContainsExtraValueKey(entry.mName)) 136 flags |= entry.mValue; 137 } 138 mFlags = flags; 139 } 140 141 static { 142 Utils.loadNativeLibrary(); 143 } 144 145 private native int openNative(String sourceDir, long dictOffset, long dictSize, 146 int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, 147 int maxWords, int maxAlternatives); 148 private native void closeNative(int dict); 149 private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); 150 private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, 151 int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars, 152 int[] scores); 153 private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, 154 int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, 155 int maxWordLength, int maxBigrams, int maxAlternatives); 156 157 private final void loadDictionary(String path, long startOffset, long length) { 158 mNativeDict = openNative(path, startOffset, length, 159 TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, 160 MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); 161 mDictLength = length; 162 } 163 164 @Override 165 public void getBigrams(final WordComposer codes, final CharSequence previousWord, 166 final WordCallback callback) { 167 if (mNativeDict == 0) return; 168 169 char[] chars = previousWord.toString().toCharArray(); 170 Arrays.fill(mOutputChars_bigrams, (char) 0); 171 Arrays.fill(mBigramScores, 0); 172 173 int codesSize = codes.size(); 174 Arrays.fill(mInputCodes, -1); 175 int[] alternatives = codes.getCodesAt(0); 176 System.arraycopy(alternatives, 0, mInputCodes, 0, 177 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 178 179 int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, 180 mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS, 181 MAX_PROXIMITY_CHARS_SIZE); 182 183 for (int j = 0; j < count; ++j) { 184 if (mBigramScores[j] < 1) break; 185 final int start = j * MAX_WORD_LENGTH; 186 int len = 0; 187 while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) { 188 ++len; 189 } 190 if (len > 0) { 191 callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], 192 mDicTypeId, DataType.BIGRAM); 193 } 194 } 195 } 196 197 @Override 198 public void getWords(final WordComposer codes, final WordCallback callback) { 199 final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(), 200 mOutputChars, mScores); 201 202 for (int j = 0; j < count; ++j) { 203 if (mScores[j] < 1) break; 204 final int start = j * MAX_WORD_LENGTH; 205 int len = 0; 206 while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { 207 ++len; 208 } 209 if (len > 0) { 210 callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, 211 DataType.UNIGRAM); 212 } 213 } 214 } 215 216 /* package for test */ boolean isValidDictionary() { 217 return mNativeDict != 0; 218 } 219 220 /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard, 221 char[] outputChars, int[] scores) { 222 if (!isValidDictionary()) return -1; 223 224 final int codesSize = codes.size(); 225 // Won't deal with really long words. 226 if (codesSize > MAX_WORD_LENGTH - 1) return -1; 227 228 Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); 229 for (int i = 0; i < codesSize; i++) { 230 int[] alternatives = codes.getCodesAt(i); 231 System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE, 232 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 233 } 234 Arrays.fill(outputChars, (char) 0); 235 Arrays.fill(scores, 0); 236 237 return getSuggestionsNative( 238 mNativeDict, keyboard.getProximityInfo(), 239 codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, 240 mFlags, outputChars, scores); 241 } 242 243 @Override 244 public boolean isValidWord(CharSequence word) { 245 if (word == null) return false; 246 char[] chars = word.toString().toCharArray(); 247 return isValidWordNative(mNativeDict, chars, chars.length); 248 } 249 250 public long getSize() { 251 return mDictLength; // This value is initialized in loadDictionary() 252 } 253 254 @Override 255 public synchronized void close() { 256 closeInternal(); 257 } 258 259 private void closeInternal() { 260 if (mNativeDict != 0) { 261 closeNative(mNativeDict); 262 mNativeDict = 0; 263 mDictLength = 0; 264 } 265 } 266 267 @Override 268 protected void finalize() throws Throwable { 269 try { 270 closeInternal(); 271 } finally { 272 super.finalize(); 273 } 274 } 275} 276