// This program must be compiled on a Japanese system. import java.lang.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.net.*; import java.applet.*; import java.util.*; final public class Inflection extends Applet implements Runnable, ActionListener, ItemListener, AdjustmentListener { boolean _fInit = false, _fError = false; Thread _thread = null; FontMetrics _fm; String _astrMorae[], _astrRoman[], _astrAccent[]; Panel _panel; Scrollbar _scrollbar; Choice _choiceFont; TextField _tf; Hashtable _hashImg; boolean _fNegative = false, _fPast = false, _fPolite = false, _fColloq = false; int _iImgSize = 0; int _cxString1, _cxString2, _iAscent, _iDescent; int _iScroll = 0; static Hashtable _hashPhon, _hashRoman, _hashRomanToPhon; static int _cKana = makeHash(); final static String _strWait = "Now loading images..."; final static String _strError = "Image load error!"; final static String _astrFonts[] = { "Gothic", "Kaisho", "MaruGothic", "Mincho", "Pop", "Textbook" }; String _strFont = "Textbook"; boolean _fKata = false; final static int GROUP_I = 1, GROUP_II = 2, KURU = 3, SURU = 4, COPULA = 5, ADJECTIVE = 6, MASU = 7, MASEN = 8; String _strRoot = "hanas"; int _iGroup = GROUP_I, _iRootAccent = 2, _cRootMorae = 2; int _cDisplayKana = 3; final String _astrVerbs[] = { "Group I: hana'su (speak)", "Group I: kiku (listen to)", "Group I: oyo'gu (swim)", "Group I: ta'tsu (stand up)", "Group I: uru (sell)", "Group I: arau (wash)", "Group I: shinu (die)", "Group I: tobu (fly)", "Group I: yo'mu (read)", "Group I: a'ru (exist)", "Group I: iku (go)", "Group II: mi'ru (watch)", "Group II: ochi'ru (fall)", "Group II: neru (sleep)", "Group II: tabe'ru (eat)", "Kuru: ku'ru (come)", "Suru: suru (do)", "Copula: da (be)", "Adjective: yo'i (good)", "Adjective: atsu'i (hot)", "Adjective: ureshi'i (glad)", "Adjective: oishi'i (tasty)", "Adjective: na'i (not exist)" }; final String _strOK = "OK", _strNegative = "Negative", _strPast = "Past", _strPolite = "Polite", _strColloq = "Colloquial"; final static String _strKana = "あいうえおかきくけこがぎぐげごさしすせそざじずぜぞたちつてとだぢづでどなにぬねのはひふへほばびぶべぼぱぴぷぺぽまみむめもやゆよらりるれろわゐゑをんっゃゅょ"; final static String _strHashKana = "あ,a,い,i,う,u,え,e,お,o,か,ka,き,ki,く,ku,け,ke,こ,ko,が,ga,ぎ,gi,ぐ,gu,げ,ge,ご,go,さ,sa,し,si,す,su,せ,se,そ,so,ざ,za,じ,zi,ず,zu,ぜ,ze,ぞ,zo,た,ta,ち,ti,つ,tu,て,te,と,to,だ,da,ぢ,di,づ,du,で,de,ど,do,な,na,に,ni,ぬ,nu,ね,ne,の,no,は,ha,ひ,hi,ふ,hu,へ,he,ほ,ho,ば,ba,び,bi,ぶ,bu,べ,be,ぼ,bo,ぱ,pa,ぴ,pi,ぷ,pu,ぺ,pe,ぽ,po,ま,ma,み,mi,む,mu,め,me,も,mo,や,ya,ゆ,yu,よ,yo,ら,ra,り,ri,る,ru,れ,re,ろ,ro,わ,wa,ゐ,wi,ゑ,we,を,wo,ん,N,っ,Q,きゃ,kya,きゅ,kyu,きょ,kyo,ぎゃ,gya,ぎゅ,gyu,ぎょ,gyo,しゃ,sya,しゅ,syu,しょ,syo,じゃ,zya,じゅ,zyu,じょ,zyo,ちゃ,tya,ちゅ,tyu,ちょ,tyo,ぢゃ,dya,ぢゅ,dyu,ぢょ,dyo,にゃ,nya,にゅ,nyu,にょ,nyo,ひゃ,hya,ひゅ,hyu,ひょ,hyo,びゃ,bya,びゅ,byu,びょ,byo,ぴゃ,pya,ぴゅ,pyu,ぴょ,pyo,みゃ,mya,みゅ,myu,みょ,myo,りゃ,rya,りゅ,ryu,りょ,ryo"; final static String _strhashRoman = "sya,sha,si,shi,syu,shu,syo,sho,zya,ja,zi,ji,zyu,ju,zyo,jo,tya,cha,ti,chi,tyu,chu,tyo,cho,tu,tsu,dya,ja,dyu,ju,dyo,jo,di,ji,du,zu,hu,fu,N,n"; final private static int makeHash() { _hashPhon = new Hashtable(); int cKana = 0; StringTokenizer st = new StringTokenizer(_strHashKana, ","); while (st.hasMoreTokens()) { String strKana = st.nextToken(), strPhon = st.nextToken(); _hashPhon.put(strPhon, strKana); cKana++; } _hashRoman = new Hashtable(); _hashRomanToPhon = new Hashtable(); st = new StringTokenizer(_strhashRoman, ","); while (st.hasMoreTokens()) { String strPhon = st.nextToken(), strRoman = st.nextToken(); _hashRoman.put(strPhon, strRoman); if (!strRoman.equals("n") && !_hashRomanToPhon.containsKey(strRoman)) { _hashRomanToPhon.put(strRoman, strPhon); } } return cKana; } final private void addCheckbox(String str, Panel panel) { Checkbox cb = new Checkbox(str); panel.add(cb); cb.addItemListener(this); } final public void init() { setForm(); Color color = new Color(0xf0, 0xf0, 0xf0); setBackground(color); Panel panel = _panel = new Panel(); panel.setBackground(color); panel.setLayout(new GridLayout(2, 4)); Choice choice = new Choice(); for (int iIndex = 0; iIndex < _astrFonts.length; iIndex++) { choice.addItem(_astrFonts[iIndex] + ", Hiragana"); choice.addItem(_astrFonts[iIndex] + ", Katakana"); } choice.select(_strFont + ", Hiragana"); choice.addItemListener(this); panel.add(_choiceFont = choice); choice = new Choice(); for (int iIndex = 0; iIndex < _astrVerbs.length; iIndex++) { choice.addItem(_astrVerbs[iIndex]); } panel.add(choice); choice.addItemListener(this); panel.add(_tf = new TextField()); Button button = new Button(_strOK); panel.add(button); button.addActionListener(this); addCheckbox(_strNegative, panel); addCheckbox(_strPast, panel); addCheckbox(_strPolite, panel); addCheckbox(_strColloq, panel); add(panel); add(_scrollbar = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, 0, 0)); _scrollbar.addAdjustmentListener(this); Font fontNew = new Font("serif", Font.BOLD, 32), fontOld = getFont(); setFont(fontNew); panel.setFont(fontOld); FontMetrics fm = _fm = getFontMetrics(fontNew); _cxString1 = fm.stringWidth(_strWait); _cxString2 = fm.stringWidth(_strError); _iAscent = fm.getMaxAscent(); _iDescent = fm.getMaxDescent(); if (!_fInit && !_fError && _thread == null) { (_thread = new Thread(this)).start(); } } final public void start() { requestFocus(); } final private void setScrollbar() { int iCharacters = _cDisplayKana * _iImgSize, iScrollbarWidth = _scrollbar.getSize().width; int iVisible = Math.min(iCharacters, iScrollbarWidth); int iOffset = Math.min(iCharacters - iVisible, _iScroll); _scrollbar.setValues(iOffset, iScrollbarWidth, 0, iCharacters); _scrollbar.setBlockIncrement(iVisible); _iScroll = iOffset; } final public synchronized void doLayout() { if (_panel != null) { Dimension dim = getSize(); int iWidth = dim.width, iHeight = dim.height; _panel.setBounds(0, 0, iWidth, Math.min(iHeight * 3 / 8, _panel.getPreferredSize().height)); if (_scrollbar != null) { int iScrollbarHeight = _scrollbar.getPreferredSize().height; _scrollbar.setBounds(0, iHeight - iScrollbarHeight, iWidth, iScrollbarHeight); setScrollbar(); } } } final public void run() { try { Image image = getImage(getCodeBase(), _strFont.toLowerCase() + (_fKata ? "_k.gif" : ".gif")); MediaTracker media = new MediaTracker(this); media.addImage(image, 0); media.waitForAll(); if (media.isErrorAny()) { _fError = true; } else { int cImages = _strKana.length(), iImgSize = _iImgSize = image.getHeight(this); ImageProducer imgprod = image.getSource(); _hashImg = new Hashtable(); for (int iIndex = 0; iIndex < cImages; iIndex++) { _hashImg.put(new Character(_strKana.charAt(iIndex)), createImage(new FilteredImageSource (imgprod, new CropImageFilter(iIndex * iImgSize, 0, iImgSize, iImgSize)))); } _scrollbar.setUnitIncrement(iImgSize); _fInit = true; } } catch (Exception e) { _fError = true; } _thread = null; repaint(); } final public void paint(Graphics g) { Dimension dim = getSize(); int yOffset = dim.height * 3 / 8; int cx = dim.width, cy = dim.height - yOffset - _scrollbar.getSize().height; int iAscent = _iAscent, iDescent = _iDescent; int yString = (cy + iAscent - iDescent) / 2 + yOffset; if (_fError) { g.drawString(_strError, (cx - _cxString2) / 2, yString); } else if (_fInit) { int x = -_iScroll; int yRoman = cy - iDescent + yOffset, yAccent = yRoman - iAscent - iDescent, yImage = yAccent - iAscent - _iImgSize; for (int iMora = 0; iMora < _astrMorae.length; iMora++) { String strRoman = _astrRoman[iMora], strAccent = _astrAccent[iMora], strKana = (String)_hashPhon.get(_astrMorae[iMora]); int cKana = strKana.length(); g.drawString(strAccent, x + (_iImgSize * cKana - _fm.stringWidth(strAccent)) / 2, yAccent); if (strRoman != null) { int cKanaRoman = ((iMora < _astrMorae.length - 1 && _astrRoman[iMora + 1] == null) ? (cKana + 1) : cKana); g.drawString(strRoman, x + (_iImgSize * cKanaRoman - _fm.stringWidth(strRoman)) / 2, yRoman); } for (int iKana = 0; iKana < cKana; iKana++) { g.drawImage((Image)_hashImg.get(new Character(strKana.charAt(iKana))), x, yImage, this); x += _iImgSize; } } } else { g.drawString(_strWait, (cx - _cxString1) / 2, yString); } } final private static String[] phonemeToMorae(String strPhon) { Vector vector = new Vector(); int cLength = strPhon.length(), iStart = 0; for (int iLimit = 1; iLimit <= cLength; iLimit++) { switch (strPhon.charAt(iLimit - 1)) { case 'a': case 'i': case 'u': case 'e': case 'o': case 'N': case 'Q': vector.addElement(strPhon.substring(iStart, iLimit)); iStart = iLimit; break; } } String astrMorae[] = new String[vector.size()]; vector.copyInto(astrMorae); return astrMorae; } final private static int countKana(String astrMorae[]) { int cKana = 0; for (int iIndex = 0; iIndex < astrMorae.length; iIndex++) { cKana += ((String)_hashPhon.get(astrMorae[iIndex])).length(); } return cKana; } final private static String normalizePhoneme(String strPhon, int aiMoraeAccent[]) { StringBuffer sbPhon = new StringBuffer(); int cLength = strPhon.length(), iStart = 0; int iAccent = 0, iMora = 0; for (int iLimit = 1; iLimit <= cLength; iLimit++) { String strKey = strPhon.substring(iStart, iLimit); if (_hashRomanToPhon.containsKey(strKey)) { strKey = (String)_hashRomanToPhon.get(strKey); } if (strKey.equals("'")) { // accent; iAccent = iMora; iStart = iLimit; } else if ((String)_hashPhon.get(strKey) != null) { sbPhon.append(strKey); iMora++; iStart = iLimit; } else if (iLimit - iStart >= 2) { char c0 = strKey.charAt(0), c1 = strKey.charAt(1); if ((c0 == 'n' && c1 != 'y') || (c0 == 'm' && (c1 == 'm' || c1 == 'b' || c1 == 'p'))) { sbPhon.append('N'); iMora++; if (c0 == 'n' && (c1 == '\'' || c1 == '-')) { iStart = iLimit; } else { iStart++; } } else if (c0 == c1 || (c0 == 't' && c1 == 'c')) { sbPhon.append('Q'); iMora++; iStart++; } } } if (iStart < cLength) { if (strPhon.substring(iStart).equals("n")) { sbPhon.append('N'); iMora++; } else { return null; } } aiMoraeAccent[0] = iMora; aiMoraeAccent[1] = iAccent; return sbPhon.toString(); } final private static String[] getAccent(int cMorae, int iAccent, int iBoundary, int iRootAccent) { String astrAccent[] = new String[cMorae]; boolean fHigh = false; for (int iMora = 0; iMora < cMorae; iMora++) { if (iMora == iAccent - 1 || (iBoundary != 0 && iMora == iRootAccent - 1)) { astrAccent[iMora] = "H'"; fHigh = false; } else if (iMora == 0 || iMora == iBoundary) { astrAccent[iMora] = "L"; fHigh = true; } else { astrAccent[iMora] = (fHigh ? "H" : "L"); } } return astrAccent; } final private static String[] romanize(String[] astrMorae, int cRootMorae) { if (astrMorae == null) { return null; } int cLength = astrMorae.length; String astrRoman[] = new String[cLength]; for (int iIndex = 0; iIndex < cLength; iIndex++) { String str = astrMorae[iIndex]; Object obj; astrRoman[iIndex] = ((obj = _hashRoman.get(str)) == null ? str : (String)obj); } for (int iIndex = 0; iIndex < cLength; iIndex++) { String str = astrRoman[iIndex]; if (str.length() == 1) { char cStr = str.charAt(0); if (cStr == 'u' || cStr == 'o' || cStr == 'i') { // do not process inflection; String strPrev; if (iIndex > 0 && iIndex < cRootMorae && ((strPrev = astrRoman[iIndex - 1]).endsWith(str) || (cStr == 'u' && strPrev.endsWith("o")))) { StringBuffer sb = new StringBuffer(strPrev); int iLast = sb.length() - 1; char cLast = sb.charAt(iLast); sb.setCharAt(iLast, (cLast == 'o' ? '\u00f4' : (cLast == 'u' ? '\u00fb' : '\u00ee'))); astrRoman[iIndex - 1] = sb.toString(); astrRoman[iIndex] = null; } } else if (cStr == 'n' || cStr == 'Q') { if (iIndex < cLength - 1) { char cNext = astrRoman[iIndex + 1].charAt(0); if (cStr == 'n') { if (cNext == 'm' || cNext == 'p' || cNext == 'b') { astrRoman[iIndex] = "m"; } else if (cNext == 'a' || cNext == 'i' || cNext == 'u' || cNext == 'e' || cNext == 'o' || cNext == 'y') { astrRoman[iIndex] = "n'"; } } else { astrRoman[iIndex] = String.valueOf(cNext == 'c' ? 't' : cNext); } } } } } return astrRoman; } final private boolean setForm() { String strRoot = _strRoot; boolean fNegative = _fNegative, fPast = _fPast, fPolite = _fPolite; int iGroup = _iGroup, iAccent = _iRootAccent, cMorae = _cRootMorae; int iBoundary = 0; if (iGroup == ADJECTIVE && strRoot.equals("na")) // the adjective nai { fNegative = !fNegative; strRoot = "ar"; iGroup = GROUP_I; iAccent = 1; cMorae = 1; } if (iGroup == SURU && iAccent > 0 && iAccent <= cMorae) { iBoundary = cMorae; } StringBuffer sb = new StringBuffer(strRoot); // polite form; if (fPolite && !(fNegative && _fColloq) // !desu && !(iGroup == ADJECTIVE && !fNegative)) // !desu { switch (iGroup) { case GROUP_I: int iChars = sb.length(); if (sb.charAt(iChars - 1) == 'w') { sb.setLength(iChars - 1); } case KURU: case SURU: sb.append("imas"); cMorae += 2; iAccent = cMorae; break; case GROUP_II: sb.append("mas"); cMorae++; iAccent = cMorae; break; case COPULA: if (fNegative) { sb.append("dearimas"); iBoundary = cMorae + 1; cMorae += 4; iAccent = cMorae; } else { sb.append("des"); cMorae++; if (iAccent == 0) { iAccent = cMorae; } } break; case ADJECTIVE: sb.append("kuarimas"); iBoundary = cMorae + 1; cMorae += 4; iAccent = cMorae; break; } iGroup = MASU; } // negative form; if (fNegative) { if (iGroup == MASU) { sb.append("eN"); cMorae += 2; iGroup = MASEN; iAccent = cMorae - 1; } else { switch (iGroup) { case GROUP_I: if (strRoot.equals("ar")) // the verb aru { sb.setLength(0); cMorae = 0; iAccent = 1; } else { sb.append('a'); cMorae++; if (iAccent != 0) { iAccent = cMorae; } } break; case KURU: sb.append('o'); cMorae++; if (iAccent != 0) { iAccent = cMorae; } break; case SURU: sb.append('i'); cMorae++; break; case COPULA: sb.append("de"); cMorae++; iBoundary = cMorae; iAccent = cMorae + 1; break; case ADJECTIVE: sb.append("ku"); cMorae++; iBoundary = cMorae; iAccent = cMorae + 1; break; } sb.append("na"); cMorae++; iGroup = ADJECTIVE; } } // present/past form; switch (iGroup) { case GROUP_I: { int iChars = sb.length(); char cLastChar = sb.charAt(iChars - 1); if (fPast) { sb.setLength(iChars - 1); switch (cLastChar) { case 'k': sb.append(iChars == 2 && sb.charAt(0) == 'i' ? "Qta" : "ita"); break; case 'g': sb.append("ida"); break; case 't': case 'r': case 'w': sb.append("Qta"); break; case 'n': case 'b': case 'm': sb.append("Nda"); break; default: sb.append(cLastChar).append("ita"); break; } cMorae += 2; } else { if (cLastChar == 'w') { sb.setLength(iChars - 1); } sb.append('u'); cMorae++; } break; } case GROUP_II: if (fPast) { if (iAccent > 1 && iAccent == cMorae) { iAccent--; } sb.append("ta"); } else { sb.append("ru"); } cMorae++; break; case KURU: case SURU: sb.append(fPast ? "ita" : "uru"); cMorae += 2; break; case COPULA: if (fPast) { if (iAccent == 0) { iAccent = cMorae + 1; } sb.append("daQta"); cMorae += 3; } else { sb.append("da"); cMorae++; } break; case ADJECTIVE: if (fPast) { if (iAccent <= iBoundary) { iAccent = cMorae; } sb.append("kaQta"); cMorae += 3; } else { sb.append('i'); cMorae++; } break; case MASU: if (fPast) { sb.append("ita"); cMorae += 2; } else { sb.append('u'); cMorae++; } break; case MASEN: if (fPast) { sb.append("desita"); cMorae += 3; } break; } if (fPolite && iGroup == ADJECTIVE) { if (iAccent <= iBoundary) { iAccent = cMorae; } sb.append("desu"); cMorae += 2; } String astrMorae[]; if ((astrMorae = phonemeToMorae(sb.toString())) != null) { _astrMorae = astrMorae; _astrRoman = romanize(astrMorae, _cRootMorae); _astrAccent = getAccent(cMorae, iAccent, iBoundary, _iRootAccent); _cDisplayKana = countKana(astrMorae); return true; } return false; } final private boolean setPredicate(String strPredicate) { int iIndex, iGroup = 0; String strGroup = null; strPredicate = strPredicate.trim(); if ((iIndex = strPredicate.indexOf(':')) >= 0) { strGroup = strPredicate.substring(0, iIndex).trim().toLowerCase(); strPredicate = strPredicate.substring(iIndex + 1); strPredicate = ((iIndex = strPredicate.indexOf('(')) >= 0 ? strPredicate.substring(0, iIndex) : strPredicate).trim(); } int aiMoraeAccent[] = new int[2]; if ((strPredicate = normalizePhoneme(strPredicate, aiMoraeAccent)) == null) { return false; } int cMorae = aiMoraeAccent[0], iAccent = aiMoraeAccent[1]; int cPhonemes = strPredicate.length(); if (strGroup != null) { if (strGroup.equals("group i")) { if (cMorae >= 2 && strPredicate.endsWith("u")) { iGroup = GROUP_I; } } else if (strGroup.equals("group ii")) { if (cMorae >= 2 && (strPredicate.endsWith("iru") || strPredicate.endsWith("eru"))) { iGroup = GROUP_II; } } else if (strGroup.equals("kuru")) { if (cMorae >= 2 && strPredicate.endsWith(strGroup)) { iGroup = KURU; } } else if (strGroup.equals("suru")) { if (cMorae >= 2 && strPredicate.endsWith(strGroup)) { iGroup = SURU; } } else if (strGroup.equals("copula")) { if (strPredicate.endsWith("da")) { iGroup = COPULA; } } else if (strGroup.equals("adjective")) { if (cMorae >= 2) { char c2ndLast; if (strPredicate.charAt(cPhonemes - 1) == 'i' && ((c2ndLast = strPredicate.charAt(cPhonemes - 2)) == 'a' || c2ndLast == 'i' || c2ndLast == 'o' || c2ndLast == 'u')) { iGroup = ADJECTIVE; } } } } else { if (strPredicate.endsWith("da")) { iGroup = COPULA; } else if (cMorae >= 2) { char c2ndLast; switch (strPredicate.charAt(cPhonemes - 1)) { case 'i': { if ((c2ndLast = strPredicate.charAt(cPhonemes - 2)) == 'a' || c2ndLast == 'i' || c2ndLast == 'o' || c2ndLast == 'u') { iGroup = ADJECTIVE; } break; } case 'u': { if (strPredicate.endsWith("iru") || strPredicate.endsWith("eru")) { iGroup = GROUP_II; } else if (strPredicate.endsWith("suru")) { iGroup = SURU; } else if (strPredicate.endsWith("kuru")) { iGroup = KURU; } else { iGroup = GROUP_I; } break; } } } } switch (iGroup) { case GROUP_I: { strPredicate = strPredicate.substring(0, cPhonemes - 1); cMorae--; switch (strPredicate.charAt(cPhonemes - 2)) { case 'a': case 'i': case 'u': case 'e': case 'o': strPredicate += 'w'; break; } break; } case GROUP_II: { strPredicate = strPredicate.substring(0, cPhonemes - 2); cMorae--; break; } case KURU: { strPredicate = strPredicate.substring(0, cPhonemes - 3); cMorae -= 2; break; } case SURU: { strPredicate = strPredicate.substring(0, cPhonemes - 3); cMorae -= 2; break; } case COPULA: { strPredicate = strPredicate.substring(0, cPhonemes - 2); cMorae--; break; } case ADJECTIVE: { strPredicate = strPredicate.substring(0, cPhonemes - 1); cMorae--; break; } default: return false; } _strRoot = strPredicate; _iGroup = iGroup; _iRootAccent = iAccent; _cRootMorae = cMorae; return setForm(); } final public void adjustmentValueChanged(AdjustmentEvent ae) { _iScroll = _scrollbar.getValue(); repaint(); } final public void actionPerformed(ActionEvent ae) { if (ae.getActionCommand().equals(_strOK)) { if (_fInit && setPredicate(_tf.getText())) { setScrollbar(); repaint(); } } } final public void itemStateChanged(ItemEvent ie) { ItemSelectable iselect = ie.getItemSelectable(); Object obj = ie.getItem(); if (iselect instanceof Choice) { String str = (String)obj; if (iselect == _choiceFont) { int iIndex = str.indexOf(','); String strFont = str.substring(0, iIndex); boolean fKata = (str.charAt(iIndex + 2) == 'K'); if (fKata != _fKata || !strFont.equals(_strFont)) { _strFont = strFont; _fKata = fKata; if (_thread != null) { _thread.stop(); } _fInit = _fError = false; (_thread = new Thread(this)).start(); repaint(); } } else if (setPredicate(str)) { setScrollbar(); repaint(); } } else if (iselect instanceof Checkbox) { boolean fState = ((Checkbox)iselect).getState(); if (obj.equals(_strPast)) { _fPast = fState; } else if (obj.equals(_strNegative)) { _fNegative = fState; } else if (obj.equals(_strPolite)) { _fPolite = fState; } else if (obj.equals(_strColloq)) { _fColloq = fState; } if (setForm()) { setScrollbar(); repaint(); } } } }