• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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