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 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 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 /** 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