BinaryDictionary.java revision c899038eee5c01d520a2707cca01ee093a674d05
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;
29import java.util.Locale;
30
31/**
32 * Implements a static, compacted, binary dictionary of standard words.
33 */
34public class BinaryDictionary extends Dictionary {
35
36    public static final String DICTIONARY_PACK_AUTHORITY =
37            "com.android.inputmethod.latin.dictionarypack";
38
39    /**
40     * There is a difference between what java and native code can handle.
41     * This value should only be used in BinaryDictionary.java
42     * It is necessary to keep it at this value because some languages e.g. German have
43     * really long words.
44     */
45    public static final int MAX_WORD_LENGTH = 48;
46    public static final int MAX_WORDS = 18;
47
48    private static final String TAG = "BinaryDictionary";
49    private static final int MAX_PROXIMITY_CHARS_SIZE = ProximityInfo.MAX_PROXIMITY_CHARS_SIZE;
50    private static final int MAX_BIGRAMS = 60;
51
52    private static final int TYPED_LETTER_MULTIPLIER = 2;
53
54    private static final BinaryDictionary sInstance = new BinaryDictionary();
55    private int mDicTypeId;
56    private int mNativeDict;
57    private long mDictLength;
58    private final int[] mInputCodes = new int[MAX_WORD_LENGTH * MAX_PROXIMITY_CHARS_SIZE];
59    private final char[] mOutputChars = new char[MAX_WORD_LENGTH * MAX_WORDS];
60    private final char[] mOutputChars_bigrams = new char[MAX_WORD_LENGTH * MAX_BIGRAMS];
61    private final int[] mScores = new int[MAX_WORDS];
62    private final int[] mBigramScores = new int[MAX_BIGRAMS];
63
64    private final KeyboardSwitcher mKeyboardSwitcher = KeyboardSwitcher.getInstance();
65
66    public static final Flag FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING =
67            new Flag(R.bool.config_require_umlaut_processing, 0x1);
68
69    // Can create a new flag from extravalue :
70    // public static final Flag FLAG_MYFLAG =
71    //         new Flag("my_flag", 0x02);
72
73    private static final Flag[] ALL_FLAGS = {
74        // Here should reside all flags that trigger some special processing
75        // These *must* match the definition in UnigramDictionary enum in
76        // unigram_dictionary.h so please update both at the same time.
77        FLAG_REQUIRES_GERMAN_UMLAUT_PROCESSING,
78    };
79
80    private int mFlags = 0;
81
82    private BinaryDictionary() {
83    }
84
85    /**
86     * Initializes a dictionary from a raw resource file
87     * @param context application context for reading resources
88     * @param resId the resource containing the raw binary dictionary
89     * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
90     * @return an initialized instance of BinaryDictionary
91     */
92    public static BinaryDictionary initDictionary(Context context, int resId, int dicTypeId) {
93        synchronized (sInstance) {
94            sInstance.closeInternal();
95            try {
96                final AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId);
97                if (afd == null) {
98                    Log.e(TAG, "Found the resource but it is compressed. resId=" + resId);
99                    return null;
100                }
101                final String sourceDir = context.getApplicationInfo().sourceDir;
102                final File packagePath = new File(sourceDir);
103                // TODO: Come up with a way to handle a directory.
104                if (!packagePath.isFile()) {
105                    Log.e(TAG, "sourceDir is not a file: " + sourceDir);
106                    return null;
107                }
108                sInstance.loadDictionary(sourceDir, afd.getStartOffset(), afd.getLength());
109                sInstance.mDicTypeId = dicTypeId;
110            } catch (android.content.res.Resources.NotFoundException e) {
111                Log.e(TAG, "Could not find the resource. resId=" + resId);
112                return null;
113            }
114        }
115        sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance());
116        return sInstance;
117    }
118
119    /* package for test */ static BinaryDictionary initDictionary(Context context, File dictionary,
120            long startOffset, long length, int dicTypeId, Flag[] flagArray) {
121        synchronized (sInstance) {
122            sInstance.closeInternal();
123            if (dictionary.isFile()) {
124                sInstance.loadDictionary(dictionary.getAbsolutePath(), startOffset, length);
125                sInstance.mDicTypeId = dicTypeId;
126            } else {
127                Log.e(TAG, "Could not find the file. path=" + dictionary.getAbsolutePath());
128                return null;
129            }
130        }
131        sInstance.mFlags = Flag.initFlags(flagArray, context, null);
132        return sInstance;
133    }
134
135    static {
136        Utils.loadNativeLibrary();
137    }
138
139    /**
140     * Initializes a dictionary from a dictionary pack.
141     *
142     * This searches for a content provider providing a dictionary pack for the specified
143     * locale. If none is found, it falls back to using the resource passed as fallBackResId
144     * as a dictionary.
145     * @param context application context for reading resources
146     * @param dicTypeId the type of the dictionary being created, out of the list in Suggest.DIC_*
147     * @param locale the locale for which to create the dictionary
148     * @param fallBackResId the id of the resource to use as a fallback if no pack is found
149     * @return an initialized instance of BinaryDictionary
150     */
151    public static BinaryDictionary initDictionaryFromManager(Context context, int dicTypeId,
152            Locale locale, int fallbackResId) {
153        if (null == locale) {
154            Log.e(TAG, "No locale defined for dictionary");
155            return initDictionary(context, fallbackResId, dicTypeId);
156        }
157        synchronized (sInstance) {
158            sInstance.closeInternal();
159
160            final AssetFileAddress dictFile = BinaryDictionaryGetter.getDictionaryFile(locale,
161                    context, fallbackResId);
162            if (null != dictFile) {
163                sInstance.loadDictionary(dictFile.mFilename, dictFile.mOffset, dictFile.mLength);
164                sInstance.mDicTypeId = dicTypeId;
165            }
166        }
167        sInstance.mFlags = Flag.initFlags(ALL_FLAGS, context, SubtypeSwitcher.getInstance());
168        return sInstance;
169    }
170
171    private native int openNative(String sourceDir, long dictOffset, long dictSize,
172            int typedLetterMultiplier, int fullWordMultiplier, int maxWordLength,
173            int maxWords, int maxAlternatives);
174    private native void closeNative(int dict);
175    private native boolean isValidWordNative(int nativeData, char[] word, int wordLength);
176    private native int getSuggestionsNative(int dict, int proximityInfo, int[] xCoordinates,
177            int[] yCoordinates, int[] inputCodes, int codesSize, int flags, char[] outputChars,
178            int[] scores);
179    private native int getBigramsNative(int dict, char[] prevWord, int prevWordLength,
180            int[] inputCodes, int inputCodesLength, char[] outputChars, int[] scores,
181            int maxWordLength, int maxBigrams, int maxAlternatives);
182
183    private final void loadDictionary(String path, long startOffset, long length) {
184        mNativeDict = openNative(path, startOffset, length,
185                    TYPED_LETTER_MULTIPLIER, FULL_WORD_SCORE_MULTIPLIER,
186                    MAX_WORD_LENGTH, MAX_WORDS, MAX_PROXIMITY_CHARS_SIZE);
187        mDictLength = length;
188    }
189
190    @Override
191    public void getBigrams(final WordComposer codes, final CharSequence previousWord,
192            final WordCallback callback) {
193        if (mNativeDict == 0) return;
194
195        char[] chars = previousWord.toString().toCharArray();
196        Arrays.fill(mOutputChars_bigrams, (char) 0);
197        Arrays.fill(mBigramScores, 0);
198
199        int codesSize = codes.size();
200        Arrays.fill(mInputCodes, -1);
201        int[] alternatives = codes.getCodesAt(0);
202        System.arraycopy(alternatives, 0, mInputCodes, 0,
203                Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
204
205        int count = getBigramsNative(mNativeDict, chars, chars.length, mInputCodes, codesSize,
206                mOutputChars_bigrams, mBigramScores, MAX_WORD_LENGTH, MAX_BIGRAMS,
207                MAX_PROXIMITY_CHARS_SIZE);
208
209        for (int j = 0; j < count; ++j) {
210            if (mBigramScores[j] < 1) break;
211            final int start = j * MAX_WORD_LENGTH;
212            int len = 0;
213            while (len <  MAX_WORD_LENGTH && mOutputChars_bigrams[start + len] != 0) {
214                ++len;
215            }
216            if (len > 0) {
217                callback.addWord(mOutputChars_bigrams, start, len, mBigramScores[j],
218                        mDicTypeId, DataType.BIGRAM);
219            }
220        }
221    }
222
223    @Override
224    public void getWords(final WordComposer codes, final WordCallback callback) {
225        final int count = getSuggestions(codes, mKeyboardSwitcher.getLatinKeyboard(),
226                mOutputChars, mScores);
227
228        for (int j = 0; j < count; ++j) {
229            if (mScores[j] < 1) break;
230            final int start = j * MAX_WORD_LENGTH;
231            int len = 0;
232            while (len < MAX_WORD_LENGTH && mOutputChars[start + len] != 0) {
233                ++len;
234            }
235            if (len > 0) {
236                callback.addWord(mOutputChars, start, len, mScores[j], mDicTypeId,
237                        DataType.UNIGRAM);
238            }
239        }
240    }
241
242    /* package for test */ boolean isValidDictionary() {
243        return mNativeDict != 0;
244    }
245
246    /* package for test */ int getSuggestions(final WordComposer codes, final Keyboard keyboard,
247            char[] outputChars, int[] scores) {
248        if (!isValidDictionary()) return -1;
249
250        final int codesSize = codes.size();
251        // Won't deal with really long words.
252        if (codesSize > MAX_WORD_LENGTH - 1) return -1;
253
254        Arrays.fill(mInputCodes, WordComposer.NOT_A_CODE);
255        for (int i = 0; i < codesSize; i++) {
256            int[] alternatives = codes.getCodesAt(i);
257            System.arraycopy(alternatives, 0, mInputCodes, i * MAX_PROXIMITY_CHARS_SIZE,
258                    Math.min(alternatives.length, MAX_PROXIMITY_CHARS_SIZE));
259        }
260        Arrays.fill(outputChars, (char) 0);
261        Arrays.fill(scores, 0);
262
263        return getSuggestionsNative(
264                mNativeDict, keyboard.getProximityInfo(),
265                codes.getXCoordinates(), codes.getYCoordinates(), mInputCodes, codesSize,
266                mFlags, outputChars, scores);
267    }
268
269    @Override
270    public boolean isValidWord(CharSequence word) {
271        if (word == null) return false;
272        char[] chars = word.toString().toCharArray();
273        return isValidWordNative(mNativeDict, chars, chars.length);
274    }
275
276    public long getSize() {
277        return mDictLength; // This value is initialized in loadDictionary()
278    }
279
280    @Override
281    public synchronized void close() {
282        closeInternal();
283    }
284
285    private void closeInternal() {
286        if (mNativeDict != 0) {
287            closeNative(mNativeDict);
288            mNativeDict = 0;
289            mDictLength = 0;
290        }
291    }
292
293    @Override
294    protected void finalize() throws Throwable {
295        try {
296            closeInternal();
297        } finally {
298            super.finalize();
299        }
300    }
301}
302