/*
org.manalith.ircbot.plugin.keyseqconv/DubeolAutomataEngine.java
ManalithBot - An open source IRC bot based on the PircBot Framework.
Copyright (C) 2012 Seong-ho, Cho <darkcircle.0426@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.manalith.ircbot.plugin.keyseqconv;
import java.text.ParseException;
import java.util.IllegalFormatException;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.manalith.ircbot.plugin.keyseqconv.symboltable.DubeolSymbol;
public class DubeolAutomataEngine implements IAutomataEngine {
private enum LetterState {
Null, IConsonant, Vowel, FConsonant, Finish
}
private boolean enableParseExceptionSyntax;
public DubeolAutomataEngine() {
}
@Override
public void setEnableParsingExceptionSyntax(boolean enable) {
enableParseExceptionSyntax = enable;
}
@Override
public boolean isEnableParsingExceptionSyntax() {
return enableParseExceptionSyntax;
}
@Override
public boolean isISingleConsonant(String tICon) {
try {
@SuppressWarnings("unused")
DubeolSymbol.DubeolISingleConsonant check1 = DubeolSymbol.DubeolISingleConsonant
.valueOf(tICon); // dummy alloc.
return true;
} catch (Exception e)
// if checked character is abnormal
{
return false;
}
}
@Override
public boolean isIDoubleConsonant(String tICon) {
try {
@SuppressWarnings("unused")
DubeolSymbol.DubeolIDoubleConsonant check1 = DubeolSymbol.DubeolIDoubleConsonant
.valueOf(tICon); // dummy alloc.
return true;
} catch (Exception e)
// if checked character is abnormal
{
return false;
}
}
@Override
public boolean isVowel(String tVow) {
try {
@SuppressWarnings("unused")
DubeolSymbol.DubeolVowel check1 = DubeolSymbol.DubeolVowel
.valueOf(tVow); // dummy alloc.
return true;
} catch (Exception e)
// if checked character is abnormal
{
return false;
}
}
@Override
public boolean isFConsonant(String tFCon) {
try {
@SuppressWarnings("unused")
DubeolSymbol.DubeolFConsonant check1 = DubeolSymbol.DubeolFConsonant
.valueOf(tFCon); // dummy alloc.
return true;
} catch (Exception e)
// if checked character is abnormal
{
return false;
}
}
@Override
public String parseKoreanStringToEngSpell(String korean)
throws IllegalFormatException {
String result = "";
for (int i = 0; i < korean.length(); i++) {
int ch = korean.charAt(i);
if (((char) ch) >= '가' && ((char) ch) <= '힣') {
ch -= 0xAC00;
int initialConsonant = ch / (21 * 28);
ch %= (21 * 28);
int Vowel = ch / 28;
ch %= 28;
int finalConsonant = ch;
result += DubeolSymbol.DubeolIConsonant.values()[initialConsonant + 1]
.toString()
+ DubeolSymbol.DubeolVowel.values()[Vowel + 1]
.toString();
if (finalConsonant != 0)
result += DubeolSymbol.DubeolFConsonant.values()[finalConsonant];
} else if (ch >= 'ㄱ' && ch <= 'ㅣ') {
ch -= 0x3130;
result += DubeolSymbol.DubeolSingleLetter.values()[ch - 1];
} else {
result += (char) ch;
}
}
return result;
}
@Override
public String parseKeySequenceToKorean(String keySequence)
throws ParseException, IllegalArgumentException {
String result = "";
LetterState stateFlag = LetterState.Null;
String tICon = "";
String tIConLookahead = "";
String tIConLookaheadCombination = "";
String tVow = "";
String tVowLookahead = "";
String tVowLookaheadCombination = "";
String tFCon = "";
String tFConLookahead = "";
String tFConLookaheadCombination = "";
LetterObject syl = new LetterObject(KeyboardLayout.Dubeol);
if (isEnableParsingExceptionSyntax()
&& StringUtils.countMatches(keySequence, "\\") % 2 == 1)
throw new ParseException("Back slashes do not match",
keySequence.lastIndexOf("\\", 0));
for (int i = 0; i < keySequence.length(); i++) {
if (stateFlag.equals(LetterState.Null)
|| stateFlag.equals(LetterState.Finish)) {
stateFlag = LetterState.IConsonant;
}
if (!CharUtils.isAsciiAlpha(keySequence.charAt(i))) {
if (keySequence.charAt(i) == '\\'
&& isEnableParsingExceptionSyntax()) {
if (i < keySequence.length() - 1)
if (keySequence.charAt(i + 1) == '\\') {
result += "\\";
continue;
}
i++;
while (true) {
if (i + 1 <= keySequence.length() - 1) {
if (keySequence.charAt(i) == '\\') {
if (keySequence.charAt(i + 1) == '\\') {
i++;
result += '\\';
} else
break;
} else {
result += keySequence.charAt(i);
}
} else {
if (keySequence.charAt(i) == '\\') {
break;
} else {
result += keySequence.charAt(i);
}
}
i++;
}
} else {
result += keySequence.charAt(i);
}
continue;
}
// 초성 (자음, 쌍자음)
if (stateFlag.equals(LetterState.IConsonant)) {
// 일단 초기화
tIConLookahead = tIConLookaheadCombination = "";
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
tICon = hasTwoSymbolinOneKey(keySequence.charAt(i)) ? Character
.toString(keySequence.charAt(i)) : Character.toString(
keySequence.charAt(i)).toLowerCase();
// 다음 키 시퀀스랑 조합 분석하기 위한 할당
if (i < keySequence.length() - 1) {
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
tIConLookahead = hasTwoSymbolinOneKey(keySequence
.charAt(i + 1)) ? Character.toString(keySequence
.charAt(i + 1)) : Character.toString(
keySequence.charAt(i + 1)).toLowerCase();
tIConLookaheadCombination = tICon + tIConLookahead;
}
// 받침자음은 첫자음을 포함하며, 수가 더 많음.
// (ㄸ와 ㅃ,ㅉ 같은 경우 제외)
if (isFConsonant(tIConLookaheadCombination)) {
// 2 step - lookahead가 가능하면 try
if (i + 2 <= keySequence.length() - 1) {
String lookOverTwoStep = hasTwoSymbolinOneKey(keySequence
.charAt(i + 2)) ? Character
.toString(keySequence.charAt(i + 2))
: Character.toString(keySequence.charAt(i + 2))
.toLowerCase();
// 자음 두번 입력 후, 자음 혹은 특수문자, 공백, 숫자
if (isISingleConsonant(lookOverTwoStep)
|| isIDoubleConsonant(lookOverTwoStep)
|| !CharUtils.isAsciiAlpha(lookOverTwoStep
.charAt(0))) {
result += getSingleChar(getSingleCharVal(tIConLookaheadCombination));
i++;
}
// 자음 두번 입력 후 모음
else if (isVowel(lookOverTwoStep)) {
// 받침자음의 앞 자는 버리고 뒤 자를 살린다
result += getSingleChar(getSingleCharVal(tICon));
continue;
}
}
// 문장의 마지막에 (받침용) 겹자음 입력했을 경우 출력 후 종료
else {
result += getSingleChar(getSingleCharVal(tIConLookaheadCombination));
stateFlag = LetterState.Null;
break;
}
}
// 쌍자음, 단자음인 경우 ( (받침용) 겹자음 제외)
else {
// 자음이면 대기 슬롯에 넣음
if (isISingleConsonant(tICon) || isIDoubleConsonant(tICon)) {
syl.setIConsonant(tICon);
// init = DubeolSymbol.DubeolIConsonant.valueOf(tICon);
}
// 모음이면 독립모음을 찍기 위해 모음 단계부터 다시 처리
else if (isVowel(tICon)) {
stateFlag = LetterState.Vowel;
i--;
continue;
}
// 문장의 마지막에 단일 자음 입력했을 경우 출력 후 종료
if (i == keySequence.length() - 1) {
result += getSingleChar(getSingleCharVal(tICon));
syl.initLetter();
stateFlag = LetterState.Null;
break;
}
// 현재 자음이고 다음 글자가 모음이면 모음을 확인하기 위해 계속 진행
if (isVowel(tIConLookahead)) {
stateFlag = LetterState.Vowel;
continue;
}
// 초성 자음이 뒤따라 오는 경우
else {
result += getSingleChar(getSingleCharVal(tICon));
syl.initLetter();
}
}
}
// 중성 (모음)
else if (stateFlag.equals(LetterState.Vowel)) {
// 일단 초기화
tVowLookahead = tVowLookaheadCombination = "";
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
tVow = hasTwoSymbolinOneKey(keySequence.charAt(i)) ? Character
.toString(keySequence.charAt(i)) : Character.toString(
keySequence.charAt(i)).toLowerCase();
// 다음 키 시퀀스랑 조합 분석하기 위한 할당
if (i < keySequence.length() - 1) {
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
tVowLookahead = hasTwoSymbolinOneKey(keySequence
.charAt(i + 1)) ? Character.toString(keySequence
.charAt(i + 1)) : Character.toString(
keySequence.charAt(i + 1)).toLowerCase();
tVowLookaheadCombination = tVow + tVowLookahead;
}
// 겹모음인 경우?
if (isVowel(tVowLookaheadCombination)) {
syl.setVowel(tVowLookaheadCombination);
// vow =
// DubeolSymbol.DubeolVowel.valueOf(tVowLookaheadCombination);
// 2 step - lookahead가 가능하면 try
if (i + 2 <= keySequence.length() - 1) {
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
String lookOverTwoStep = hasTwoSymbolinOneKey(keySequence
.charAt(i + 2)) ? Character
.toString(keySequence.charAt(i + 2))
: Character.toString(keySequence.charAt(i + 2))
.toLowerCase();
i++;
// 겹모음에 모음이 또 따라오면 현재 글자는 완성, 다음 모음은 독립적인 존재.
// 다음에 오는 자음은 받침에 쓸 수 없을 경우 이 과정을 밟는다
if (isVowel(lookOverTwoStep)
|| ((isISingleConsonant(lookOverTwoStep) || isIDoubleConsonant(lookOverTwoStep)) && !isFConsonant(lookOverTwoStep))) {
stateFlag = LetterState.Finish;
}
// 겹모음에 받침이 따라오는 경우 받침을 찾을 차례, ex: 왠
else if (isFConsonant(lookOverTwoStep)) {
if (!syl.isCompleteSyllable()) {
result += getSingleChar(getSingleCharVal(tVowLookaheadCombination));
stateFlag = LetterState.Null;
} else {
stateFlag = LetterState.FConsonant;
continue;
}
}
// 빈 칸 혹은 특수문자
else if (!CharUtils.isAsciiAlpha(lookOverTwoStep
.charAt(0))) {
if (!syl.isCompleteSyllable()) {
result += getSingleChar(getSingleCharVal(tVowLookaheadCombination));
syl.initLetter();
stateFlag = LetterState.Null;
} else
stateFlag = LetterState.Finish;
}
}
// 다음 문자 밖에 볼 수 없을 경우 (시퀀스의 마지막)
else {
// 자음이 대기 슬롯에 없으면 독립 모음 출력
if (!syl.isCompleteSyllable()) {
result += getSingleChar(getSingleCharVal(tVowLookaheadCombination));
syl.initLetter();
i++;
stateFlag = LetterState.Null;
} else {
stateFlag = LetterState.Finish;
}
// 포인터가 시퀀스의 마지막에 있으면 종료
if (i == keySequence.length() - 1)
break;
}
}
// 겹모음이 아닌 경우, ㅏ ㅐ ㅑ ㅒ ㅓ ㅖ ㅕ ㅖ ㅗ ㅛ ㅜ ㅠ ㅡ ㅣ
else {
// 현재 키 시퀀스가 모음에 해당해야 함
if (isVowel(tVow)) {
// 모음을 대기 슬롯에 넣는다.
if (!syl.isCompleteSyllable())
syl.setVowel(tVow);
// 키 시퀀스의 마지막이라면
if (i == keySequence.length() - 1) {
// 자음이 존재하지 않는다면 독립 모음 입력
if (!syl.isCompleteSyllable()) {
result += getSingleChar(getSingleCharVal(tVow));
syl.initLetter();
stateFlag = LetterState.Null;
}
// 자음이 있으면 받침이 없는 완전한 글자 완성
else {
stateFlag = LetterState.Finish;
}
break;
}
// 2벌식 초성 중성 종성은 영문글자의 위치 영역에 있으므로
// 영문자가 아닌 문자를 별개문자 혹은 delimiter로 취급
// 뒤에 공백, 숫자, 특수문자 따라오는 경우.
if (!CharUtils.isAsciiAlpha(tVowLookahead.charAt(0))) {
// 초성이 없다면 독립 모음 입력.
if (!syl.isCompleteSyllable()) {
result += getSingleChar(getSingleCharVal(tVow));
syl.initLetter();
stateFlag = LetterState.IConsonant;
continue;
} else
stateFlag = LetterState.Finish;
}
// *// 접근이 안되는 코드인듯. 주석처리
// 자음이 입력되지 않았을 경우
if (!syl.isCompleteSyllable()) {
// 독립 모음
result += getSingleChar(getSingleCharVal(tVow));
syl.initLetter();
// 다음이 자음이면 자음으로
if (isISingleConsonant(tVowLookahead)
|| isIDoubleConsonant(tVowLookahead))
stateFlag = LetterState.IConsonant;
// 모음이면 모음으로 검색모드 전환
else if (isVowel(tVowLookahead))
stateFlag = LetterState.Vowel;
continue;
} else {
// 자음 + 모음 + 받침 : good!
if (isFConsonant(tVowLookahead))
stateFlag = LetterState.FConsonant;
// 자음이 입력되었을 때 모음 다음 모음 오는 경우, 예: 거ㅣ
// ㄸ 과 같은 자음은 받침으로 쓰이지 않는다.
else
stateFlag = LetterState.Finish;
}
}
}
}
// 종성
else if (stateFlag.equals(LetterState.FConsonant)) {
// 일단 초기화
tFConLookahead = tFConLookaheadCombination = "";
// 대 소문자에 따라 값이 바뀌는 키라면 그냥 넣어주고 아니면 소문자로 바꿔준다
tFCon = hasTwoSymbolinOneKey(keySequence.charAt(i)) ? Character
.toString(keySequence.charAt(i)) : Character.toString(
keySequence.charAt(i)).toLowerCase();
// 다음 키 시퀀스랑 조합 분석하기 위한 할당
if (i < keySequence.length() - 1) {
tFConLookahead = hasTwoSymbolinOneKey(keySequence
.charAt(i + 1)) ? Character.toString(keySequence
.charAt(i + 1)) : Character.toString(
keySequence.charAt(i + 1)).toLowerCase();
tFConLookaheadCombination = tFCon + tFConLookahead;
}
stateFlag = LetterState.Finish; // 받침이 나오면 한 글자 완성이 끝남.
// 받침용 겹모음이라면?
if (isFConsonant(tFConLookaheadCombination)) {
// 2 step - lookahead가 가능하면 try
if (i + 2 <= keySequence.length() - 1) {
String lookOverTwoStep = hasTwoSymbolinOneKey(keySequence
.charAt(i)) ? Character.toString(keySequence
.charAt(i + 2)) : Character.toString(
keySequence.charAt(i + 2)).toLowerCase();
// (받침용) 겹자음에 자음이 뒤따라오는 모양새
if (isISingleConsonant(lookOverTwoStep)
|| isIDoubleConsonant(lookOverTwoStep)
|| !CharUtils.isAsciiAlpha(lookOverTwoStep
.charAt(0))) {
// 받침을 대기 슬롯에 넣는다. 겹자음이므로 키 시퀀스를 하나 건너뛴다
syl.setFConsonant(tFConLookaheadCombination);
i++;
// 단자음 받침일 수도 있다. 받침, 자음 + 모음
} else if (isVowel(lookOverTwoStep))
syl.setFConsonant(tFCon);
} else {
// 키 시퀀스의 마지막이라면 대기 슬롯 받침자리에 받침을 채우고 끝낸다.
if (isFConsonant(tFConLookaheadCombination)) {
syl.setFConsonant(tFConLookaheadCombination);
}
break;
}
} else {
// 단자음 받침이나 쌍자음 받침을 슬롯에 넣는다.
if (isFConsonant(tFCon))
syl.setFConsonant(tFCon);
// 키 시퀀스의 끝이라면 끝낸다.
if (i == keySequence.length() - 1)
break;
// 다음 글자가 모음이면 받침으로 간주하지 않고 backtracking.
// 대기 슬롯의 받침 자리를 비워둔다
if (isVowel(tFConLookahead)) {
syl.setFConsonant("nul");
stateFlag = LetterState.Finish;
i--;
}
}
}
// 한 글자가 완성되었으니 대기 슬롯에서 글자를 빼내어 결과 스트링에 붙여준다
if (stateFlag == LetterState.Finish) {
result += syl.getLetter();
syl.initLetter();
}
}
// 마무리.
if (stateFlag == LetterState.Finish)
result += syl.getLetter();
return result;
}
private boolean hasTwoSymbolinOneKey(char ch) {
return (ch == 'q' || ch == 'Q') || (ch == 'w' || ch == 'W')
|| (ch == 'e' || ch == 'E') || (ch == 'r' || ch == 'R')
|| (ch == 't' || ch == 'T')
|| ((ch == 'o' || ch == 'O') || (ch == 'p' || ch == 'P'));
}
@Override
public int getSingleCharVal(String keySequence) {
return DubeolSymbol.DubeolSingleLetter.valueOf(keySequence).value();
}
@Override
public String getSingleChar(int charVal) {
char[] ch = new char[1];
// single char value starts from 0x3130
ch[0] = (char) (charVal + 0x3130);
return new String(ch);
}
}