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