• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2017 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 package ohos.global.icu.impl.number.parse;
5 
6 import java.util.ArrayList;
7 import java.util.Collections;
8 import java.util.Comparator;
9 import java.util.Objects;
10 
11 import ohos.global.icu.impl.StandardPlural;
12 import ohos.global.icu.impl.StringSegment;
13 import ohos.global.icu.impl.number.AffixPatternProvider;
14 import ohos.global.icu.impl.number.AffixUtils;
15 import ohos.global.icu.impl.number.PatternStringUtils;
16 import ohos.global.icu.impl.number.PatternStringUtils.PatternSignType;
17 
18 /**
19  * @author sffc
20  * @hide exposed on OHOS
21  *
22  */
23 public class AffixMatcher implements NumberParseMatcher {
24     private final AffixPatternMatcher prefix;
25     private final AffixPatternMatcher suffix;
26     private final int flags;
27 
28     /**
29      * Comparator for two AffixMatcher instances which prioritizes longer prefixes followed by longer
30      * suffixes, ensuring that the longest prefix/suffix pair is always chosen.
31      */
32     public static final Comparator<AffixMatcher> COMPARATOR = new Comparator<AffixMatcher>() {
33         @Override
34         public int compare(AffixMatcher lhs, AffixMatcher rhs) {
35             if (length(lhs.prefix) != length(rhs.prefix)) {
36                 return length(lhs.prefix) > length(rhs.prefix) ? -1 : 1;
37             } else if (length(lhs.suffix) != length(rhs.suffix)) {
38                 return length(lhs.suffix) > length(rhs.suffix) ? -1 : 1;
39             } else if (!lhs.equals(rhs)) {
40                 // If the prefix and suffix are the same length, arbitrarily break ties.
41                 // We can't return zero unless the elements are equal.
42                 return lhs.hashCode() > rhs.hashCode() ? -1 : 1;
43             } else {
44                 return 0;
45             }
46         }
47     };
48 
isInteresting( AffixPatternProvider patternInfo, IgnorablesMatcher ignorables, int parseFlags)49     private static boolean isInteresting(
50             AffixPatternProvider patternInfo,
51             IgnorablesMatcher ignorables,
52             int parseFlags) {
53         String posPrefixString = patternInfo.getString(AffixPatternProvider.FLAG_POS_PREFIX);
54         String posSuffixString = patternInfo.getString(AffixPatternProvider.FLAG_POS_SUFFIX);
55         String negPrefixString = null;
56         String negSuffixString = null;
57         if (patternInfo.hasNegativeSubpattern()) {
58             negPrefixString = patternInfo.getString(AffixPatternProvider.FLAG_NEG_PREFIX);
59             negSuffixString = patternInfo.getString(AffixPatternProvider.FLAG_NEG_SUFFIX);
60         }
61 
62         if (0 == (parseFlags & ParsingUtils.PARSE_FLAG_USE_FULL_AFFIXES)
63                 && AffixUtils.containsOnlySymbolsAndIgnorables(posPrefixString, ignorables.getSet())
64                 && AffixUtils.containsOnlySymbolsAndIgnorables(posSuffixString, ignorables.getSet())
65                 && AffixUtils.containsOnlySymbolsAndIgnorables(negPrefixString, ignorables.getSet())
66                 && AffixUtils.containsOnlySymbolsAndIgnorables(negSuffixString, ignorables.getSet())
67                 // HACK: Plus and minus sign are a special case: we accept them trailing only if they are
68                 // trailing in the pattern string.
69                 && !AffixUtils.containsType(posSuffixString, AffixUtils.TYPE_PLUS_SIGN)
70                 && !AffixUtils.containsType(posSuffixString, AffixUtils.TYPE_MINUS_SIGN)
71                 && !AffixUtils.containsType(negSuffixString, AffixUtils.TYPE_PLUS_SIGN)
72                 && !AffixUtils.containsType(negSuffixString, AffixUtils.TYPE_MINUS_SIGN)) {
73             // The affixes contain only symbols and ignorables.
74             // No need to generate affix matchers.
75             return false;
76         }
77         return true;
78     }
79 
createMatchers( AffixPatternProvider patternInfo, NumberParserImpl output, AffixTokenMatcherFactory factory, IgnorablesMatcher ignorables, int parseFlags)80     public static void createMatchers(
81             AffixPatternProvider patternInfo,
82             NumberParserImpl output,
83             AffixTokenMatcherFactory factory,
84             IgnorablesMatcher ignorables,
85             int parseFlags) {
86         if (!isInteresting(patternInfo, ignorables, parseFlags)) {
87             return;
88         }
89 
90         // The affixes have interesting characters, or we are in strict mode.
91         // Use initial capacity of 6, the highest possible number of AffixMatchers.
92         StringBuilder sb = new StringBuilder();
93         ArrayList<AffixMatcher> matchers = new ArrayList<>(6);
94         boolean includeUnpaired = 0 != (parseFlags & ParsingUtils.PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES);
95 
96         AffixPatternMatcher posPrefix = null;
97         AffixPatternMatcher posSuffix = null;
98 
99         // Pre-process the affix strings to resolve LDML rules like sign display.
100         for (PatternSignType type : PatternSignType.VALUES) {
101 
102             // Skip affixes in some cases
103             if (type == PatternSignType.POS
104                     && 0 != (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
105                 continue;
106             }
107             if (type == PatternSignType.POS_SIGN
108                     && 0 == (parseFlags & ParsingUtils.PARSE_FLAG_PLUS_SIGN_ALLOWED)) {
109                 continue;
110             }
111 
112             // Generate Prefix
113             PatternStringUtils.patternInfoToStringBuilder(patternInfo,
114                     true,
115                     type,
116                     StandardPlural.OTHER,
117                     false,
118                     sb);
119             AffixPatternMatcher prefix = AffixPatternMatcher
120                     .fromAffixPattern(sb.toString(), factory, parseFlags);
121 
122             // Generate Suffix
123             PatternStringUtils.patternInfoToStringBuilder(patternInfo,
124                     false,
125                     type,
126                     StandardPlural.OTHER,
127                     false,
128                     sb);
129             AffixPatternMatcher suffix = AffixPatternMatcher
130                     .fromAffixPattern(sb.toString(), factory, parseFlags);
131 
132             if (type == PatternSignType.POS) {
133                 posPrefix = prefix;
134                 posSuffix = suffix;
135             } else if (Objects.equals(prefix, posPrefix) && Objects.equals(suffix, posSuffix)) {
136                 // Skip adding these matchers (we already have equivalents)
137                 continue;
138             }
139 
140             // Flags for setting in the ParsedNumber; the token matchers may add more.
141             int flags = (type == PatternSignType.NEG) ? ParsedNumber.FLAG_NEGATIVE : 0;
142 
143             // Note: it is indeed possible for posPrefix and posSuffix to both be null.
144             // We still need to add that matcher for strict mode to work.
145             matchers.add(getInstance(prefix, suffix, flags));
146             if (includeUnpaired && prefix != null && suffix != null) {
147                 // The following if statements are designed to prevent adding two identical matchers.
148                 if (type == PatternSignType.POS || !Objects.equals(prefix, posPrefix)) {
149                     matchers.add(getInstance(prefix, null, flags));
150                 }
151                 if (type == PatternSignType.POS || !Objects.equals(suffix, posSuffix)) {
152                     matchers.add(getInstance(null, suffix, flags));
153                 }
154             }
155         }
156 
157         // Put the AffixMatchers in order, and then add them to the output.
158         Collections.sort(matchers, COMPARATOR);
159         output.addMatchers(matchers);
160     }
161 
getInstance( AffixPatternMatcher prefix, AffixPatternMatcher suffix, int flags)162     private static final AffixMatcher getInstance(
163             AffixPatternMatcher prefix,
164             AffixPatternMatcher suffix,
165             int flags) {
166         // TODO: Special handling for common cases like both strings empty.
167         return new AffixMatcher(prefix, suffix, flags);
168     }
169 
AffixMatcher(AffixPatternMatcher prefix, AffixPatternMatcher suffix, int flags)170     private AffixMatcher(AffixPatternMatcher prefix, AffixPatternMatcher suffix, int flags) {
171         this.prefix = prefix;
172         this.suffix = suffix;
173         this.flags = flags;
174     }
175 
176     @Override
match(StringSegment segment, ParsedNumber result)177     public boolean match(StringSegment segment, ParsedNumber result) {
178         if (!result.seenNumber()) {
179             // Prefix
180             // Do not match if:
181             // 1. We have already seen a prefix (result.prefix != null)
182             // 2. The prefix in this AffixMatcher is empty (prefix == null)
183             if (result.prefix != null || prefix == null) {
184                 return false;
185             }
186 
187             // Attempt to match the prefix.
188             int initialOffset = segment.getOffset();
189             boolean maybeMore = prefix.match(segment, result);
190             if (initialOffset != segment.getOffset()) {
191                 result.prefix = prefix.getPattern();
192             }
193             return maybeMore;
194 
195         } else {
196             // Suffix
197             // Do not match if:
198             // 1. We have already seen a suffix (result.suffix != null)
199             // 2. The suffix in this AffixMatcher is empty (suffix == null)
200             // 3. The matched prefix does not equal this AffixMatcher's prefix
201             if (result.suffix != null || suffix == null || !matched(prefix, result.prefix)) {
202                 return false;
203             }
204 
205             // Attempt to match the suffix.
206             int initialOffset = segment.getOffset();
207             boolean maybeMore = suffix.match(segment, result);
208             if (initialOffset != segment.getOffset()) {
209                 result.suffix = suffix.getPattern();
210             }
211             return maybeMore;
212         }
213     }
214 
215     @Override
smokeTest(StringSegment segment)216     public boolean smokeTest(StringSegment segment) {
217         return (prefix != null && prefix.smokeTest(segment))
218                 || (suffix != null && suffix.smokeTest(segment));
219     }
220 
221     @Override
postProcess(ParsedNumber result)222     public void postProcess(ParsedNumber result) {
223         // Check to see if our affix is the one that was matched. If so, set the flags in the result.
224         if (matched(prefix, result.prefix) && matched(suffix, result.suffix)) {
225             // Fill in the result prefix and suffix with non-null values (empty string).
226             // Used by strict mode to determine whether an entire affix pair was matched.
227             if (result.prefix == null) {
228                 result.prefix = "";
229             }
230             if (result.suffix == null) {
231                 result.suffix = "";
232             }
233             result.flags |= flags;
234             if (prefix != null) {
235                 prefix.postProcess(result);
236             }
237             if (suffix != null) {
238                 suffix.postProcess(result);
239             }
240         }
241     }
242 
243     /**
244      * Helper method to return whether the given AffixPatternMatcher equals the given pattern string.
245      * Either both arguments must be null or the pattern string inside the AffixPatternMatcher must equal
246      * the given pattern string.
247      */
matched(AffixPatternMatcher affix, String patternString)248     static boolean matched(AffixPatternMatcher affix, String patternString) {
249         return (affix == null && patternString == null)
250                 || (affix != null && affix.getPattern().equals(patternString));
251     }
252 
253     /**
254      * Helper method to return the length of the given AffixPatternMatcher. Returns 0 for null.
255      */
length(AffixPatternMatcher matcher)256     private static int length(AffixPatternMatcher matcher) {
257         return matcher == null ? 0 : matcher.getPattern().length();
258     }
259 
260     @Override
equals(Object _other)261     public boolean equals(Object _other) {
262         if (!(_other instanceof AffixMatcher)) {
263             return false;
264         }
265         AffixMatcher other = (AffixMatcher) _other;
266         return Objects.equals(prefix, other.prefix)
267                 && Objects.equals(suffix, other.suffix)
268                 && flags == other.flags;
269     }
270 
271     @Override
hashCode()272     public int hashCode() {
273         return Objects.hashCode(prefix) ^ Objects.hashCode(suffix) ^ flags;
274     }
275 
276     @Override
toString()277     public String toString() {
278         boolean isNegative = 0 != (flags & ParsedNumber.FLAG_NEGATIVE);
279         return "<AffixMatcher" + (isNegative ? ":negative " : " ") + prefix + "#" + suffix + ">";
280     }
281 }
282