• 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.number;
5 
6 import ohos.global.icu.impl.FormattedStringBuilder;
7 import ohos.global.icu.impl.StandardPlural;
8 import ohos.global.icu.impl.number.CompactData.CompactType;
9 import ohos.global.icu.impl.number.ConstantAffixModifier;
10 import ohos.global.icu.impl.number.DecimalQuantity;
11 import ohos.global.icu.impl.number.DecimalQuantity_DualStorageBCD;
12 import ohos.global.icu.impl.number.Grouper;
13 import ohos.global.icu.impl.number.LongNameHandler;
14 import ohos.global.icu.impl.number.MacroProps;
15 import ohos.global.icu.impl.number.MicroProps;
16 import ohos.global.icu.impl.number.MicroPropsGenerator;
17 import ohos.global.icu.impl.number.MultiplierFormatHandler;
18 import ohos.global.icu.impl.number.MutablePatternModifier;
19 import ohos.global.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier;
20 import ohos.global.icu.impl.number.Padder;
21 import ohos.global.icu.impl.number.PatternStringParser;
22 import ohos.global.icu.impl.number.PatternStringParser.ParsedPatternInfo;
23 import ohos.global.icu.impl.number.RoundingUtils;
24 import ohos.global.icu.number.NumberFormatter.DecimalSeparatorDisplay;
25 import ohos.global.icu.number.NumberFormatter.GroupingStrategy;
26 import ohos.global.icu.number.NumberFormatter.SignDisplay;
27 import ohos.global.icu.number.NumberFormatter.UnitWidth;
28 import ohos.global.icu.text.DecimalFormatSymbols;
29 import ohos.global.icu.text.NumberFormat;
30 import ohos.global.icu.text.NumberingSystem;
31 import ohos.global.icu.text.PluralRules;
32 import ohos.global.icu.util.Currency;
33 import ohos.global.icu.util.MeasureUnit;
34 
35 /**
36  * This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a
37  * MacroProps and a DecimalQuantity and outputting a properly formatted number string.
38  *
39  * <p>
40  * This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too
41  * many package-private members of the public APIs.
42  */
43 class NumberFormatterImpl {
44 
45     /** Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly. */
NumberFormatterImpl(MacroProps macros)46     public NumberFormatterImpl(MacroProps macros) {
47         micros = new MicroProps(true);
48         microPropsGenerator = macrosToMicroGenerator(macros, micros, true);
49     }
50 
51     /**
52      * Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
53      */
formatStatic( MacroProps macros, DecimalQuantity inValue, FormattedStringBuilder outString)54     public static int formatStatic(
55             MacroProps macros,
56             DecimalQuantity inValue,
57             FormattedStringBuilder outString) {
58         MicroProps micros = preProcessUnsafe(macros, inValue);
59         int length = writeNumber(micros, inValue, outString, 0);
60         length += writeAffixes(micros, outString, 0, length);
61         return length;
62     }
63 
64     /**
65      * Prints only the prefix and suffix; used for DecimalFormat getters.
66      *
67      * @return The index into the output at which the prefix ends and the suffix starts; in other words,
68      *         the prefix length.
69      */
getPrefixSuffixStatic( MacroProps macros, byte signum, StandardPlural plural, FormattedStringBuilder output)70     public static int getPrefixSuffixStatic(
71             MacroProps macros,
72             byte signum,
73             StandardPlural plural,
74             FormattedStringBuilder output) {
75         MicroProps micros = new MicroProps(false);
76         MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
77         return getPrefixSuffixImpl(microPropsGenerator, signum, output);
78     }
79 
80     private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
81 
82     final MicroProps micros;
83     final MicroPropsGenerator microPropsGenerator;
84 
85     /**
86      * Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
87      */
format(DecimalQuantity inValue, FormattedStringBuilder outString)88     public int format(DecimalQuantity inValue, FormattedStringBuilder outString) {
89         MicroProps micros = preProcess(inValue);
90         int length = writeNumber(micros, inValue, outString, 0);
91         length += writeAffixes(micros, outString, 0, length);
92         return length;
93     }
94 
95     /**
96      * Like format(), but saves the result into an output MicroProps without additional processing.
97      */
preProcess(DecimalQuantity inValue)98     public MicroProps preProcess(DecimalQuantity inValue) {
99         MicroProps micros = microPropsGenerator.processQuantity(inValue);
100         if (micros.integerWidth.maxInt == -1) {
101             inValue.setMinInteger(micros.integerWidth.minInt);
102         } else {
103             inValue.setMinInteger(micros.integerWidth.minInt);
104             inValue.applyMaxInteger(micros.integerWidth.maxInt);
105         }
106         return micros;
107     }
108 
preProcessUnsafe(MacroProps macros, DecimalQuantity inValue)109     private static MicroProps preProcessUnsafe(MacroProps macros, DecimalQuantity inValue) {
110         MicroProps micros = new MicroProps(false);
111         MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
112         micros = microPropsGenerator.processQuantity(inValue);
113         if (micros.integerWidth.maxInt == -1) {
114             inValue.setMinInteger(micros.integerWidth.minInt);
115         } else {
116             inValue.setMinInteger(micros.integerWidth.minInt);
117             inValue.applyMaxInteger(micros.integerWidth.maxInt);
118         }
119         return micros;
120     }
121 
getPrefixSuffix(byte signum, StandardPlural plural, FormattedStringBuilder output)122     public int getPrefixSuffix(byte signum, StandardPlural plural, FormattedStringBuilder output) {
123         return getPrefixSuffixImpl(microPropsGenerator, signum, output);
124     }
125 
getPrefixSuffixImpl(MicroPropsGenerator generator, byte signum, FormattedStringBuilder output)126     private static int getPrefixSuffixImpl(MicroPropsGenerator generator, byte signum, FormattedStringBuilder output) {
127         // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
128         // TODO: Clean this up, closer to C++. The pattern modifier is not as accessible as in C++.
129         // Right now, ignore the plural form, run the pipeline with number 0, and get the modifier from the result.
130         DecimalQuantity_DualStorageBCD quantity = new DecimalQuantity_DualStorageBCD(0);
131         if (signum < 0) {
132             quantity.negate();
133         }
134         MicroProps micros = generator.processQuantity(quantity);
135         micros.modMiddle.apply(output, 0, 0);
136         return micros.modMiddle.getPrefixLength();
137     }
138 
getRawMicroProps()139     public MicroProps getRawMicroProps() {
140         return micros;
141     }
142 
143     //////////
144 
unitIsCurrency(MeasureUnit unit)145     private static boolean unitIsCurrency(MeasureUnit unit) {
146         // TODO: Check using "instanceof" operator instead?
147         return unit != null && "currency".equals(unit.getType());
148     }
149 
unitIsNoUnit(MeasureUnit unit)150     private static boolean unitIsNoUnit(MeasureUnit unit) {
151         // NOTE: In ICU4C, units cannot be null, and the default unit is a NoUnit.
152         // In ICU4J, return TRUE for a null unit from this method.
153         return unit == null || "none".equals(unit.getType());
154     }
155 
unitIsPercent(MeasureUnit unit)156     private static boolean unitIsPercent(MeasureUnit unit) {
157         return unit != null && "percent".equals(unit.getSubtype());
158     }
159 
unitIsPermille(MeasureUnit unit)160     private static boolean unitIsPermille(MeasureUnit unit) {
161         return unit != null && "permille".equals(unit.getSubtype());
162     }
163 
164     /**
165      * Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is
166      * encoded into the MicroPropsGenerator, except for the quantity itself, which is left abstract and
167      * must be provided to the returned MicroPropsGenerator instance.
168      *
169      * @see MicroPropsGenerator
170      * @param macros
171      *            The {@link MacroProps} to consume. This method does not mutate the MacroProps instance.
172      * @param safe
173      *            If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned
174      *            value will <em>not</em> be thread-safe, intended for a single "one-shot" use only.
175      *            Building the thread-safe object is more expensive.
176      */
macrosToMicroGenerator(MacroProps macros, MicroProps micros, boolean safe)177     private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, MicroProps micros, boolean safe) {
178         MicroPropsGenerator chain = micros;
179 
180         // TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)?
181         // currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols);
182 
183         // Pre-compute a few values for efficiency.
184         boolean isCurrency = unitIsCurrency(macros.unit);
185         boolean isNoUnit = unitIsNoUnit(macros.unit);
186         boolean isPercent = unitIsPercent(macros.unit);
187         boolean isPermille = unitIsPermille(macros.unit);
188         boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING
189                 || macros.sign == SignDisplay.ACCOUNTING_ALWAYS
190                 || macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO;
191         Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY;
192         UnitWidth unitWidth = UnitWidth.SHORT;
193         if (macros.unitWidth != null) {
194             unitWidth = macros.unitWidth;
195         }
196         boolean isCldrUnit = !isCurrency && !isNoUnit &&
197             (unitWidth == UnitWidth.FULL_NAME || !(isPercent || isPermille));
198         PluralRules rules = macros.rules;
199 
200         // Select the numbering system.
201         NumberingSystem ns;
202         if (macros.symbols instanceof NumberingSystem) {
203             ns = (NumberingSystem) macros.symbols;
204         } else {
205             // TODO: Is there a way to avoid creating the NumberingSystem object?
206             ns = NumberingSystem.getInstance(macros.loc);
207         }
208         micros.nsName = ns.getName();
209 
210         // Resolve the symbols. Do this here because currency may need to customize them.
211         if (macros.symbols instanceof DecimalFormatSymbols) {
212             micros.symbols = (DecimalFormatSymbols) macros.symbols;
213         } else {
214             micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
215             if (isCurrency) {
216                 micros.symbols.setCurrency(currency);
217             }
218         }
219 
220         // Load and parse the pattern string. It is used for grouping sizes and affixes only.
221         // If we are formatting currency, check for a currency-specific pattern.
222         String pattern = null;
223         if (isCurrency && micros.symbols.getCurrencyPattern() != null) {
224             pattern = micros.symbols.getCurrencyPattern();
225         }
226         if (pattern == null) {
227             int patternStyle;
228             if (isCldrUnit) {
229                 patternStyle = NumberFormat.NUMBERSTYLE;
230             } else if (isPercent || isPermille) {
231                 patternStyle = NumberFormat.PERCENTSTYLE;
232             } else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
233                 patternStyle = NumberFormat.NUMBERSTYLE;
234             } else if (isAccounting) {
235                 // NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies
236                 // right now, the API contract allows us to add support to other units in the future.
237                 patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
238             } else {
239                 patternStyle = NumberFormat.CURRENCYSTYLE;
240             }
241             pattern = NumberFormat
242                     .getPatternForStyleAndNumberingSystem(macros.loc, micros.nsName, patternStyle);
243         }
244         ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
245 
246         /////////////////////////////////////////////////////////////////////////////////////
247         /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
248         /////////////////////////////////////////////////////////////////////////////////////
249 
250         // Multiplier
251         if (macros.scale != null) {
252             chain = new MultiplierFormatHandler(macros.scale, chain);
253         }
254 
255         // Rounding strategy
256         if (macros.precision != null) {
257             micros.rounder = macros.precision;
258         } else if (macros.notation instanceof CompactNotation) {
259             micros.rounder = Precision.COMPACT_STRATEGY;
260         } else if (isCurrency) {
261             micros.rounder = Precision.MONETARY_STANDARD;
262         } else {
263             micros.rounder = Precision.DEFAULT_MAX_FRAC_6;
264         }
265         if (macros.roundingMode != null) {
266             micros.rounder = micros.rounder.withMode(
267                     RoundingUtils.mathContextUnlimited(macros.roundingMode));
268         }
269         micros.rounder = micros.rounder.withLocaleData(currency);
270 
271         // Grouping strategy
272         if (macros.grouping instanceof Grouper) {
273             micros.grouping = (Grouper) macros.grouping;
274         } else if (macros.grouping instanceof GroupingStrategy) {
275             micros.grouping = Grouper.forStrategy((GroupingStrategy) macros.grouping);
276         } else if (macros.notation instanceof CompactNotation) {
277             // Compact notation uses minGrouping by default since ICU 59
278             micros.grouping = Grouper.forStrategy(GroupingStrategy.MIN2);
279         } else {
280             micros.grouping = Grouper.forStrategy(GroupingStrategy.AUTO);
281         }
282         micros.grouping = micros.grouping.withLocaleData(macros.loc, patternInfo);
283 
284         // Padding strategy
285         if (macros.padder != null) {
286             micros.padding = macros.padder;
287         } else {
288             micros.padding = Padder.NONE;
289         }
290 
291         // Integer width
292         if (macros.integerWidth != null) {
293             micros.integerWidth = macros.integerWidth;
294         } else {
295             micros.integerWidth = IntegerWidth.DEFAULT;
296         }
297 
298         // Sign display
299         if (macros.sign != null) {
300             micros.sign = macros.sign;
301         } else {
302             micros.sign = SignDisplay.AUTO;
303         }
304 
305         // Decimal mark display
306         if (macros.decimal != null) {
307             micros.decimal = macros.decimal;
308         } else {
309             micros.decimal = DecimalSeparatorDisplay.AUTO;
310         }
311 
312         // Use monetary separator symbols
313         micros.useCurrency = isCurrency;
314 
315         // Inner modifier (scientific notation)
316         if (macros.notation instanceof ScientificNotation) {
317             chain = ((ScientificNotation) macros.notation).withLocaleData(micros.symbols, safe, chain);
318         } else {
319             // No inner modifier required
320             micros.modInner = ConstantAffixModifier.EMPTY;
321         }
322 
323         // Middle modifier (patterns, positive/negative, currency symbols, percent)
324         // The default middle modifier is weak (thus the false argument).
325         MutablePatternModifier patternMod = new MutablePatternModifier(false);
326         patternMod.setPatternInfo((macros.affixProvider != null) ? macros.affixProvider : patternInfo, null);
327         patternMod.setPatternAttributes(micros.sign, isPermille);
328         if (patternMod.needsPlurals()) {
329             if (rules == null) {
330                 // Lazily create PluralRules
331                 rules = PluralRules.forLocale(macros.loc);
332             }
333             patternMod.setSymbols(micros.symbols, currency, unitWidth, rules);
334         } else {
335             patternMod.setSymbols(micros.symbols, currency, unitWidth, null);
336         }
337         ImmutablePatternModifier immPatternMod = null;
338         if (safe) {
339             immPatternMod = patternMod.createImmutable();
340         }
341 
342         // Outer modifier (CLDR units and currency long names)
343         if (isCldrUnit) {
344             if (rules == null) {
345                 // Lazily create PluralRules
346                 rules = PluralRules.forLocale(macros.loc);
347             }
348             chain = LongNameHandler
349                     .forMeasureUnit(macros.loc, macros.unit, macros.perUnit, unitWidth, rules, chain);
350         } else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
351             if (rules == null) {
352                 // Lazily create PluralRules
353                 rules = PluralRules.forLocale(macros.loc);
354             }
355             chain = LongNameHandler.forCurrencyLongNames(macros.loc, currency, rules, chain);
356         } else {
357             // No outer modifier required
358             micros.modOuter = ConstantAffixModifier.EMPTY;
359         }
360 
361         // Compact notation
362         if (macros.notation instanceof CompactNotation) {
363             if (rules == null) {
364                 // Lazily create PluralRules
365                 rules = PluralRules.forLocale(macros.loc);
366             }
367             CompactType compactType = (macros.unit instanceof Currency
368                     && macros.unitWidth != UnitWidth.FULL_NAME) ? CompactType.CURRENCY
369                             : CompactType.DECIMAL;
370             chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc,
371                     micros.nsName,
372                     compactType,
373                     rules,
374                     patternMod,
375                     safe,
376                     chain);
377         }
378 
379         // Always add the pattern modifier as the last element of the chain.
380         if (safe) {
381             chain = immPatternMod.addToChain(chain);
382         } else {
383             chain = patternMod.addToChain(chain);
384         }
385 
386         return chain;
387     }
388 
389     //////////
390 
391     /**
392      * Adds the affixes.  Intended to be called immediately after formatNumber.
393      */
writeAffixes( MicroProps micros, FormattedStringBuilder string, int start, int end)394     public static int writeAffixes(
395             MicroProps micros,
396             FormattedStringBuilder string,
397             int start,
398             int end) {
399         // Always apply the inner modifier (which is "strong").
400         int length = micros.modInner.apply(string, start, end);
401         if (micros.padding.isValid()) {
402             micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, start, end + length);
403         } else {
404             length += micros.modMiddle.apply(string, start, end + length);
405             length += micros.modOuter.apply(string, start, end + length);
406         }
407         return length;
408     }
409 
410     /**
411      * Synthesizes the output string from a MicroProps and DecimalQuantity.
412      * This method formats only the main number, not affixes.
413      */
writeNumber( MicroProps micros, DecimalQuantity quantity, FormattedStringBuilder string, int index)414     public static int writeNumber(
415             MicroProps micros,
416             DecimalQuantity quantity,
417             FormattedStringBuilder string,
418             int index) {
419         int length = 0;
420         if (quantity.isInfinite()) {
421             length += string.insert(length + index, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
422 
423         } else if (quantity.isNaN()) {
424             length += string.insert(length + index, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
425 
426         } else {
427             // Add the integer digits
428             length += writeIntegerDigits(micros, quantity, string, length + index);
429 
430             // Add the decimal point
431             if (quantity.getLowerDisplayMagnitude() < 0
432                     || micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
433                 length += string.insert(length + index,
434                         micros.useCurrency ? micros.symbols.getMonetaryDecimalSeparatorString()
435                                 : micros.symbols.getDecimalSeparatorString(),
436                         NumberFormat.Field.DECIMAL_SEPARATOR);
437             }
438 
439             // Add the fraction digits
440             length += writeFractionDigits(micros, quantity, string, length + index);
441 
442             if (length == 0) {
443                 // Force output of the digit for value 0
444                 if (micros.symbols.getCodePointZero() != -1) {
445                     length += string.insertCodePoint(index,
446                             micros.symbols.getCodePointZero(),
447                             NumberFormat.Field.INTEGER);
448                 } else {
449                     length += string.insert(index,
450                             micros.symbols.getDigitStringsLocal()[0],
451                             NumberFormat.Field.INTEGER);
452                 }
453             }
454         }
455 
456         return length;
457     }
458 
writeIntegerDigits( MicroProps micros, DecimalQuantity quantity, FormattedStringBuilder string, int index)459     private static int writeIntegerDigits(
460             MicroProps micros,
461             DecimalQuantity quantity,
462             FormattedStringBuilder string,
463             int index) {
464         int length = 0;
465         int integerCount = quantity.getUpperDisplayMagnitude() + 1;
466         for (int i = 0; i < integerCount; i++) {
467             // Add grouping separator
468             if (micros.grouping.groupAtPosition(i, quantity)) {
469                 length += string.insert(index,
470                         micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
471                                 : micros.symbols.getGroupingSeparatorString(),
472                         NumberFormat.Field.GROUPING_SEPARATOR);
473             }
474 
475             // Get and append the next digit value
476             byte nextDigit = quantity.getDigit(i);
477             if (micros.symbols.getCodePointZero() != -1) {
478                 length += string.insertCodePoint(index,
479                         micros.symbols.getCodePointZero() + nextDigit,
480                         NumberFormat.Field.INTEGER);
481             } else {
482                 length += string.insert(index,
483                         micros.symbols.getDigitStringsLocal()[nextDigit],
484                         NumberFormat.Field.INTEGER);
485             }
486         }
487         return length;
488     }
489 
writeFractionDigits( MicroProps micros, DecimalQuantity quantity, FormattedStringBuilder string, int index)490     private static int writeFractionDigits(
491             MicroProps micros,
492             DecimalQuantity quantity,
493             FormattedStringBuilder string,
494             int index) {
495         int length = 0;
496         int fractionCount = -quantity.getLowerDisplayMagnitude();
497         for (int i = 0; i < fractionCount; i++) {
498             // Get and append the next digit value
499             byte nextDigit = quantity.getDigit(-i - 1);
500             if (micros.symbols.getCodePointZero() != -1) {
501                 length += string.insertCodePoint(length + index, micros.symbols.getCodePointZero() + nextDigit,
502                         NumberFormat.Field.FRACTION);
503             } else {
504                 length += string.insert(length + index, micros.symbols.getDigitStringsLocal()[nextDigit],
505                         NumberFormat.Field.FRACTION);
506             }
507         }
508         return length;
509     }
510 }
511