1/*
2 * Copyright (C) 2008,2009  OMRON SOFTWARE Co., Ltd.
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 jp.co.omronsoft.openwnn.JAJP;
18
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.Iterator;
22import java.util.List;
23import java.util.Arrays;
24
25import jp.co.omronsoft.openwnn.CandidateFilter;
26import jp.co.omronsoft.openwnn.ComposingText;
27import jp.co.omronsoft.openwnn.OpenWnn;
28import jp.co.omronsoft.openwnn.OpenWnnDictionaryImpl;
29import jp.co.omronsoft.openwnn.StrSegmentClause;
30import jp.co.omronsoft.openwnn.WnnClause;
31import jp.co.omronsoft.openwnn.WnnDictionary;
32import jp.co.omronsoft.openwnn.WnnEngine;
33import jp.co.omronsoft.openwnn.WnnSentence;
34import jp.co.omronsoft.openwnn.WnnWord;
35import android.content.SharedPreferences;
36import android.util.Log;
37
38/**
39 * The OpenWnn engine class for Japanese IME.
40 *
41 * @author Copyright (C) 2009 OMRON SOFTWARE CO., LTD.  All Rights Reserved.
42 */
43public class OpenWnnEngineJAJP implements WnnEngine {
44    /** Current dictionary type */
45    private int mDictType = DIC_LANG_INIT;
46    /** Dictionary type (default) */
47    public static final int DIC_LANG_INIT = 0;
48    /** Dictionary type (Japanese standard) */
49    public static final int DIC_LANG_JP = 0;
50    /** Dictionary type (English standard) */
51    public static final int DIC_LANG_EN = 1;
52    /** Dictionary type (Japanese person's name) */
53    public static final int DIC_LANG_JP_PERSON_NAME = 2;
54    /** Dictionary type (User dictionary) */
55    public static final int DIC_USERDIC = 3;
56    /** Dictionary type (Japanese EISU-KANA conversion) */
57    public static final int DIC_LANG_JP_EISUKANA = 4;
58    /** Dictionary type (e-mail/URI) */
59    public static final int DIC_LANG_EN_EMAIL_ADDRESS = 5;
60    /** Dictionary type (Japanese postal address) */
61    public static final int DIC_LANG_JP_POSTAL_ADDRESS = 6;
62
63    /** Type of the keyboard */
64    private int mKeyboardType = KEYBOARD_UNDEF;
65    /** Keyboard type (not defined) */
66    public static final int KEYBOARD_UNDEF = 0;
67    /** Keyboard type (12-keys) */
68    public static final int KEYBOARD_KEYPAD12 = 1;
69    /** Keyboard type (Qwerty) */
70    public static final int KEYBOARD_QWERTY = 2;
71
72    /** Score(frequency value) of word in the learning dictionary */
73    public static final int FREQ_LEARN = 600;
74    /** Score(frequency value) of word in the user dictionary */
75    public static final int FREQ_USER = 500;
76
77    /** Maximum limit length of output */
78    public static final int MAX_OUTPUT_LENGTH = 50;
79    /** Limitation of predicted candidates */
80    public static final int PREDICT_LIMIT = 100;
81
82    /** OpenWnn dictionary */
83    private WnnDictionary mDictionaryJP;
84
85    /** Word list */
86    private ArrayList<WnnWord> mConvResult;
87
88    /** HashMap for checking duplicate word */
89    private HashMap<String, WnnWord> mCandTable;
90
91    /** Input string (Hiragana) */
92    private String mInputHiragana;
93
94    /** Input string (Romaji) */
95    private String mInputRomaji;
96
97    /** Number of output candidates */
98    private int mOutputNum;
99
100    /**
101     * Where to get the next candidates from.<br>
102     * (0:prefix search from the dictionary, 1:single clause converter, 2:Kana converter)
103     */
104    private int mGetCandidateFrom;
105
106    /** Previously selected word */
107    private WnnWord mPreviousWord;
108
109    /** Converter for single/consecutive clause conversion */
110    private OpenWnnClauseConverterJAJP mClauseConverter;
111
112    /** Kana converter (for EISU-KANA conversion) */
113    private KanaConverter mKanaConverter;
114
115    /** Whether exact match search or prefix match search */
116    private boolean mExactMatchMode;
117
118    /** Whether displaying single clause candidates or not */
119    private boolean mSingleClauseMode;
120
121    /** A result of consecutive clause conversion */
122    private WnnSentence mConvertSentence;
123
124    /** The candidate filter */
125    private CandidateFilter mFilter = null;
126
127    /**
128     * Constructor
129     *
130     * @param writableDictionaryName    Writable dictionary file name(null if not use)
131     */
132    public OpenWnnEngineJAJP(String writableDictionaryName) {
133        /* load Japanese dictionary library */
134        mDictionaryJP = new OpenWnnDictionaryImpl(
135                "/data/data/jp.co.omronsoft.openwnn/lib/libWnnJpnDic.so",
136                writableDictionaryName );
137        if (!mDictionaryJP.isActive()) {
138            mDictionaryJP = new OpenWnnDictionaryImpl(
139                    "/system/lib/libWnnJpnDic.so",
140                    writableDictionaryName );
141        }
142
143        /* clear dictionary settings */
144        mDictionaryJP.clearDictionary();
145        mDictionaryJP.clearApproxPattern();
146        mDictionaryJP.setInUseState(false);
147
148        /* work buffers */
149        mConvResult = new ArrayList<WnnWord>();
150        mCandTable = new HashMap<String, WnnWord>();
151
152        /* converters */
153        mClauseConverter = new OpenWnnClauseConverterJAJP();
154        mKanaConverter = new KanaConverter();
155    }
156
157    /**
158     * Set dictionary for prediction.
159     *
160     * @param strlen        Length of input string
161     */
162    private void setDictionaryForPrediction(int strlen) {
163        WnnDictionary dict = mDictionaryJP;
164
165        dict.clearDictionary();
166
167        if (mDictType != DIC_LANG_JP_EISUKANA) {
168            dict.clearApproxPattern();
169            if (strlen == 0) {
170                dict.setDictionary(2, 245, 245);
171                dict.setDictionary(3, 100, 244);
172
173                dict.setDictionary(WnnDictionary.INDEX_LEARN_DICTIONARY, FREQ_LEARN, FREQ_LEARN);
174            } else {
175                dict.setDictionary(0, 100, 400);
176                if (strlen > 1) {
177                    dict.setDictionary(1, 100, 400);
178                }
179                dict.setDictionary(2, 245, 245);
180                dict.setDictionary(3, 100, 244);
181
182                dict.setDictionary(WnnDictionary.INDEX_USER_DICTIONARY, FREQ_USER, FREQ_USER);
183                dict.setDictionary(WnnDictionary.INDEX_LEARN_DICTIONARY, FREQ_LEARN, FREQ_LEARN);
184                if (mKeyboardType != KEYBOARD_QWERTY) {
185                    dict.setApproxPattern(WnnDictionary.APPROX_PATTERN_JAJP_12KEY_NORMAL);
186                }
187            }
188        }
189    }
190
191    /**
192     * Get a candidate.
193     *
194     * @param index     Index of a candidate.
195     * @return          The candidate; {@code null} if there is no candidate.
196     */
197    private WnnWord getCandidate(int index) {
198        WnnWord word;
199
200        if (mGetCandidateFrom == 0) {
201            if (mDictType == OpenWnnEngineJAJP.DIC_LANG_JP_EISUKANA) {
202                /* skip to Kana conversion if EISU-KANA conversion mode */
203                mGetCandidateFrom = 2;
204            } else if (mSingleClauseMode) {
205                /* skip to single clause conversion if single clause conversion mode */
206                mGetCandidateFrom = 1;
207            } else {
208                if (mConvResult.size() < PREDICT_LIMIT) {
209                    /* get prefix matching words from the dictionaries */
210                    while (index >= mConvResult.size()) {
211                        if ((word = mDictionaryJP.getNextWord()) == null) {
212                            mGetCandidateFrom = 1;
213                            break;
214                        }
215                        if (!mExactMatchMode || mInputHiragana.equals(word.stroke)) {
216                            addCandidate(word);
217                            if (mConvResult.size() >= PREDICT_LIMIT) {
218                                mGetCandidateFrom = 1;
219                                break;
220                            }
221                        }
222                    }
223                } else {
224                    mGetCandidateFrom = 1;
225                }
226            }
227        }
228
229        /* get candidates by single clause conversion */
230        if (mGetCandidateFrom == 1) {
231            Iterator<?> convResult = mClauseConverter.convert(mInputHiragana);
232            if (convResult != null) {
233                while (convResult.hasNext()) {
234                    addCandidate((WnnWord)convResult.next());
235                }
236            }
237            /* end of candidates by single clause conversion */
238            mGetCandidateFrom = 2;
239        }
240
241        /* get candidates from Kana converter */
242        if (mGetCandidateFrom == 2) {
243            List<WnnWord> addCandidateList
244            = mKanaConverter.createPseudoCandidateList(mInputHiragana, mInputRomaji, mKeyboardType);
245
246            Iterator<WnnWord> it = addCandidateList.iterator();
247            while(it.hasNext()) {
248                addCandidate(it.next());
249            }
250
251            mGetCandidateFrom = 3;
252        }
253
254        if (index >= mConvResult.size()) {
255            return null;
256        }
257        return (WnnWord)mConvResult.get(index);
258    }
259
260    /**
261     * Add a candidate to the conversion result buffer.
262     * <br>
263     * This method adds a word to the result buffer if there is not
264     * the same one in the buffer and the length of the candidate
265     * string is not longer than {@code MAX_OUTPUT_LENGTH}.
266     *
267     * @param word      A word to be add
268     * @return          {@code true} if the word added; {@code false} if not.
269     */
270    private boolean addCandidate(WnnWord word) {
271        if (word.candidate == null || mCandTable.containsKey(word.candidate)
272                || word.candidate.length() > MAX_OUTPUT_LENGTH) {
273            return false;
274        }
275        if (mFilter != null && !mFilter.isAllowed(word)) {
276            return false;
277        }
278        mCandTable.put(word.candidate, word);
279        mConvResult.add(word);
280        return true;
281    }
282
283    /**
284     * Clear work area that hold candidates information.
285     */
286    private void clearCandidates() {
287        mConvResult.clear();
288        mCandTable.clear();
289        mOutputNum = 0;
290        mInputHiragana = null;
291        mInputRomaji = null;
292        mGetCandidateFrom = 0;
293        mSingleClauseMode = false;
294    }
295
296    /**
297     * Set dictionary type.
298     *
299     * @param type      Type of dictionary
300     * @return          {@code true} if the dictionary is changed; {@code false} if not.
301     */
302    public boolean setDictionary(int type) {
303        mDictType = type;
304        return true;
305    }
306
307    /**
308     * Set the search key and the search mode from {@link ComposingText}.
309     *
310     * @param text      Input text
311     * @param maxLen    Maximum length to convert
312     * @return          Length of the search key
313     */
314    private int setSearchKey(ComposingText text, int maxLen) {
315        String input = text.toString(ComposingText.LAYER1);
316        if (0 <= maxLen && maxLen <= input.length()) {
317            input = input.substring(0, maxLen);
318            mExactMatchMode = true;
319        } else {
320            mExactMatchMode = false;
321        }
322
323        if (input.length() == 0) {
324            mInputHiragana = "";
325            mInputRomaji = "";
326            return 0;
327        }
328
329        mInputHiragana = input;
330        mInputRomaji = text.toString(ComposingText.LAYER0);
331
332        return input.length();
333    }
334
335    /**
336     * Clear the previous word's information.
337     */
338    public void clearPreviousWord() {
339        mPreviousWord = null;
340    }
341
342    /**
343     * Set keyboard type.
344     *
345     * @param keyboardType      Type of keyboard
346     */
347    public void setKeyboardType(int keyboardType) {
348        mKeyboardType = keyboardType;
349    }
350
351    /**
352     * Set the candidate filter
353     *
354     * @param filter    The candidate filter
355     */
356    public void setFilter(CandidateFilter filter) {
357        mFilter = filter;
358        mClauseConverter.setFilter(filter);
359    }
360
361    /***********************************************************************
362     * WnnEngine's interface
363     **********************************************************************/
364    /** @see jp.co.omronsoft.openwnn.WnnEngine#init */
365    public void init() {
366        clearPreviousWord();
367        mClauseConverter.setDictionary(mDictionaryJP);
368        mKanaConverter.setDictionary(mDictionaryJP);
369    }
370
371    /** @see jp.co.omronsoft.openwnn.WnnEngine#close */
372    public void close() {}
373
374    /** @see jp.co.omronsoft.openwnn.WnnEngine#predict */
375    public int predict(ComposingText text, int minLen, int maxLen) {
376        clearCandidates();
377        if (text == null) { return 0; }
378
379        /* set mInputHiragana and mInputRomaji */
380        int len = setSearchKey(text, maxLen);
381
382        /* set dictionaries by the length of input */
383        setDictionaryForPrediction(len);
384
385        /* search dictionaries */
386        mDictionaryJP.setInUseState( true );
387
388        if (len == 0) {
389            /* search by previously selected word */
390            return mDictionaryJP.searchWord(WnnDictionary.SEARCH_LINK, WnnDictionary.ORDER_BY_FREQUENCY,
391                                            mInputHiragana, mPreviousWord);
392        } else {
393            if (mExactMatchMode) {
394                /* exact matching */
395                mDictionaryJP.searchWord(WnnDictionary.SEARCH_EXACT, WnnDictionary.ORDER_BY_FREQUENCY,
396                                         mInputHiragana);
397            } else {
398                /* prefix matching */
399                mDictionaryJP.searchWord(WnnDictionary.SEARCH_PREFIX, WnnDictionary.ORDER_BY_FREQUENCY,
400                                         mInputHiragana);
401            }
402            return 1;
403        }
404    }
405
406    /** @see jp.co.omronsoft.openwnn.WnnEngine#convert */
407    public int convert(ComposingText text) {
408        clearCandidates();
409
410        if (text == null) {
411            return 0;
412        }
413
414        mDictionaryJP.setInUseState( true );
415
416        int cursor = text.getCursor(ComposingText.LAYER1);
417        String input;
418        WnnClause head = null;
419        if (cursor > 0) {
420            /* convert previous part from cursor */
421            input = text.toString(ComposingText.LAYER1, 0, cursor - 1);
422            Iterator headCandidates = mClauseConverter.convert(input);
423            if ((headCandidates == null) || (!headCandidates.hasNext())) {
424                return 0;
425            }
426            head = new WnnClause(input, (WnnWord)headCandidates.next());
427
428            /* set the rest of input string */
429            input = text.toString(ComposingText.LAYER1, cursor, text.size(ComposingText.LAYER1) - 1);
430        } else {
431            /* set whole of input string */
432            input = text.toString(ComposingText.LAYER1);
433        }
434
435        WnnSentence sentence = null;
436        if (input.length() != 0) {
437            sentence = mClauseConverter.consecutiveClauseConvert(input);
438        }
439        if (head != null) {
440            sentence = new WnnSentence(head, sentence);
441        }
442        if (sentence == null) {
443            return 0;
444        }
445
446        StrSegmentClause[] ss = new StrSegmentClause[sentence.elements.size()];
447        int pos = 0;
448        int idx = 0;
449        Iterator<WnnClause> it = sentence.elements.iterator();
450        while(it.hasNext()) {
451            WnnClause clause = (WnnClause)it.next();
452            int len = clause.stroke.length();
453            ss[idx] = new StrSegmentClause(clause, pos, pos + len - 1);
454            pos += len;
455            idx += 1;
456        }
457        text.setCursor(ComposingText.LAYER2, text.size(ComposingText.LAYER2));
458        text.replaceStrSegment(ComposingText.LAYER2, ss,
459                               text.getCursor(ComposingText.LAYER2));
460        mConvertSentence = sentence;
461
462        return 0;
463    }
464
465    /** @see jp.co.omronsoft.openwnn.WnnEngine#searchWords */
466    public int searchWords(String key) {
467        clearCandidates();
468        return 0;
469    }
470
471    /** @see jp.co.omronsoft.openwnn.WnnEngine#searchWords */
472    public int searchWords(WnnWord word) {
473        clearCandidates();
474        return 0;
475    }
476
477    /** @see jp.co.omronsoft.openwnn.WnnEngine#getNextCandidate */
478    public WnnWord getNextCandidate() {
479        if (mInputHiragana == null) {
480            return null;
481        }
482        WnnWord word = getCandidate(mOutputNum);
483        if (word != null) {
484            mOutputNum++;
485        }
486        return word;
487    }
488
489    /** @see jp.co.omronsoft.openwnn.WnnEngine#learn */
490    public boolean learn(WnnWord word) {
491        int ret = -1;
492        if (word.partOfSpeech.right == 0) {
493            word.partOfSpeech = mDictionaryJP.getPOS(WnnDictionary.POS_TYPE_MEISI);
494        }
495
496        WnnDictionary dict = mDictionaryJP;
497        if (word instanceof WnnSentence) {
498            Iterator<WnnClause> clauses = ((WnnSentence)word).elements.iterator();
499            while (clauses.hasNext()) {
500                WnnWord wd = clauses.next();
501                if (mPreviousWord != null) {
502                    ret = dict.learnWord(wd, mPreviousWord);
503                } else {
504                    ret = dict.learnWord(wd);
505                }
506                mPreviousWord = wd;
507                if (ret != 0) {
508                    break;
509                }
510            }
511        } else {
512            if (mPreviousWord != null) {
513                ret = dict.learnWord(word, mPreviousWord);
514            } else {
515                ret = dict.learnWord(word);
516            }
517            mPreviousWord = word;
518            mClauseConverter.setDictionary(dict);
519        }
520
521        return (ret == 0);
522    }
523
524    /** @see jp.co.omronsoft.openwnn.WnnEngine#addWord */
525    public int addWord(WnnWord word) {
526        mDictionaryJP.setInUseState( true );
527        if (word.partOfSpeech.right == 0) {
528            word.partOfSpeech = mDictionaryJP.getPOS(WnnDictionary.POS_TYPE_MEISI);
529        }
530        mDictionaryJP.addWordToUserDictionary(word);
531        mDictionaryJP.setInUseState( false );
532        return 0;
533    }
534
535    /** @see jp.co.omronsoft.openwnn.WnnEngine#deleteWord */
536    public boolean deleteWord(WnnWord word) {
537        mDictionaryJP.setInUseState( true );
538        mDictionaryJP.removeWordFromUserDictionary(word);
539        mDictionaryJP.setInUseState( false );
540        return false;
541    }
542
543    /** @see jp.co.omronsoft.openwnn.WnnEngine#setPreferences */
544    public void setPreferences(SharedPreferences pref) {}
545
546    /** @see jp.co.omronsoft.openwnn.WnnEngine#breakSequence */
547    public void breakSequence()  {
548        clearPreviousWord();
549    }
550
551    /** @see jp.co.omronsoft.openwnn.WnnEngine#makeCandidateListOf */
552    public int makeCandidateListOf(int clausePosition)  {
553        clearCandidates();
554
555        if ((mConvertSentence == null) || (mConvertSentence.elements.size() <= clausePosition)) {
556            return 0;
557        }
558        mSingleClauseMode = true;
559        WnnClause clause = mConvertSentence.elements.get(clausePosition);
560        mInputHiragana = clause.stroke;
561        mInputRomaji = clause.candidate;
562
563        return 1;
564    }
565
566    /** @see jp.co.omronsoft.openwnn.WnnEngine#initializeDictionary */
567    public boolean initializeDictionary(int dictionary)  {
568        switch( dictionary ) {
569        case WnnEngine.DICTIONARY_TYPE_LEARN:
570            mDictionaryJP.setInUseState( true );
571            mDictionaryJP.clearLearnDictionary();
572            mDictionaryJP.setInUseState( false );
573            return true;
574
575        case WnnEngine.DICTIONARY_TYPE_USER:
576            mDictionaryJP.setInUseState( true );
577            mDictionaryJP.clearUserDictionary();
578            mDictionaryJP.setInUseState( false );
579            return true;
580        }
581        return false;
582    }
583
584    /** @see jp.co.omronsoft.openwnn.WnnEngine#initializeDictionary */
585    public boolean initializeDictionary(int dictionary, int type) {
586        return initializeDictionary(dictionary);
587    }
588
589    /** @see jp.co.omronsoft.openwnn.WnnEngine#getUserDictionaryWords */
590    public WnnWord[] getUserDictionaryWords( ) {
591        /* get words in the user dictionary */
592        mDictionaryJP.setInUseState(true);
593        WnnWord[] result = mDictionaryJP.getUserDictionaryWords( );
594        mDictionaryJP.setInUseState(false);
595
596        /* sort the array of words */
597        Arrays.sort(result, new WnnWordComparator());
598
599        return result;
600    }
601
602    /* {@link WnnWord} comparator for listing up words in the user dictionary */
603    private class WnnWordComparator implements java.util.Comparator {
604        public int compare(Object object1, Object object2) {
605            WnnWord wnnWord1 = (WnnWord) object1;
606            WnnWord wnnWord2 = (WnnWord) object2;
607            return wnnWord1.stroke.compareTo(wnnWord2.stroke);
608        }
609    }
610}
611