• 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.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