621 lines
26 KiB
Java
621 lines
26 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.number;
|
||
|
|
||
|
import android.icu.impl.FormattedStringBuilder;
|
||
|
import android.icu.impl.IllegalIcuArgumentException;
|
||
|
import android.icu.impl.StandardPlural;
|
||
|
import android.icu.impl.number.AffixPatternProvider;
|
||
|
import android.icu.impl.number.CompactData.CompactType;
|
||
|
import android.icu.impl.number.ConstantAffixModifier;
|
||
|
import android.icu.impl.number.DecimalQuantity;
|
||
|
import android.icu.impl.number.DecimalQuantity_DualStorageBCD;
|
||
|
import android.icu.impl.number.Grouper;
|
||
|
import android.icu.impl.number.LongNameHandler;
|
||
|
import android.icu.impl.number.LongNameMultiplexer;
|
||
|
import android.icu.impl.number.MacroProps;
|
||
|
import android.icu.impl.number.MicroProps;
|
||
|
import android.icu.impl.number.MicroPropsGenerator;
|
||
|
import android.icu.impl.number.MixedUnitLongNameHandler;
|
||
|
import android.icu.impl.number.MultiplierFormatHandler;
|
||
|
import android.icu.impl.number.MutablePatternModifier;
|
||
|
import android.icu.impl.number.MutablePatternModifier.ImmutablePatternModifier;
|
||
|
import android.icu.impl.number.Padder;
|
||
|
import android.icu.impl.number.PatternStringParser;
|
||
|
import android.icu.impl.number.PatternStringParser.ParsedPatternInfo;
|
||
|
import android.icu.impl.number.RoundingUtils;
|
||
|
import android.icu.impl.number.UnitConversionHandler;
|
||
|
import android.icu.impl.number.UsagePrefsHandler;
|
||
|
import android.icu.number.NumberFormatter.DecimalSeparatorDisplay;
|
||
|
import android.icu.number.NumberFormatter.GroupingStrategy;
|
||
|
import android.icu.number.NumberFormatter.SignDisplay;
|
||
|
import android.icu.number.NumberFormatter.UnitWidth;
|
||
|
import android.icu.text.DecimalFormatSymbols;
|
||
|
import android.icu.text.NumberFormat;
|
||
|
import android.icu.text.NumberingSystem;
|
||
|
import android.icu.text.PluralRules;
|
||
|
import android.icu.util.Currency;
|
||
|
import android.icu.util.MeasureUnit;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* This is the "brain" of the number formatting pipeline. It ties all the pieces together, taking in a
|
||
|
* MacroProps and a DecimalQuantity and outputting a properly formatted number string.
|
||
|
*
|
||
|
* <p>
|
||
|
* This class, as well as NumberPropertyMapper, could go into the impl package, but they depend on too
|
||
|
* many package-private members of the public APIs.
|
||
|
*/
|
||
|
class NumberFormatterImpl {
|
||
|
|
||
|
/**
|
||
|
* Builds a "safe" MicroPropsGenerator, which is thread-safe and can be used repeatedly.
|
||
|
*/
|
||
|
public NumberFormatterImpl(MacroProps macros) {
|
||
|
micros = new MicroProps(true);
|
||
|
microPropsGenerator = macrosToMicroGenerator(macros, micros, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
|
||
|
*/
|
||
|
public static MicroProps formatStatic(
|
||
|
MacroProps macros,
|
||
|
DecimalQuantity inValue,
|
||
|
FormattedStringBuilder outString) {
|
||
|
MicroProps result = preProcessUnsafe(macros, inValue);
|
||
|
int length = writeNumber(result, inValue, outString, 0);
|
||
|
writeAffixes(result, outString, 0, length);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prints only the prefix and suffix; used for DecimalFormat getters.
|
||
|
*
|
||
|
* @return The index into the output at which the prefix ends and the suffix starts; in other words,
|
||
|
* the prefix length.
|
||
|
*/
|
||
|
public static int getPrefixSuffixStatic(
|
||
|
MacroProps macros,
|
||
|
byte signum,
|
||
|
StandardPlural plural,
|
||
|
FormattedStringBuilder output) {
|
||
|
MicroProps micros = new MicroProps(false);
|
||
|
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
|
||
|
return getPrefixSuffixImpl(microPropsGenerator, signum, output);
|
||
|
}
|
||
|
|
||
|
private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
|
||
|
|
||
|
final MicroProps micros;
|
||
|
final MicroPropsGenerator microPropsGenerator;
|
||
|
|
||
|
/**
|
||
|
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
|
||
|
*/
|
||
|
public MicroProps format(DecimalQuantity inValue, FormattedStringBuilder outString) {
|
||
|
MicroProps result = preProcess(inValue);
|
||
|
int length = writeNumber(result, inValue, outString, 0);
|
||
|
writeAffixes(result, outString, 0, length);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Like format(), but saves the result into an output MicroProps without additional processing.
|
||
|
*/
|
||
|
public MicroProps preProcess(DecimalQuantity inValue) {
|
||
|
MicroProps micros = microPropsGenerator.processQuantity(inValue);
|
||
|
if (micros.integerWidth.maxInt == -1) {
|
||
|
inValue.setMinInteger(micros.integerWidth.minInt);
|
||
|
} else {
|
||
|
inValue.setMinInteger(micros.integerWidth.minInt);
|
||
|
inValue.applyMaxInteger(micros.integerWidth.maxInt);
|
||
|
}
|
||
|
return micros;
|
||
|
}
|
||
|
|
||
|
private static MicroProps preProcessUnsafe(MacroProps macros, DecimalQuantity inValue) {
|
||
|
MicroProps micros = new MicroProps(false);
|
||
|
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, micros, false);
|
||
|
micros = microPropsGenerator.processQuantity(inValue);
|
||
|
if (micros.integerWidth.maxInt == -1) {
|
||
|
inValue.setMinInteger(micros.integerWidth.minInt);
|
||
|
} else {
|
||
|
inValue.setMinInteger(micros.integerWidth.minInt);
|
||
|
inValue.applyMaxInteger(micros.integerWidth.maxInt);
|
||
|
}
|
||
|
return micros;
|
||
|
}
|
||
|
|
||
|
public int getPrefixSuffix(byte signum, StandardPlural plural, FormattedStringBuilder output) {
|
||
|
return getPrefixSuffixImpl(microPropsGenerator, signum, output);
|
||
|
}
|
||
|
|
||
|
private static int getPrefixSuffixImpl(MicroPropsGenerator generator, byte signum, FormattedStringBuilder output) {
|
||
|
// #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
|
||
|
// TODO: Clean this up, closer to C++. The pattern modifier is not as accessible as in C++.
|
||
|
// Right now, ignore the plural form, run the pipeline with number 0, and get the modifier from the result.
|
||
|
DecimalQuantity_DualStorageBCD quantity = new DecimalQuantity_DualStorageBCD(0);
|
||
|
if (signum < 0) {
|
||
|
quantity.negate();
|
||
|
}
|
||
|
MicroProps micros = generator.processQuantity(quantity);
|
||
|
micros.modMiddle.apply(output, 0, 0);
|
||
|
return micros.modMiddle.getPrefixLength();
|
||
|
}
|
||
|
|
||
|
public MicroProps getRawMicroProps() {
|
||
|
return micros;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
|
||
|
private static boolean unitIsCurrency(MeasureUnit unit) {
|
||
|
// TODO: Check using "instanceof" operator instead?
|
||
|
return unit != null && "currency".equals(unit.getType());
|
||
|
}
|
||
|
|
||
|
private static boolean unitIsBaseUnit(MeasureUnit unit) {
|
||
|
return unit == null;
|
||
|
}
|
||
|
|
||
|
private static boolean unitIsPercent(MeasureUnit unit) {
|
||
|
return unit != null && "percent".equals(unit.getSubtype());
|
||
|
}
|
||
|
|
||
|
private static boolean unitIsPermille(MeasureUnit unit) {
|
||
|
return unit != null && "permille".equals(unit.getSubtype());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Synthesizes the MacroProps into a MicroPropsGenerator. All information, including the locale, is
|
||
|
* encoded into the MicroPropsGenerator, except for the quantity itself, which is left abstract and
|
||
|
* must be provided to the returned MicroPropsGenerator instance.
|
||
|
*
|
||
|
* @see MicroPropsGenerator
|
||
|
* @param macros
|
||
|
* The {@link MacroProps} to consume. This method does not mutate the MacroProps instance.
|
||
|
* @param safe
|
||
|
* If true, the returned MicroPropsGenerator will be thread-safe. If false, the returned
|
||
|
* value will <em>not</em> be thread-safe, intended for a single "one-shot" use only.
|
||
|
* Building the thread-safe object is more expensive.
|
||
|
*/
|
||
|
private static MicroPropsGenerator macrosToMicroGenerator(MacroProps macros, MicroProps micros, boolean safe) {
|
||
|
MicroPropsGenerator chain = micros;
|
||
|
|
||
|
// TODO: Normalize the currency (accept symbols from DecimalFormatSymbols)?
|
||
|
// currency = CustomSymbolCurrency.resolve(currency, input.loc, micros.symbols);
|
||
|
|
||
|
// Pre-compute a few values for efficiency.
|
||
|
boolean isCurrency = unitIsCurrency(macros.unit);
|
||
|
boolean isBaseUnit = unitIsBaseUnit(macros.unit);
|
||
|
boolean isPercent = unitIsPercent(macros.unit);
|
||
|
boolean isPermille = unitIsPermille(macros.unit);
|
||
|
boolean isCompactNotation = (macros.notation instanceof CompactNotation);
|
||
|
boolean isAccounting = macros.sign == SignDisplay.ACCOUNTING
|
||
|
|| macros.sign == SignDisplay.ACCOUNTING_ALWAYS
|
||
|
|| macros.sign == SignDisplay.ACCOUNTING_EXCEPT_ZERO
|
||
|
|| macros.sign == SignDisplay.ACCOUNTING_NEGATIVE;
|
||
|
Currency currency = isCurrency ? (Currency) macros.unit : DEFAULT_CURRENCY;
|
||
|
UnitWidth unitWidth = UnitWidth.SHORT;
|
||
|
if (macros.unitWidth != null) {
|
||
|
unitWidth = macros.unitWidth;
|
||
|
}
|
||
|
// Use CLDR unit data for all MeasureUnits (not currency and not
|
||
|
// no-unit), except use the dedicated percent pattern for percent and
|
||
|
// permille. However, use the CLDR unit data for percent/permille if a
|
||
|
// long name was requested OR if compact notation is being used, since
|
||
|
// compact notation overrides the middle modifier (micros.modMiddle)
|
||
|
// normally used for the percent pattern.
|
||
|
boolean isCldrUnit = !isCurrency
|
||
|
&& !isBaseUnit
|
||
|
&& (unitWidth == UnitWidth.FULL_NAME
|
||
|
|| !(isPercent || isPermille)
|
||
|
|| isCompactNotation
|
||
|
);
|
||
|
boolean isMixedUnit = isCldrUnit && macros.unit.getType() == null &&
|
||
|
macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
|
||
|
|
||
|
PluralRules rules = macros.rules;
|
||
|
|
||
|
// Select the numbering system.
|
||
|
NumberingSystem ns;
|
||
|
if (macros.symbols instanceof NumberingSystem) {
|
||
|
ns = (NumberingSystem) macros.symbols;
|
||
|
} else {
|
||
|
// TODO: Is there a way to avoid creating the NumberingSystem object?
|
||
|
ns = NumberingSystem.getInstance(macros.loc);
|
||
|
}
|
||
|
micros.nsName = ns.getName();
|
||
|
|
||
|
// Default gender: none.
|
||
|
micros.gender = "";
|
||
|
|
||
|
// Resolve the symbols. Do this here because currency may need to customize them.
|
||
|
if (macros.symbols instanceof DecimalFormatSymbols) {
|
||
|
micros.symbols = (DecimalFormatSymbols) macros.symbols;
|
||
|
} else {
|
||
|
micros.symbols = DecimalFormatSymbols.forNumberingSystem(macros.loc, ns);
|
||
|
if (isCurrency) {
|
||
|
micros.symbols.setCurrency(currency);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load and parse the pattern string. It is used for grouping sizes and affixes only.
|
||
|
// If we are formatting currency, check for a currency-specific pattern.
|
||
|
String pattern = null;
|
||
|
if (isCurrency && micros.symbols.getCurrencyPattern() != null) {
|
||
|
pattern = micros.symbols.getCurrencyPattern();
|
||
|
}
|
||
|
if (pattern == null) {
|
||
|
int patternStyle;
|
||
|
if (isCldrUnit) {
|
||
|
patternStyle = NumberFormat.NUMBERSTYLE;
|
||
|
} else if (isPercent || isPermille) {
|
||
|
patternStyle = NumberFormat.PERCENTSTYLE;
|
||
|
} else if (!isCurrency || unitWidth == UnitWidth.FULL_NAME) {
|
||
|
patternStyle = NumberFormat.NUMBERSTYLE;
|
||
|
} else if (isAccounting) {
|
||
|
// NOTE: Although ACCOUNTING and ACCOUNTING_ALWAYS are only supported in currencies
|
||
|
// right now, the API contract allows us to add support to other units in the future.
|
||
|
patternStyle = NumberFormat.ACCOUNTINGCURRENCYSTYLE;
|
||
|
} else {
|
||
|
patternStyle = NumberFormat.CURRENCYSTYLE;
|
||
|
}
|
||
|
pattern = NumberFormat
|
||
|
.getPatternForStyleAndNumberingSystem(macros.loc, micros.nsName, patternStyle);
|
||
|
}
|
||
|
ParsedPatternInfo patternInfo = PatternStringParser.parseToPatternInfo(pattern);
|
||
|
|
||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||
|
/// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR ///
|
||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// Unit Preferences and Conversions as our first step
|
||
|
UsagePrefsHandler usagePrefsHandler = null;
|
||
|
if (macros.usage != null) {
|
||
|
if (!isCldrUnit) {
|
||
|
throw new IllegalIcuArgumentException(
|
||
|
"We only support \"usage\" when the input unit is specified, and is a CLDR Unit.");
|
||
|
}
|
||
|
chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
|
||
|
} else if (isMixedUnit) {
|
||
|
chain = new UnitConversionHandler(macros.unit, chain);
|
||
|
}
|
||
|
|
||
|
// Multiplier
|
||
|
if (macros.scale != null) {
|
||
|
chain = new MultiplierFormatHandler(macros.scale, chain);
|
||
|
}
|
||
|
|
||
|
// Rounding strategy
|
||
|
if (macros.precision != null) {
|
||
|
micros.rounder = macros.precision;
|
||
|
} else if (isCompactNotation) {
|
||
|
micros.rounder = Precision.COMPACT_STRATEGY;
|
||
|
} else if (isCurrency) {
|
||
|
micros.rounder = Precision.MONETARY_STANDARD;
|
||
|
} else if (macros.usage != null) {
|
||
|
// Bogus Precision - it will get set in the UsagePrefsHandler instead
|
||
|
micros.rounder = Precision.BOGUS_PRECISION;
|
||
|
} else {
|
||
|
micros.rounder = Precision.DEFAULT_MAX_FRAC_6;
|
||
|
}
|
||
|
if (macros.roundingMode != null) {
|
||
|
micros.rounder = micros.rounder.withMode(
|
||
|
RoundingUtils.mathContextUnlimited(macros.roundingMode));
|
||
|
}
|
||
|
micros.rounder = micros.rounder.withLocaleData(currency);
|
||
|
|
||
|
// Grouping strategy
|
||
|
if (macros.grouping instanceof Grouper) {
|
||
|
micros.grouping = (Grouper) macros.grouping;
|
||
|
} else if (macros.grouping instanceof GroupingStrategy) {
|
||
|
micros.grouping = Grouper.forStrategy((GroupingStrategy) macros.grouping);
|
||
|
} else if (isCompactNotation) {
|
||
|
// Compact notation uses minGrouping by default since ICU 59
|
||
|
micros.grouping = Grouper.forStrategy(GroupingStrategy.MIN2);
|
||
|
} else {
|
||
|
micros.grouping = Grouper.forStrategy(GroupingStrategy.AUTO);
|
||
|
}
|
||
|
micros.grouping = micros.grouping.withLocaleData(macros.loc, patternInfo);
|
||
|
|
||
|
// Padding strategy
|
||
|
if (macros.padder != null) {
|
||
|
micros.padding = macros.padder;
|
||
|
} else {
|
||
|
micros.padding = Padder.NONE;
|
||
|
}
|
||
|
|
||
|
// Integer width
|
||
|
if (macros.integerWidth != null) {
|
||
|
micros.integerWidth = macros.integerWidth;
|
||
|
} else {
|
||
|
micros.integerWidth = IntegerWidth.DEFAULT;
|
||
|
}
|
||
|
|
||
|
// Sign display
|
||
|
if (macros.sign != null) {
|
||
|
micros.sign = macros.sign;
|
||
|
} else {
|
||
|
micros.sign = SignDisplay.AUTO;
|
||
|
}
|
||
|
|
||
|
// Decimal mark display
|
||
|
if (macros.decimal != null) {
|
||
|
micros.decimal = macros.decimal;
|
||
|
} else {
|
||
|
micros.decimal = DecimalSeparatorDisplay.AUTO;
|
||
|
}
|
||
|
|
||
|
// Use monetary separator symbols
|
||
|
micros.useCurrency = isCurrency;
|
||
|
|
||
|
// Inner modifier (scientific notation)
|
||
|
if (macros.notation instanceof ScientificNotation) {
|
||
|
chain = ((ScientificNotation) macros.notation).withLocaleData(micros.symbols, safe, chain);
|
||
|
} else {
|
||
|
// No inner modifier required
|
||
|
micros.modInner = ConstantAffixModifier.EMPTY;
|
||
|
}
|
||
|
|
||
|
// Middle modifier (patterns, positive/negative, currency symbols, percent)
|
||
|
// The default middle modifier is weak (thus the false argument).
|
||
|
MutablePatternModifier patternMod = new MutablePatternModifier(false);
|
||
|
AffixPatternProvider affixProvider =
|
||
|
(macros.affixProvider != null && (
|
||
|
// For more information on this condition, see ICU-22073
|
||
|
!isCompactNotation || isCurrency == macros.affixProvider.hasCurrencySign()))
|
||
|
? macros.affixProvider
|
||
|
: patternInfo;
|
||
|
patternMod.setPatternInfo(affixProvider, null);
|
||
|
boolean approximately = (macros.approximately != null) ? macros.approximately : false;
|
||
|
patternMod.setPatternAttributes(micros.sign, isPermille, approximately);
|
||
|
if (patternMod.needsPlurals()) {
|
||
|
if (rules == null) {
|
||
|
// Lazily create PluralRules
|
||
|
rules = PluralRules.forLocale(macros.loc);
|
||
|
}
|
||
|
patternMod.setSymbols(micros.symbols, currency, unitWidth, rules);
|
||
|
} else {
|
||
|
patternMod.setSymbols(micros.symbols, currency, unitWidth, null);
|
||
|
}
|
||
|
ImmutablePatternModifier immPatternMod = null;
|
||
|
if (safe) {
|
||
|
immPatternMod = patternMod.createImmutable();
|
||
|
}
|
||
|
|
||
|
// currencyAsDecimal
|
||
|
if (affixProvider.currencyAsDecimal()) {
|
||
|
micros.currencyAsDecimal = patternMod.getCurrencySymbolForUnitWidth();
|
||
|
}
|
||
|
|
||
|
// Outer modifier (CLDR units and currency long names)
|
||
|
if (isCldrUnit) {
|
||
|
String unitDisplayCase = null;
|
||
|
if (macros.unitDisplayCase != null) {
|
||
|
unitDisplayCase = macros.unitDisplayCase;
|
||
|
}
|
||
|
if (rules == null) {
|
||
|
// Lazily create PluralRules
|
||
|
rules = PluralRules.forLocale(macros.loc);
|
||
|
}
|
||
|
PluralRules pluralRules = macros.rules != null ?
|
||
|
macros.rules :
|
||
|
PluralRules.forLocale(macros.loc);
|
||
|
|
||
|
if (macros.usage != null) {
|
||
|
assert usagePrefsHandler != null;
|
||
|
chain = LongNameMultiplexer.forMeasureUnits(
|
||
|
macros.loc,
|
||
|
usagePrefsHandler.getOutputUnits(),
|
||
|
unitWidth,
|
||
|
unitDisplayCase,
|
||
|
pluralRules,
|
||
|
chain);
|
||
|
} else if (isMixedUnit) {
|
||
|
chain = MixedUnitLongNameHandler.forMeasureUnit(
|
||
|
macros.loc,
|
||
|
macros.unit,
|
||
|
unitWidth,
|
||
|
unitDisplayCase,
|
||
|
pluralRules,
|
||
|
chain);
|
||
|
} else {
|
||
|
MeasureUnit unit = macros.unit;
|
||
|
if (macros.perUnit != null) {
|
||
|
unit = unit.product(macros.perUnit.reciprocal());
|
||
|
// This isn't strictly necessary, but was what we specced
|
||
|
// out when perUnit became a backward-compatibility thing:
|
||
|
// unit/perUnit use case is only valid if both units are
|
||
|
// built-ins, or the product is a built-in.
|
||
|
if (unit.getType() == null && (macros.unit.getType() == null || macros.perUnit.getType() == null)) {
|
||
|
throw new UnsupportedOperationException(
|
||
|
"perUnit() can only be used if unit and perUnit are both built-ins, or the combination is a built-in");
|
||
|
}
|
||
|
}
|
||
|
chain = LongNameHandler.forMeasureUnit(
|
||
|
macros.loc,
|
||
|
unit,
|
||
|
unitWidth,
|
||
|
unitDisplayCase,
|
||
|
pluralRules,
|
||
|
chain);
|
||
|
}
|
||
|
} else if (isCurrency && unitWidth == UnitWidth.FULL_NAME) {
|
||
|
if (rules == null) {
|
||
|
// Lazily create PluralRules
|
||
|
rules = PluralRules.forLocale(macros.loc);
|
||
|
}
|
||
|
chain = LongNameHandler.forCurrencyLongNames(macros.loc, currency, rules, chain);
|
||
|
} else {
|
||
|
// No outer modifier required
|
||
|
micros.modOuter = ConstantAffixModifier.EMPTY;
|
||
|
}
|
||
|
|
||
|
// Compact notation
|
||
|
if (isCompactNotation) {
|
||
|
if (rules == null) {
|
||
|
// Lazily create PluralRules
|
||
|
rules = PluralRules.forLocale(macros.loc);
|
||
|
}
|
||
|
CompactType compactType = (macros.unit instanceof Currency
|
||
|
&& macros.unitWidth != UnitWidth.FULL_NAME) ? CompactType.CURRENCY
|
||
|
: CompactType.DECIMAL;
|
||
|
chain = ((CompactNotation) macros.notation).withLocaleData(macros.loc,
|
||
|
micros.nsName,
|
||
|
compactType,
|
||
|
rules,
|
||
|
patternMod,
|
||
|
safe,
|
||
|
chain);
|
||
|
}
|
||
|
|
||
|
// Always add the pattern modifier as the last element of the chain.
|
||
|
if (safe) {
|
||
|
chain = immPatternMod.addToChain(chain);
|
||
|
} else {
|
||
|
chain = patternMod.addToChain(chain);
|
||
|
}
|
||
|
|
||
|
return chain;
|
||
|
}
|
||
|
|
||
|
//////////
|
||
|
|
||
|
/**
|
||
|
* Adds the affixes. Intended to be called immediately after formatNumber.
|
||
|
*/
|
||
|
public static int writeAffixes(
|
||
|
MicroProps micros,
|
||
|
FormattedStringBuilder string,
|
||
|
int start,
|
||
|
int end) {
|
||
|
// Always apply the inner modifier (which is "strong").
|
||
|
int length = micros.modInner.apply(string, start, end);
|
||
|
if (micros.padding.isValid()) {
|
||
|
micros.padding.padAndApply(micros.modMiddle, micros.modOuter, string, start, end + length);
|
||
|
} else {
|
||
|
length += micros.modMiddle.apply(string, start, end + length);
|
||
|
length += micros.modOuter.apply(string, start, end + length);
|
||
|
}
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Synthesizes the output string from a MicroProps and DecimalQuantity.
|
||
|
* This method formats only the main number, not affixes.
|
||
|
*/
|
||
|
public static int writeNumber(
|
||
|
MicroProps micros,
|
||
|
DecimalQuantity quantity,
|
||
|
FormattedStringBuilder string,
|
||
|
int index) {
|
||
|
int length = 0;
|
||
|
if (quantity.isInfinite()) {
|
||
|
length += string.insert(length + index, micros.symbols.getInfinity(), NumberFormat.Field.INTEGER);
|
||
|
|
||
|
} else if (quantity.isNaN()) {
|
||
|
length += string.insert(length + index, micros.symbols.getNaN(), NumberFormat.Field.INTEGER);
|
||
|
|
||
|
} else {
|
||
|
// Add the integer digits
|
||
|
length += writeIntegerDigits(micros, quantity, string, length + index);
|
||
|
|
||
|
// Add the decimal point
|
||
|
if (quantity.getLowerDisplayMagnitude() < 0
|
||
|
|| micros.decimal == DecimalSeparatorDisplay.ALWAYS) {
|
||
|
if (micros.currencyAsDecimal != null) {
|
||
|
// Note: This unconditionally substitutes the standard short symbol.
|
||
|
// TODO: Should we support narrow or other variants?
|
||
|
length += string.insert(
|
||
|
length + index,
|
||
|
micros.currencyAsDecimal,
|
||
|
NumberFormat.Field.CURRENCY);
|
||
|
} else if (micros.useCurrency) {
|
||
|
length += string.insert(
|
||
|
length + index,
|
||
|
micros.symbols.getMonetaryDecimalSeparatorString(),
|
||
|
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||
|
} else {
|
||
|
length += string.insert(
|
||
|
length + index,
|
||
|
micros.symbols.getDecimalSeparatorString(),
|
||
|
NumberFormat.Field.DECIMAL_SEPARATOR);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Add the fraction digits
|
||
|
length += writeFractionDigits(micros, quantity, string, length + index);
|
||
|
|
||
|
if (length == 0) {
|
||
|
// Force output of the digit for value 0
|
||
|
if (micros.symbols.getCodePointZero() != -1) {
|
||
|
length += string.insertCodePoint(index,
|
||
|
micros.symbols.getCodePointZero(),
|
||
|
NumberFormat.Field.INTEGER);
|
||
|
} else {
|
||
|
length += string.insert(index,
|
||
|
micros.symbols.getDigitStringsLocal()[0],
|
||
|
NumberFormat.Field.INTEGER);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
private static int writeIntegerDigits(
|
||
|
MicroProps micros,
|
||
|
DecimalQuantity quantity,
|
||
|
FormattedStringBuilder string,
|
||
|
int index) {
|
||
|
int length = 0;
|
||
|
int integerCount = quantity.getUpperDisplayMagnitude() + 1;
|
||
|
for (int i = 0; i < integerCount; i++) {
|
||
|
// Add grouping separator
|
||
|
if (micros.grouping.groupAtPosition(i, quantity)) {
|
||
|
length += string.insert(index,
|
||
|
micros.useCurrency ? micros.symbols.getMonetaryGroupingSeparatorString()
|
||
|
: micros.symbols.getGroupingSeparatorString(),
|
||
|
NumberFormat.Field.GROUPING_SEPARATOR);
|
||
|
}
|
||
|
|
||
|
// Get and append the next digit value
|
||
|
byte nextDigit = quantity.getDigit(i);
|
||
|
if (micros.symbols.getCodePointZero() != -1) {
|
||
|
length += string.insertCodePoint(index,
|
||
|
micros.symbols.getCodePointZero() + nextDigit,
|
||
|
NumberFormat.Field.INTEGER);
|
||
|
} else {
|
||
|
length += string.insert(index,
|
||
|
micros.symbols.getDigitStringsLocal()[nextDigit],
|
||
|
NumberFormat.Field.INTEGER);
|
||
|
}
|
||
|
}
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
private static int writeFractionDigits(
|
||
|
MicroProps micros,
|
||
|
DecimalQuantity quantity,
|
||
|
FormattedStringBuilder string,
|
||
|
int index) {
|
||
|
int length = 0;
|
||
|
int fractionCount = -quantity.getLowerDisplayMagnitude();
|
||
|
for (int i = 0; i < fractionCount; i++) {
|
||
|
// Get and append the next digit value
|
||
|
byte nextDigit = quantity.getDigit(-i - 1);
|
||
|
if (micros.symbols.getCodePointZero() != -1) {
|
||
|
length += string.insertCodePoint(length + index, micros.symbols.getCodePointZero() + nextDigit,
|
||
|
NumberFormat.Field.FRACTION);
|
||
|
} else {
|
||
|
length += string.insert(length + index, micros.symbols.getDigitStringsLocal()[nextDigit],
|
||
|
NumberFormat.Field.FRACTION);
|
||
|
}
|
||
|
}
|
||
|
return length;
|
||
|
}
|
||
|
}
|