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