]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-4_2_1-src/src/com/ibm/icu/text/ThaiBreakIterator.java
icu4jsrc
[Dictionary.git] / jars / icu4j-4_2_1-src / src / com / ibm / icu / text / ThaiBreakIterator.java
1 /*\r
2  *******************************************************************************\r
3  * Copyright (C) 1996-2008, International Business Machines Corporation and    *\r
4  * others. All Rights Reserved.                                                *\r
5  *******************************************************************************\r
6  */\r
7 package com.ibm.icu.text;\r
8 \r
9 import com.ibm.icu.impl.Assert;\r
10 import com.ibm.icu.text.UnicodeSet;\r
11 \r
12 import java.util.Stack;\r
13 import java.io.InputStream;\r
14 import java.text.CharacterIterator;\r
15 import java.io.IOException;\r
16 \r
17 class ThaiBreakIterator extends DictionaryBasedBreakIterator {\r
18 \r
19     /* Helper class for improving readability of the Thai word break\r
20      * algorithm.\r
21      */\r
22     static class PossibleWord {\r
23         // List size, limited by the maximum number of words in the dictionary\r
24         // that form a nested sequence.\r
25         private final int POSSIBLE_WORD_LIST_MAX = 20;\r
26         //list of word candidate lengths, in increasing length order\r
27         private int lengths[];\r
28         private int count[];    // Count of candidates\r
29         private int prefix;     // The longeset match with a dictionary word\r
30         private int offset;     // Offset in the text of these candidates\r
31         private int mark;       // The preferred candidate's offset\r
32         private int current;    // The candidate we're currently looking at\r
33 \r
34         // Default constructor\r
35         public PossibleWord() {\r
36             lengths = new int[POSSIBLE_WORD_LIST_MAX];\r
37             count = new int[1]; // count needs to be an array of 1 so that it can be pass as reference\r
38             offset = -1;\r
39         }\r
40 \r
41         // Fill the list of candidates if needed, select the longest, and return the number found\r
42         public int candidates(CharacterIterator fIter, BreakCTDictionary dict, int rangeEnd) {\r
43             int start = fIter.getIndex();\r
44             if (start != offset) {\r
45                 offset = start;\r
46                 prefix = dict.matches(fIter, rangeEnd - start, lengths, count, lengths.length);\r
47                 // Dictionary leaves text after longest prefix, not longest word. Back up.\r
48                 if (count[0] <= 0) {\r
49                     fIter.setIndex(start);\r
50                 }\r
51             }\r
52             if (count[0] > 0) {\r
53                 fIter.setIndex(start + lengths[count[0]-1]);\r
54             }\r
55             current = count[0] - 1;\r
56             mark = current;\r
57             return count[0];\r
58         }\r
59 \r
60         // Select the currently marked candidate, point after it in the text, and invalidate self\r
61         public int acceptMarked(CharacterIterator fIter) {\r
62             fIter.setIndex(offset + lengths[mark]);\r
63             return lengths[mark];\r
64         }\r
65 \r
66         // Backup from the current candidate to the next shorter one; rreturn true if that exists\r
67         // and point the text after it\r
68         public boolean backUp(CharacterIterator fIter) {\r
69             if (current > 0) {\r
70                 fIter.setIndex(offset + lengths[--current]);\r
71                 return true;\r
72             }\r
73             return false;\r
74         }\r
75 \r
76         // Return the longest prefix this candidate location shares with a dictionary word\r
77         public int longestPrefix() {\r
78             return prefix;\r
79         }\r
80 \r
81         // Mark the current candidate as the one we like\r
82         public void markCurrent() {\r
83             mark = current;\r
84         }\r
85     }\r
86 \r
87     private static UnicodeSet fThaiWordSet;\r
88     private static UnicodeSet fEndWordSet;\r
89     private static UnicodeSet fBeginWordSet;\r
90     private static UnicodeSet fSuffixSet;\r
91     private static UnicodeSet fMarkSet;\r
92     private BreakCTDictionary fDictionary;\r
93 \r
94     // Constants for ThaiBreakIterator\r
95     // How many words in a row are "good enough"?\r
96     private static final byte THAI_LOOKAHEAD = 3;\r
97     // Will not combine a non-word with a preceding dictionary word longer than this\r
98     private static final byte THAI_ROOT_COMBINE_THRESHOLD = 3;\r
99     // Will not combine a non-word that shares at least this much prefix with a\r
100     // dictionary word with a preceding word\r
101     private static final byte THAI_PREFIX_COMBINE_THRESHOLD = 3;\r
102     // Ellision character\r
103     private static final char THAI_PAIYANNOI = 0x0E2F;\r
104     // Repeat character\r
105     private static final char THAI_MAIYAMOK = 0x0E46;\r
106     // Minimum word size\r
107     private static final byte THAI_MIN_WORD = 2;\r
108     // Minimum number of characters for two words\r
109     //private final int THAI_MIN_WORD_SPAN = THAI_MIN_WORD * 2;\r
110 \r
111     static {\r
112         // Initialize UnicodeSets\r
113         fThaiWordSet = new UnicodeSet();\r
114         fMarkSet = new UnicodeSet();\r
115         fEndWordSet = new UnicodeSet();\r
116         fBeginWordSet = new UnicodeSet();\r
117         fSuffixSet = new UnicodeSet();\r
118 \r
119         fThaiWordSet.applyPattern(new String("[[:Thai:]&[:LineBreak=SA:]]"));\r
120         fThaiWordSet.compact();\r
121 \r
122         fMarkSet.applyPattern(new String("[[:Thai:]&[:LineBreak=SA:]&[:M:]]"));\r
123         fMarkSet.add(0x0020);\r
124         fEndWordSet = fThaiWordSet;\r
125         fEndWordSet.remove(0x0E31); // MAI HAN-AKAT\r
126         fEndWordSet.remove(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI\r
127         fBeginWordSet.add(0x0E01, 0x0E2E); //KO KAI through HO NOKHUK\r
128         fBeginWordSet.add(0x0E40, 0x0E44); // SARA E through SARA AI MAIMALAI\r
129         fSuffixSet.add(THAI_PAIYANNOI);\r
130         fSuffixSet.add(THAI_MAIYAMOK);\r
131 \r
132         // Compact for caching\r
133         fMarkSet.compact();\r
134         fEndWordSet.compact();\r
135         fBeginWordSet.compact();\r
136         fSuffixSet.compact();\r
137         \r
138         // Freeze the static UnicodeSet\r
139         fThaiWordSet.freeze();\r
140         fMarkSet.freeze();\r
141         fEndWordSet.freeze();\r
142         fBeginWordSet.freeze();\r
143         fSuffixSet.freeze();\r
144     }\r
145 \r
146     public ThaiBreakIterator(InputStream ruleStream, InputStream dictionaryStream) throws IOException {\r
147         super(ruleStream);\r
148         // Initialize diciontary\r
149         fDictionary = new BreakCTDictionary(dictionaryStream);\r
150     }\r
151 \r
152     /**\r
153      * This is the implementation function for next().\r
154      * @internal\r
155      * @deprecated This API is ICU internal only.\r
156      */\r
157     protected int handleNext() {\r
158         CharacterIterator text = getText();\r
159 \r
160         // if there are no cached break positions, or if we've just moved\r
161         // off the end of the range covered by the cache, we have to dump\r
162         // and possibly regenerate the cache\r
163         if (cachedBreakPositions == null || positionInCache == cachedBreakPositions.length - 1) {\r
164 \r
165             // start by using the inherited handleNext() to find a tentative return\r
166             // value.   dictionaryCharCount tells us how many dictionary characters\r
167             // we passed over on our way to the tentative return value\r
168             int startPos = text.getIndex();\r
169             fDictionaryCharCount = 0;\r
170             int result = super.handleNext();\r
171 \r
172             // if we passed over more than one dictionary character, then we use\r
173             // divideUpDictionaryRange() to regenerate the cached break positions\r
174             // for the new range\r
175             if (fDictionaryCharCount > 1 && result - startPos > 1) {\r
176                 divideUpDictionaryRange(startPos, result);\r
177             }\r
178 \r
179             // otherwise, the value we got back from the inherited fuction\r
180             // is our return value, and we can dump the cache\r
181             else {\r
182                 cachedBreakPositions = null;\r
183                 return result;\r
184             }\r
185         }\r
186         // if the cache of break positions has been regenerated (or existed all\r
187         // along), then just advance to the next break position in the cache\r
188         // and return it\r
189         if (cachedBreakPositions != null) {\r
190             ++positionInCache;\r
191             text.setIndex(cachedBreakPositions[positionInCache]);\r
192             return cachedBreakPositions[positionInCache];\r
193         }\r
194         Assert.assrt(false);\r
195         return -9999;   // SHOULD NEVER GET HERE!\r
196     }\r
197 \r
198     /**\r
199      * Divide up a range of known dictionary characters.\r
200      *\r
201      * @param rangeStart The start of the range of dictionary characters\r
202      * @param rangeEnd The end of the range of dictionary characters\r
203      * @return The number of breaks found\r
204      */\r
205     private int divideUpDictionaryRange(int rangeStart, int rangeEnd) {\r
206         if ((rangeEnd - rangeStart) < THAI_MIN_WORD) {\r
207             return 0;  // Not enough chacters for word\r
208         }\r
209         CharacterIterator fIter = getText();\r
210         int wordsFound = 0;\r
211         int wordLength;\r
212         int current;\r
213         Stack foundBreaks = new Stack();\r
214         PossibleWord words[] = new PossibleWord[THAI_LOOKAHEAD];\r
215         for (int i = 0; i < THAI_LOOKAHEAD; i++) {\r
216             words[i] = new PossibleWord();\r
217         }\r
218         int uc;\r
219 \r
220         fIter.setIndex(rangeStart);\r
221 \r
222         while ((current = fIter.getIndex()) < rangeEnd) {\r
223             wordLength = 0;\r
224 \r
225             //Look for candidate words at the current position\r
226             int candidates = words[wordsFound%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);\r
227 \r
228             // If we found exactly one, use that\r
229             if (candidates == 1) {\r
230                 wordLength = words[wordsFound%THAI_LOOKAHEAD].acceptMarked(fIter);\r
231                 wordsFound += 1;\r
232             }\r
233 \r
234             // If there was more than one, see which one can take use forward the most words\r
235             else if (candidates > 1) {\r
236                 boolean foundBest = false;\r
237                 // If we're already at the end of the range, we're done\r
238                 if (fIter.getIndex() < rangeEnd) {\r
239                     do {\r
240                         int wordsMatched = 1;\r
241                         if (words[(wordsFound+1)%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {\r
242                             if (wordsMatched < 2) {\r
243                                 // Followed by another dictionary word; mark first word as a good candidate\r
244                                 words[wordsFound%THAI_LOOKAHEAD].markCurrent();\r
245                                 wordsMatched = 2;\r
246                             }\r
247 \r
248                             // If we're already at the end of the range, we're done\r
249                             if (fIter.getIndex() >= rangeEnd) {\r
250                                 break;\r
251                             }\r
252 \r
253                             // See if any of the possible second words is followed by a third word\r
254                             do {\r
255                                 // If we find a third word, stop right away\r
256                                 if (words[(wordsFound+2)%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {\r
257                                     words[wordsFound%THAI_LOOKAHEAD].markCurrent();\r
258                                     foundBest = true;\r
259                                     break;\r
260                                 }\r
261                             } while (words[(wordsFound+1)%THAI_LOOKAHEAD].backUp(fIter));\r
262                         }\r
263                     } while (words[wordsFound%THAI_LOOKAHEAD].backUp(fIter) && !foundBest);\r
264                 }\r
265                 /* foundBest: */wordLength = words[wordsFound%THAI_LOOKAHEAD].acceptMarked(fIter);\r
266                 wordsFound += 1;\r
267             }\r
268             // We come here after having either found a word or not. We look ahead to the\r
269             // next word. If it's not a dictionary word, we will combine it with the word we\r
270             // just found (if there is one), but only if the preceding word does not exceed\r
271             // the threshold.\r
272             // The text iterator should now be positioned at the end of the word we found.\r
273             if (fIter.getIndex() < rangeEnd && wordLength < THAI_ROOT_COMBINE_THRESHOLD) {\r
274                 // If it is a dictionary word, do nothing. If it isn't, then if there is\r
275                 // no preceding word, or the non-word shares less than the minimum threshold\r
276                 // of characters with a dictionary word, then scan to resynchronize\r
277                 if (words[wordsFound%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) <= 0 &&\r
278                         (wordLength == 0 || \r
279                                 words[wordsFound%THAI_LOOKAHEAD].longestPrefix() < THAI_PREFIX_COMBINE_THRESHOLD)) {\r
280                     // Look for a plausible word boundary\r
281                     int remaining = rangeEnd - (current + wordLength);\r
282                     int pc = fIter.current();\r
283                     int chars = 0;\r
284                     for (;;) {\r
285                         fIter.next();\r
286                         uc = fIter.current();\r
287                         chars += 1;\r
288                         if (--remaining <= 0) {\r
289                             break;\r
290                         }\r
291                         if (fEndWordSet.contains(pc) && fBeginWordSet.contains(uc)) {\r
292                             // Maybe. See if it's in the dictionary.\r
293                             // Note: In the original Apple code, checked that the next\r
294                             // two characters after uc were not 0x0E4C THANTHAKHAT before\r
295                             // checking the dictionary. That is just a performance filter,\r
296                             // but it's not clear it's faster than checking the trie\r
297                             int candidate = words[(wordsFound+1)%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);\r
298                             fIter.setIndex(current+wordLength+chars);\r
299                             if (candidate > 0) {\r
300                                 break;\r
301                             }\r
302                         }\r
303                         pc = uc;\r
304                     }\r
305 \r
306                     // Bump the word cound if there wasn't already one\r
307                     if (wordLength <= 0) {\r
308                         wordsFound += 1;\r
309                     }\r
310 \r
311                     // Update the length with the passed-over characters\r
312                     wordLength += chars;\r
313                 } else {\r
314                     // Backup to where we were for next iteration\r
315                     fIter.setIndex(current+wordLength);\r
316                 }\r
317             }\r
318 \r
319             // Never stop before a combining mark.\r
320             int currPos;\r
321             while ((currPos = fIter.getIndex()) < rangeEnd && fMarkSet.contains(fIter.current())) {\r
322                 fIter.next();\r
323                 wordLength += fIter.getIndex() - currPos;\r
324             }\r
325 \r
326             // Look ahead for possible suffixes if a dictionary word does not follow.\r
327             // We do this in code rather than using a rule so that the heuristic\r
328             // resynch continues to function. For example, one of the suffix characters \r
329             // could be a typo in the middle of a word.\r
330             if (fIter.getIndex() < rangeEnd && wordLength > 0) {\r
331                 if (words[wordsFound%THAI_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) <= 0 &&\r
332                         fSuffixSet.contains(uc = fIter.current())) {\r
333                     if (uc == THAI_PAIYANNOI) {\r
334                         if (!fSuffixSet.contains(fIter.previous())) {\r
335                             // Skip over previous end and PAIYANNOI\r
336                             fIter.next();\r
337                             fIter.next();\r
338                             wordLength += 1;\r
339                             uc = fIter.current();\r
340                         } else {\r
341                             // Restore prior position\r
342                             fIter.next();\r
343                         }\r
344                     }\r
345                     if (uc == THAI_MAIYAMOK) {\r
346                         if (fIter.previous() != THAI_MAIYAMOK) {\r
347                             // Skip over previous end and MAIYAMOK\r
348                             fIter.next();\r
349                             fIter.next();\r
350                             wordLength += 1;\r
351                         } else {\r
352                             // restore prior position\r
353                             fIter.next();\r
354                         }\r
355                     }\r
356                 } else {\r
357                     fIter.setIndex(current+wordLength);\r
358                 }\r
359             }\r
360 \r
361             // Did we find a word on this iteration? If so, push it on the break stack\r
362             if (wordLength > 0) {\r
363                 foundBreaks.push(new Integer(current+wordLength));\r
364             }\r
365         }\r
366 \r
367         // Don't return a break for the end of the dictionary range if there is one there\r
368         if (((Integer)foundBreaks.peek()).intValue() >= rangeEnd) {\r
369             foundBreaks.pop();\r
370             wordsFound -= 1;\r
371         }\r
372 \r
373         // Store the break points in cachedBreakPositions.\r
374         cachedBreakPositions = new int[foundBreaks.size() + 2];\r
375         cachedBreakPositions[0] = rangeStart;\r
376         int i;\r
377         for (i = 0; i < foundBreaks.size(); i++) {\r
378             cachedBreakPositions[i + 1] = ((Integer)foundBreaks.elementAt(i)).intValue();\r
379         }\r
380         cachedBreakPositions[i + 1] = rangeEnd;\r
381         positionInCache = 0;\r
382 \r
383         return wordsFound;\r
384     }\r
385 }\r