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.impl.number; 5 6 import ohos.global.icu.impl.FormattedStringBuilder; 7 import ohos.global.icu.impl.StandardPlural; 8 import ohos.global.icu.impl.number.AffixUtils.SymbolProvider; 9 import ohos.global.icu.number.NumberFormatter.SignDisplay; 10 import ohos.global.icu.number.NumberFormatter.UnitWidth; 11 import ohos.global.icu.text.DecimalFormatSymbols; 12 import ohos.global.icu.text.NumberFormat.Field; 13 import ohos.global.icu.text.PluralRules; 14 import ohos.global.icu.util.Currency; 15 16 /** 17 * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes 18 * in {@link Modifier#apply}. 19 * 20 * <p> 21 * In addition to being a Modifier, this class contains the business logic for substituting the correct 22 * locale symbols into the affixes of the decimal format pattern. 23 * 24 * <p> 25 * In order to use this class, create a new instance and call the following four setters: 26 * {@link #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and 27 * {@link #setNumberProperties}. After calling these four setters, the instance will be ready for use as 28 * a Modifier. 29 * 30 * <p> 31 * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or 32 * attempt to use it from multiple threads! Instead, you can obtain a safe, immutable decimal format 33 * pattern modifier by calling {@link MutablePatternModifier#createImmutable}, in effect treating this 34 * instance as a builder for the immutable variant. 35 * @hide exposed on OHOS 36 */ 37 public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPropsGenerator { 38 39 // Modifier details 40 final boolean isStrong; 41 42 // Pattern details 43 AffixPatternProvider patternInfo; 44 Field field; 45 SignDisplay signDisplay; 46 boolean perMilleReplacesPercent; 47 48 // Symbol details 49 DecimalFormatSymbols symbols; 50 UnitWidth unitWidth; 51 Currency currency; 52 PluralRules rules; 53 54 // Number details 55 Signum signum; 56 StandardPlural plural; 57 58 // QuantityChain details 59 MicroPropsGenerator parent; 60 61 // Transient fields for rendering 62 StringBuilder currentAffix; 63 64 /** 65 * @param isStrong 66 * Whether the modifier should be considered strong. For more information, see 67 * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should 68 * be considered as non-strong. 69 */ MutablePatternModifier(boolean isStrong)70 public MutablePatternModifier(boolean isStrong) { 71 this.isStrong = isStrong; 72 } 73 74 /** 75 * Sets a reference to the parsed decimal format pattern, usually obtained from 76 * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of 77 * {@link AffixPatternProvider} is accepted. 78 * 79 * @param field 80 * Which field to use for literal characters in the pattern. 81 */ setPatternInfo(AffixPatternProvider patternInfo, Field field)82 public void setPatternInfo(AffixPatternProvider patternInfo, Field field) { 83 this.patternInfo = patternInfo; 84 this.field = field; 85 } 86 87 /** 88 * Sets attributes that imply changes to the literal interpretation of the pattern string affixes. 89 * 90 * @param signDisplay 91 * Whether to force a plus sign on positive numbers. 92 * @param perMille 93 * Whether to substitute the percent sign in the pattern with a permille sign. 94 */ setPatternAttributes(SignDisplay signDisplay, boolean perMille)95 public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) { 96 this.signDisplay = signDisplay; 97 this.perMilleReplacesPercent = perMille; 98 } 99 100 /** 101 * Sets locale-specific details that affect the symbols substituted into the pattern string affixes. 102 * 103 * @param symbols 104 * The desired instance of DecimalFormatSymbols. 105 * @param currency 106 * The currency to be used when substituting currency values into the affixes. 107 * @param unitWidth 108 * The width used to render currencies. 109 * @param rules 110 * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be 111 * determined from the convenience method {@link #needsPlurals()}. 112 */ setSymbols( DecimalFormatSymbols symbols, Currency currency, UnitWidth unitWidth, PluralRules rules)113 public void setSymbols( 114 DecimalFormatSymbols symbols, 115 Currency currency, 116 UnitWidth unitWidth, 117 PluralRules rules) { 118 assert (rules != null) == needsPlurals(); 119 this.symbols = symbols; 120 this.currency = currency; 121 this.unitWidth = unitWidth; 122 this.rules = rules; 123 } 124 125 /** 126 * Sets attributes of the current number being processed. 127 * 128 * @param signum 129 * -1 if negative; +1 if positive; or 0 if zero. 130 * @param plural 131 * The plural form of the number, required only if the pattern contains the triple 132 * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}). 133 */ setNumberProperties(Signum signum, StandardPlural plural)134 public void setNumberProperties(Signum signum, StandardPlural plural) { 135 assert (plural != null) == needsPlurals(); 136 this.signum = signum; 137 this.plural = plural; 138 } 139 140 /** 141 * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order 142 * to localize. This is currently true only if there is a currency long name placeholder in the 143 * pattern ("¤¤¤"). 144 */ needsPlurals()145 public boolean needsPlurals() { 146 return patternInfo.containsSymbolType(AffixUtils.TYPE_CURRENCY_TRIPLE); 147 } 148 149 /** 150 * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which 151 * is immutable and can be saved for future use. The number properties in the current instance are 152 * mutated; all other properties are left untouched. 153 * 154 * <p> 155 * The resulting modifier cannot be used in a QuantityChain. 156 * 157 * @return An immutable that supports both positive and negative numbers. 158 */ createImmutable()159 public ImmutablePatternModifier createImmutable() { 160 FormattedStringBuilder a = new FormattedStringBuilder(); 161 FormattedStringBuilder b = new FormattedStringBuilder(); 162 if (needsPlurals()) { 163 // Slower path when we require the plural keyword. 164 AdoptingModifierStore pm = new AdoptingModifierStore(); 165 for (StandardPlural plural : StandardPlural.VALUES) { 166 setNumberProperties(Signum.POS, plural); 167 pm.setModifier(Signum.POS, plural, createConstantModifier(a, b)); 168 setNumberProperties(Signum.POS_ZERO, plural); 169 pm.setModifier(Signum.POS_ZERO, plural, createConstantModifier(a, b)); 170 setNumberProperties(Signum.NEG_ZERO, plural); 171 pm.setModifier(Signum.NEG_ZERO, plural, createConstantModifier(a, b)); 172 setNumberProperties(Signum.NEG, plural); 173 pm.setModifier(Signum.NEG, plural, createConstantModifier(a, b)); 174 } 175 pm.freeze(); 176 return new ImmutablePatternModifier(pm, rules); 177 } else { 178 // Faster path when plural keyword is not needed. 179 setNumberProperties(Signum.POS, null); 180 Modifier positive = createConstantModifier(a, b); 181 setNumberProperties(Signum.POS_ZERO, null); 182 Modifier posZero = createConstantModifier(a, b); 183 setNumberProperties(Signum.NEG_ZERO, null); 184 Modifier negZero = createConstantModifier(a, b); 185 setNumberProperties(Signum.NEG, null); 186 Modifier negative = createConstantModifier(a, b); 187 AdoptingModifierStore pm = new AdoptingModifierStore(positive, posZero, negZero, negative); 188 return new ImmutablePatternModifier(pm, null); 189 } 190 } 191 192 /** 193 * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency 194 * spacing support if required. 195 * 196 * @param a 197 * A working FormattedStringBuilder object; passed from the outside to prevent the need to 198 * create many new instances if this method is called in a loop. 199 * @param b 200 * Another working FormattedStringBuilder object. 201 * @return The constant modifier object. 202 */ createConstantModifier( FormattedStringBuilder a, FormattedStringBuilder b)203 private ConstantMultiFieldModifier createConstantModifier( 204 FormattedStringBuilder a, 205 FormattedStringBuilder b) { 206 insertPrefix(a.clear(), 0); 207 insertSuffix(b.clear(), 0); 208 if (patternInfo.hasCurrencySign()) { 209 return new CurrencySpacingEnabledModifier(a, b, !patternInfo.hasBody(), isStrong, symbols); 210 } else { 211 return new ConstantMultiFieldModifier(a, b, !patternInfo.hasBody(), isStrong); 212 } 213 } 214 215 /** 216 * @hide exposed on OHOS 217 */ 218 public static class ImmutablePatternModifier implements MicroPropsGenerator { 219 final AdoptingModifierStore pm; 220 final PluralRules rules; 221 /* final */ MicroPropsGenerator parent; 222 ImmutablePatternModifier( AdoptingModifierStore pm, PluralRules rules)223 ImmutablePatternModifier( 224 AdoptingModifierStore pm, 225 PluralRules rules) { 226 this.pm = pm; 227 this.rules = rules; 228 this.parent = null; 229 } 230 addToChain(MicroPropsGenerator parent)231 public ImmutablePatternModifier addToChain(MicroPropsGenerator parent) { 232 this.parent = parent; 233 return this; 234 } 235 236 @Override processQuantity(DecimalQuantity quantity)237 public MicroProps processQuantity(DecimalQuantity quantity) { 238 MicroProps micros = parent.processQuantity(quantity); 239 if (micros.rounder != null) { 240 micros.rounder.apply(quantity); 241 } 242 if (micros.modMiddle != null) { 243 return micros; 244 } 245 applyToMicros(micros, quantity); 246 return micros; 247 } 248 applyToMicros(MicroProps micros, DecimalQuantity quantity)249 public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { 250 if (rules == null) { 251 micros.modMiddle = pm.getModifierWithoutPlural(quantity.signum()); 252 } else { 253 StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity); 254 micros.modMiddle = pm.getModifier(quantity.signum(), pluralForm); 255 } 256 } 257 258 // NOTE: This method is not used in ICU4J right now. 259 // In ICU4C, it is used by getPrefixSuffix(). 260 // Un-comment this method when getPrefixSuffix() is cleaned up in ICU4J. 261 // public Modifier getModifier(byte signum, StandardPlural plural) { 262 // if (rules == null) { 263 // return pm.getModifier(signum); 264 // } else { 265 // return pm.getModifier(signum, plural); 266 // } 267 // } 268 } 269 270 /** Used by the unsafe code path. */ addToChain(MicroPropsGenerator parent)271 public MicroPropsGenerator addToChain(MicroPropsGenerator parent) { 272 this.parent = parent; 273 return this; 274 } 275 276 @Override processQuantity(DecimalQuantity fq)277 public MicroProps processQuantity(DecimalQuantity fq) { 278 MicroProps micros = parent.processQuantity(fq); 279 if (micros.rounder != null) { 280 micros.rounder.apply(fq); 281 } 282 if (micros.modMiddle != null) { 283 return micros; 284 } 285 if (needsPlurals()) { 286 StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fq); 287 setNumberProperties(fq.signum(), pluralForm); 288 } else { 289 setNumberProperties(fq.signum(), null); 290 } 291 micros.modMiddle = this; 292 return micros; 293 } 294 295 @Override apply(FormattedStringBuilder output, int leftIndex, int rightIndex)296 public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) { 297 int prefixLen = insertPrefix(output, leftIndex); 298 int suffixLen = insertSuffix(output, rightIndex + prefixLen); 299 // If the pattern had no decimal stem body (like #,##0.00), overwrite the value. 300 int overwriteLen = 0; 301 if (!patternInfo.hasBody()) { 302 overwriteLen = output.splice(leftIndex + prefixLen, rightIndex + prefixLen, "", 0, 0, null); 303 } 304 CurrencySpacingEnabledModifier.applyCurrencySpacing(output, 305 leftIndex, 306 prefixLen, 307 rightIndex + prefixLen + overwriteLen, 308 suffixLen, 309 symbols); 310 return prefixLen + overwriteLen + suffixLen; 311 } 312 313 @Override getPrefixLength()314 public int getPrefixLength() { 315 // Render the affix to get the length 316 prepareAffix(true); 317 int result = AffixUtils.unescapedCount(currentAffix, true, this); // prefix length 318 return result; 319 } 320 321 @Override getCodePointCount()322 public int getCodePointCount() { 323 // Render the affixes to get the length 324 prepareAffix(true); 325 int result = AffixUtils.unescapedCount(currentAffix, false, this); // prefix length 326 prepareAffix(false); 327 result += AffixUtils.unescapedCount(currentAffix, false, this); // suffix length 328 return result; 329 } 330 331 @Override isStrong()332 public boolean isStrong() { 333 return isStrong; 334 } 335 336 @Override containsField(java.text.Format.Field field)337 public boolean containsField(java.text.Format.Field field) { 338 // This method is not currently used. (unsafe path not used in range formatting) 339 assert false; 340 return false; 341 } 342 343 @Override getParameters()344 public Parameters getParameters() { 345 // This method is not currently used. 346 assert false; 347 return null; 348 } 349 350 @Override semanticallyEquivalent(Modifier other)351 public boolean semanticallyEquivalent(Modifier other) { 352 // This method is not currently used. (unsafe path not used in range formatting) 353 assert false; 354 return false; 355 } 356 insertPrefix(FormattedStringBuilder sb, int position)357 private int insertPrefix(FormattedStringBuilder sb, int position) { 358 prepareAffix(true); 359 int length = AffixUtils.unescape(currentAffix, sb, position, this, field); 360 return length; 361 } 362 insertSuffix(FormattedStringBuilder sb, int position)363 private int insertSuffix(FormattedStringBuilder sb, int position) { 364 prepareAffix(false); 365 int length = AffixUtils.unescape(currentAffix, sb, position, this, field); 366 return length; 367 } 368 369 /** 370 * Pre-processes the prefix or suffix into the currentAffix field, creating and mutating that field 371 * if necessary. Calls down to {@link PatternStringUtils#affixPatternProviderToStringBuilder}. 372 * 373 * @param isPrefix 374 * true to prepare the prefix; false to prepare the suffix. 375 */ prepareAffix(boolean isPrefix)376 private void prepareAffix(boolean isPrefix) { 377 if (currentAffix == null) { 378 currentAffix = new StringBuilder(); 379 } 380 PatternStringUtils.patternInfoToStringBuilder(patternInfo, 381 isPrefix, 382 PatternStringUtils.resolveSignDisplay(signDisplay, signum), 383 plural, 384 perMilleReplacesPercent, 385 currentAffix); 386 } 387 388 /** 389 * Returns the string that substitutes a given symbol type in a pattern. 390 */ 391 @Override getSymbol(int type)392 public CharSequence getSymbol(int type) { 393 switch (type) { 394 case AffixUtils.TYPE_MINUS_SIGN: 395 return symbols.getMinusSignString(); 396 case AffixUtils.TYPE_PLUS_SIGN: 397 return symbols.getPlusSignString(); 398 case AffixUtils.TYPE_PERCENT: 399 return symbols.getPercentString(); 400 case AffixUtils.TYPE_PERMILLE: 401 return symbols.getPerMillString(); 402 case AffixUtils.TYPE_CURRENCY_SINGLE: 403 // UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol. 404 if (unitWidth == UnitWidth.ISO_CODE) { 405 return currency.getCurrencyCode(); 406 } else if (unitWidth == UnitWidth.HIDDEN) { 407 return ""; 408 } else { 409 int selector; 410 switch (unitWidth) { 411 case SHORT: 412 selector = Currency.SYMBOL_NAME; 413 break; 414 case NARROW: 415 selector = Currency.NARROW_SYMBOL_NAME; 416 break; 417 case FORMAL: 418 selector = Currency.FORMAL_SYMBOL_NAME; 419 break; 420 case VARIANT: 421 selector = Currency.VARIANT_SYMBOL_NAME; 422 break; 423 default: 424 throw new AssertionError(); 425 } 426 return currency.getName(symbols.getULocale(), selector, null); 427 } 428 case AffixUtils.TYPE_CURRENCY_DOUBLE: 429 return currency.getCurrencyCode(); 430 case AffixUtils.TYPE_CURRENCY_TRIPLE: 431 // NOTE: This is the code path only for patterns containing "¤¤¤". 432 // Plural currencies set via the API are formatted in LongNameHandler. 433 // This code path is used by DecimalFormat via CurrencyPluralInfo. 434 assert plural != null; 435 return currency 436 .getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null); 437 case AffixUtils.TYPE_CURRENCY_QUAD: 438 return "\uFFFD"; 439 case AffixUtils.TYPE_CURRENCY_QUINT: 440 return currency.getName(symbols.getULocale(), Currency.NARROW_SYMBOL_NAME, null); 441 default: 442 throw new AssertionError(); 443 } 444 } 445 } 446