| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| // © 2017 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| package android.icu.impl.number; |
| |
| import android.icu.impl.FormattedStringBuilder; |
| import android.icu.impl.StandardPlural; |
| import android.icu.impl.number.AffixUtils.SymbolProvider; |
| import android.icu.number.NumberFormatter.SignDisplay; |
| import android.icu.number.NumberFormatter.UnitWidth; |
| import android.icu.text.DecimalFormatSymbols; |
| import android.icu.text.NumberFormat.Field; |
| import android.icu.text.PluralRules; |
| import android.icu.util.Currency; |
| |
| /** |
| * This class is a {@link Modifier} that wraps a decimal format pattern. It applies the pattern's affixes |
| * in {@link Modifier#apply}. |
| * |
| * <p> |
| * In addition to being a Modifier, this class contains the business logic for substituting the correct |
| * locale symbols into the affixes of the decimal format pattern. |
| * |
| * <p> |
| * In order to use this class, create a new instance and call the following four setters: |
| * {@link #setPatternInfo}, {@link #setPatternAttributes}, {@link #setSymbols}, and |
| * {@link #setNumberProperties}. After calling these four setters, the instance will be ready for use as |
| * a Modifier. |
| * |
| * <p> |
| * This is a MUTABLE, NON-THREAD-SAFE class designed for performance. Do NOT save references to this or |
| * attempt to use it from multiple threads! Instead, you can obtain a safe, immutable decimal format |
| * pattern modifier by calling {@link MutablePatternModifier#createImmutable}, in effect treating this |
| * instance as a builder for the immutable variant. |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPropsGenerator { |
| |
| // Modifier details |
| final boolean isStrong; |
| |
| // Pattern details |
| AffixPatternProvider patternInfo; |
| Field field; |
| SignDisplay signDisplay; |
| boolean perMilleReplacesPercent; |
| boolean approximately; |
| |
| // Symbol details |
| DecimalFormatSymbols symbols; |
| UnitWidth unitWidth; |
| Currency currency; |
| PluralRules rules; |
| |
| // Number details |
| Signum signum; |
| StandardPlural plural; |
| |
| // QuantityChain details |
| MicroPropsGenerator parent; |
| |
| // Transient fields for rendering |
| StringBuilder currentAffix; |
| |
| /** |
| * @param isStrong |
| * Whether the modifier should be considered strong. For more information, see |
| * {@link Modifier#isStrong()}. Most of the time, decimal format pattern modifiers should |
| * be considered as non-strong. |
| */ |
| public MutablePatternModifier(boolean isStrong) { |
| this.isStrong = isStrong; |
| } |
| |
| /** |
| * Sets a reference to the parsed decimal format pattern, usually obtained from |
| * {@link PatternStringParser#parseToPatternInfo(String)}, but any implementation of |
| * {@link AffixPatternProvider} is accepted. |
| * |
| * @param field |
| * Which field to use for literal characters in the pattern. |
| */ |
| public void setPatternInfo(AffixPatternProvider patternInfo, Field field) { |
| this.patternInfo = patternInfo; |
| this.field = field; |
| } |
| |
| /** |
| * Sets attributes that imply changes to the literal interpretation of the pattern string affixes. |
| * |
| * @param signDisplay |
| * Whether to force a plus sign on positive numbers. |
| * @param perMille |
| * Whether to substitute the percent sign in the pattern with a permille sign. |
| * @param approximately |
| * Whether to prepend approximately to the sign |
| */ |
| public void setPatternAttributes(SignDisplay signDisplay, boolean perMille, boolean approximately) { |
| this.signDisplay = signDisplay; |
| this.perMilleReplacesPercent = perMille; |
| this.approximately = approximately; |
| } |
| |
| /** |
| * Sets locale-specific details that affect the symbols substituted into the pattern string affixes. |
| * |
| * @param symbols |
| * The desired instance of DecimalFormatSymbols. |
| * @param currency |
| * The currency to be used when substituting currency values into the affixes. |
| * @param unitWidth |
| * The width used to render currencies. |
| * @param rules |
| * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be |
| * determined from the convenience method {@link #needsPlurals()}. |
| */ |
| public void setSymbols( |
| DecimalFormatSymbols symbols, |
| Currency currency, |
| UnitWidth unitWidth, |
| PluralRules rules) { |
| assert (rules != null) == needsPlurals(); |
| this.symbols = symbols; |
| this.currency = currency; |
| this.unitWidth = unitWidth; |
| this.rules = rules; |
| } |
| |
| /** |
| * Sets attributes of the current number being processed. |
| * |
| * @param signum |
| * -1 if negative; +1 if positive; or 0 if zero. |
| * @param plural |
| * The plural form of the number, required only if the pattern contains the triple |
| * currency sign, "¤¤¤" (and as indicated by {@link #needsPlurals()}). |
| */ |
| public void setNumberProperties(Signum signum, StandardPlural plural) { |
| assert (plural != null) == needsPlurals(); |
| this.signum = signum; |
| this.plural = plural; |
| } |
| |
| /** |
| * Returns true if the pattern represented by this MurkyModifier requires a plural keyword in order |
| * to localize. This is currently true only if there is a currency long name placeholder in the |
| * pattern ("¤¤¤"). |
| */ |
| public boolean needsPlurals() { |
| return patternInfo.containsSymbolType(AffixUtils.TYPE_CURRENCY_TRIPLE); |
| } |
| |
| /** |
| * Creates a new quantity-dependent Modifier that behaves the same as the current instance, but which |
| * is immutable and can be saved for future use. The number properties in the current instance are |
| * mutated; all other properties are left untouched. |
| * |
| * <p> |
| * The resulting modifier cannot be used in a QuantityChain. |
| * |
| * @return An immutable that supports both positive and negative numbers. |
| */ |
| public ImmutablePatternModifier createImmutable() { |
| FormattedStringBuilder a = new FormattedStringBuilder(); |
| FormattedStringBuilder b = new FormattedStringBuilder(); |
| if (needsPlurals()) { |
| // Slower path when we require the plural keyword. |
| AdoptingModifierStore pm = new AdoptingModifierStore(); |
| for (StandardPlural plural : StandardPlural.VALUES) { |
| setNumberProperties(Signum.POS, plural); |
| pm.setModifier(Signum.POS, plural, createConstantModifier(a, b)); |
| setNumberProperties(Signum.POS_ZERO, plural); |
| pm.setModifier(Signum.POS_ZERO, plural, createConstantModifier(a, b)); |
| setNumberProperties(Signum.NEG_ZERO, plural); |
| pm.setModifier(Signum.NEG_ZERO, plural, createConstantModifier(a, b)); |
| setNumberProperties(Signum.NEG, plural); |
| pm.setModifier(Signum.NEG, plural, createConstantModifier(a, b)); |
| } |
| pm.freeze(); |
| return new ImmutablePatternModifier(pm, rules); |
| } else { |
| // Faster path when plural keyword is not needed. |
| setNumberProperties(Signum.POS, null); |
| Modifier positive = createConstantModifier(a, b); |
| setNumberProperties(Signum.POS_ZERO, null); |
| Modifier posZero = createConstantModifier(a, b); |
| setNumberProperties(Signum.NEG_ZERO, null); |
| Modifier negZero = createConstantModifier(a, b); |
| setNumberProperties(Signum.NEG, null); |
| Modifier negative = createConstantModifier(a, b); |
| AdoptingModifierStore pm = new AdoptingModifierStore(positive, posZero, negZero, negative); |
| return new ImmutablePatternModifier(pm, null); |
| } |
| } |
| |
| /** |
| * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency |
| * spacing support if required. |
| * |
| * @param a |
| * A working FormattedStringBuilder object; passed from the outside to prevent the need to |
| * create many new instances if this method is called in a loop. |
| * @param b |
| * Another working FormattedStringBuilder object. |
| * @return The constant modifier object. |
| */ |
| private ConstantMultiFieldModifier createConstantModifier( |
| FormattedStringBuilder a, |
| FormattedStringBuilder b) { |
| insertPrefix(a.clear(), 0); |
| insertSuffix(b.clear(), 0); |
| if (patternInfo.hasCurrencySign()) { |
| return new CurrencySpacingEnabledModifier(a, b, !patternInfo.hasBody(), isStrong, symbols); |
| } else { |
| return new ConstantMultiFieldModifier(a, b, !patternInfo.hasBody(), isStrong); |
| } |
| } |
| |
| /** |
| * @hide Only a subset of ICU is exposed in Android |
| */ |
| public static class ImmutablePatternModifier implements MicroPropsGenerator { |
| final AdoptingModifierStore pm; |
| final PluralRules rules; |
| /* final */ MicroPropsGenerator parent; |
| |
| ImmutablePatternModifier( |
| AdoptingModifierStore pm, |
| PluralRules rules) { |
| this.pm = pm; |
| this.rules = rules; |
| this.parent = null; |
| } |
| |
| public ImmutablePatternModifier addToChain(MicroPropsGenerator parent) { |
| this.parent = parent; |
| return this; |
| } |
| |
| @Override |
| public MicroProps processQuantity(DecimalQuantity quantity) { |
| MicroProps micros = parent.processQuantity(quantity); |
| if (micros.rounder != null) { |
| micros.rounder.apply(quantity); |
| } |
| if (micros.modMiddle != null) { |
| return micros; |
| } |
| applyToMicros(micros, quantity); |
| return micros; |
| } |
| |
| public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { |
| if (rules == null) { |
| micros.modMiddle = pm.getModifierWithoutPlural(quantity.signum()); |
| } else { |
| StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity); |
| micros.modMiddle = pm.getModifier(quantity.signum(), pluralForm); |
| } |
| } |
| |
| // NOTE: This method is not used in ICU4J right now. |
| // In ICU4C, it is used by getPrefixSuffix(). |
| // Un-comment this method when getPrefixSuffix() is cleaned up in ICU4J. |
| // public Modifier getModifier(byte signum, StandardPlural plural) { |
| // if (rules == null) { |
| // return pm.getModifier(signum); |
| // } else { |
| // return pm.getModifier(signum, plural); |
| // } |
| // } |
| } |
| |
| /** Used by the unsafe code path. */ |
| public MicroPropsGenerator addToChain(MicroPropsGenerator parent) { |
| this.parent = parent; |
| return this; |
| } |
| |
| @Override |
| public MicroProps processQuantity(DecimalQuantity fq) { |
| MicroProps micros = parent.processQuantity(fq); |
| if (micros.rounder != null) { |
| micros.rounder.apply(fq); |
| } |
| if (micros.modMiddle != null) { |
| return micros; |
| } |
| if (needsPlurals()) { |
| StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, fq); |
| setNumberProperties(fq.signum(), pluralForm); |
| } else { |
| setNumberProperties(fq.signum(), null); |
| } |
| micros.modMiddle = this; |
| return micros; |
| } |
| |
| @Override |
| public int apply(FormattedStringBuilder output, int leftIndex, int rightIndex) { |
| int prefixLen = insertPrefix(output, leftIndex); |
| int suffixLen = insertSuffix(output, rightIndex + prefixLen); |
| // If the pattern had no decimal stem body (like #,##0.00), overwrite the value. |
| int overwriteLen = 0; |
| if (!patternInfo.hasBody()) { |
| overwriteLen = output.splice(leftIndex + prefixLen, rightIndex + prefixLen, "", 0, 0, null); |
| } |
| CurrencySpacingEnabledModifier.applyCurrencySpacing(output, |
| leftIndex, |
| prefixLen, |
| rightIndex + prefixLen + overwriteLen, |
| suffixLen, |
| symbols); |
| return prefixLen + overwriteLen + suffixLen; |
| } |
| |
| @Override |
| public int getPrefixLength() { |
| // Render the affix to get the length |
| prepareAffix(true); |
| int result = AffixUtils.unescapedCount(currentAffix, true, this); // prefix length |
| return result; |
| } |
| |
| @Override |
| public int getCodePointCount() { |
| // Render the affixes to get the length |
| prepareAffix(true); |
| int result = AffixUtils.unescapedCount(currentAffix, false, this); // prefix length |
| prepareAffix(false); |
| result += AffixUtils.unescapedCount(currentAffix, false, this); // suffix length |
| return result; |
| } |
| |
| @Override |
| public boolean isStrong() { |
| return isStrong; |
| } |
| |
| @Override |
| public boolean containsField(java.text.Format.Field field) { |
| // This method is not currently used. (unsafe path not used in range formatting) |
| assert false; |
| return false; |
| } |
| |
| @Override |
| public Parameters getParameters() { |
| // This method is not currently used. |
| assert false; |
| return null; |
| } |
| |
| @Override |
| public boolean strictEquals(Modifier other) { |
| // This method is not currently used. (unsafe path not used in range formatting) |
| assert false; |
| return false; |
| } |
| |
| private int insertPrefix(FormattedStringBuilder sb, int position) { |
| prepareAffix(true); |
| int length = AffixUtils.unescape(currentAffix, sb, position, this, field); |
| return length; |
| } |
| |
| private int insertSuffix(FormattedStringBuilder sb, int position) { |
| prepareAffix(false); |
| int length = AffixUtils.unescape(currentAffix, sb, position, this, field); |
| return length; |
| } |
| |
| /** |
| * Pre-processes the prefix or suffix into the currentAffix field, creating and mutating that field |
| * if necessary. Calls down to {@link PatternStringUtils#affixPatternProviderToStringBuilder}. |
| * |
| * @param isPrefix |
| * true to prepare the prefix; false to prepare the suffix. |
| */ |
| private void prepareAffix(boolean isPrefix) { |
| if (currentAffix == null) { |
| currentAffix = new StringBuilder(); |
| } |
| PatternStringUtils.patternInfoToStringBuilder(patternInfo, |
| isPrefix, |
| PatternStringUtils.resolveSignDisplay(signDisplay, signum), |
| approximately, |
| plural, |
| perMilleReplacesPercent, |
| currentAffix); |
| } |
| |
| /** |
| * Returns the string that substitutes a given symbol type in a pattern. |
| */ |
| @Override |
| public CharSequence getSymbol(int type) { |
| switch (type) { |
| case AffixUtils.TYPE_MINUS_SIGN: |
| return symbols.getMinusSignString(); |
| case AffixUtils.TYPE_PLUS_SIGN: |
| return symbols.getPlusSignString(); |
| case AffixUtils.TYPE_APPROXIMATELY_SIGN: |
| return symbols.getApproximatelySignString(); |
| case AffixUtils.TYPE_PERCENT: |
| return symbols.getPercentString(); |
| case AffixUtils.TYPE_PERMILLE: |
| return symbols.getPerMillString(); |
| case AffixUtils.TYPE_CURRENCY_SINGLE: |
| return getCurrencySymbolForUnitWidth(); |
| case AffixUtils.TYPE_CURRENCY_DOUBLE: |
| return currency.getCurrencyCode(); |
| case AffixUtils.TYPE_CURRENCY_TRIPLE: |
| // NOTE: This is the code path only for patterns containing "¤¤¤". |
| // Plural currencies set via the API are formatted in LongNameHandler. |
| // This code path is used by DecimalFormat via CurrencyPluralInfo. |
| assert plural != null; |
| return currency |
| .getName(symbols.getULocale(), Currency.PLURAL_LONG_NAME, plural.getKeyword(), null); |
| case AffixUtils.TYPE_CURRENCY_QUAD: |
| return "\uFFFD"; |
| case AffixUtils.TYPE_CURRENCY_QUINT: |
| return currency.getName(symbols.getULocale(), Currency.NARROW_SYMBOL_NAME, null); |
| default: |
| throw new AssertionError(); |
| } |
| } |
| |
| /** |
| * Returns the currency symbol for the unit width specified in setSymbols() |
| */ |
| public String getCurrencySymbolForUnitWidth() { |
| // UnitWidth ISO, HIDDEN, or NARROW overrides the singular currency symbol. |
| if (unitWidth == UnitWidth.ISO_CODE) { |
| return currency.getCurrencyCode(); |
| } else if (unitWidth == UnitWidth.HIDDEN) { |
| return ""; |
| } else { |
| int selector; |
| switch (unitWidth) { |
| case SHORT: |
| selector = Currency.SYMBOL_NAME; |
| break; |
| case NARROW: |
| selector = Currency.NARROW_SYMBOL_NAME; |
| break; |
| case FORMAL: |
| selector = Currency.FORMAL_SYMBOL_NAME; |
| break; |
| case VARIANT: |
| selector = Currency.VARIANT_SYMBOL_NAME; |
| break; |
| default: |
| throw new AssertionError(); |
| } |
| return currency.getName(symbols.getULocale(), selector, null); |
| } |
| } |
| } |