BinaryDictionary.java revision 673cebf9e97289b3b0cd343ff7193dff69684a48
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; 24 25import java.util.Arrays; 26 27/** 28 * Implements a static, compacted, binary dictionary of standard words. 29 */ 30public class BinaryDictionary extends Dictionary { 31 32 public static final String DICTIONARY_PACK_AUTHORITY = 33 "com.android.inputmethod.latin.dictionarypack"; 34 35 /** 36 * There is a 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 @SuppressWarnings("unused") 45 private static final String TAG = "BinaryDictionary"; 46 private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE; 47 private static final int MAX_BIGRAMS = 60; 48 49 private static final int TYPED_LETTER_MULTIPLIER = 2; 50 51 private int mDicTypeId; 52 private int mNativeDict; 53 private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE]; 54 private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS]; 55 private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS]; 56 private final int[] mScores = new int[MAX_WORDS]; 57 private final int[] mBigramScores = new int[MAX_BIGRAMS]; 58 59 private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance(); 60 61 public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING = 62 new Flag(R.bool.config_require_umlaut_processing, 0x1); 63 64 // FULL_EDIT_DISTANCE is a flag that forces the dictionary to use full words 65 // when computing edit distance, instead of the default behavior of stopping 66 // the evaluation at the size the user typed. 67 public static final Flag FLAG_USE_FULL_EDIT_DISTANCE = new Flag(0x2); 68 69 // Can create a new flag from extravalue : 70 // public static final Flag FLAG_MYFLAG = 71 // new Flag("my_flag", 0x02); 72 73 // ALL_CONFIG_FLAGS is a collection of flags that enable reading all flags from configuration. 74 // This is but a mask - it does not mean the flags will be on, only that the configuration 75 // will be read for this particular flag. 76 public static final Flag[] ALL_CONFIG_FLAGS = { 77 // Here should reside all flags that trigger some special processing 78 // These *must* match the definition in UnigramDictionary enum in 79 // unigram_dictionary.h so please update both at the same time. 80 // Please note that flags created with a resource are of type CONFIG while flags 81 // created with a string are of type EXTRAVALUE. These behave like masks, and the 82 // actual value will be read from the configuration/extra value at run time for 83 // the configuration at dictionary creation time. 84 FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING, 85 }; 86 87 private int mFlags = 0; 88 89 /** 90 * Constructor for the binary dictionary. This is supposed to be called from the 91 * dictionary factory. 92 * All implementations should pass null into flagArray, except for testing purposes. 93 * @param context the context to access the environment from. 94 * @param filename the name of the file to read through native code. 95 * @param offset the offset of the dictionary data within the file. 96 * @param length the length of the binary data. 97 * @param flagArray the flags to limit the dictionary to, or null for default. 98 */ 99 public BinaryDictionary(final Context context, 100 final String filename, final long offset, final long length, Flag[] flagArray) { 101 // Note: at the moment a binary dictionary is always of the "main" type. 102 // Initializing this here will help transitioning out of the scheme where 103 // the Suggest class knows everything about every single dictionary. 104 mDicTypeId = Suggest.DIC_MAIN; 105 // TODO: Stop relying on the state of SubtypeSwitcher, get it as a parameter 106 mFlags = Flag.initFlags(null == flagArray ? ALL_CONFIG_FLAGS : flagArray, context, 107 SubtypeSwitcher.getInstance()); 108 loadDictionary(filename, offset, length); 109 } 110 111 static { 112 Utils.loadNativeLibrary(); 113 } 114 115 private native int openNative(String sourceDir, long dictOffset, long dictSize, 116 int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength, 117 int maxWords, int maxAlternatives); 118 private native void closeNative(int dict); 119 private native boolean isValidWordNative(int nativeData, char[] word, int wordLength); 120 private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates, 121 int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars, 122 int[] scores); 123 private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength, 124 int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores, 125 int maxWordLength, int maxBigrams, int maxAlternatives); 126 127 private final void loadDictionary(String path, long startOffset, long length) { 128 mNativeDict = openNative(path, startOffset, length, 129 TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER, 130 MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE); 131 } 132 133 @Override 134 public void getBigrams(final WordComposer codes, final CharSequence previousWord, 135 final WordCallback callback) { 136 if (mNativeDict == 0) return; 137 138 char[] chars = previousWord.toString().toCharArray(); 139 Arrays.fill(mOutputChars_bigrams, (char) 0); 140 Arrays.fill(mBigramScores, 0); 141 142 int codesSize = codes.size(); 143 if (codesSize <= 0) { 144 // Do not return bigrams from BinaryDictionary when nothing was typed. 145 // Only use user-history bigrams (or whatever other bigram dictionaries decide). 146 return; 147 } 148 Arrays.fill(mInputCodes, -1); 149 int[] alternatives = codes.getCodesAt(0); 150 System.arraycopy(alternatives, 0, mInputCodes, 0, 151 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 152 153 int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize, 154 mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS, 155 MAX_PROXIMITY_CHARS_SIZE); 156 157 for (int j = 0; j < count; ++j) { 158 if (mBigramScores[j] < 1) break; 159 final int start = j * MAX_WORD_LENGTH; 160 int len = 0; 161 while (len < MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) { 162 ++len; 163 } 164 if (len > 0) { 165 callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j], 166 mDicTypeId, DataType.BIGRAM); 167 } 168 } 169 } 170 171 // proximityInfo may not be null. 172 @Override 173 public void getWords(final WordComposer codes, final WordCallback callback, 174 final ProximityInfo proximityInfo) { 175 final int count = getSuggestions(codes, proximityInfo, mOutputChars, mScores); 176 177 for (int j = 0; j < count; ++j) { 178 if (mScores[j] < 1) break; 179 final int start = j * MAX_WORD_LENGTH; 180 int len = 0; 181 while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) { 182 ++len; 183 } 184 if (len > 0) { 185 callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId, 186 DataType.UNIGRAM); 187 } 188 } 189 } 190 191 /* package for test */ boolean isValidDictionary() { 192 return mNativeDict != 0; 193 } 194 195 // proximityInfo may not be null. 196 /* package for test */ int getSuggestions(final WordComposer codes, 197 final ProximityInfo proximityInfo, char[] outputChars, int[] scores) { 198 if (!isValidDictionary()) return -1; 199 200 final int codesSize = codes.size(); 201 // Won't deal with really long words. 202 if (codesSize > MAX_WORD_LENGTH - 1) return -1; 203 204 Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE); 205 for (int i = 0; i < codesSize; i++) { 206 int[] alternatives = codes.getCodesAt(i); 207 System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE, 208 Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE)); 209 } 210 Arrays.fill(outputChars, (char) 0); 211 Arrays.fill(scores, 0); 212 213 return getSuggestionsNative( 214 mNativeDict, proximityInfo.getNativeProximityInfo(), 215 codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize, 216 mFlags, outputChars, scores); 217 } 218 219 @Override 220 public boolean isValidWord(CharSequence word) { 221 if (word == null) return false; 222 char[] chars = word.toString().toCharArray(); 223 return isValidWordNative(mNativeDict, chars, chars.length); 224 } 225 226 @Override 227 public synchronized void close() { 228 closeInternal(); 229 } 230 231 private void closeInternal() { 232 if (mNativeDict != 0) { 233 closeNative(mNativeDict); 234 mNativeDict = 0; 235 } 236 } 237 238 @Override 239 protected void finalize() throws Throwable { 240 try { 241 closeInternal(); 242 } finally { 243 super.finalize(); 244 } 245 } 246} 247