1 /* GENERATED SOURCE. DO NOT MODIFY. */ 2 // © 2018 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.Iterator; 7 8 import ohos.global.icu.impl.StandardPlural; 9 import ohos.global.icu.impl.StringSegment; 10 import ohos.global.icu.impl.TextTrieMap; 11 import ohos.global.icu.text.DecimalFormatSymbols; 12 import ohos.global.icu.util.Currency; 13 import ohos.global.icu.util.Currency.CurrencyStringInfo; 14 15 /** 16 * Matches a currency, either a custom currency or one from the data bundle. The class is called 17 * "combined" to emphasize that the currency string may come from one of multiple sources. 18 * 19 * Will match currency spacing either before or after the number depending on whether we are currently in 20 * the prefix or suffix. 21 * 22 * The implementation of this class is slightly different between J and C. See #13584 for a follow-up. 23 * 24 * @author sffc 25 * @hide exposed on OHOS 26 */ 27 public class CombinedCurrencyMatcher implements NumberParseMatcher { 28 29 private final String isoCode; 30 private final String currency1; 31 private final String currency2; 32 33 private final String[] localLongNames; 34 35 private final String afterPrefixInsert; 36 private final String beforeSuffixInsert; 37 38 private final TextTrieMap<CurrencyStringInfo> longNameTrie; 39 private final TextTrieMap<CurrencyStringInfo> symbolTrie; 40 41 // TODO: See comments in constructor. 42 // private final UnicodeSet leadCodePoints; 43 getInstance(Currency currency, DecimalFormatSymbols dfs, int parseFlags)44 public static CombinedCurrencyMatcher getInstance(Currency currency, DecimalFormatSymbols dfs, int parseFlags) { 45 // TODO: Cache these instances. They are somewhat expensive. 46 return new CombinedCurrencyMatcher(currency, dfs, parseFlags); 47 } 48 CombinedCurrencyMatcher(Currency currency, DecimalFormatSymbols dfs, int parseFlags)49 private CombinedCurrencyMatcher(Currency currency, DecimalFormatSymbols dfs, int parseFlags) { 50 this.isoCode = currency.getSubtype(); 51 this.currency1 = currency.getSymbol(dfs.getULocale()); 52 this.currency2 = currency.getCurrencyCode(); 53 54 afterPrefixInsert = dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, 55 false); 56 beforeSuffixInsert = dfs.getPatternForCurrencySpacing(DecimalFormatSymbols.CURRENCY_SPC_INSERT, 57 true); 58 59 if (0 == (parseFlags & ParsingUtils.PARSE_FLAG_NO_FOREIGN_CURRENCIES)) { 60 // TODO: Currency trie does not currently have an option for case folding. It defaults to use 61 // case folding on long-names but not symbols. 62 longNameTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.LONG_NAME); 63 symbolTrie = Currency.getParsingTrie(dfs.getULocale(), Currency.SYMBOL_NAME); 64 localLongNames = null; 65 66 } else { 67 longNameTrie = null; 68 symbolTrie = null; 69 localLongNames = new String[StandardPlural.COUNT]; 70 for (int i = 0; i < StandardPlural.COUNT; i++) { 71 String pluralKeyword = StandardPlural.VALUES.get(i).getKeyword(); 72 localLongNames[i] = currency 73 .getName(dfs.getLocale(), Currency.PLURAL_LONG_NAME, pluralKeyword, null); 74 } 75 } 76 77 // TODO: Figure out how to make this faster and re-enable. 78 // Computing the "lead code points" set for fastpathing is too slow to use in production. 79 // See http://bugs.icu-project.org/trac/ticket/13584 80 // // Compute the full set of characters that could be the first in a currency to allow for 81 // // efficient smoke test. 82 // leadCodePoints = new UnicodeSet(); 83 // leadCodePoints.add(currency1.codePointAt(0)); 84 // leadCodePoints.add(currency2.codePointAt(0)); 85 // leadCodePoints.add(beforeSuffixInsert.codePointAt(0)); 86 // longNameTrie.putLeadCodePoints(leadCodePoints); 87 // symbolTrie.putLeadCodePoints(leadCodePoints); 88 // // Always apply case mapping closure for currencies 89 // leadCodePoints.closeOver(UnicodeSet.ADD_CASE_MAPPINGS); 90 // leadCodePoints.freeze(); 91 } 92 93 @Override match(StringSegment segment, ParsedNumber result)94 public boolean match(StringSegment segment, ParsedNumber result) { 95 if (result.currencyCode != null) { 96 return false; 97 } 98 99 // Try to match a currency spacing separator. 100 int initialOffset = segment.getOffset(); 101 boolean maybeMore = false; 102 if (result.seenNumber() && !beforeSuffixInsert.isEmpty()) { 103 int overlap = segment.getCommonPrefixLength(beforeSuffixInsert); 104 if (overlap == beforeSuffixInsert.length()) { 105 segment.adjustOffset(overlap); 106 // Note: let currency spacing be a weak match. Don't update chars consumed. 107 } 108 maybeMore = maybeMore || overlap == segment.length(); 109 } 110 111 // Match the currency string, and reset if we didn't find one. 112 maybeMore = maybeMore || matchCurrency(segment, result); 113 if (result.currencyCode == null) { 114 segment.setOffset(initialOffset); 115 return maybeMore; 116 } 117 118 // Try to match a currency spacing separator. 119 if (!result.seenNumber() && !afterPrefixInsert.isEmpty()) { 120 int overlap = segment.getCommonPrefixLength(afterPrefixInsert); 121 if (overlap == afterPrefixInsert.length()) { 122 segment.adjustOffset(overlap); 123 // Note: let currency spacing be a weak match. Don't update chars consumed. 124 } 125 maybeMore = maybeMore || overlap == segment.length(); 126 } 127 128 return maybeMore; 129 } 130 131 /** Matches the currency string without concern for currency spacing. */ matchCurrency(StringSegment segment, ParsedNumber result)132 private boolean matchCurrency(StringSegment segment, ParsedNumber result) { 133 boolean maybeMore = false; 134 135 int overlap1; 136 if (!currency1.isEmpty()) { 137 overlap1 = segment.getCaseSensitivePrefixLength(currency1); 138 } else { 139 overlap1 = -1; 140 } 141 maybeMore = maybeMore || overlap1 == segment.length(); 142 if (overlap1 == currency1.length()) { 143 result.currencyCode = isoCode; 144 segment.adjustOffset(overlap1); 145 result.setCharsConsumed(segment); 146 return maybeMore; 147 } 148 149 int overlap2; 150 if (!currency2.isEmpty()) { 151 // ISO codes should be accepted case-insensitive. 152 // https://unicode-org.atlassian.net/browse/ICU-13696 153 overlap2 = segment.getCommonPrefixLength(currency2); 154 } else { 155 overlap2 = -1; 156 } 157 maybeMore = maybeMore || overlap2 == segment.length(); 158 if (overlap2 == currency2.length()) { 159 result.currencyCode = isoCode; 160 segment.adjustOffset(overlap2); 161 result.setCharsConsumed(segment); 162 return maybeMore; 163 } 164 165 if (longNameTrie != null) { 166 // Use the full currency data. 167 TextTrieMap.Output trieOutput = new TextTrieMap.Output(); 168 Iterator<CurrencyStringInfo> values = longNameTrie.get(segment, 0, trieOutput); 169 maybeMore = maybeMore || trieOutput.partialMatch; 170 if (values == null) { 171 values = symbolTrie.get(segment, 0, trieOutput); 172 maybeMore = maybeMore || trieOutput.partialMatch; 173 } 174 if (values != null) { 175 result.currencyCode = values.next().getISOCode(); 176 segment.adjustOffset(trieOutput.matchLength); 177 result.setCharsConsumed(segment); 178 return maybeMore; 179 } 180 181 } else { 182 // Use the locale long names. 183 int longestFullMatch = 0; 184 for (int i=0; i<StandardPlural.COUNT; i++) { 185 String name = localLongNames[i]; 186 if (name.isEmpty()) { 187 continue; 188 } 189 int overlap = segment.getCommonPrefixLength(name); 190 if (overlap == name.length() && name.length() > longestFullMatch) { 191 longestFullMatch = name.length(); 192 } 193 maybeMore = maybeMore || overlap > 0; 194 } 195 if (longestFullMatch > 0) { 196 result.currencyCode = isoCode; 197 segment.adjustOffset(longestFullMatch); 198 result.setCharsConsumed(segment); 199 return maybeMore; 200 } 201 } 202 203 // No match found. 204 return maybeMore; 205 } 206 207 @Override smokeTest(StringSegment segment)208 public boolean smokeTest(StringSegment segment) { 209 // TODO: See constructor 210 return true; 211 // return segment.startsWith(leadCodePoints); 212 } 213 214 @Override postProcess(ParsedNumber result)215 public void postProcess(ParsedNumber result) { 216 // No-op 217 } 218 219 @Override toString()220 public String toString() { 221 return "<CombinedCurrencyMatcher " + isoCode + ">"; 222 } 223 224 } 225