460 lines
17 KiB
Java
460 lines
17 KiB
Java
/* 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);
|
|
}
|
|
}
|
|
}
|