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 
17 package jp.co.omronsoft.openwnn.JAJP;
18 
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Arrays;
24 
25 import jp.co.omronsoft.openwnn.CandidateFilter;
26 import jp.co.omronsoft.openwnn.ComposingText;
27 import jp.co.omronsoft.openwnn.OpenWnn;
28 import jp.co.omronsoft.openwnn.OpenWnnDictionaryImpl;
29 import jp.co.omronsoft.openwnn.StrSegmentClause;
30 import jp.co.omronsoft.openwnn.WnnClause;
31 import jp.co.omronsoft.openwnn.WnnDictionary;
32 import jp.co.omronsoft.openwnn.WnnEngine;
33 import jp.co.omronsoft.openwnn.WnnSentence;
34 import jp.co.omronsoft.openwnn.WnnWord;
35 import android.content.SharedPreferences;
36 import 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  */
43 public 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      */
OpenWnnEngineJAJP(String writableDictionaryName)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      */
setDictionaryForPrediction(int strlen)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      */
getCandidate(int index)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      */
addCandidate(WnnWord word)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      */
clearCandidates()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      */
setDictionary(int type)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      */
setSearchKey(ComposingText text, int maxLen)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      */
clearPreviousWord()341     public void clearPreviousWord() {
342         mPreviousWord = null;
343     }
344 
345     /**
346      * Set keyboard type.
347      *
348      * @param keyboardType      Type of keyboard
349      */
setKeyboardType(int keyboardType)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      */
setFilter(CandidateFilter filter)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 */
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 */
close()375     public void close() {}
376 
377     /** @see jp.co.omronsoft.openwnn.WnnEngine#predict */
predict(ComposingText text, int minLen, int maxLen)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 */
convert(ComposingText text)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 */
searchWords(String key)469     public int searchWords(String key) {
470         clearCandidates();
471         return 0;
472     }
473 
474     /** @see jp.co.omronsoft.openwnn.WnnEngine#searchWords */
searchWords(WnnWord word)475     public int searchWords(WnnWord word) {
476         clearCandidates();
477         return 0;
478     }
479 
480     /** @see jp.co.omronsoft.openwnn.WnnEngine#getNextCandidate */
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 */
learn(WnnWord word)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 */
addWord(WnnWord word)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 */
deleteWord(WnnWord word)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 */
setPreferences(SharedPreferences pref)547     public void setPreferences(SharedPreferences pref) {}
548 
549     /** @see jp.co.omronsoft.openwnn.WnnEngine#breakSequence */
breakSequence()550     public void breakSequence()  {
551         clearPreviousWord();
552     }
553 
554     /** @see jp.co.omronsoft.openwnn.WnnEngine#makeCandidateListOf */
makeCandidateListOf(int clausePosition)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 */
initializeDictionary(int dictionary)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 */
initializeDictionary(int dictionary, int type)588     public boolean initializeDictionary(int dictionary, int type) {
589         return initializeDictionary(dictionary);
590     }
591 
592     /** @see jp.co.omronsoft.openwnn.WnnEngine#getUserDictionaryWords */
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 {
compare(Object object1, Object object2)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