1/*
2 * Copyright (C) 2010 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 android.content.Context;
20import android.text.TextUtils;
21import android.util.Log;
22import com.android.inputmethod.latin.Suggest;
23import com.android.inputmethod.latin.UserBigramDictionary;
24import com.android.inputmethod.latin.WordComposer;
25
26import java.io.IOException;
27import java.io.InputStream;
28import java.nio.ByteBuffer;
29import java.nio.ByteOrder;
30import java.nio.channels.Channels;
31import java.util.List;
32import java.util.Locale;
33import java.util.StringTokenizer;
34
35public class SuggestHelper {
36    private Suggest mSuggest;
37    private UserBigramDictionary mUserBigram;
38    private final String TAG;
39
40    /** Uses main dictionary only **/
41    public SuggestHelper(String tag, Context context, int[] resId) {
42        TAG = tag;
43        InputStream[] is = null;
44        try {
45            // merging separated dictionary into one if dictionary is separated
46            int total = 0;
47            is = new InputStream[resId.length];
48            for (int i = 0; i < resId.length; i++) {
49                is[i] = context.getResources().openRawResource(resId[i]);
50                total += is[i].available();
51            }
52
53            ByteBuffer byteBuffer =
54                ByteBuffer.allocateDirect(total).order(ByteOrder.nativeOrder());
55            int got = 0;
56            for (int i = 0; i < resId.length; i++) {
57                 got += Channels.newChannel(is[i]).read(byteBuffer);
58            }
59            if (got != total) {
60                Log.w(TAG, "Read " + got + " bytes, expected " + total);
61            } else {
62                mSuggest = new Suggest(context, byteBuffer);
63                Log.i(TAG, "Created mSuggest " + total + " bytes");
64            }
65        } catch (IOException e) {
66            Log.w(TAG, "No available memory for binary dictionary");
67        } finally {
68            try {
69                if (is != null) {
70                    for (int i = 0; i < is.length; i++) {
71                        is[i].close();
72                    }
73                }
74            } catch (IOException e) {
75                Log.w(TAG, "Failed to close input stream");
76            }
77        }
78        mSuggest.setAutoTextEnabled(false);
79        mSuggest.setCorrectionMode(Suggest.CORRECTION_FULL_BIGRAM);
80    }
81
82    /** Uses both main dictionary and user-bigram dictionary **/
83    public SuggestHelper(String tag, Context context, int[] resId, int userBigramMax,
84            int userBigramDelete) {
85        this(tag, context, resId);
86        mUserBigram = new UserBigramDictionary(context, null, Locale.US.toString(),
87                Suggest.DIC_USER);
88        mUserBigram.setDatabaseMax(userBigramMax);
89        mUserBigram.setDatabaseDelete(userBigramDelete);
90        mSuggest.setUserBigramDictionary(mUserBigram);
91    }
92
93    void changeUserBigramLocale(Context context, Locale locale) {
94        if (mUserBigram != null) {
95            flushUserBigrams();
96            mUserBigram.close();
97            mUserBigram = new UserBigramDictionary(context, null, locale.toString(),
98                    Suggest.DIC_USER);
99            mSuggest.setUserBigramDictionary(mUserBigram);
100        }
101    }
102
103    private WordComposer createWordComposer(CharSequence s) {
104        WordComposer word = new WordComposer();
105        for (int i = 0; i < s.length(); i++) {
106            final char c = s.charAt(i);
107            int[] codes;
108            // If it's not a lowercase letter, don't find adjacent letters
109            if (c < 'a' || c > 'z') {
110                codes = new int[] { c };
111            } else {
112                codes = adjacents[c - 'a'];
113            }
114            word.add(c, codes);
115        }
116        return word;
117    }
118
119    private void showList(String title, List<CharSequence> suggestions) {
120        Log.i(TAG, title);
121        for (int i = 0; i < suggestions.size(); i++) {
122            Log.i(title, suggestions.get(i) + ", ");
123        }
124    }
125
126    private boolean isDefaultSuggestion(List<CharSequence> suggestions, CharSequence word) {
127        // Check if either the word is what you typed or the first alternative
128        return suggestions.size() > 0 &&
129                (/*TextUtils.equals(suggestions.get(0), word) || */
130                  (suggestions.size() > 1 && TextUtils.equals(suggestions.get(1), word)));
131    }
132
133    boolean isDefaultSuggestion(CharSequence typed, CharSequence expected) {
134        WordComposer word = createWordComposer(typed);
135        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, null);
136        return isDefaultSuggestion(suggestions, expected);
137    }
138
139    boolean isDefaultCorrection(CharSequence typed, CharSequence expected) {
140        WordComposer word = createWordComposer(typed);
141        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, null);
142        return isDefaultSuggestion(suggestions, expected) && mSuggest.hasMinimalCorrection();
143    }
144
145    boolean isASuggestion(CharSequence typed, CharSequence expected) {
146        WordComposer word = createWordComposer(typed);
147        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, null);
148        for (int i = 1; i < suggestions.size(); i++) {
149            if (TextUtils.equals(suggestions.get(i), expected)) return true;
150        }
151        return false;
152    }
153
154    private void getBigramSuggestions(CharSequence previous, CharSequence typed) {
155        if (!TextUtils.isEmpty(previous) && (typed.length() > 1)) {
156            WordComposer firstChar = createWordComposer(Character.toString(typed.charAt(0)));
157            mSuggest.getSuggestions(null, firstChar, false, previous);
158        }
159    }
160
161    boolean isDefaultNextSuggestion(CharSequence previous, CharSequence typed,
162            CharSequence expected) {
163        WordComposer word = createWordComposer(typed);
164        getBigramSuggestions(previous, typed);
165        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, previous);
166        return isDefaultSuggestion(suggestions, expected);
167    }
168
169    boolean isDefaultNextCorrection(CharSequence previous, CharSequence typed,
170            CharSequence expected) {
171        WordComposer word = createWordComposer(typed);
172        getBigramSuggestions(previous, typed);
173        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, previous);
174        return isDefaultSuggestion(suggestions, expected) && mSuggest.hasMinimalCorrection();
175    }
176
177    boolean isASuggestion(CharSequence previous, CharSequence typed,
178            CharSequence expected) {
179        WordComposer word = createWordComposer(typed);
180        getBigramSuggestions(previous, typed);
181        List<CharSequence> suggestions = mSuggest.getSuggestions(null, word, false, previous);
182        for (int i = 1; i < suggestions.size(); i++) {
183            if (TextUtils.equals(suggestions.get(i), expected)) return true;
184        }
185        return false;
186    }
187
188    boolean isValid(CharSequence typed) {
189        return mSuggest.isValidWord(typed);
190    }
191
192    boolean isUserBigramSuggestion(CharSequence previous, char typed,
193           CharSequence expected) {
194        WordComposer word = createWordComposer(Character.toString(typed));
195
196        if (mUserBigram == null) return false;
197
198        flushUserBigrams();
199        if (!TextUtils.isEmpty(previous) && !TextUtils.isEmpty(Character.toString(typed))) {
200            WordComposer firstChar = createWordComposer(Character.toString(typed));
201            mSuggest.getSuggestions(null, firstChar, false, previous);
202            boolean reloading = mUserBigram.reloadDictionaryIfRequired();
203            if (reloading) mUserBigram.waitForDictionaryLoading();
204            mUserBigram.getBigrams(firstChar, previous, mSuggest, null);
205        }
206
207        List<CharSequence> suggestions = mSuggest.mBigramSuggestions;
208        for (int i = 0; i < suggestions.size(); i++) {
209            if (TextUtils.equals(suggestions.get(i), expected)) return true;
210        }
211
212        return false;
213    }
214
215    void addToUserBigram(String sentence) {
216        StringTokenizer st = new StringTokenizer(sentence);
217        String previous = null;
218        while (st.hasMoreTokens()) {
219            String current = st.nextToken();
220            if (previous != null) {
221                addToUserBigram(new String[] {previous, current});
222            }
223            previous = current;
224        }
225    }
226
227    void addToUserBigram(String[] pair) {
228        if (mUserBigram != null && pair.length == 2) {
229            mUserBigram.addBigrams(pair[0], pair[1]);
230        }
231    }
232
233    void flushUserBigrams() {
234        if (mUserBigram != null) {
235            mUserBigram.flushPendingWrites();
236            mUserBigram.waitUntilUpdateDBDone();
237        }
238    }
239
240    final int[][] adjacents = {
241                               {'a','s','w','q',-1},
242                               {'b','h','v','n','g','j',-1},
243                               {'c','v','f','x','g',},
244                               {'d','f','r','e','s','x',-1},
245                               {'e','w','r','s','d',-1},
246                               {'f','g','d','c','t','r',-1},
247                               {'g','h','f','y','t','v',-1},
248                               {'h','j','u','g','b','y',-1},
249                               {'i','o','u','k',-1},
250                               {'j','k','i','h','u','n',-1},
251                               {'k','l','o','j','i','m',-1},
252                               {'l','k','o','p',-1},
253                               {'m','k','n','l',-1},
254                               {'n','m','j','k','b',-1},
255                               {'o','p','i','l',-1},
256                               {'p','o',-1},
257                               {'q','w',-1},
258                               {'r','t','e','f',-1},
259                               {'s','d','e','w','a','z',-1},
260                               {'t','y','r',-1},
261                               {'u','y','i','h','j',-1},
262                               {'v','b','g','c','h',-1},
263                               {'w','e','q',-1},
264                               {'x','c','d','z','f',-1},
265                               {'y','u','t','h','g',-1},
266                               {'z','s','x','a','d',-1},
267                              };
268}
269