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