BinaryDictionary.java revision 5bf1be71629607e7206e6203489cf742d2f8ed79
1/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of 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,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.inputmethod.latin;
18
19import android.text.TextUtils;
20import android.util.SparseArray;
21
22import com.android.inputmethod.keyboard.ProximityInfo;
23import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
24import com.android.inputmethod.latin.utils.AdditionalFeaturesSettingUtils;
25import com.android.inputmethod.latin.utils.CollectionUtils;
26import com.android.inputmethod.latin.utils.JniUtils;
27import com.android.inputmethod.latin.utils.StringUtils;
28
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Locale;
32
33/**
34 * Implements a static, compacted, binary dictionary of standard words.
35 */
36public final class BinaryDictionary extends Dictionary {
37    private static final String TAG = BinaryDictionary.class.getSimpleName();
38
39    // Must be equal to MAX_WORD_LENGTH in native/jni/src/defines.h
40    private static final int MAX_WORD_LENGTH = Constants.Dictionary.MAX_WORD_LENGTH;
41    // Must be equal to MAX_RESULTS in native/jni/src/defines.h
42    private static final int MAX_RESULTS = 18;
43
44    private long mNativeDict;
45    private final Locale mLocale;
46    private final int[] mInputCodePoints = new int[MAX_WORD_LENGTH];
47    private final int[] mOutputCodePoints = new int[MAX_WORD_LENGTH * MAX_RESULTS];
48    private final int[] mSpaceIndices = new int[MAX_RESULTS];
49    private final int[] mOutputScores = new int[MAX_RESULTS];
50    private final int[] mOutputTypes = new int[MAX_RESULTS];
51
52    private final NativeSuggestOptions mNativeSuggestOptions = new NativeSuggestOptions();
53
54    private final SparseArray<DicTraverseSession> mDicTraverseSessions =
55            CollectionUtils.newSparseArray();
56
57    // TODO: There should be a way to remove used DicTraverseSession objects from
58    // {@code mDicTraverseSessions}.
59    private DicTraverseSession getTraverseSession(final int traverseSessionId) {
60        synchronized(mDicTraverseSessions) {
61            DicTraverseSession traverseSession = mDicTraverseSessions.get(traverseSessionId);
62            if (traverseSession == null) {
63                traverseSession = mDicTraverseSessions.get(traverseSessionId);
64                if (traverseSession == null) {
65                    traverseSession = new DicTraverseSession(mLocale, mNativeDict);
66                    mDicTraverseSessions.put(traverseSessionId, traverseSession);
67                }
68            }
69            return traverseSession;
70        }
71    }
72
73    /**
74     * Constructor for the binary dictionary. This is supposed to be called from the
75     * dictionary factory.
76     * @param filename the name of the file to read through native code.
77     * @param offset the offset of the dictionary data within the file.
78     * @param length the length of the binary data.
79     * @param useFullEditDistance whether to use the full edit distance in suggestions
80     * @param dictType the dictionary type, as a human-readable string
81     * @param isUpdatable whether to open the dictionary file in writable mode.
82     */
83    public BinaryDictionary(final String filename, final long offset, final long length,
84            final boolean useFullEditDistance, final Locale locale, final String dictType,
85            final boolean isUpdatable) {
86        super(dictType);
87        mLocale = locale;
88        mNativeSuggestOptions.setUseFullEditDistance(useFullEditDistance);
89        loadDictionary(filename, offset, length, isUpdatable);
90    }
91
92    static {
93        JniUtils.loadNativeLibrary();
94    }
95
96    private static native long openNative(String sourceDir, long dictOffset, long dictSize,
97            boolean isUpdatable);
98    private static native void closeNative(long dict);
99    private static native int getProbabilityNative(long dict, int[] word);
100    private static native boolean isValidBigramNative(long dict, int[] word0, int[] word1);
101    private static native int getSuggestionsNative(long dict, long proximityInfo,
102            long traverseSession, int[] xCoordinates, int[] yCoordinates, int[] times,
103            int[] pointerIds, int[] inputCodePoints, int inputSize, int commitPoint,
104            int[] suggestOptions, int[] prevWordCodePointArray,
105            int[] outputCodePoints, int[] outputScores, int[] outputIndices, int[] outputTypes);
106    private static native float calcNormalizedScoreNative(int[] before, int[] after, int score);
107    private static native int editDistanceNative(int[] before, int[] after);
108    private static native void addUnigramWordNative(long dict, int[] word, int probability);
109    private static native void addBigramWordsNative(long dict, int[] word0, int[] word1,
110            int probability);
111    private static native void removeBigramWordsNative(long dict, int[] word0, int[] word1);
112
113    // TODO: Move native dict into session
114    private final void loadDictionary(final String path, final long startOffset,
115            final long length, final boolean isUpdatable) {
116        mNativeDict = openNative(path, startOffset, length, isUpdatable);
117    }
118
119    @Override
120    public ArrayList<SuggestedWordInfo> getSuggestions(final WordComposer composer,
121            final String prevWord, final ProximityInfo proximityInfo,
122            final boolean blockOffensiveWords) {
123        return getSuggestionsWithSessionId(composer, prevWord, proximityInfo, blockOffensiveWords,
124                0 /* sessionId */);
125    }
126
127    @Override
128    public ArrayList<SuggestedWordInfo> getSuggestionsWithSessionId(final WordComposer composer,
129            final String prevWord, final ProximityInfo proximityInfo,
130            final boolean blockOffensiveWords, final int sessionId) {
131        if (!isValidDictionary()) return null;
132
133        Arrays.fill(mInputCodePoints, Constants.NOT_A_CODE);
134        // TODO: toLowerCase in the native code
135        final int[] prevWordCodePointArray = (null == prevWord)
136                ? null : StringUtils.toCodePointArray(prevWord);
137        final int composerSize = composer.size();
138
139        final boolean isGesture = composer.isBatchMode();
140        if (composerSize <= 1 || !isGesture) {
141            if (composerSize > MAX_WORD_LENGTH - 1) return null;
142            for (int i = 0; i < composerSize; i++) {
143                mInputCodePoints[i] = composer.getCodeAt(i);
144            }
145        }
146
147        final InputPointers ips = composer.getInputPointers();
148        final int inputSize = isGesture ? ips.getPointerSize() : composerSize;
149        mNativeSuggestOptions.setIsGesture(isGesture);
150        mNativeSuggestOptions.setAdditionalFeaturesOptions(
151                AdditionalFeaturesSettingUtils.getAdditionalNativeSuggestOptions());
152        // proximityInfo and/or prevWordForBigrams may not be null.
153        final int count = getSuggestionsNative(mNativeDict, proximityInfo.getNativeProximityInfo(),
154                getTraverseSession(sessionId).getSession(), ips.getXCoordinates(),
155                ips.getYCoordinates(), ips.getTimes(), ips.getPointerIds(), mInputCodePoints,
156                inputSize, 0 /* commitPoint */, mNativeSuggestOptions.getOptions(),
157                prevWordCodePointArray, mOutputCodePoints, mOutputScores, mSpaceIndices,
158                mOutputTypes);
159        final ArrayList<SuggestedWordInfo> suggestions = CollectionUtils.newArrayList();
160        for (int j = 0; j < count; ++j) {
161            final int start = j * MAX_WORD_LENGTH;
162            int len = 0;
163            while (len < MAX_WORD_LENGTH && mOutputCodePoints[start + len] != 0) {
164                ++len;
165            }
166            if (len > 0) {
167                final int flags = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_FLAGS;
168                if (blockOffensiveWords
169                        && 0 != (flags & SuggestedWordInfo.KIND_FLAG_POSSIBLY_OFFENSIVE)
170                        && 0 == (flags & SuggestedWordInfo.KIND_FLAG_EXACT_MATCH)) {
171                    // If we block potentially offensive words, and if the word is possibly
172                    // offensive, then we don't output it unless it's also an exact match.
173                    continue;
174                }
175                final int kind = mOutputTypes[j] & SuggestedWordInfo.KIND_MASK_KIND;
176                final int score = SuggestedWordInfo.KIND_WHITELIST == kind
177                        ? SuggestedWordInfo.MAX_SCORE : mOutputScores[j];
178                // TODO: check that all users of the `kind' parameter are ready to accept
179                // flags too and pass mOutputTypes[j] instead of kind
180                suggestions.add(new SuggestedWordInfo(new String(mOutputCodePoints, start, len),
181                        score, kind, mDictType));
182            }
183        }
184        return suggestions;
185    }
186
187    public boolean isValidDictionary() {
188        return mNativeDict != 0;
189    }
190
191    public static float calcNormalizedScore(final String before, final String after,
192            final int score) {
193        return calcNormalizedScoreNative(StringUtils.toCodePointArray(before),
194                StringUtils.toCodePointArray(after), score);
195    }
196
197    public static int editDistance(final String before, final String after) {
198        if (before == null || after == null) {
199            throw new IllegalArgumentException();
200        }
201        return editDistanceNative(StringUtils.toCodePointArray(before),
202                StringUtils.toCodePointArray(after));
203    }
204
205    @Override
206    public boolean isValidWord(final String word) {
207        return getFrequency(word) >= 0;
208    }
209
210    @Override
211    public int getFrequency(final String word) {
212        if (word == null) return -1;
213        int[] codePoints = StringUtils.toCodePointArray(word);
214        return getProbabilityNative(mNativeDict, codePoints);
215    }
216
217    // TODO: Add a batch process version (isValidBigramMultiple?) to avoid excessive numbers of jni
218    // calls when checking for changes in an entire dictionary.
219    public boolean isValidBigram(final String word1, final String word2) {
220        if (TextUtils.isEmpty(word1) || TextUtils.isEmpty(word2)) return false;
221        final int[] codePoints1 = StringUtils.toCodePointArray(word1);
222        final int[] codePoints2 = StringUtils.toCodePointArray(word2);
223        return isValidBigramNative(mNativeDict, codePoints1, codePoints2);
224    }
225
226    @Override
227    public void close() {
228        synchronized (mDicTraverseSessions) {
229            final int sessionsSize = mDicTraverseSessions.size();
230            for (int index = 0; index < sessionsSize; ++index) {
231                final DicTraverseSession traverseSession = mDicTraverseSessions.valueAt(index);
232                if (traverseSession != null) {
233                    traverseSession.close();
234                }
235            }
236        }
237        closeInternal();
238    }
239
240    private synchronized void closeInternal() {
241        if (mNativeDict != 0) {
242            closeNative(mNativeDict);
243            mNativeDict = 0;
244        }
245    }
246
247    @Override
248    protected void finalize() throws Throwable {
249        try {
250            closeInternal();
251        } finally {
252            super.finalize();
253        }
254    }
255}
256