]> gitweb.fperrin.net Git - Dictionary.git/blob - jars/icu4j-52_1/main/classes/core/src/com/ibm/icu/text/LaoBreakEngine.java
Clean up imports.
[Dictionary.git] / jars / icu4j-52_1 / main / classes / core / src / com / ibm / icu / text / LaoBreakEngine.java
1 /*
2  *******************************************************************************
3  * Copyright (C) 2013, International Business Machines Corporation and         *
4  * others. All Rights Reserved.                                                *
5  *******************************************************************************
6  */
7 package com.ibm.icu.text;
8
9 import java.io.IOException;
10 import java.text.CharacterIterator;
11 import java.util.Stack;
12
13 import com.ibm.icu.lang.UCharacter;
14 import com.ibm.icu.lang.UProperty;
15 import com.ibm.icu.lang.UScript;
16
17 class LaoBreakEngine implements LanguageBreakEngine {
18     /* Helper class for improving readability of the Lao word break
19      * algorithm.
20      */
21     static class PossibleWord {
22         // List size, limited by the maximum number of words in the dictionary
23         // that form a nested sequence.
24         private final static int POSSIBLE_WORD_LIST_MAX = 20;
25         //list of word candidate lengths, in increasing length order
26         private int lengths[];
27         private int count[];    // Count of candidates
28         private int prefix;     // The longest match with a dictionary word
29         private int offset;     // Offset in the text of these candidates
30         private int mark;       // The preferred candidate's offset
31         private int current;    // The candidate we're currently looking at
32
33         // Default constructor
34         public PossibleWord() {
35             lengths = new int[POSSIBLE_WORD_LIST_MAX];
36             count = new int[1]; // count needs to be an array of 1 so that it can be pass as reference
37             offset = -1;
38         }
39
40         // Fill the list of candidates if needed, select the longest, and return the number found
41         public int candidates(CharacterIterator fIter, DictionaryMatcher dict, int rangeEnd) {
42             int start = fIter.getIndex();
43             if (start != offset) {
44                 offset = start;
45                 prefix = dict.matches(fIter, rangeEnd - start, lengths, count, lengths.length);
46                 // Dictionary leaves text after longest prefix, not longest word. Back up.
47                 if (count[0] <= 0) {
48                     fIter.setIndex(start);
49                 }
50             }
51             if (count[0] > 0) {
52                 fIter.setIndex(start + lengths[count[0]-1]);
53             }
54             current = count[0] - 1;
55             mark = current;
56             return count[0];
57         }
58
59         // Select the currently marked candidate, point after it in the text, and invalidate self
60         public int acceptMarked(CharacterIterator fIter) {
61             fIter.setIndex(offset + lengths[mark]);
62             return lengths[mark];
63         }
64
65         // Backup from the current candidate to the next shorter one; return true if that exists
66         // and point the text after it
67         public boolean backUp(CharacterIterator fIter) {
68             if (current > 0) {
69                 fIter.setIndex(offset + lengths[--current]);
70                 return true;
71             }
72             return false;
73         }
74
75         // Return the longest prefix this candidate location shares with a dictionary word
76         public int longestPrefix() {
77             return prefix;
78         }
79
80         // Mark the current candidate as the one we like
81         public void markCurrent() {
82             mark = current;
83         }
84     }
85     
86     // Constants for LaoBreakIterator
87     // How many words in a row are "good enough"?
88     private static final byte LAO_LOOKAHEAD = 3;
89     // Will not combine a non-word with a preceding dictionary word longer than this
90     private static final byte LAO_ROOT_COMBINE_THRESHOLD = 3;
91     // Will not combine a non-word that shares at least this much prefix with a
92     // dictionary word with a preceding word
93     private static final byte LAO_PREFIX_COMBINE_THRESHOLD = 3;
94     // Minimum word size
95     private static final byte LAO_MIN_WORD = 2;
96     
97     private DictionaryMatcher fDictionary;
98     private static UnicodeSet fLaoWordSet;
99     private static UnicodeSet fEndWordSet;
100     private static UnicodeSet fBeginWordSet;
101     private static UnicodeSet fMarkSet;
102     
103     static {
104         // Initialize UnicodeSets
105         fLaoWordSet = new UnicodeSet();
106         fMarkSet = new UnicodeSet();
107         fEndWordSet = new UnicodeSet();
108         fBeginWordSet = new UnicodeSet();
109
110         fLaoWordSet.applyPattern("[[:Laoo:]&[:LineBreak=SA:]]");
111         fLaoWordSet.compact();
112
113         fMarkSet.applyPattern("[[:Laoo:]&[:LineBreak=SA:]&[:M:]]");
114         fMarkSet.add(0x0020);
115         fEndWordSet = fLaoWordSet;
116         fEndWordSet.remove(0x0EC0, 0x0EC4); // prefix vowels
117         fBeginWordSet.add(0x0E81, 0x0EAE); // basic consonants (including holes for corresponding Thai characters)
118         fBeginWordSet.add(0x0EDC, 0x0EDD); // digraph consonants (no Thai equivalent)
119         fBeginWordSet.add(0x0EC0, 0x0EC4); // prefix vowels
120
121         // Compact for caching
122         fMarkSet.compact();
123         fEndWordSet.compact();
124         fBeginWordSet.compact();
125         
126         // Freeze the static UnicodeSet
127         fLaoWordSet.freeze();
128         fMarkSet.freeze();
129         fEndWordSet.freeze();
130         fBeginWordSet.freeze();
131     }
132     
133     public LaoBreakEngine() throws IOException {
134         // Initialize dictionary
135         fDictionary = DictionaryData.loadDictionaryFor("Laoo");
136     }
137
138     public boolean handles(int c, int breakType) {
139         if (breakType == BreakIterator.KIND_WORD || breakType == BreakIterator.KIND_LINE) {
140             int script = UCharacter.getIntPropertyValue(c, UProperty.SCRIPT);
141             return (script == UScript.LAO);
142         }
143         return false;
144     }
145
146     public int findBreaks(CharacterIterator fIter, int rangeStart, int rangeEnd, boolean reverse, int breakType,
147             Stack<Integer> foundBreaks) {
148         if ((rangeEnd - rangeStart) < LAO_MIN_WORD) {
149             return 0;  // Not enough characters for word
150         }
151         int wordsFound = 0;
152         int wordLength;
153         int current;
154         PossibleWord words[] = new PossibleWord[LAO_LOOKAHEAD];
155         for (int i = 0; i < LAO_LOOKAHEAD; i++) {
156             words[i] = new PossibleWord();
157         }
158         int uc;
159
160         fIter.setIndex(rangeStart);
161
162         while ((current = fIter.getIndex()) < rangeEnd) {
163             wordLength = 0;
164
165             //Look for candidate words at the current position
166             int candidates = words[wordsFound%LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);
167
168             // If we found exactly one, use that
169             if (candidates == 1) {
170                 wordLength = words[wordsFound%LAO_LOOKAHEAD].acceptMarked(fIter);
171                 wordsFound += 1;
172             }
173
174             // If there was more than one, see which one can take us forward the most words
175             else if (candidates > 1) {
176                 boolean foundBest = false;
177                 // If we're already at the end of the range, we're done
178                 if (fIter.getIndex() < rangeEnd) {
179                     do {
180                         int wordsMatched = 1;
181                         if (words[(wordsFound+1)%LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
182                             if (wordsMatched < 2) {
183                                 // Followed by another dictionary word; mark first word as a good candidate
184                                 words[wordsFound%LAO_LOOKAHEAD].markCurrent();
185                                 wordsMatched = 2;
186                             }
187
188                             // If we're already at the end of the range, we're done
189                             if (fIter.getIndex() >= rangeEnd) {
190                                 break;
191                             }
192
193                             // See if any of the possible second words is followed by a third word
194                             do {
195                                 // If we find a third word, stop right away
196                                 if (words[(wordsFound+2)%LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
197                                     words[wordsFound%LAO_LOOKAHEAD].markCurrent();
198                                     foundBest = true;
199                                     break;
200                                 }
201                             } while (words[(wordsFound+1)%LAO_LOOKAHEAD].backUp(fIter));
202                         }
203                     } while (words[wordsFound%LAO_LOOKAHEAD].backUp(fIter) && !foundBest);
204                 }
205                 wordLength = words[wordsFound%LAO_LOOKAHEAD].acceptMarked(fIter);
206                 wordsFound += 1;
207             }
208
209             // We come here after having either found a word or not. We look ahead to the
210             // next word. If it's not a dictionary word, we will combine it with the word we
211             // just found (if there is one), but only if the preceding word does not exceed
212             // the threshold.
213             // The text iterator should now be positioned at the end of the word we found.
214             if (fIter.getIndex() < rangeEnd && wordLength < LAO_ROOT_COMBINE_THRESHOLD) {
215                 // If it is a dictionary word, do nothing. If it isn't, then if there is
216                 // no preceding word, or the non-word shares less than the minimum threshold
217                 // of characters with a dictionary word, then scan to resynchronize
218                 if (words[wordsFound%LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) <= 0 &&
219                         (wordLength == 0 || 
220                                 words[wordsFound%LAO_LOOKAHEAD].longestPrefix() < LAO_PREFIX_COMBINE_THRESHOLD)) {
221                     // Look for a plausible word boundary
222                     int remaining = rangeEnd - (current + wordLength);
223                     int pc = fIter.current();
224                     int chars = 0;
225                     for (;;) {
226                         fIter.next();
227                         uc = fIter.current();
228                         chars += 1;
229                         if (--remaining <= 0) {
230                             break;
231                         }
232                         if (fEndWordSet.contains(pc) && fBeginWordSet.contains(uc)) {
233                             // Maybe. See if it's in the dictionary.
234                             int candidate = words[(wordsFound + 1) %LAO_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);
235                             fIter.setIndex(current + wordLength + chars);
236                             if (candidate > 0) {
237                                 break;
238                             }
239                         }
240                         pc = uc;
241                     }
242
243                     // Bump the word count if there wasn't already one
244                     if (wordLength <= 0) {
245                         wordsFound += 1;
246                     }
247
248                     // Update the length with the passed-over characters
249                     wordLength += chars;
250                 } else {
251                     // Backup to where we were for next iteration
252                     fIter.setIndex(current+wordLength);
253                 }
254             }
255
256             // Never stop before a combining mark.
257             int currPos;
258             while ((currPos = fIter.getIndex()) < rangeEnd && fMarkSet.contains(fIter.current())) {
259                 fIter.next();
260                 wordLength += fIter.getIndex() - currPos;
261             }
262
263             // Look ahead for possible suffixes if a dictionary word does not follow.
264             // We do this in code rather than using a rule so that the heuristic
265             // resynch continues to function. For example, one of the suffix characters 
266             // could be a typo in the middle of a word.
267             // NOT CURRENTLY APPLICABLE TO LAO
268
269             // Did we find a word on this iteration? If so, push it on the break stack
270             if (wordLength > 0) {
271                 foundBreaks.push(Integer.valueOf(current + wordLength));
272             }
273         }
274
275         // Don't return a break for the end of the dictionary range if there is one there
276         if (foundBreaks.peek().intValue() >= rangeEnd) {
277             foundBreaks.pop();
278             wordsFound -= 1;
279         }
280
281         return wordsFound;
282     }
283
284 }