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