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

com.ibm.icu.number.NumberPropertyMapper Maven / Gradle / Ivy

Go to download

International Component for Unicode for Java (ICU4J) is a mature, widely used Java library providing Unicode and Globalization support

There is a newer version: 76.1
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.number;

import java.math.BigDecimal;
import java.math.MathContext;

import com.ibm.icu.impl.number.AffixPatternProvider;
import com.ibm.icu.impl.number.CurrencyPluralInfoAffixProvider;
import com.ibm.icu.impl.number.CustomSymbolCurrency;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.MacroProps;
import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringUtils;
import com.ibm.icu.impl.number.PropertiesAffixPatternProvider;
import com.ibm.icu.impl.number.RoundingUtils;
import com.ibm.icu.number.NumberFormatter.DecimalSeparatorDisplay;
import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.Precision.FractionRounderImpl;
import com.ibm.icu.number.Precision.IncrementRounderImpl;
import com.ibm.icu.number.Precision.SignificantRounderImpl;
import com.ibm.icu.text.CompactDecimalFormat.CompactStyle;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.Currency.CurrencyUsage;
import com.ibm.icu.util.ULocale;

/**
 * 

* This class, as well as NumberFormatterImpl, could go into the impl package, but they depend on too * many package-private members of the public APIs. */ final class NumberPropertyMapper { /** Convenience method to create a NumberFormatter directly from Properties. */ public static UnlocalizedNumberFormatter create( DecimalFormatProperties properties, DecimalFormatSymbols symbols) { MacroProps macros = oldToNew(properties, symbols, null); return NumberFormatter.with().macros(macros); } /** Convenience method to create a NumberFormatter directly from Properties. */ public static UnlocalizedNumberFormatter create( DecimalFormatProperties properties, DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) { MacroProps macros = oldToNew(properties, symbols, exportedProperties); return NumberFormatter.with().macros(macros); } /** * Convenience method to create a NumberFormatter directly from a pattern string. Something like this * could become public API if there is demand. * * NOTE: This appears to be dead code. */ public static UnlocalizedNumberFormatter create(String pattern, DecimalFormatSymbols symbols) { DecimalFormatProperties properties = PatternStringParser.parseToProperties(pattern); return create(properties, symbols); } /** * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} * object. In other words, maps Properties to MacroProps. This function is used by the * JDK-compatibility API to call into the ICU 60 fluent number formatting pipeline. * * @param properties * The property bag to be mapped. * @param symbols * The symbols associated with the property bag. * @param exportedProperties * A property bag in which to store validated properties. Used by some DecimalFormat * getters. * @return A new MacroProps containing all of the information in the Properties. */ public static MacroProps oldToNew( DecimalFormatProperties properties, DecimalFormatSymbols symbols, DecimalFormatProperties exportedProperties) { MacroProps macros = new MacroProps(); ULocale locale = symbols.getULocale(); ///////////// // SYMBOLS // ///////////// macros.symbols = symbols; ////////////////// // PLURAL RULES // ////////////////// PluralRules rules = properties.getPluralRules(); if (rules == null && properties.getCurrencyPluralInfo() != null) { rules = properties.getCurrencyPluralInfo().getPluralRules(); } macros.rules = rules; ///////////// // AFFIXES // ///////////// AffixPatternProvider affixProvider; if (properties.getCurrencyPluralInfo() == null) { affixProvider = new PropertiesAffixPatternProvider(properties); } else { affixProvider = new CurrencyPluralInfoAffixProvider(properties.getCurrencyPluralInfo(), properties); } macros.affixProvider = affixProvider; /////////// // UNITS // /////////// boolean useCurrency = ((properties.getCurrency() != null) || properties.getCurrencyPluralInfo() != null || properties.getCurrencyUsage() != null || affixProvider.hasCurrencySign()); Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols); CurrencyUsage currencyUsage = properties.getCurrencyUsage(); boolean explicitCurrencyUsage = currencyUsage != null; if (!explicitCurrencyUsage) { currencyUsage = CurrencyUsage.STANDARD; } if (useCurrency) { macros.unit = currency; } /////////////////////// // ROUNDING STRATEGY // /////////////////////// int maxInt = properties.getMaximumIntegerDigits(); int minInt = properties.getMinimumIntegerDigits(); int maxFrac = properties.getMaximumFractionDigits(); int minFrac = properties.getMinimumFractionDigits(); int minSig = properties.getMinimumSignificantDigits(); int maxSig = properties.getMaximumSignificantDigits(); BigDecimal roundingIncrement = properties.getRoundingIncrement(); MathContext mathContext = RoundingUtils.getMathContextOrUnlimited(properties); boolean explicitMinMaxFrac = minFrac != -1 || maxFrac != -1; boolean explicitMinMaxSig = minSig != -1 || maxSig != -1; // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or // maxFrac was set (but not both) on a currency instance. // NOTE: Increments are handled in "Rounder.constructCurrency()". if (useCurrency) { if (minFrac == -1 && maxFrac == -1) { minFrac = currency.getDefaultFractionDigits(currencyUsage); maxFrac = currency.getDefaultFractionDigits(currencyUsage); } else if (minFrac == -1) { minFrac = Math.min(maxFrac, currency.getDefaultFractionDigits(currencyUsage)); } else if (maxFrac == -1) { maxFrac = Math.max(minFrac, currency.getDefaultFractionDigits(currencyUsage)); } else { // No-op: user override for both minFrac and maxFrac } } // Validate min/max int/frac. // For backwards compatibility, minimum overrides maximum if the two conflict. // The following logic ensures that there is always a minimum of at least one digit. if (minInt == 0 && maxFrac != 0) { // Force a digit after the decimal point. minFrac = minFrac <= 0 ? 1 : minFrac; maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; minInt = 0; maxInt = maxInt < 0 ? -1 : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt; } else { // Force a digit before the decimal point. minFrac = minFrac < 0 ? 0 : minFrac; maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; minInt = minInt <= 0 ? 1 : minInt > RoundingUtils.MAX_INT_FRAC_SIG ? 1 : minInt; maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > RoundingUtils.MAX_INT_FRAC_SIG ? -1 : maxInt; } Precision rounding = null; if (explicitCurrencyUsage) { rounding = Precision.constructCurrency(currencyUsage).withCurrency(currency); } else if (roundingIncrement != null) { if (PatternStringUtils.ignoreRoundingIncrement(roundingIncrement, maxFrac)) { rounding = Precision.constructFraction(minFrac, maxFrac); } else { rounding = Precision.constructIncrement(roundingIncrement); } } else if (explicitMinMaxSig) { minSig = minSig < 1 ? 1 : minSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : minSig; maxSig = maxSig < 0 ? RoundingUtils.MAX_INT_FRAC_SIG : maxSig < minSig ? minSig : maxSig > RoundingUtils.MAX_INT_FRAC_SIG ? RoundingUtils.MAX_INT_FRAC_SIG : maxSig; rounding = Precision.constructSignificant(minSig, maxSig); } else if (explicitMinMaxFrac) { rounding = Precision.constructFraction(minFrac, maxFrac); } else if (useCurrency) { rounding = Precision.constructCurrency(currencyUsage); } if (rounding != null) { rounding = rounding.withMode(mathContext); macros.precision = rounding; } /////////////////// // INTEGER WIDTH // /////////////////// macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); /////////////////////// // GROUPING STRATEGY // /////////////////////// macros.grouping = Grouper.forProperties(properties); ///////////// // PADDING // ///////////// if (properties.getFormatWidth() > 0) { macros.padder = Padder.forProperties(properties); } /////////////////////////////// // DECIMAL MARK ALWAYS SHOWN // /////////////////////////////// macros.decimal = properties.getDecimalSeparatorAlwaysShown() ? DecimalSeparatorDisplay.ALWAYS : DecimalSeparatorDisplay.AUTO; /////////////////////// // SIGN ALWAYS SHOWN // /////////////////////// macros.sign = properties.getSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO; ///////////////////////// // SCIENTIFIC NOTATION // ///////////////////////// if (properties.getMinimumExponentDigits() != -1) { // Scientific notation is required. // This whole section feels like a hack, but it is needed for regression tests. // The mapping from property bag to scientific notation is nontrivial due to LDML rules. if (maxInt > 8) { // But #13110: The maximum of 8 digits has unknown origins and is not in the spec. // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8. maxInt = minInt; macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); } else if (maxInt > minInt && minInt > 1) { // Bug #13289: if maxInt > minInt > 1, then minInt should be 1. minInt = 1; macros.integerWidth = IntegerWidth.zeroFillTo(minInt).truncateAt(maxInt); } int engineering = maxInt < 0 ? -1 : maxInt; macros.notation = new ScientificNotation( // Engineering interval: engineering, // Enforce minimum integer digits (for patterns like "000.00E0"): (engineering == minInt), // Minimum exponent digits: properties.getMinimumExponentDigits(), // Exponent sign always shown: properties.getExponentSignAlwaysShown() ? SignDisplay.ALWAYS : SignDisplay.AUTO); // Scientific notation also involves overriding the rounding mode. // TODO: Overriding here is a bit of a hack. Should this logic go earlier? if (macros.precision instanceof FractionPrecision) { // For the purposes of rounding, get the original min/max int/frac, since the local // variables have been manipulated for display purposes. int maxInt_ = properties.getMaximumIntegerDigits(); int minInt_ = properties.getMinimumIntegerDigits(); int minFrac_ = properties.getMinimumFractionDigits(); int maxFrac_ = properties.getMaximumFractionDigits(); if (minInt_ == 0 && maxFrac_ == 0) { // Patterns like "#E0" and "##E0", which mean no rounding! macros.precision = Precision.constructInfinite().withMode(mathContext); } else if (minInt_ == 0 && minFrac_ == 0) { // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 macros.precision = Precision.constructSignificant(1, maxFrac_ + 1).withMode(mathContext); } else { int maxSig_ = minInt_ + maxFrac_; // Bug #20058: if maxInt_ > minInt_ > 1, then minInt_ should be 1. if (maxInt_ > minInt_ && minInt_ > 1) { minInt_ = 1; } int minSig_ = minInt_ + minFrac_; // To avoid regression, maxSig is not reset when minInt_ set to 1. // TODO: Reset maxSig_ = 1 + minFrac_ to follow the spec. macros.precision = Precision.constructSignificant(minSig_, maxSig_) .withMode(mathContext); } } } ////////////////////// // COMPACT NOTATION // ////////////////////// if (properties.getCompactStyle() != null) { if (properties.getCompactCustomData() != null) { macros.notation = new CompactNotation(properties.getCompactCustomData()); } else if (properties.getCompactStyle() == CompactStyle.LONG) { macros.notation = Notation.compactLong(); } else { macros.notation = Notation.compactShort(); } // Do not forward the affix provider. macros.affixProvider = null; } ///////////////// // MULTIPLIERS // ///////////////// macros.scale = RoundingUtils.scaleFromProperties(properties); ////////////////////// // PROPERTY EXPORTS // ////////////////////// if (exportedProperties != null) { exportedProperties.setCurrency(currency); exportedProperties.setMathContext(mathContext); exportedProperties.setRoundingMode(mathContext.getRoundingMode()); exportedProperties.setMinimumIntegerDigits(minInt); exportedProperties.setMaximumIntegerDigits(maxInt == -1 ? Integer.MAX_VALUE : maxInt); Precision rounding_; if (rounding instanceof CurrencyPrecision) { rounding_ = ((CurrencyPrecision) rounding).withCurrency(currency); } else { rounding_ = rounding; } int minFrac_ = minFrac; int maxFrac_ = maxFrac; int minSig_ = minSig; int maxSig_ = maxSig; BigDecimal increment_ = null; if (rounding_ instanceof FractionRounderImpl) { minFrac_ = ((FractionRounderImpl) rounding_).minFrac; maxFrac_ = ((FractionRounderImpl) rounding_).maxFrac; } else if (rounding_ instanceof IncrementRounderImpl) { increment_ = ((IncrementRounderImpl) rounding_).increment; minFrac_ = increment_.scale(); maxFrac_ = increment_.scale(); } else if (rounding_ instanceof SignificantRounderImpl) { minSig_ = ((SignificantRounderImpl) rounding_).minSig; maxSig_ = ((SignificantRounderImpl) rounding_).maxSig; } exportedProperties.setMinimumFractionDigits(minFrac_); exportedProperties.setMaximumFractionDigits(maxFrac_); exportedProperties.setMinimumSignificantDigits(minSig_); exportedProperties.setMaximumSignificantDigits(maxSig_); exportedProperties.setRoundingIncrement(increment_); } return macros; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy