1 // © 2016 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html#License
3 /*
4  *******************************************************************************
5  * Copyright (C) 2014, International Business Machines Corporation and         *
6  * others. All Rights Reserved.                                                *
7  *******************************************************************************
8  */
9 package com.ibm.icu.text;
10 
11 import java.io.IOException;
12 import java.text.CharacterIterator;
13 
14 import com.ibm.icu.lang.UCharacter;
15 import com.ibm.icu.lang.UProperty;
16 import com.ibm.icu.lang.UScript;
17 
18 class BurmeseBreakEngine extends DictionaryBreakEngine {
19 
20     // Constants for BurmeseBreakIterator
21     // How many words in a row are "good enough"?
22     private static final byte BURMESE_LOOKAHEAD = 3;
23     // Will not combine a non-word with a preceding dictionary word longer than this
24     private static final byte BURMESE_ROOT_COMBINE_THRESHOLD = 3;
25     // Will not combine a non-word that shares at least this much prefix with a
26     // dictionary word with a preceding word
27     private static final byte BURMESE_PREFIX_COMBINE_THRESHOLD = 3;
28     // Minimum word size
29     private static final byte BURMESE_MIN_WORD = 2;
30 
31     private DictionaryMatcher fDictionary;
32     private static UnicodeSet fBurmeseWordSet;
33     private static UnicodeSet fEndWordSet;
34     private static UnicodeSet fBeginWordSet;
35     private static UnicodeSet fMarkSet;
36 
37     static {
38         // Initialize UnicodeSets
39         fBurmeseWordSet = new UnicodeSet();
40         fMarkSet = new UnicodeSet();
41         fBeginWordSet = new UnicodeSet();
42 
43         fBurmeseWordSet.applyPattern("[[:Mymr:]&[:LineBreak=SA:]]");
fBurmeseWordSet.compact()44         fBurmeseWordSet.compact();
45 
46         fMarkSet.applyPattern("[[:Mymr:]&[:LineBreak=SA:]&[:M:]]");
47         fMarkSet.add(0x0020);
48         fEndWordSet = new UnicodeSet(fBurmeseWordSet);
49         fBeginWordSet.add(0x1000, 0x102A);      // basic consonants and independent vowels
50 
51         // Compact for caching
fMarkSet.compact()52         fMarkSet.compact();
fEndWordSet.compact()53         fEndWordSet.compact();
fBeginWordSet.compact()54         fBeginWordSet.compact();
55 
56         // Freeze the static UnicodeSet
fBurmeseWordSet.freeze()57         fBurmeseWordSet.freeze();
fMarkSet.freeze()58         fMarkSet.freeze();
fEndWordSet.freeze()59         fEndWordSet.freeze();
fBeginWordSet.freeze()60         fBeginWordSet.freeze();
61     }
62 
BurmeseBreakEngine()63     public BurmeseBreakEngine() throws IOException {
64         setCharacters(fBurmeseWordSet);
65         // Initialize dictionary
66         fDictionary = DictionaryData.loadDictionaryFor("Mymr");
67     }
68 
69     @Override
equals(Object obj)70     public boolean equals(Object obj) {
71         // Normally is a singleton, but it's possible to have duplicates
72         //   during initialization. All are equivalent.
73         return obj instanceof BurmeseBreakEngine;
74     }
75 
76     @Override
hashCode()77     public int hashCode() {
78         return getClass().hashCode();
79     }
80 
81     @Override
handles(int c)82     public boolean handles(int c) {
83         int script = UCharacter.getIntPropertyValue(c, UProperty.SCRIPT);
84         return (script == UScript.MYANMAR);
85     }
86 
87     @Override
divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd, DequeI foundBreaks)88     public int divideUpDictionaryRange(CharacterIterator fIter, int rangeStart, int rangeEnd,
89             DequeI foundBreaks) {
90 
91 
92         if ((rangeEnd - rangeStart) < BURMESE_MIN_WORD) {
93             return 0;  // Not enough characters for word
94         }
95         int wordsFound = 0;
96         int wordLength;
97         int current;
98         PossibleWord words[] = new PossibleWord[BURMESE_LOOKAHEAD];
99         for (int i = 0; i < BURMESE_LOOKAHEAD; i++) {
100             words[i] = new PossibleWord();
101         }
102         int uc;
103 
104         fIter.setIndex(rangeStart);
105         while ((current = fIter.getIndex()) < rangeEnd) {
106             wordLength = 0;
107 
108             //Look for candidate words at the current position
109             int candidates = words[wordsFound%BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);
110 
111             // If we found exactly one, use that
112             if (candidates == 1) {
113                 wordLength = words[wordsFound%BURMESE_LOOKAHEAD].acceptMarked(fIter);
114                 wordsFound += 1;
115             }
116 
117             // If there was more than one, see which one can take us forward the most words
118             else if (candidates > 1) {
119                 boolean foundBest = false;
120                 // If we're already at the end of the range, we're done
121                 if (fIter.getIndex() < rangeEnd) {
122                     do {
123                         int wordsMatched = 1;
124                         if (words[(wordsFound+1)%BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
125                             if (wordsMatched < 2) {
126                                 // Followed by another dictionary word; mark first word as a good candidate
127                                 words[wordsFound%BURMESE_LOOKAHEAD].markCurrent();
128                                 wordsMatched = 2;
129                             }
130 
131                             // If we're already at the end of the range, we're done
132                             if (fIter.getIndex() >= rangeEnd) {
133                                 break;
134                             }
135 
136                             // See if any of the possible second words is followed by a third word
137                             do {
138                                 // If we find a third word, stop right away
139                                 if (words[(wordsFound+2)%BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) > 0) {
140                                     words[wordsFound%BURMESE_LOOKAHEAD].markCurrent();
141                                     foundBest = true;
142                                     break;
143                                 }
144                             } while (words[(wordsFound+1)%BURMESE_LOOKAHEAD].backUp(fIter));
145                         }
146                     } while (words[wordsFound%BURMESE_LOOKAHEAD].backUp(fIter) && !foundBest);
147                 }
148                 wordLength = words[wordsFound%BURMESE_LOOKAHEAD].acceptMarked(fIter);
149                 wordsFound += 1;
150             }
151 
152             // We come here after having either found a word or not. We look ahead to the
153             // next word. If it's not a dictionary word, we will combine it with the word we
154             // just found (if there is one), but only if the preceding word does not exceed
155             // the threshold.
156             // The text iterator should now be positioned at the end of the word we found.
157             if (fIter.getIndex() < rangeEnd && wordLength < BURMESE_ROOT_COMBINE_THRESHOLD) {
158                 // If it is a dictionary word, do nothing. If it isn't, then if there is
159                 // no preceding word, or the non-word shares less than the minimum threshold
160                 // of characters with a dictionary word, then scan to resynchronize
161                 if (words[wordsFound%BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd) <= 0 &&
162                         (wordLength == 0 ||
163                                 words[wordsFound%BURMESE_LOOKAHEAD].longestPrefix() < BURMESE_PREFIX_COMBINE_THRESHOLD)) {
164                     // Look for a plausible word boundary
165                     int remaining = rangeEnd - (current + wordLength);
166                     int pc = fIter.current();
167                     int chars = 0;
168                     for (;;) {
169                         fIter.next();
170                         uc = fIter.current();
171                         chars += 1;
172                         if (--remaining <= 0) {
173                             break;
174                         }
175                         if (fEndWordSet.contains(pc) && fBeginWordSet.contains(uc)) {
176                             // Maybe. See if it's in the dictionary.
177                             int candidate = words[(wordsFound + 1) %BURMESE_LOOKAHEAD].candidates(fIter, fDictionary, rangeEnd);
178                             fIter.setIndex(current + wordLength + chars);
179                             if (candidate > 0) {
180                                 break;
181                             }
182                         }
183                         pc = uc;
184                     }
185 
186                     // Bump the word count if there wasn't already one
187                     if (wordLength <= 0) {
188                         wordsFound += 1;
189                     }
190 
191                     // Update the length with the passed-over characters
192                     wordLength += chars;
193                 } else {
194                     // Backup to where we were for next iteration
195                     fIter.setIndex(current+wordLength);
196                 }
197             }
198 
199             // Never stop before a combining mark.
200             int currPos;
201             while ((currPos = fIter.getIndex()) < rangeEnd && fMarkSet.contains(fIter.current())) {
202                 fIter.next();
203                 wordLength += fIter.getIndex() - currPos;
204             }
205 
206             // Look ahead for possible suffixes if a dictionary word does not follow.
207             // We do this in code rather than using a rule so that the heuristic
208             // resynch continues to function. For example, one of the suffix characters
209             // could be a typo in the middle of a word.
210             // NOT CURRENTLY APPLICABLE TO BURMESE
211 
212             // Did we find a word on this iteration? If so, push it on the break stack
213             if (wordLength > 0) {
214                 foundBreaks.push(Integer.valueOf(current + wordLength));
215             }
216         }
217 
218         // Don't return a break for the end of the dictionary range if there is one there
219         if (foundBreaks.peek() >= rangeEnd) {
220             foundBreaks.pop();
221             wordsFound -= 1;
222         }
223 
224         return wordsFound;
225     }
226 
227 }
228