• 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.number;
5 
6 import java.util.MissingResourceException;
7 
8 import ohos.global.icu.impl.FormattedStringBuilder;
9 import ohos.global.icu.impl.ICUData;
10 import ohos.global.icu.impl.ICUResourceBundle;
11 import ohos.global.icu.impl.PatternProps;
12 import ohos.global.icu.impl.SimpleFormatterImpl;
13 import ohos.global.icu.impl.StandardPlural;
14 import ohos.global.icu.impl.UResource;
15 import ohos.global.icu.impl.number.DecimalQuantity;
16 import ohos.global.icu.impl.number.MicroProps;
17 import ohos.global.icu.impl.number.Modifier;
18 import ohos.global.icu.impl.number.SimpleModifier;
19 import ohos.global.icu.impl.number.range.PrefixInfixSuffixLengthHelper;
20 import ohos.global.icu.impl.number.range.RangeMacroProps;
21 import ohos.global.icu.impl.number.range.StandardPluralRanges;
22 import ohos.global.icu.number.NumberRangeFormatter.RangeCollapse;
23 import ohos.global.icu.number.NumberRangeFormatter.RangeIdentityFallback;
24 import ohos.global.icu.number.NumberRangeFormatter.RangeIdentityResult;
25 import ohos.global.icu.text.NumberFormat;
26 import ohos.global.icu.util.ULocale;
27 import ohos.global.icu.util.UResourceBundle;
28 
29 /**
30  * Business logic behind NumberRangeFormatter.
31  */
32 class NumberRangeFormatterImpl {
33 
34     final NumberFormatterImpl formatterImpl1;
35     final NumberFormatterImpl formatterImpl2;
36     final boolean fSameFormatters;
37 
38     final NumberRangeFormatter.RangeCollapse fCollapse;
39     final NumberRangeFormatter.RangeIdentityFallback fIdentityFallback;
40 
41     // Should be final, but they are set in a helper function, not the constructor proper.
42     // TODO: Clean up to make these fields actually final.
43     /* final */ String fRangePattern;
44     /* final */ SimpleModifier fApproximatelyModifier;
45 
46     final StandardPluralRanges fPluralRanges;
47 
48     ////////////////////
49 
50      // Helper function for 2-dimensional switch statement
identity2d(RangeIdentityFallback a, RangeIdentityResult b)51      int identity2d(RangeIdentityFallback a, RangeIdentityResult b) {
52          return a.ordinal() | (b.ordinal() << 4);
53      }
54 
55     private static final class NumberRangeDataSink extends UResource.Sink {
56 
57         String rangePattern;
58         String approximatelyPattern;
59 
60         // For use with SimpleFormatterImpl
61         StringBuilder sb;
62 
NumberRangeDataSink(StringBuilder sb)63         NumberRangeDataSink(StringBuilder sb) {
64             this.sb = sb;
65         }
66 
67         @Override
put(UResource.Key key, UResource.Value value, boolean noFallback)68         public void put(UResource.Key key, UResource.Value value, boolean noFallback) {
69             UResource.Table miscTable = value.getTable();
70             for (int i = 0; miscTable.getKeyAndValue(i, key, value); ++i) {
71                 if (key.contentEquals("range") && !hasRangeData()) {
72                     String pattern = value.getString();
73                     rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 2, 2);
74                 }
75                 if (key.contentEquals("approximately") && !hasApproxData()) {
76                     String pattern = value.getString();
77                     approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments(pattern, sb, 1, 1); // 1 arg, as in "~{0}"
78                 }
79             }
80         }
81 
hasRangeData()82         private boolean hasRangeData() {
83             return rangePattern != null;
84         }
85 
hasApproxData()86         private boolean hasApproxData() {
87             return approximatelyPattern != null;
88         }
89 
isComplete()90         public boolean isComplete() {
91             return hasRangeData() && hasApproxData();
92         }
93 
fillInDefaults()94         public void fillInDefaults() {
95             if (!hasRangeData()) {
96                 rangePattern = SimpleFormatterImpl.compileToStringMinMaxArguments("{0}–{1}", sb, 2, 2);
97             }
98             if (!hasApproxData()) {
99                 approximatelyPattern = SimpleFormatterImpl.compileToStringMinMaxArguments("~{0}", sb, 1, 1);
100             }
101         }
102     }
103 
getNumberRangeData( ULocale locale, String nsName, NumberRangeFormatterImpl out)104     private static void getNumberRangeData(
105             ULocale locale,
106             String nsName,
107             NumberRangeFormatterImpl out) {
108         StringBuilder sb = new StringBuilder();
109         NumberRangeDataSink sink = new NumberRangeDataSink(sb);
110         ICUResourceBundle resource;
111         resource = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUData.ICU_BASE_NAME, locale);
112         sb.append("NumberElements/");
113         sb.append(nsName);
114         sb.append("/miscPatterns");
115         String key = sb.toString();
116         try {
117             resource.getAllItemsWithFallback(key, sink);
118         } catch (MissingResourceException e) {
119             // ignore; fall back to latn
120         }
121 
122         // Fall back to latn if necessary
123         if (!sink.isComplete()) {
124             resource.getAllItemsWithFallback("NumberElements/latn/miscPatterns", sink);
125         }
126 
127         sink.fillInDefaults();
128 
129         out.fRangePattern = sink.rangePattern;
130         out.fApproximatelyModifier = new SimpleModifier(sink.approximatelyPattern, null, false);
131     }
132 
133     ////////////////////
134 
NumberRangeFormatterImpl(RangeMacroProps macros)135     public NumberRangeFormatterImpl(RangeMacroProps macros) {
136         formatterImpl1 = new NumberFormatterImpl(macros.formatter1 != null ? macros.formatter1.resolve()
137                 : NumberFormatter.withLocale(macros.loc).resolve());
138         formatterImpl2 = new NumberFormatterImpl(macros.formatter2 != null ? macros.formatter2.resolve()
139                 : NumberFormatter.withLocale(macros.loc).resolve());
140         fSameFormatters = macros.sameFormatters != 0;
141         fCollapse = macros.collapse != null ? macros.collapse : NumberRangeFormatter.RangeCollapse.AUTO;
142         fIdentityFallback = macros.identityFallback != null ? macros.identityFallback
143                 : NumberRangeFormatter.RangeIdentityFallback.APPROXIMATELY;
144 
145         String nsName = formatterImpl1.getRawMicroProps().nsName;
146         if (nsName == null || !nsName.equals(formatterImpl2.getRawMicroProps().nsName)) {
147             throw new IllegalArgumentException("Both formatters must have same numbering system");
148         }
149         getNumberRangeData(macros.loc, nsName, this);
150 
151         // TODO: Get locale from PluralRules instead?
152         fPluralRanges = new StandardPluralRanges(macros.loc);
153     }
154 
format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding)155     public FormattedNumberRange format(DecimalQuantity quantity1, DecimalQuantity quantity2, boolean equalBeforeRounding) {
156         FormattedStringBuilder string = new FormattedStringBuilder();
157         MicroProps micros1 = formatterImpl1.preProcess(quantity1);
158         MicroProps micros2;
159         if (fSameFormatters) {
160             micros2 = formatterImpl1.preProcess(quantity2);
161         } else {
162             micros2 = formatterImpl2.preProcess(quantity2);
163         }
164 
165         // If any of the affixes are different, an identity is not possible
166         // and we must use formatRange().
167         // TODO: Write this as MicroProps operator==() ?
168         // TODO: Avoid the redundancy of these equality operations with the
169         // ones in formatRange?
170         if (!micros1.modInner.semanticallyEquivalent(micros2.modInner)
171                 || !micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle)
172                 || !micros1.modOuter.semanticallyEquivalent(micros2.modOuter)) {
173             formatRange(quantity1, quantity2, string, micros1, micros2);
174             return new FormattedNumberRange(string, quantity1, quantity2, RangeIdentityResult.NOT_EQUAL);
175         }
176 
177         // Check for identity
178         RangeIdentityResult identityResult;
179         if (equalBeforeRounding) {
180             identityResult = RangeIdentityResult.EQUAL_BEFORE_ROUNDING;
181         } else if (quantity1.equals(quantity2)) {
182             identityResult = RangeIdentityResult.EQUAL_AFTER_ROUNDING;
183         } else {
184             identityResult = RangeIdentityResult.NOT_EQUAL;
185         }
186 
187         // Java does not let us use a constexpr like C++;
188         // we need to expand identity2d calls.
189         switch (identity2d(fIdentityFallback, identityResult)) {
190         case (3 | (2 << 4)): // RANGE, NOT_EQUAL
191         case (3 | (1 << 4)): // RANGE, EQUAL_AFTER_ROUNDING
192         case (3 | (0 << 4)): // RANGE, EQUAL_BEFORE_ROUNDING
193         case (2 | (2 << 4)): // APPROXIMATELY, NOT_EQUAL
194         case (1 | (2 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, NOT_EQUAL
195         case (0 | (2 << 4)): // SINGLE_VALUE, NOT_EQUAL
196             formatRange(quantity1, quantity2, string, micros1, micros2);
197             break;
198 
199         case (2 | (1 << 4)): // APPROXIMATELY, EQUAL_AFTER_ROUNDING
200         case (2 | (0 << 4)): // APPROXIMATELY, EQUAL_BEFORE_ROUNDING
201         case (1 | (1 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_AFTER_ROUNDING
202             formatApproximately(quantity1, quantity2, string, micros1, micros2);
203             break;
204 
205         case (1 | (0 << 4)): // APPROXIMATE_OR_SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
206         case (0 | (1 << 4)): // SINGLE_VALUE, EQUAL_AFTER_ROUNDING
207         case (0 | (0 << 4)): // SINGLE_VALUE, EQUAL_BEFORE_ROUNDING
208             formatSingleValue(quantity1, quantity2, string, micros1, micros2);
209             break;
210 
211         default:
212             assert false;
213             break;
214         }
215 
216         return new FormattedNumberRange(string, quantity1, quantity2, identityResult);
217     }
218 
formatSingleValue(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string, MicroProps micros1, MicroProps micros2)219     private void formatSingleValue(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string,
220             MicroProps micros1, MicroProps micros2) {
221         if (fSameFormatters) {
222             int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
223             NumberFormatterImpl.writeAffixes(micros1, string, 0, length);
224         } else {
225             formatRange(quantity1, quantity2, string, micros1, micros2);
226         }
227 
228     }
229 
formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string, MicroProps micros1, MicroProps micros2)230     private void formatApproximately(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string,
231             MicroProps micros1, MicroProps micros2) {
232         if (fSameFormatters) {
233             int length = NumberFormatterImpl.writeNumber(micros1, quantity1, string, 0);
234             // HEURISTIC: Desired modifier order: inner, middle, approximately, outer.
235             length += micros1.modInner.apply(string, 0, length);
236             length += micros1.modMiddle.apply(string, 0, length);
237             length += fApproximatelyModifier.apply(string, 0, length);
238             micros1.modOuter.apply(string, 0, length);
239         } else {
240             formatRange(quantity1, quantity2, string, micros1, micros2);
241         }
242     }
243 
formatRange(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string, MicroProps micros1, MicroProps micros2)244     private void formatRange(DecimalQuantity quantity1, DecimalQuantity quantity2, FormattedStringBuilder string,
245             MicroProps micros1, MicroProps micros2) {
246         // modInner is always notation (scientific); collapsable in ALL.
247         // modOuter is always units; collapsable in ALL, AUTO, and UNIT.
248         // modMiddle could be either; collapsable in ALL and sometimes AUTO and UNIT.
249         // Never collapse an outer mod but not an inner mod.
250         boolean collapseOuter, collapseMiddle, collapseInner;
251         switch (fCollapse) {
252             case ALL:
253             case AUTO:
254             case UNIT:
255             {
256                 // OUTER MODIFIER
257                 collapseOuter = micros1.modOuter.semanticallyEquivalent(micros2.modOuter);
258 
259                 if (!collapseOuter) {
260                     // Never collapse inner mods if outer mods are not collapsable
261                     collapseMiddle = false;
262                     collapseInner = false;
263                     break;
264                 }
265 
266                 // MIDDLE MODIFIER
267                 collapseMiddle = micros1.modMiddle.semanticallyEquivalent(micros2.modMiddle);
268 
269                 if (!collapseMiddle) {
270                     // Never collapse inner mods if outer mods are not collapsable
271                     collapseInner = false;
272                     break;
273                 }
274 
275                 // MIDDLE MODIFIER HEURISTICS
276                 // (could disable collapsing of the middle modifier)
277                 // The modifiers are equal by this point, so we can look at just one of them.
278                 Modifier mm = micros1.modMiddle;
279                 if (fCollapse == RangeCollapse.UNIT) {
280                     // Only collapse if the modifier is a unit.
281                     // TODO: Make a better way to check for a unit?
282                     // TODO: Handle case where the modifier has both notation and unit (compact currency)?
283                     if (!mm.containsField(NumberFormat.Field.CURRENCY) && !mm.containsField(NumberFormat.Field.PERCENT)) {
284                         collapseMiddle = false;
285                     }
286                 } else if (fCollapse == RangeCollapse.AUTO) {
287                     // Heuristic as of ICU 63: collapse only if the modifier is more than one code point.
288                     if (mm.getCodePointCount() <= 1) {
289                         collapseMiddle = false;
290                     }
291                 }
292 
293                 if (!collapseMiddle || fCollapse != RangeCollapse.ALL) {
294                     collapseInner = false;
295                     break;
296                 }
297 
298                 // INNER MODIFIER
299                 collapseInner = micros1.modInner.semanticallyEquivalent(micros2.modInner);
300 
301                 // All done checking for collapsability.
302                 break;
303             }
304 
305             default:
306                 collapseOuter = false;
307                 collapseMiddle = false;
308                 collapseInner = false;
309                 break;
310         }
311 
312         // Java doesn't have macros, constexprs, or stack objects.
313         // Use a helper object instead.
314         PrefixInfixSuffixLengthHelper h = new PrefixInfixSuffixLengthHelper();
315 
316         SimpleModifier.formatTwoArgPattern(fRangePattern, string, 0, h, null);
317         assert h.lengthInfix > 0;
318 
319         // SPACING HEURISTIC
320         // Add spacing unless all modifiers are collapsed.
321         // TODO: add API to control this?
322         // TODO: Use a data-driven heuristic like currency spacing?
323         // TODO: Use Unicode [:whitespace:] instead of PatternProps whitespace? (consider speed implications)
324         {
325             boolean repeatInner = !collapseInner && micros1.modInner.getCodePointCount() > 0;
326             boolean repeatMiddle = !collapseMiddle && micros1.modMiddle.getCodePointCount() > 0;
327             boolean repeatOuter = !collapseOuter && micros1.modOuter.getCodePointCount() > 0;
328             if (repeatInner || repeatMiddle || repeatOuter) {
329                 // Add spacing if there is not already spacing
330                 if (!PatternProps.isWhiteSpace(string.charAt(h.index1()))) {
331                     h.lengthInfix += string.insertCodePoint(h.index1(), '\u0020', null);
332                 }
333                 if (!PatternProps.isWhiteSpace(string.charAt(h.index2() - 1))) {
334                     h.lengthInfix += string.insertCodePoint(h.index2(), '\u0020', null);
335                 }
336             }
337         }
338 
339         h.length1 += NumberFormatterImpl.writeNumber(micros1, quantity1, string, h.index0());
340         h.length2 += NumberFormatterImpl.writeNumber(micros2, quantity2, string, h.index2());
341 
342         // TODO: Support padding?
343 
344         if (collapseInner) {
345             // Note: this is actually a mix of prefix and suffix, but adding to infix length works
346             Modifier mod = resolveModifierPlurals(micros1.modInner, micros2.modInner);
347             h.lengthInfix += mod.apply(string, h.index0(), h.index3());
348         } else {
349             h.length1 += micros1.modInner.apply(string, h.index0(), h.index1());
350             h.length2 += micros2.modInner.apply(string, h.index2(), h.index3());
351         }
352 
353         if (collapseMiddle) {
354             // Note: this is actually a mix of prefix and suffix, but adding to infix length works
355             Modifier mod = resolveModifierPlurals(micros1.modMiddle, micros2.modMiddle);
356             h.lengthInfix += mod.apply(string, h.index0(), h.index3());
357         } else {
358             h.length1 += micros1.modMiddle.apply(string, h.index0(), h.index1());
359             h.length2 += micros2.modMiddle.apply(string, h.index2(), h.index3());
360         }
361 
362         if (collapseOuter) {
363             // Note: this is actually a mix of prefix and suffix, but adding to infix length works
364             Modifier mod = resolveModifierPlurals(micros1.modOuter, micros2.modOuter);
365             h.lengthInfix += mod.apply(string, h.index0(), h.index3());
366         } else {
367             h.length1 += micros1.modOuter.apply(string, h.index0(), h.index1());
368             h.length2 += micros2.modOuter.apply(string, h.index2(), h.index3());
369         }
370     }
371 
resolveModifierPlurals(Modifier first, Modifier second)372     Modifier resolveModifierPlurals(Modifier first, Modifier second) {
373         Modifier.Parameters firstParameters = first.getParameters();
374         if (firstParameters == null) {
375             // No plural form; return a fallback (e.g., the first)
376             return first;
377         }
378 
379         Modifier.Parameters secondParameters = second.getParameters();
380         if (secondParameters == null) {
381             // No plural form; return a fallback (e.g., the first)
382             return first;
383         }
384 
385         // Get the required plural form from data
386         StandardPlural resultPlural = fPluralRanges.resolve(firstParameters.plural, secondParameters.plural);
387 
388         // Get and return the new Modifier
389         assert firstParameters.obj == secondParameters.obj;
390         assert firstParameters.signum == secondParameters.signum;
391         Modifier mod = firstParameters.obj.getModifier(firstParameters.signum, resultPlural);
392         assert mod != null;
393         return mod;
394     }
395 
396 }
397