All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ibm.icu.impl.number.MutablePatternModifier Maven / Gradle / Ivy

There is a newer version: 2.12.15
Show newest version
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number;

import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.AffixUtils.SymbolProvider;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.PluralRules;
import com.ibm.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}.
 *
 * 

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

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

* 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. */ public class MutablePatternModifier implements Modifier, SymbolProvider, MicroPropsGenerator { // Modifier details final boolean isStrong; // Pattern details AffixPatternProvider patternInfo; SignDisplay signDisplay; boolean perMilleReplacesPercent; // Symbol details DecimalFormatSymbols symbols; UnitWidth unitWidth; Currency currency; PluralRules rules; // Number details int 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. */ public void setPatternInfo(AffixPatternProvider patternInfo) { this.patternInfo = patternInfo; } /** * 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. */ public void setPatternAttributes(SignDisplay signDisplay, boolean perMille) { this.signDisplay = signDisplay; this.perMilleReplacesPercent = perMille; } /** * 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(int 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. * *

* The resulting modifier cannot be used in a QuantityChain. * * @return An immutable that supports both positive and negative numbers. */ public ImmutablePatternModifier createImmutable() { return createImmutableAndChain(null); } /** * 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. * * @param parent * The QuantityChain to which to chain this immutable. * @return An immutable that supports both positive and negative numbers. */ public ImmutablePatternModifier createImmutableAndChain(MicroPropsGenerator parent) { NumberStringBuilder a = new NumberStringBuilder(); NumberStringBuilder b = new NumberStringBuilder(); if (needsPlurals()) { // Slower path when we require the plural keyword. ParameterizedModifier pm = new ParameterizedModifier(); for (StandardPlural plural : StandardPlural.VALUES) { setNumberProperties(1, plural); pm.setModifier(1, plural, createConstantModifier(a, b)); setNumberProperties(0, plural); pm.setModifier(0, plural, createConstantModifier(a, b)); setNumberProperties(-1, plural); pm.setModifier(-1, plural, createConstantModifier(a, b)); } pm.freeze(); return new ImmutablePatternModifier(pm, rules, parent); } else { // Faster path when plural keyword is not needed. setNumberProperties(1, null); Modifier positive = createConstantModifier(a, b); setNumberProperties(0, null); Modifier zero = createConstantModifier(a, b); setNumberProperties(-1, null); Modifier negative = createConstantModifier(a, b); ParameterizedModifier pm = new ParameterizedModifier(positive, zero, negative); return new ImmutablePatternModifier(pm, null, parent); } } /** * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency * spacing support if required. * * @param a * A working NumberStringBuilder 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 NumberStringBuilder object. * @return The constant modifier object. */ private ConstantMultiFieldModifier createConstantModifier( NumberStringBuilder a, NumberStringBuilder 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); } } public static class ImmutablePatternModifier implements MicroPropsGenerator { final ParameterizedModifier pm; final PluralRules rules; final MicroPropsGenerator parent; ImmutablePatternModifier( ParameterizedModifier pm, PluralRules rules, MicroPropsGenerator parent) { this.pm = pm; this.rules = rules; this.parent = parent; } @Override public MicroProps processQuantity(DecimalQuantity quantity) { MicroProps micros = parent.processQuantity(quantity); applyToMicros(micros, quantity); return micros; } public void applyToMicros(MicroProps micros, DecimalQuantity quantity) { if (rules == null) { micros.modMiddle = pm.getModifier(quantity.signum()); } else { // TODO: Fix this. Avoid the copy. DecimalQuantity copy = quantity.createCopy(); copy.roundToInfinity(); StandardPlural plural = copy.getStandardPlural(rules); micros.modMiddle = pm.getModifier(quantity.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 (needsPlurals()) { // TODO: Fix this. Avoid the copy. DecimalQuantity copy = fq.createCopy(); micros.rounding.apply(copy); setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); } else { setNumberProperties(fq.signum(), null); } micros.modMiddle = this; return micros; } @Override public int apply(NumberStringBuilder 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; } private int insertPrefix(NumberStringBuilder sb, int position) { prepareAffix(true); int length = AffixUtils.unescape(currentAffix, sb, position, this); return length; } private int insertSuffix(NumberStringBuilder sb, int position) { prepareAffix(false); int length = AffixUtils.unescape(currentAffix, sb, position, this); 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, signum, signDisplay, 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_PERCENT: return symbols.getPercentString(); case AffixUtils.TYPE_PERMILLE: return symbols.getPerMillString(); case AffixUtils.TYPE_CURRENCY_SINGLE: // 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 = unitWidth == UnitWidth.NARROW ? Currency.NARROW_SYMBOL_NAME : Currency.SYMBOL_NAME; return currency.getName(symbols.getULocale(), selector, null); } 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy